This patchset adds a new mode to KASAN [1], which is called KHWASAN
(Kernel HardWare assisted Address SANitizer).
The plan is to implement HWASan [2] for the kernel with the incentive,
that it's going to have comparable to KASAN performance, but in the same
time consume much less memory, trading that off for somewhat imprecise
bug detection and being supported only for arm64.
The overall idea of the approach used by KHWASAN is the following:
1. By using the Top Byte Ignore arm64 CPU feature, we can store pointer
tags in the top byte of each kernel pointer.
2. Using shadow memory, we can store memory tags for each chunk of kernel
memory.
3. On each memory allocation, we can generate a random tag, embed it into
the returned pointer and set the memory tags that correspond to this
chunk of memory to the same value.
4. By using compiler instrumentation, before each memory access we can add
a check that the pointer tag matches the tag of the memory that is being
accessed.
5. On a tag mismatch we report an error.
[1] https://www.kernel.org/doc/html/latest/dev-tools/kasan.html
[2] http://clang.llvm.org/docs/HardwareAssistedAddressSanitizerDesign.html
====== Rationale
On mobile devices KASAN's memory usage is significant problem. One of the
main reasons to have KWHASAN is to be able to perform a similar set of
checks that KASAN does, but with lower memory requirements.
Comment from Vishwath Mohan [off-list ref]:
I don't have data on-hand, but anecdotally both ASAN and KASAN have proven
problematic to enable for environments that don't tolerate the increased
memory pressure well. This includes,
(a) Low-memory form factors - Wear, TV, Things, lower-tier phones like Go,
(c) Connected components like Pixel's visual core [1].
These are both places I'd love to have a low(er) memory footprint option at
my disposal.
Comment from Evgenii Stepanov [off-list ref]:
Looking at a live Android device under load, slab (according to
/proc/meminfo) + kernel stack take 8-10% available RAM (~350MB). KASAN's
overhead of 2x - 3x on top of it is not insignificant.
Not having this overhead enables near-production use - ex. running
KASAN/KHWASAN kernel on a personal, daily-use device to catch bugs that do
not reproduce in test configuration. These are the ones that often cost
the most engineering time to track down.
CPU overhead is bad, but generally tolerable. RAM is critical, in our
experience. Once it gets low enough, OOM-killer makes your life miserable.
[1] https://www.blog.google/products/pixel/pixel-visual-core-image-processing-and-machine-learning-pixel-2/
====== Technical details
KHWASAN is implemented in a very similar way to KASAN. This patchset
essentially does the following:
1. TCR_TBI1 is set to enable Top Byte Ignore.
2. Shadow memory is used (with a different scale, 1:16, so each shadow
byte corresponds to 16 bytes of kernel memory) to store memory tags.
3. All slab objects are aligned to shadow scale, which is 16 bytes.
4. All pointers returned from the slab allocator are tagged with a random
tag and the corresponding shadow memory is poisoned with the same value.
5. Compiler instrumentation is used to insert tag checks. Either by
calling callbacks or by inlining them (CONFIG_KASAN_OUTLINE and
CONFIG_KASAN_INLINE flags are reused).
6. When a tag mismatch is detected in callback instrumentation mode
KHWASAN simply prints a bug report. In case of inline instrumentation,
clang inserts a brk instruction, and KHWASAN has it's own brk handler,
which reports the bug.
7. The memory in between slab objects is marked with a reserved tag, and
acts as a redzone.
8. When a slab object is freed it's marked with a reserved tag.
Bug detection is imprecise for two reasons:
1. We won't catch some small out-of-bounds accesses, that fall into the
same shadow cell, as the last byte of a slab object.
2. We only have 1 byte to store tags, which means we have a 1/256
probability of a tag match for an incorrect access (actually even
slightly less due to reserved tag values).
Despite that there's a particular type of bugs that KHWASAN can detect
compared to KASAN: use-after-free after the object has been allocated by
someone else.
====== Testing
Some kernel developers voiced a concern that changing the top byte of
kernel pointers may lead to subtle bugs that are difficult to discover.
To address this concern deliberate testing has been performed.
It doesn't seem feasible to do some kind of static checking to find
potential issues with pointer tagging, so a dynamic approach was taken.
All pointer comparisons/subtractions have been instrumented in an LLVM
compiler pass and a kernel module that would print a bug report whenever
two pointers with different tags are being compared/subtracted (ignoring
comparisons with NULL pointers and with pointers obtained by casting an
error code to a pointer type) has been used. Then the kernel has been
booted in QEMU and on an Odroid C2 board and syzkaller has been run.
This yielded the following results.
The two places that look interesting are:
is_vmalloc_addr in include/linux/mm.h
is_kernel_rodata in mm/util.c
Here we compare a pointer with some fixed untagged values to make sure
that the pointer lies in a particular part of the kernel address space.
Since KWHASAN doesn't add tags to pointers that belong to rodata or
vmalloc regions, this should work as is. To make sure debug checks to
those two functions that check that the result doesn't change whether
we operate on pointers with or without untagging has been added.
A few other cases that don't look that interesting:
Comparing pointers to achieve unique sorting order of pointee objects
(e.g. sorting locks addresses before performing a double lock):
tty_ldisc_lock_pair_timeout in drivers/tty/tty_ldisc.c
pipe_double_lock in fs/pipe.c
unix_state_double_lock in net/unix/af_unix.c
lock_two_nondirectories in fs/inode.c
mutex_lock_double in kernel/events/core.c
ep_cmp_ffd in fs/eventpoll.c
fsnotify_compare_groups fs/notify/mark.c
Nothing needs to be done here, since the tags embedded into pointers
don't change, so the sorting order would still be unique.
Checks that a pointer belongs to some particular allocation:
is_sibling_entry in lib/radix-tree.c
object_is_on_stack in include/linux/sched/task_stack.h
Nothing needs to be done here either, since two pointers can only belong
to the same allocation if they have the same tag.
Overall, since the kernel boots and works, there are no critical bugs.
As for the rest, the traditional kernel testing way (use until fails) is
the only one that looks feasible.
Another point here is that KWHASAN is available under a separate config
option that needs to be deliberately enabled. Even though it might be used
in a "near-production" environment to find bugs that are not found during
fuzzing or running tests, it is still a debug tool.
====== Benchmarks
The following numbers were collected on Odroid C2 board. Both KASAN and
KHWASAN were used in inline instrumentation mode.
Boot time [1]:
* ~1.7 sec for clean kernel
* ~5.0 sec for KASAN
* ~5.0 sec for KHWASAN
Network performance [2]:
* 8.33 Gbits/sec for clean kernel
* 3.17 Gbits/sec for KASAN
* 2.85 Gbits/sec for KHWASAN
Slab memory usage after boot [3]:
* ~40 kb for clean kernel
* ~105 kb (~260% overhead) for KASAN
* ~47 kb (~20% overhead) for KHWASAN
KASAN memory overhead consists of three main parts:
1. Increased slab memory usage due to redzones.
2. Shadow memory (the whole reserved once during boot).
3. Quaratine (grows gradually until some preset limit; the more the limit,
the more the chance to detect a use-after-free).
Comparing KWHASAN vs KASAN for each of these points:
1. 20% vs 260% overhead.
2. 1/16th vs 1/8th of physical memory.
3. KHWASAN doesn't require quarantine.
[1] Time before the ext4 driver is initialized.
[2] Measured as `iperf -s & iperf -c 127.0.0.1 -t 30`.
[3] Measured as `cat /proc/meminfo | grep Slab`.
====== Some notes
A few notes:
1. The patchset can be found here:
https://github.com/xairy/kasan-prototype/tree/khwasan
2. Building requires a recent LLVM version (r330044 or later).
3. Stack instrumentation is not supported yet and will be added later.
====== Changes
Changes in v6:
- Rebased onto 050cdc6c (4.19-rc1+).
- Added notes regarding patchset testing into the cover letter.
Changes in v5:
- Rebased onto 1ffaddd029 (4.18-rc8).
- Preassign tags for objects from caches with constructors and
SLAB_TYPESAFE_BY_RCU caches.
- Fix SLAB allocator support by untagging page->s_mem in
kasan_poison_slab().
- Performed dynamic testing to find potential places where pointer tagging
might result in bugs [1].
- Clarified and fixed memory usage benchmarks in the cover letter.
- Added a rationale for having KHWASAN to the cover letter.
Changes in v4:
- Fixed SPDX comment style in mm/kasan/kasan.h.
- Fixed mm/kasan/kasan.h changes being included in a wrong patch.
- Swapped "khwasan, arm64: fix up fault handling logic" and "khwasan: add
tag related helper functions" patches order.
- Rebased onto 6f0d349d (4.18-rc2+).
Changes in v3:
- Minor documentation fixes.
- Fixed CFLAGS variable name in KASAN makefile.
- Added a "SPDX-License-Identifier: GPL-2.0" line to all source files
under mm/kasan.
- Rebased onto 81e97f013 (4.18-rc1+).
Changes in v2:
- Changed kmalloc_large_node_hook to return tagged pointer instead of
using an output argument.
- Fix checking whether -fsanitize=hwaddress is supported by the compiler.
- Removed duplication of -fno-builtin for KASAN and KHWASAN.
- Removed {} block for one line for_each_possible_cpu loop.
- Made set_track() static inline as it is used only in common.c.
- Moved optimal_redzone() to common.c.
- Fixed using tagged pointer for shadow calculation in
kasan_unpoison_shadow().
- Restored setting cache->align in kasan_cache_create(), which was
accidentally lost.
- Simplified __kasan_slab_free(), kasan_alloc_pages() and kasan_kmalloc().
- Removed tagging from kasan_kmalloc_large().
- Added page_kasan_tag_reset() to kasan_poison_slab() and removed
!PageSlab() check from page_to_virt.
- Reset pointer tag in _virt_addr_is_linear.
- Set page tag for each page when multiple pages are allocated or freed.
- Added a comment as to why we ignore cma allocated pages.
Changes in v1:
- Rebased onto 4.17-rc4.
- Updated benchmarking stats.
- Documented compiler version requirements, memory usage and slowdown.
- Dropped kvm patches, as clang + arm64 + kvm is completely broken [1].
Changes in RFC v3:
- Renamed CONFIG_KASAN_CLASSIC and CONFIG_KASAN_TAGS to
CONFIG_KASAN_GENERIC and CONFIG_KASAN_HW respectively.
- Switch to -fsanitize=kernel-hwaddress instead of -fsanitize=hwaddress.
- Removed unnecessary excessive shadow initialization.
- Removed khwasan_enabled flag (it?s not needed since KHWASAN is
initialized before any slab caches are used).
- Split out kasan_report.c and khwasan_report.c from report.c.
- Moved more common KASAN and KHWASAN functions to common.c.
- Added tagging to pagealloc.
- Rebased onto 4.17-rc1.
- Temporarily dropped patch that adds kvm support (arm64 + kvm + clang
combo is broken right now [2]).
Changes in RFC v2:
- Removed explicit casts to u8 * for kasan_mem_to_shadow() calls.
- Introduced KASAN_TCR_FLAGS for setting the TCR_TBI1 flag.
- Added a comment regarding the non-atomic RMW sequence in
khwasan_random_tag().
- Made all tag related functions accept const void *.
- Untagged pointers in __kimg_to_phys, which is used by virt_to_phys.
- Untagged pointers in show_ptr in fault handling logic.
- Untagged pointers passed to KVM.
- Added two reserved tag values: 0xFF and 0xFE.
- Used the reserved tag 0xFF to disable validity checking (to resolve the
issue with pointer tag being lost after page_address + kmap usage).
- Used the reserved tag 0xFE to mark redzones and freed objects.
- Added mnemonics for esr manipulation in KHWASAN brk handler.
- Added a comment about the -recover flag.
- Some minor cleanups and fixes.
- Rebased onto 3215b9d5 (4.16-rc6+).
- Tested on real hardware (Odroid C2 board).
- Added better benchmarks.
[1] https://lkml.org/lkml/2018/7/18/765
[2] https://lkml.org/lkml/2018/4/19/775
Andrey Konovalov (18):
khwasan, mm: change kasan hooks signatures
khwasan: move common kasan and khwasan code to common.c
khwasan: add CONFIG_KASAN_GENERIC and CONFIG_KASAN_HW
khwasan, arm64: adjust shadow size for CONFIG_KASAN_HW
khwasan: initialize shadow to 0xff
khwasan, arm64: untag virt address in __kimg_to_phys and
_virt_addr_is_linear
khwasan: add tag related helper functions
khwasan: preassign tags to objects with ctors or SLAB_TYPESAFE_BY_RCU
khwasan, arm64: fix up fault handling logic
khwasan, arm64: enable top byte ignore for the kernel
khwasan, mm: perform untagged pointers comparison in krealloc
khwasan: split out kasan_report.c from report.c
khwasan: add bug reporting routines
khwasan: add hooks implementation
khwasan, arm64: add brk handler for inline instrumentation
khwasan, mm, arm64: tag non slab memory allocated via pagealloc
khwasan: update kasan documentation
kasan: add SPDX-License-Identifier mark to source files
Documentation/dev-tools/kasan.rst | 213 ++++----
arch/arm64/Kconfig | 1 +
arch/arm64/Makefile | 2 +-
arch/arm64/include/asm/brk-imm.h | 2 +
arch/arm64/include/asm/memory.h | 41 +-
arch/arm64/include/asm/pgtable-hwdef.h | 1 +
arch/arm64/kernel/traps.c | 69 ++-
arch/arm64/mm/fault.c | 3 +
arch/arm64/mm/kasan_init.c | 18 +-
arch/arm64/mm/proc.S | 8 +-
include/linux/compiler-clang.h | 3 +-
include/linux/compiler-gcc.h | 4 +
include/linux/compiler.h | 3 +-
include/linux/kasan.h | 90 +++-
include/linux/mm.h | 29 ++
include/linux/page-flags-layout.h | 10 +
lib/Kconfig.kasan | 77 ++-
mm/cma.c | 11 +
mm/kasan/Makefile | 9 +-
mm/kasan/common.c | 663 +++++++++++++++++++++++++
mm/kasan/kasan.c | 565 +--------------------
mm/kasan/kasan.h | 85 +++-
mm/kasan/kasan_init.c | 1 +
mm/kasan/kasan_report.c | 156 ++++++
mm/kasan/khwasan.c | 181 +++++++
mm/kasan/khwasan_report.c | 61 +++
mm/kasan/quarantine.c | 1 +
mm/kasan/report.c | 272 +++-------
mm/page_alloc.c | 1 +
mm/slab.c | 18 +-
mm/slab.h | 2 +-
mm/slab_common.c | 6 +-
mm/slub.c | 21 +-
scripts/Makefile.kasan | 27 +-
34 files changed, 1734 insertions(+), 920 deletions(-)
create mode 100644 mm/kasan/common.c
create mode 100644 mm/kasan/kasan_report.c
create mode 100644 mm/kasan/khwasan.c
create mode 100644 mm/kasan/khwasan_report.c
--
2.19.0.rc0.228.g281dcd1b4d0-goog
KHWASAN will reuse a significant part of KASAN code, so move the common
parts to common.c without any functional changes.
Signed-off-by: Andrey Konovalov <redacted>
---
mm/kasan/Makefile | 5 +-
mm/kasan/common.c | 604 ++++++++++++++++++++++++++++++++++++++++++++++
mm/kasan/kasan.c | 568 +------------------------------------------
mm/kasan/kasan.h | 5 +
4 files changed, 615 insertions(+), 567 deletions(-)
create mode 100644 mm/kasan/common.c
@@ -0,0 +1,604 @@+/*+*ThisfilecontainscommonKASANandKHWASANcode.+*+*Copyright(c)2014SamsungElectronicsCo.,Ltd.+*Author:AndreyRyabinin<ryabinin.a.a@gmail.com>+*+*Somecodeborrowedfromhttps://github.com/xairy/kasan-prototype by+*AndreyKonovalov<andreyknvl@gmail.com>+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseversion2as+*publishedbytheFreeSoftwareFoundation.+*+*/++#include<linux/export.h>+#include<linux/interrupt.h>+#include<linux/init.h>+#include<linux/kasan.h>+#include<linux/kernel.h>+#include<linux/kmemleak.h>+#include<linux/linkage.h>+#include<linux/memblock.h>+#include<linux/memory.h>+#include<linux/mm.h>+#include<linux/module.h>+#include<linux/printk.h>+#include<linux/sched.h>+#include<linux/sched/task_stack.h>+#include<linux/slab.h>+#include<linux/stacktrace.h>+#include<linux/string.h>+#include<linux/types.h>+#include<linux/vmalloc.h>+#include<linux/bug.h>++#include"kasan.h"+#include"../slab.h"++staticinlineintin_irqentry_text(unsignedlongptr)+{+return(ptr>=(unsignedlong)&__irqentry_text_start&&+ptr<(unsignedlong)&__irqentry_text_end)||+(ptr>=(unsignedlong)&__softirqentry_text_start&&+ptr<(unsignedlong)&__softirqentry_text_end);+}++staticinlinevoidfilter_irq_stacks(structstack_trace*trace)+{+inti;++if(!trace->nr_entries)+return;+for(i=0;i<trace->nr_entries;i++)+if(in_irqentry_text(trace->entries[i])){+/* Include the irqentry function into the stack. */+trace->nr_entries=i+1;+break;+}+}++staticinlinedepot_stack_handle_tsave_stack(gfp_tflags)+{+unsignedlongentries[KASAN_STACK_DEPTH];+structstack_tracetrace={+.nr_entries=0,+.entries=entries,+.max_entries=KASAN_STACK_DEPTH,+.skip=0+};++save_stack_trace(&trace);+filter_irq_stacks(&trace);+if(trace.nr_entries!=0&&+trace.entries[trace.nr_entries-1]==ULONG_MAX)+trace.nr_entries--;++returndepot_save_stack(&trace,flags);+}++staticinlinevoidset_track(structkasan_track*track,gfp_tflags)+{+track->pid=current->pid;+track->stack=save_stack(flags);+}++voidkasan_enable_current(void)+{+current->kasan_depth++;+}++voidkasan_disable_current(void)+{+current->kasan_depth--;+}++voidkasan_check_read(constvolatilevoid*p,unsignedintsize)+{+check_memory_region((unsignedlong)p,size,false,_RET_IP_);+}+EXPORT_SYMBOL(kasan_check_read);++voidkasan_check_write(constvolatilevoid*p,unsignedintsize)+{+check_memory_region((unsignedlong)p,size,true,_RET_IP_);+}+EXPORT_SYMBOL(kasan_check_write);++#undef memset+void*memset(void*addr,intc,size_tlen)+{+check_memory_region((unsignedlong)addr,len,true,_RET_IP_);++return__memset(addr,c,len);+}++#undef memmove+void*memmove(void*dest,constvoid*src,size_tlen)+{+check_memory_region((unsignedlong)src,len,false,_RET_IP_);+check_memory_region((unsignedlong)dest,len,true,_RET_IP_);++return__memmove(dest,src,len);+}++#undef memcpy+void*memcpy(void*dest,constvoid*src,size_tlen)+{+check_memory_region((unsignedlong)src,len,false,_RET_IP_);+check_memory_region((unsignedlong)dest,len,true,_RET_IP_);++return__memcpy(dest,src,len);+}++/*+*Poisonstheshadowmemoryfor'size'bytesstartingfrom'addr'.+*MemoryaddressesshouldbealignedtoKASAN_SHADOW_SCALE_SIZE.+*/+voidkasan_poison_shadow(constvoid*address,size_tsize,u8value)+{+void*shadow_start,*shadow_end;++shadow_start=kasan_mem_to_shadow(address);+shadow_end=kasan_mem_to_shadow(address+size);++__memset(shadow_start,value,shadow_end-shadow_start);+}++voidkasan_unpoison_shadow(constvoid*address,size_tsize)+{+kasan_poison_shadow(address,size,0);++if(size&KASAN_SHADOW_MASK){+u8*shadow=(u8*)kasan_mem_to_shadow(address+size);+*shadow=size&KASAN_SHADOW_MASK;+}+}++staticvoid__kasan_unpoison_stack(structtask_struct*task,constvoid*sp)+{+void*base=task_stack_page(task);+size_tsize=sp-base;++kasan_unpoison_shadow(base,size);+}++/* Unpoison the entire stack for a task. */+voidkasan_unpoison_task_stack(structtask_struct*task)+{+__kasan_unpoison_stack(task,task_stack_page(task)+THREAD_SIZE);+}++/* Unpoison the stack for the current task beyond a watermark sp value. */+asmlinkagevoidkasan_unpoison_task_stack_below(constvoid*watermark)+{+/*+*Calculatethetaskstackbaseaddress.Avoidusing'current'+*becausethisfunctioniscalledbyearlyresumecodewhichhasn't+*yetsetupthepercpuregister(%gs).+*/+void*base=(void*)((unsignedlong)watermark&~(THREAD_SIZE-1));++kasan_unpoison_shadow(base,watermark-base);+}++/*+*ClearallpoisonfortheregionbetweenthecurrentSPandaprovided+*watermarkvalue,asissometimesrequiredpriortohand-craftedasmfunction+*returnsinthemiddleoffunctions.+*/+voidkasan_unpoison_stack_above_sp_to(constvoid*watermark)+{+constvoid*sp=__builtin_frame_address(0);+size_tsize=watermark-sp;++if(WARN_ON(sp>watermark))+return;+kasan_unpoison_shadow(sp,size);+}++voidkasan_alloc_pages(structpage*page,unsignedintorder)+{+if(likely(!PageHighMem(page)))+kasan_unpoison_shadow(page_address(page),PAGE_SIZE<<order);+}++voidkasan_free_pages(structpage*page,unsignedintorder)+{+if(likely(!PageHighMem(page)))+kasan_poison_shadow(page_address(page),+PAGE_SIZE<<order,+KASAN_FREE_PAGE);+}++/*+*AdaptiveredzonepolicytakenfromtheuserspaceAddressSanitizerruntime.+*Forlargerallocationslargerredzonesareused.+*/+staticinlineunsignedintoptimal_redzone(unsignedintobject_size)+{+if(IS_ENABLED(CONFIG_KASAN_HW))+return0;++return+object_size<=64-16?16:+object_size<=128-32?32:+object_size<=512-64?64:+object_size<=4096-128?128:+object_size<=(1<<14)-256?256:+object_size<=(1<<15)-512?512:+object_size<=(1<<16)-1024?1024:2048;+}++voidkasan_cache_create(structkmem_cache*cache,unsignedint*size,+slab_flags_t*flags)+{+unsignedintorig_size=*size;+intredzone_adjust;++/* Add alloc meta. */+cache->kasan_info.alloc_meta_offset=*size;+*size+=sizeof(structkasan_alloc_meta);++/* Add free meta. */+if(cache->flags&SLAB_TYPESAFE_BY_RCU||cache->ctor||+cache->object_size<sizeof(structkasan_free_meta)){+cache->kasan_info.free_meta_offset=*size;+*size+=sizeof(structkasan_free_meta);+}+redzone_adjust=optimal_redzone(cache->object_size)-+(*size-cache->object_size);++if(redzone_adjust>0)+*size+=redzone_adjust;++*size=min_t(unsignedint,KMALLOC_MAX_SIZE,+max(*size,cache->object_size++optimal_redzone(cache->object_size)));++/*+*Ifthemetadatadoesn'tfit,don'tenableKASANatall.+*/+if(*size<=cache->kasan_info.alloc_meta_offset||+*size<=cache->kasan_info.free_meta_offset){+cache->kasan_info.alloc_meta_offset=0;+cache->kasan_info.free_meta_offset=0;+*size=orig_size;+return;+}++*flags|=SLAB_KASAN;+}++size_tkasan_metadata_size(structkmem_cache*cache)+{+return(cache->kasan_info.alloc_meta_offset?+sizeof(structkasan_alloc_meta):0)++(cache->kasan_info.free_meta_offset?+sizeof(structkasan_free_meta):0);+}++structkasan_alloc_meta*get_alloc_info(structkmem_cache*cache,+constvoid*object)+{+BUILD_BUG_ON(sizeof(structkasan_alloc_meta)>32);+return(void*)object+cache->kasan_info.alloc_meta_offset;+}++structkasan_free_meta*get_free_info(structkmem_cache*cache,+constvoid*object)+{+BUILD_BUG_ON(sizeof(structkasan_free_meta)>32);+return(void*)object+cache->kasan_info.free_meta_offset;+}++voidkasan_poison_slab(structpage*page)+{+kasan_poison_shadow(page_address(page),+PAGE_SIZE<<compound_order(page),+KASAN_KMALLOC_REDZONE);+}++voidkasan_unpoison_object_data(structkmem_cache*cache,void*object)+{+kasan_unpoison_shadow(object,cache->object_size);+}++voidkasan_poison_object_data(structkmem_cache*cache,void*object)+{+kasan_poison_shadow(object,+round_up(cache->object_size,KASAN_SHADOW_SCALE_SIZE),+KASAN_KMALLOC_REDZONE);+}++voidkasan_init_slab_obj(structkmem_cache*cache,constvoid*object)+{+structkasan_alloc_meta*alloc_info;++if(!(cache->flags&SLAB_KASAN))+return;++alloc_info=get_alloc_info(cache,object);+__memset(alloc_info,0,sizeof(*alloc_info));+}++void*kasan_slab_alloc(structkmem_cache*cache,void*object,gfp_tflags)+{+returnkasan_kmalloc(cache,object,cache->object_size,flags);+}++staticbool__kasan_slab_free(structkmem_cache*cache,void*object,+unsignedlongip,boolquarantine)+{+s8shadow_byte;+unsignedlongrounded_up_size;++if(unlikely(nearest_obj(cache,virt_to_head_page(object),object)!=+object)){+kasan_report_invalid_free(object,ip);+returntrue;+}++/* RCU slabs could be legally used after free within the RCU period */+if(unlikely(cache->flags&SLAB_TYPESAFE_BY_RCU))+returnfalse;++shadow_byte=READ_ONCE(*(s8*)kasan_mem_to_shadow(object));+if(shadow_byte<0||shadow_byte>=KASAN_SHADOW_SCALE_SIZE){+kasan_report_invalid_free(object,ip);+returntrue;+}++rounded_up_size=round_up(cache->object_size,KASAN_SHADOW_SCALE_SIZE);+kasan_poison_shadow(object,rounded_up_size,KASAN_KMALLOC_FREE);++if(!quarantine||unlikely(!(cache->flags&SLAB_KASAN)))+returnfalse;++set_track(&get_alloc_info(cache,object)->free_track,GFP_NOWAIT);+quarantine_put(get_free_info(cache,object),cache);+returntrue;+}++boolkasan_slab_free(structkmem_cache*cache,void*object,unsignedlongip)+{+return__kasan_slab_free(cache,object,ip,true);+}++void*kasan_kmalloc(structkmem_cache*cache,constvoid*object,size_tsize,+gfp_tflags)+{+unsignedlongredzone_start;+unsignedlongredzone_end;++if(gfpflags_allow_blocking(flags))+quarantine_reduce();++if(unlikely(object==NULL))+returnNULL;++redzone_start=round_up((unsignedlong)(object+size),+KASAN_SHADOW_SCALE_SIZE);+redzone_end=round_up((unsignedlong)object+cache->object_size,+KASAN_SHADOW_SCALE_SIZE);++kasan_unpoison_shadow(object,size);+kasan_poison_shadow((void*)redzone_start,redzone_end-redzone_start,+KASAN_KMALLOC_REDZONE);++if(cache->flags&SLAB_KASAN)+set_track(&get_alloc_info(cache,object)->alloc_track,flags);++return(void*)object;+}+EXPORT_SYMBOL(kasan_kmalloc);++void*kasan_kmalloc_large(constvoid*ptr,size_tsize,gfp_tflags)+{+structpage*page;+unsignedlongredzone_start;+unsignedlongredzone_end;++if(gfpflags_allow_blocking(flags))+quarantine_reduce();++if(unlikely(ptr==NULL))+returnNULL;++page=virt_to_page(ptr);+redzone_start=round_up((unsignedlong)(ptr+size),+KASAN_SHADOW_SCALE_SIZE);+redzone_end=(unsignedlong)ptr+(PAGE_SIZE<<compound_order(page));++kasan_unpoison_shadow(ptr,size);+kasan_poison_shadow((void*)redzone_start,redzone_end-redzone_start,+KASAN_PAGE_REDZONE);++return(void*)ptr;+}++void*kasan_krealloc(constvoid*object,size_tsize,gfp_tflags)+{+structpage*page;++if(unlikely(object==ZERO_SIZE_PTR))+return(void*)object;++page=virt_to_head_page(object);++if(unlikely(!PageSlab(page)))+returnkasan_kmalloc_large(object,size,flags);+else+returnkasan_kmalloc(page->slab_cache,object,size,flags);+}++voidkasan_poison_kfree(void*ptr,unsignedlongip)+{+structpage*page;++page=virt_to_head_page(ptr);++if(unlikely(!PageSlab(page))){+if(ptr!=page_address(page)){+kasan_report_invalid_free(ptr,ip);+return;+}+kasan_poison_shadow(ptr,PAGE_SIZE<<compound_order(page),+KASAN_FREE_PAGE);+}else{+__kasan_slab_free(page->slab_cache,ptr,ip,false);+}+}++voidkasan_kfree_large(void*ptr,unsignedlongip)+{+if(ptr!=page_address(virt_to_head_page(ptr)))+kasan_report_invalid_free(ptr,ip);+/* The object will be poisoned by page_alloc. */+}++intkasan_module_alloc(void*addr,size_tsize)+{+void*ret;+size_tscaled_size;+size_tshadow_size;+unsignedlongshadow_start;++shadow_start=(unsignedlong)kasan_mem_to_shadow(addr);+scaled_size=(size+KASAN_SHADOW_MASK)>>KASAN_SHADOW_SCALE_SHIFT;+shadow_size=round_up(scaled_size,PAGE_SIZE);++if(WARN_ON(!PAGE_ALIGNED(shadow_start)))+return-EINVAL;++ret=__vmalloc_node_range(shadow_size,1,shadow_start,+shadow_start+shadow_size,+GFP_KERNEL|__GFP_ZERO,+PAGE_KERNEL,VM_NO_GUARD,NUMA_NO_NODE,+__builtin_return_address(0));++if(ret){+find_vm_area(addr)->flags|=VM_KASAN;+kmemleak_ignore(ret);+return0;+}++return-ENOMEM;+}++voidkasan_free_shadow(conststructvm_struct*vm)+{+if(vm->flags&VM_KASAN)+vfree(kasan_mem_to_shadow(vm->addr));+}++#ifdef CONFIG_MEMORY_HOTPLUG+staticboolshadow_mapped(unsignedlongaddr)+{+pgd_t*pgd=pgd_offset_k(addr);+p4d_t*p4d;+pud_t*pud;+pmd_t*pmd;+pte_t*pte;++if(pgd_none(*pgd))+returnfalse;+p4d=p4d_offset(pgd,addr);+if(p4d_none(*p4d))+returnfalse;+pud=pud_offset(p4d,addr);+if(pud_none(*pud))+returnfalse;++/*+*Wecan'tusepud_large()orpud_huge(),thefirstoneis+*arch-specific,thelastonedependsonHUGETLB_PAGE.Solet'sabuse+*pud_bad(),ifpudisbadthenit'sbadbecauseit'shuge.+*/+if(pud_bad(*pud))+returntrue;+pmd=pmd_offset(pud,addr);+if(pmd_none(*pmd))+returnfalse;++if(pmd_bad(*pmd))+returntrue;+pte=pte_offset_kernel(pmd,addr);+return!pte_none(*pte);+}++staticint__meminitkasan_mem_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structmemory_notify*mem_data=data;+unsignedlongnr_shadow_pages,start_kaddr,shadow_start;+unsignedlongshadow_end,shadow_size;++nr_shadow_pages=mem_data->nr_pages>>KASAN_SHADOW_SCALE_SHIFT;+start_kaddr=(unsignedlong)pfn_to_kaddr(mem_data->start_pfn);+shadow_start=(unsignedlong)kasan_mem_to_shadow((void*)start_kaddr);+shadow_size=nr_shadow_pages<<PAGE_SHIFT;+shadow_end=shadow_start+shadow_size;++if(WARN_ON(mem_data->nr_pages%KASAN_SHADOW_SCALE_SIZE)||+WARN_ON(start_kaddr%(KASAN_SHADOW_SCALE_SIZE<<PAGE_SHIFT)))+returnNOTIFY_BAD;++switch(action){+caseMEM_GOING_ONLINE:{+void*ret;++/*+*Ifshadowismappedalreadythanitmusthavebeenmapped+*duringtheboot.Thiscouldhappenifweonliningpreviously+*offlinedmemory.+*/+if(shadow_mapped(shadow_start))+returnNOTIFY_OK;++ret=__vmalloc_node_range(shadow_size,PAGE_SIZE,shadow_start,+shadow_end,GFP_KERNEL,+PAGE_KERNEL,VM_NO_GUARD,+pfn_to_nid(mem_data->start_pfn),+__builtin_return_address(0));+if(!ret)+returnNOTIFY_BAD;++kmemleak_ignore(ret);+returnNOTIFY_OK;+}+caseMEM_CANCEL_ONLINE:+caseMEM_OFFLINE:{+structvm_struct*vm;++/*+*shadow_startwaseithermappedduringbootbykasan_init()+*orduringmemoryonlineby__vmalloc_node_range().+*Inthelattercasewecanusevfree()tofreeshadow.+*Non-NULLresultofthefind_vm_area()willtellusif+*thatwasthesecondcase.+*+*Currentlyit'snotpossibletofreeshadowmapped+*duringbootbykasan_init().It'sbecausethecode+*todothathasn'tbeenwrittenyet.Sowe'lljust+*leakthememory.+*/+vm=find_vm_area((void*)shadow_start);+if(vm)+vfree((void*)shadow_start);+}+}++returnNOTIFY_OK;+}++staticint__initkasan_memhotplug_init(void)+{+hotplug_memory_notifier(kasan_mem_notifier,0);++return0;+}++core_initcall(kasan_memhotplug_init);+#endif
@@ -40,82 +40,6 @@#include"kasan.h"#include"../slab.h"-voidkasan_enable_current(void)-{-current->kasan_depth++;-}--voidkasan_disable_current(void)-{-current->kasan_depth--;-}--/*-*Poisonstheshadowmemoryfor'size'bytesstartingfrom'addr'.-*MemoryaddressesshouldbealignedtoKASAN_SHADOW_SCALE_SIZE.-*/-staticvoidkasan_poison_shadow(constvoid*address,size_tsize,u8value)-{-void*shadow_start,*shadow_end;--shadow_start=kasan_mem_to_shadow(address);-shadow_end=kasan_mem_to_shadow(address+size);--memset(shadow_start,value,shadow_end-shadow_start);-}--voidkasan_unpoison_shadow(constvoid*address,size_tsize)-{-kasan_poison_shadow(address,size,0);--if(size&KASAN_SHADOW_MASK){-u8*shadow=(u8*)kasan_mem_to_shadow(address+size);-*shadow=size&KASAN_SHADOW_MASK;-}-}--staticvoid__kasan_unpoison_stack(structtask_struct*task,constvoid*sp)-{-void*base=task_stack_page(task);-size_tsize=sp-base;--kasan_unpoison_shadow(base,size);-}--/* Unpoison the entire stack for a task. */-voidkasan_unpoison_task_stack(structtask_struct*task)-{-__kasan_unpoison_stack(task,task_stack_page(task)+THREAD_SIZE);-}--/* Unpoison the stack for the current task beyond a watermark sp value. */-asmlinkagevoidkasan_unpoison_task_stack_below(constvoid*watermark)-{-/*-*Calculatethetaskstackbaseaddress.Avoidusing'current'-*becausethisfunctioniscalledbyearlyresumecodewhichhasn't-*yetsetupthepercpuregister(%gs).-*/-void*base=(void*)((unsignedlong)watermark&~(THREAD_SIZE-1));--kasan_unpoison_shadow(base,watermark-base);-}--/*-*ClearallpoisonfortheregionbetweenthecurrentSPandaprovided-*watermarkvalue,asissometimesrequiredpriortohand-craftedasmfunction-*returnsinthemiddleoffunctions.-*/-voidkasan_unpoison_stack_above_sp_to(constvoid*watermark)-{-constvoid*sp=__builtin_frame_address(0);-size_tsize=watermark-sp;--if(WARN_ON(sp>watermark))-return;-kasan_unpoison_shadow(sp,size);-}-/**Allfunctionsbelowalwaysinlinedsocompilercould*performbetteroptimizationsineachof__asan_loadX/__assn_storeX
@@ -386,275 +201,6 @@ void kasan_cache_shutdown(struct kmem_cache *cache)quarantine_remove_cache(cache);}-size_tkasan_metadata_size(structkmem_cache*cache)-{-return(cache->kasan_info.alloc_meta_offset?-sizeof(structkasan_alloc_meta):0)+-(cache->kasan_info.free_meta_offset?-sizeof(structkasan_free_meta):0);-}--voidkasan_poison_slab(structpage*page)-{-kasan_poison_shadow(page_address(page),-PAGE_SIZE<<compound_order(page),-KASAN_KMALLOC_REDZONE);-}--voidkasan_unpoison_object_data(structkmem_cache*cache,void*object)-{-kasan_unpoison_shadow(object,cache->object_size);-}--voidkasan_poison_object_data(structkmem_cache*cache,void*object)-{-kasan_poison_shadow(object,-round_up(cache->object_size,KASAN_SHADOW_SCALE_SIZE),-KASAN_KMALLOC_REDZONE);-}--staticinlineintin_irqentry_text(unsignedlongptr)-{-return(ptr>=(unsignedlong)&__irqentry_text_start&&-ptr<(unsignedlong)&__irqentry_text_end)||-(ptr>=(unsignedlong)&__softirqentry_text_start&&-ptr<(unsignedlong)&__softirqentry_text_end);-}--staticinlinevoidfilter_irq_stacks(structstack_trace*trace)-{-inti;--if(!trace->nr_entries)-return;-for(i=0;i<trace->nr_entries;i++)-if(in_irqentry_text(trace->entries[i])){-/* Include the irqentry function into the stack. */-trace->nr_entries=i+1;-break;-}-}--staticinlinedepot_stack_handle_tsave_stack(gfp_tflags)-{-unsignedlongentries[KASAN_STACK_DEPTH];-structstack_tracetrace={-.nr_entries=0,-.entries=entries,-.max_entries=KASAN_STACK_DEPTH,-.skip=0-};--save_stack_trace(&trace);-filter_irq_stacks(&trace);-if(trace.nr_entries!=0&&-trace.entries[trace.nr_entries-1]==ULONG_MAX)-trace.nr_entries--;--returndepot_save_stack(&trace,flags);-}--staticinlinevoidset_track(structkasan_track*track,gfp_tflags)-{-track->pid=current->pid;-track->stack=save_stack(flags);-}--structkasan_alloc_meta*get_alloc_info(structkmem_cache*cache,-constvoid*object)-{-BUILD_BUG_ON(sizeof(structkasan_alloc_meta)>32);-return(void*)object+cache->kasan_info.alloc_meta_offset;-}--structkasan_free_meta*get_free_info(structkmem_cache*cache,-constvoid*object)-{-BUILD_BUG_ON(sizeof(structkasan_free_meta)>32);-return(void*)object+cache->kasan_info.free_meta_offset;-}--voidkasan_init_slab_obj(structkmem_cache*cache,constvoid*object)-{-structkasan_alloc_meta*alloc_info;--if(!(cache->flags&SLAB_KASAN))-return;--alloc_info=get_alloc_info(cache,object);-__memset(alloc_info,0,sizeof(*alloc_info));-}--void*kasan_slab_alloc(structkmem_cache*cache,void*object,gfp_tflags)-{-returnkasan_kmalloc(cache,object,cache->object_size,flags);-}--staticbool__kasan_slab_free(structkmem_cache*cache,void*object,-unsignedlongip,boolquarantine)-{-s8shadow_byte;-unsignedlongrounded_up_size;--if(unlikely(nearest_obj(cache,virt_to_head_page(object),object)!=-object)){-kasan_report_invalid_free(object,ip);-returntrue;-}--/* RCU slabs could be legally used after free within the RCU period */-if(unlikely(cache->flags&SLAB_TYPESAFE_BY_RCU))-returnfalse;--shadow_byte=READ_ONCE(*(s8*)kasan_mem_to_shadow(object));-if(shadow_byte<0||shadow_byte>=KASAN_SHADOW_SCALE_SIZE){-kasan_report_invalid_free(object,ip);-returntrue;-}--rounded_up_size=round_up(cache->object_size,KASAN_SHADOW_SCALE_SIZE);-kasan_poison_shadow(object,rounded_up_size,KASAN_KMALLOC_FREE);--if(!quarantine||unlikely(!(cache->flags&SLAB_KASAN)))-returnfalse;--set_track(&get_alloc_info(cache,object)->free_track,GFP_NOWAIT);-quarantine_put(get_free_info(cache,object),cache);-returntrue;-}--boolkasan_slab_free(structkmem_cache*cache,void*object,unsignedlongip)-{-return__kasan_slab_free(cache,object,ip,true);-}--void*kasan_kmalloc(structkmem_cache*cache,constvoid*object,size_tsize,-gfp_tflags)-{-unsignedlongredzone_start;-unsignedlongredzone_end;--if(gfpflags_allow_blocking(flags))-quarantine_reduce();--if(unlikely(object==NULL))-returnNULL;--redzone_start=round_up((unsignedlong)(object+size),-KASAN_SHADOW_SCALE_SIZE);-redzone_end=round_up((unsignedlong)object+cache->object_size,-KASAN_SHADOW_SCALE_SIZE);--kasan_unpoison_shadow(object,size);-kasan_poison_shadow((void*)redzone_start,redzone_end-redzone_start,-KASAN_KMALLOC_REDZONE);--if(cache->flags&SLAB_KASAN)-set_track(&get_alloc_info(cache,object)->alloc_track,flags);--return(void*)object;-}-EXPORT_SYMBOL(kasan_kmalloc);--void*kasan_kmalloc_large(constvoid*ptr,size_tsize,gfp_tflags)-{-structpage*page;-unsignedlongredzone_start;-unsignedlongredzone_end;--if(gfpflags_allow_blocking(flags))-quarantine_reduce();--if(unlikely(ptr==NULL))-returnNULL;--page=virt_to_page(ptr);-redzone_start=round_up((unsignedlong)(ptr+size),-KASAN_SHADOW_SCALE_SIZE);-redzone_end=(unsignedlong)ptr+(PAGE_SIZE<<compound_order(page));--kasan_unpoison_shadow(ptr,size);-kasan_poison_shadow((void*)redzone_start,redzone_end-redzone_start,-KASAN_PAGE_REDZONE);--return(void*)ptr;-}--void*kasan_krealloc(constvoid*object,size_tsize,gfp_tflags)-{-structpage*page;--if(unlikely(object==ZERO_SIZE_PTR))-returnZERO_SIZE_PTR;--page=virt_to_head_page(object);--if(unlikely(!PageSlab(page)))-returnkasan_kmalloc_large(object,size,flags);-else-returnkasan_kmalloc(page->slab_cache,object,size,flags);-}--voidkasan_poison_kfree(void*ptr,unsignedlongip)-{-structpage*page;--page=virt_to_head_page(ptr);--if(unlikely(!PageSlab(page))){-if(ptr!=page_address(page)){-kasan_report_invalid_free(ptr,ip);-return;-}-kasan_poison_shadow(ptr,PAGE_SIZE<<compound_order(page),-KASAN_FREE_PAGE);-}else{-__kasan_slab_free(page->slab_cache,ptr,ip,false);-}-}--voidkasan_kfree_large(void*ptr,unsignedlongip)-{-if(ptr!=page_address(virt_to_head_page(ptr)))-kasan_report_invalid_free(ptr,ip);-/* The object will be poisoned by page_alloc. */-}--intkasan_module_alloc(void*addr,size_tsize)-{-void*ret;-size_tscaled_size;-size_tshadow_size;-unsignedlongshadow_start;--shadow_start=(unsignedlong)kasan_mem_to_shadow(addr);-scaled_size=(size+KASAN_SHADOW_MASK)>>KASAN_SHADOW_SCALE_SHIFT;-shadow_size=round_up(scaled_size,PAGE_SIZE);--if(WARN_ON(!PAGE_ALIGNED(shadow_start)))-return-EINVAL;--ret=__vmalloc_node_range(shadow_size,1,shadow_start,-shadow_start+shadow_size,-GFP_KERNEL|__GFP_ZERO,-PAGE_KERNEL,VM_NO_GUARD,NUMA_NO_NODE,-__builtin_return_address(0));--if(ret){-find_vm_area(addr)->flags|=VM_KASAN;-kmemleak_ignore(ret);-return0;-}--return-ENOMEM;-}--voidkasan_free_shadow(conststructvm_struct*vm)-{-if(vm->flags&VM_KASAN)-vfree(kasan_mem_to_shadow(vm->addr));-}-staticvoidregister_global(structkasan_global*global){size_taligned_size=round_up(global->size,KASAN_SHADOW_SCALE_SIZE);
This commit splits the current CONFIG_KASAN config option into two:
1. CONFIG_KASAN_GENERIC, that enables the generic software-only KASAN
version (the one that exists now);
2. CONFIG_KASAN_HW, that enables KHWASAN.
With CONFIG_KASAN_HW enabled, compiler options are changed to instrument
kernel files wiht -fsantize=hwaddress (except the ones for which
KASAN_SANITIZE := n is set).
Both CONFIG_KASAN_GENERIC and CONFIG_KASAN_HW support both
CONFIG_KASAN_INLINE and CONFIG_KASAN_OUTLINE instrumentation modes.
This commit also adds empty placeholder (for now) implementation of
KHWASAN specific hooks inserted by the compiler and adjusts common hooks
implementation to compile correctly with each of the config options.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/Kconfig | 1 +
include/linux/compiler-clang.h | 3 +-
include/linux/compiler-gcc.h | 4 ++
include/linux/compiler.h | 3 +-
include/linux/kasan.h | 16 +++++--
lib/Kconfig.kasan | 77 ++++++++++++++++++++++++++--------
mm/kasan/Makefile | 6 ++-
mm/kasan/kasan.h | 3 +-
mm/kasan/khwasan.c | 75 +++++++++++++++++++++++++++++++++
mm/slub.c | 2 +-
scripts/Makefile.kasan | 27 +++++++++++-
11 files changed, 188 insertions(+), 29 deletions(-)
create mode 100644 mm/kasan/khwasan.c
@@ -1,34 +1,75 @@configHAVE_ARCH_KASANbool+configHAVE_ARCH_KASAN_HW+bool+ifHAVE_ARCH_KASANconfigKASAN-bool"KASan: runtime memory debugger"+bool"KASAN: runtime memory debugger"+help+EnablesKASAN(KernelAddressSANitizer)-runtimememorydebugger,+designedtofindout-of-boundsaccessesanduse-after-freebugs.++choice+prompt"KASAN mode"+depends onKASAN+defaultKASAN_GENERIC+help+KASANhastwomodes:KASAN(aclassicversion,similartouserspace+ASan,enabledwithCONFIG_KASAN_GENERIC)andKHWASAN(aversion+basedonpointertagging,onlyforarm64,similartouserspace+HWASan,enabledwithCONFIG_KASAN_HW).++configKASAN_GENERIC+bool"KASAN: the generic mode"depends on(SLUB&&SYSFS)||(SLAB&&!DEBUG_SLAB)selectSLUB_DEBUGifSLUBselectCONSTRUCTORSselectSTACKDEPOThelp-Enableskerneladdresssanitizer-runtimememorydebugger,-designedtofindout-of-boundsaccessesanduse-after-freebugs.-Thisisstrictlyadebuggingfeatureanditrequiresagccversion-of4.9.2orlater.Detectionofoutofboundsaccessestostackor-globalvariablesrequiresgcc5.0orlater.-Thisfeatureconsumesabout1/8ofavailablememoryandbringsabout-~x3performanceslowdown.+EnablesthegenericmodeofKASAN.+ThisisstrictlyadebuggingfeatureanditrequiresaGCCversion+of4.9.2orlater.Detectionofout-of-boundsaccessestostackor+globalvariablesrequiresGCC5.0orlater.+Thismodeconsumesabout1/8ofavailablememoryatkernelstart+andintroducesanoverheadof~x1.5fortherestoftheallocations.+Theperformanceslowdownis~x3.ForbettererrordetectionenableCONFIG_STACKTRACE.-CurrentlyCONFIG_KASANdoesn'tworkwithCONFIG_DEBUG_SLAB+CurrentlyCONFIG_KASAN_GENERICdoesn'tworkwithCONFIG_DEBUG_SLAB(theresultingkerneldoesnotboot).+ifHAVE_ARCH_KASAN_HW++configKASAN_HW+bool"KHWASAN: the hardware assisted mode"+depends on(SLUB&&SYSFS)||(SLAB&&!DEBUG_SLAB)+selectSLUB_DEBUGifSLUB+selectCONSTRUCTORS+selectSTACKDEPOT+help+EnabledKHWASAN(KASANmodebasedonpointertagging).+ThismoderequiresTopByteIgnoresupportbytheCPUandtherefore+onlysupportedforarm64.+Thisfeaturerequiresclangrevision330044orlater.+Thismodeconsumesabout1/16ofavailablememoryatkernelstart+andintroducesanoverheadof~20%fortherestoftheallocations.+ForbettererrordetectionenableCONFIG_STACKTRACE.+CurrentlyCONFIG_KASAN_HWdoesn'tworkwithCONFIG_DEBUG_SLAB+(theresultingkerneldoesnotboot).++endif++endchoice+configKASAN_EXTRA-bool"KAsan: extra checks"-depends onKASAN&&DEBUG_KERNEL&&!COMPILE_TEST+bool"KASAN: extra checks"+depends onKASAN_GENERIC&&DEBUG_KERNEL&&!COMPILE_TESThelp-Thisenablesfurtherchecksinthekerneladdresssanitizer,fornow-itonlyincludestheaddress-use-after-scopecheckthatcanlead-toexcessivekernelstackusage,framesizewarningsandlonger-compiletime.+ThisenablesfurtherchecksinKASAN,fornowitonlyincludesthe+address-use-after-scopecheckthatcanleadtoexcessivekernel+stackusage,framesizewarningsandlongercompiletime.https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81715hasmore
@@ -53,16 +94,16 @@ config KASAN_INLINEmemoryaccesses.Thisisfasterthanoutline(insomeworkloadsitgivesaboutx2boostoveroutlineinstrumentation),butmakekernel's.textsizemuchbigger.-Thisrequiresagccversionof5.0orlater.+ForCONFIG_KASAN_GENERICthisrequiresGCC5.0orlater.endchoiceconfigTEST_KASAN-tristate"Module for testing kasan for bug detection"+tristate"Module for testing KASAN for bug detection"depends onm&&KASANhelpThisisatestmoduledoingvariousnastythingslikeoutofboundsaccesses,useafterfree.Itisusefulfortesting-kerneldebuggingfeatureslikekerneladdresssanitizer.+kerneldebuggingfeatureslikeKASAN.endif
A KHWASAN shadow memory cell contains a memory tag, that corresponds to
the tag in the top byte of the pointer, that points to that memory. The
native top byte value of kernel pointers is 0xff, so with KHWASAN we
need to initialize shadow memory to 0xff. This commit does that.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/mm/kasan_init.c | 16 ++++++++++++++--
include/linux/kasan.h | 8 ++++++++
mm/kasan/common.c | 3 ++-
3 files changed, 24 insertions(+), 3 deletions(-)
@@ -235,7 +247,7 @@ void __init kasan_init(void)set_pte(&kasan_zero_pte[i],pfn_pte(sym_to_pfn(kasan_zero_page),PAGE_KERNEL_RO));-memset(kasan_zero_page,0,PAGE_SIZE);+memset(kasan_zero_page,KASAN_SHADOW_INIT,PAGE_SIZE);cpu_replace_ttbr1(lm_alias(swapper_pg_dir));/* At this point kasan is fully initialized. Enable error messages */
__kimg_to_phys (which is used by virt_to_phys) and _virt_addr_is_linear
(which is used by virt_addr_valid) assume that the top byte of the address
is 0xff, which isn't always the case with KHWASAN enabled.
The solution is to reset the tag in those macros.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/include/asm/memory.h | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
An object constructor can initialize pointers within this objects based on
the address of the object. Since the object address might be tagged, we
need to assign a tag before calling constructor.
The implemented approach is to assign tags to objects with constructors
when a slab is allocated and call constructors once as usual. The
downside is that such object would always have the same tag when it is
reallocated, so we won't catch use-after-frees on it.
Also pressign tags for objects from SLAB_TYPESAFE_BY_RCU caches, since
they can be validy accessed after having been freed.
Signed-off-by: Andrey Konovalov <redacted>
---
mm/slab.c | 6 +++++-
mm/slub.c | 4 ++++
2 files changed, 9 insertions(+), 1 deletion(-)
@@ -1531,12 +1531,14 @@ static bool shuffle_freelist(struct kmem_cache *s, struct page *page)/* First entry is used as the base of the freelist */cur=next_freelist_entry(s,page,&pos,start,page_limit,freelist_count);+cur=khwasan_preset_slub_tag(s,cur);page->freelist=cur;for(idx=1;idx<page->objects;idx++){setup_object(s,page,cur);next=next_freelist_entry(s,page,&pos,start,page_limit,freelist_count);+next=khwasan_preset_slub_tag(s,next);set_freepointer(s,cur,next);cur=next;}
show_pte in arm64 fault handling relies on the fact that the top byte of
a kernel pointer is 0xff, which isn't always the case with KHWASAN enabled.
Reset the top byte.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/mm/fault.c | 3 +++
1 file changed, 3 insertions(+)
The krealloc function checks where the same buffer was reused or a new one
allocated by comparing kernel pointers. KHWASAN changes memory tag on the
krealloc'ed chunk of memory and therefore also changes the pointer tag of
the returned pointer. Therefore we need to perform comparison on untagged
(with tags reset) pointers to check whether it's the same memory region or
not.
Signed-off-by: Andrey Konovalov <redacted>
---
mm/slab_common.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
This commit adds rountines, that print KHWASAN error reports. Those are
quite similar to KASAN, the difference is:
1. The way KHWASAN finds the first bad shadow cell (with a mismatching
tag). KHWASAN compares memory tags from the shadow memory to the pointer
tag.
2. KHWASAN reports all bugs with the "KASAN: invalid-access" header. This
is done, so various external tools that already parse the kernel logs
looking for KASAN reports wouldn't need to be changed.
Signed-off-by: Andrey Konovalov <redacted>
---
include/linux/kasan.h | 3 +++
mm/kasan/kasan.h | 7 +++++
mm/kasan/kasan_report.c | 7 ++---
mm/kasan/khwasan_report.c | 21 +++++++++++++++
mm/kasan/report.c | 57 +++++++++++++++++++++------------------
5 files changed, 64 insertions(+), 31 deletions(-)
@@ -64,11 +64,10 @@ static int __init kasan_set_multi_shot(char *str)}__setup("kasan_multi_shot",kasan_set_multi_shot);-staticvoidprint_error_description(structkasan_access_info*info,-constchar*bug_type)+staticvoidprint_error_description(structkasan_access_info*info){pr_err("BUG: KASAN: %s in %pS\n",-bug_type,(void*)info->ip);+get_bug_type(info),(void*)info->ip);pr_err("%s of size %zu at addr %px by task %s/%d\n",info->is_write?"Write":"Read",info->access_size,info->access_addr,current->comm,task_pid_nr(current));
@@ -272,6 +271,8 @@ void kasan_report_invalid_free(void *object, unsigned long ip)start_report(&flags);pr_err("BUG: KASAN: double-free or invalid-free in %pS\n",(void*)ip);+print_tags(get_tag(object),reset_tag(object));+object=reset_tag(object);pr_err("\n");print_address_description(object);pr_err("\n");
@@ -279,41 +280,45 @@ void kasan_report_invalid_free(void *object, unsigned long ip)end_report(&flags);}-staticvoidkasan_report_error(structkasan_access_info*info)-{-unsignedlongflags;--start_report(&flags);--print_error_description(info,get_bug_type(info));-pr_err("\n");--if(!addr_has_shadow(info->access_addr)){-dump_stack();-}else{-print_address_description((void*)info->access_addr);-pr_err("\n");-print_shadow_for_address(info->first_bad_addr);-}--end_report(&flags);-}-voidkasan_report(unsignedlongaddr,size_tsize,boolis_write,unsignedlongip){structkasan_access_infoinfo;+void*tagged_addr;+void*untagged_addr;+unsignedlongflags;if(likely(!report_enabled()))return;disable_trace_on_warning();-info.access_addr=(void*)addr;-info.first_bad_addr=(void*)addr;+tagged_addr=(void*)addr;+untagged_addr=reset_tag(tagged_addr);++info.access_addr=tagged_addr;+if(addr_has_shadow(untagged_addr))+info.first_bad_addr=find_first_bad_addr(tagged_addr,size);+else+info.first_bad_addr=untagged_addr;info.access_size=size;info.is_write=is_write;info.ip=ip;-kasan_report_error(&info);+start_report(&flags);++print_error_description(&info);+if(addr_has_shadow(untagged_addr))+print_tags(get_tag(tagged_addr),info.first_bad_addr);+pr_err("\n");++if(addr_has_shadow(untagged_addr)){+print_address_description(untagged_addr);+pr_err("\n");+print_shadow_for_address(info.first_bad_addr);+}else{+dump_stack();+}++end_report(&flags);}
This commit adds KHWASAN specific hooks implementation and adjusts
common KASAN and KHWASAN ones.
1. When a new slab cache is created, KHWASAN rounds up the size of the
objects in this cache to KASAN_SHADOW_SCALE_SIZE (== 16).
2. On each kmalloc KHWASAN generates a random tag, sets the shadow memory,
that corresponds to this object to this tag, and embeds this tag value
into the top byte of the returned pointer.
3. On each kfree KHWASAN poisons the shadow memory with a random tag to
allow detection of use-after-free bugs.
The rest of the logic of the hook implementation is very much similar to
the one provided by KASAN. KHWASAN saves allocation and free stack metadata
to the slab object the same was KASAN does this.
Signed-off-by: Andrey Konovalov <redacted>
---
mm/kasan/common.c | 82 +++++++++++++++++++++++++++++++++++-----------
mm/kasan/kasan.h | 8 +++++
mm/kasan/khwasan.c | 40 ++++++++++++++++++++++
3 files changed, 111 insertions(+), 19 deletions(-)
@@ -440,7 +484,7 @@ void kasan_poison_kfree(void *ptr, unsigned long ip)page=virt_to_head_page(ptr);if(unlikely(!PageSlab(page))){-if(ptr!=page_address(page)){+if(reset_tag(ptr)!=page_address(page)){kasan_report_invalid_free(ptr,ip);return;}
@@ -453,7 +497,7 @@ void kasan_poison_kfree(void *ptr, unsigned long ip)voidkasan_kfree_large(void*ptr,unsignedlongip){-if(ptr!=page_address(virt_to_head_page(ptr)))+if(reset_tag(ptr)!=page_address(virt_to_head_page(ptr)))kasan_report_invalid_free(ptr,ip);/* The object will be poisoned by page_alloc. */}
KHWASAN inline instrumentation mode (which embeds checks of shadow memory
into the generated code, instead of inserting a callback) generates a brk
instruction when a tag mismatch is detected.
This commit add a KHWASAN brk handler, that decodes the immediate value
passed to the brk instructions (to extract information about the memory
access that triggered the mismatch), reads the register values (x0 contains
the guilty address) and reports the bug.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/include/asm/brk-imm.h | 2 +
arch/arm64/kernel/traps.c | 69 +++++++++++++++++++++++++++++++-
2 files changed, 69 insertions(+), 2 deletions(-)
@@ -775,7 +780,7 @@ static int bug_handler(struct pt_regs *regs, unsigned int esr)}/* If thread survives, skip over the BUG instruction and continue: */-arm64_skip_faulting_instruction(regs,AARCH64_INSN_SIZE);+__arm64_skip_faulting_instruction(regs,AARCH64_INSN_SIZE);returnDBG_HOOK_HANDLED;}
@@ -799,4 +861,7 @@ int __init early_brk64(unsigned long addr, unsigned int esr,void__inittrap_init(void){register_break_hook(&bug_break_hook);+#ifdef CONFIG_KASAN_HW+register_break_hook(&khwasan_break_hook);+#endif}
@@ -8,11 +8,19 @@ KernelAddressSANitizer (KASAN) is a dynamic memory error detector. It provides a fast and comprehensive solution for finding use-after-free and out-of-bounds bugs.-KASAN uses compile-time instrumentation for checking every memory access,-therefore you will need a GCC version 4.9.2 or later. GCC 5.0 or later is-required for detection of out-of-bounds accesses to stack or global variables.+KASAN has two modes: classic KASAN (a classic version, similar to user space+ASan) and KHWASAN (a version based on memory tagging, similar to user space+HWASan).-Currently KASAN is supported only for the x86_64 and arm64 architectures.+KASAN uses compile-time instrumentation to insert validity checks before every+memory access, and therefore requires a compiler version that supports that.+For classic KASAN you need GCC version 4.9.2 or later. GCC 5.0 or later is+required for detection of out-of-bounds accesses on stack and global variables.+KHWASAN in turns is only supported in clang and requires revision 330044 or+later.++Currently classic KASAN is supported for the x86_64, arm64 and xtensa+architectures, and KHWASAN is supported only for arm64. Usage -----
@@ -21,12 +29,14 @@ To enable KASAN configure kernel with:: CONFIG_KASAN = y-and choose between CONFIG_KASAN_OUTLINE and CONFIG_KASAN_INLINE. Outline and-inline are compiler instrumentation types. The former produces smaller binary-the latter is 1.1 - 2 times faster. Inline instrumentation requires a GCC+and choose between CONFIG_KASAN_GENERIC (to enable classic KASAN) and+CONFIG_KASAN_HW (to enabled KHWASAN). You also need to choose choose between+CONFIG_KASAN_OUTLINE and CONFIG_KASAN_INLINE. Outline and inline are compiler+instrumentation types. The former produces smaller binary while the latter is+1.1 - 2 times faster. For classic KASAN inline instrumentation requires GCC version 5.0 or later.-KASAN works with both SLUB and SLAB memory allocators.+Both KASAN modes work with both SLUB and SLAB memory allocators. For better bug detection and nicer reporting, enable CONFIG_STACKTRACE. To disable instrumentation for specific files or directories, add a line
@@ -43,85 +53,80 @@ similar to the following to the respective kernel Makefile: Error reports ~~~~~~~~~~~~~-A typical out of bounds access report looks like this::+A typical out-of-bounds access classic KASAN report looks like this:: ==================================================================- BUG: AddressSanitizer: out of bounds access in kmalloc_oob_right+0x65/0x75 [test_kasan] at addr ffff8800693bc5d3- Write of size 1 by task modprobe/1689- =============================================================================- BUG kmalloc-128 (Not tainted): kasan error- ------------------------------------------------------------------------------- Disabling lock debugging due to kernel taint- INFO: Allocated in kmalloc_oob_right+0x3d/0x75 [test_kasan] age=0 cpu=0 pid=1689- __slab_alloc+0x4b4/0x4f0- kmem_cache_alloc_trace+0x10b/0x190- kmalloc_oob_right+0x3d/0x75 [test_kasan]- init_module+0x9/0x47 [test_kasan]- do_one_initcall+0x99/0x200- load_module+0x2cb3/0x3b20- SyS_finit_module+0x76/0x80- system_call_fastpath+0x12/0x17- INFO: Slab 0xffffea0001a4ef00 objects=17 used=7 fp=0xffff8800693bd728 flags=0x100000000004080- INFO: Object 0xffff8800693bc558 @offset=1368 fp=0xffff8800693bc720-- Bytes b4 ffff8800693bc548: 00 00 00 00 00 00 00 00 5a 5a 5a 5a 5a 5a 5a 5a ........ZZZZZZZZ- Object ffff8800693bc558: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b kkkkkkkkkkkkkkkk- Object ffff8800693bc568: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b kkkkkkkkkkkkkkkk- Object ffff8800693bc578: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b kkkkkkkkkkkkkkkk- Object ffff8800693bc588: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b kkkkkkkkkkkkkkkk- Object ffff8800693bc598: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b kkkkkkkkkkkkkkkk- Object ffff8800693bc5a8: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b kkkkkkkkkkkkkkkk- Object ffff8800693bc5b8: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b kkkkkkkkkkkkkkkk- Object ffff8800693bc5c8: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b a5 kkkkkkkkkkkkkkk.- Redzone ffff8800693bc5d8: cc cc cc cc cc cc cc cc ........- Padding ffff8800693bc718: 5a 5a 5a 5a 5a 5a 5a 5a ZZZZZZZZ- CPU: 0 PID: 1689 Comm: modprobe Tainted: G B 3.18.0-rc1-mm1+ #98- Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.7.5-0-ge51488c-20140602_164612-nilsson.home.kraxel.org 04/01/2014- ffff8800693bc000 0000000000000000 ffff8800693bc558 ffff88006923bb78- ffffffff81cc68ae 00000000000000f3 ffff88006d407600 ffff88006923bba8- ffffffff811fd848 ffff88006d407600 ffffea0001a4ef00 ffff8800693bc558+ BUG: KASAN: slab-out-of-bounds in kmalloc_oob_right+0xa8/0xbc [test_kasan]+ Write of size 1 at addr ffff8800696f3d3b by task insmod/2734++ CPU: 0 PID: 2734 Comm: insmod Not tainted 4.15.0+ #98+ Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1 04/01/2014 Call Trace:- [<ffffffff81cc68ae>] dump_stack+0x46/0x58- [<ffffffff811fd848>] print_trailer+0xf8/0x160- [<ffffffffa00026a7>] ? kmem_cache_oob+0xc3/0xc3 [test_kasan]- [<ffffffff811ff0f5>] object_err+0x35/0x40- [<ffffffffa0002065>] ? kmalloc_oob_right+0x65/0x75 [test_kasan]- [<ffffffff8120b9fa>] kasan_report_error+0x38a/0x3f0- [<ffffffff8120a79f>] ? kasan_poison_shadow+0x2f/0x40- [<ffffffff8120b344>] ? kasan_unpoison_shadow+0x14/0x40- [<ffffffff8120a79f>] ? kasan_poison_shadow+0x2f/0x40- [<ffffffffa00026a7>] ? kmem_cache_oob+0xc3/0xc3 [test_kasan]- [<ffffffff8120a995>] __asan_store1+0x75/0xb0- [<ffffffffa0002601>] ? kmem_cache_oob+0x1d/0xc3 [test_kasan]- [<ffffffffa0002065>] ? kmalloc_oob_right+0x65/0x75 [test_kasan]- [<ffffffffa0002065>] kmalloc_oob_right+0x65/0x75 [test_kasan]- [<ffffffffa00026b0>] init_module+0x9/0x47 [test_kasan]- [<ffffffff810002d9>] do_one_initcall+0x99/0x200- [<ffffffff811e4e5c>] ? __vunmap+0xec/0x160- [<ffffffff81114f63>] load_module+0x2cb3/0x3b20- [<ffffffff8110fd70>] ? m_show+0x240/0x240- [<ffffffff81115f06>] SyS_finit_module+0x76/0x80- [<ffffffff81cd3129>] system_call_fastpath+0x12/0x17+ __dump_stack lib/dump_stack.c:17+ dump_stack+0x83/0xbc lib/dump_stack.c:53+ print_address_description+0x73/0x280 mm/kasan/report.c:254+ kasan_report_error mm/kasan/report.c:352+ kasan_report+0x10e/0x220 mm/kasan/report.c:410+ __asan_report_store1_noabort+0x17/0x20 mm/kasan/report.c:505+ kmalloc_oob_right+0xa8/0xbc [test_kasan] lib/test_kasan.c:42+ kmalloc_tests_init+0x16/0x769 [test_kasan]+ do_one_initcall+0x9e/0x240 init/main.c:832+ do_init_module+0x1b6/0x542 kernel/module.c:3462+ load_module+0x6042/0x9030 kernel/module.c:3786+ SYSC_init_module+0x18f/0x1c0 kernel/module.c:3858+ SyS_init_module+0x9/0x10 kernel/module.c:3841+ do_syscall_64+0x198/0x480 arch/x86/entry/common.c:287+ entry_SYSCALL_64_after_hwframe+0x21/0x86 arch/x86/entry/entry_64.S:251+ RIP: 0033:0x7fdd79df99da+ RSP: 002b:00007fff2229bdf8 EFLAGS: 00000202 ORIG_RAX: 00000000000000af+ RAX: ffffffffffffffda RBX: 000055c408121190 RCX: 00007fdd79df99da+ RDX: 00007fdd7a0b8f88 RSI: 0000000000055670 RDI: 00007fdd7a47e000+ RBP: 000055c4081200b0 R08: 0000000000000003 R09: 0000000000000000+ R10: 00007fdd79df5d0a R11: 0000000000000202 R12: 00007fdd7a0b8f88+ R13: 000055c408120090 R14: 0000000000000000 R15: 0000000000000000++ Allocated by task 2734:+ save_stack+0x43/0xd0 mm/kasan/common.c:176+ set_track+0x20/0x30 mm/kasan/common.c:188+ kasan_kmalloc+0x9a/0xc0 mm/kasan/kasan.c:372+ kmem_cache_alloc_trace+0xcd/0x1a0 mm/slub.c:2761+ kmalloc ./include/linux/slab.h:512+ kmalloc_oob_right+0x56/0xbc [test_kasan] lib/test_kasan.c:36+ kmalloc_tests_init+0x16/0x769 [test_kasan]+ do_one_initcall+0x9e/0x240 init/main.c:832+ do_init_module+0x1b6/0x542 kernel/module.c:3462+ load_module+0x6042/0x9030 kernel/module.c:3786+ SYSC_init_module+0x18f/0x1c0 kernel/module.c:3858+ SyS_init_module+0x9/0x10 kernel/module.c:3841+ do_syscall_64+0x198/0x480 arch/x86/entry/common.c:287+ entry_SYSCALL_64_after_hwframe+0x21/0x86 arch/x86/entry/entry_64.S:251++ The buggy address belongs to the object at ffff8800696f3cc0+ which belongs to the cache kmalloc-128 of size 128+ The buggy address is located 123 bytes inside of+ 128-byte region [ffff8800696f3cc0, ffff8800696f3d40)+ The buggy address belongs to the page:+ page:ffffea0001a5bcc0 count:1 mapcount:0 mapping: (null) index:0x0+ flags: 0x100000000000100(slab)+ raw: 0100000000000100 0000000000000000 0000000000000000 0000000180150015+ raw: ffffea0001a8ce40 0000000300000003 ffff88006d001640 0000000000000000+ page dumped because: kasan: bad access detected+ Memory state around the buggy address:- ffff8800693bc300: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc- ffff8800693bc380: fc fc 00 00 00 00 00 00 00 00 00 00 00 00 00 fc- ffff8800693bc400: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc- ffff8800693bc480: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc- ffff8800693bc500: fc fc fc fc fc fc fc fc fc fc fc 00 00 00 00 00- >ffff8800693bc580: 00 00 00 00 00 00 00 00 00 00 03 fc fc fc fc fc- ^- ffff8800693bc600: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc- ffff8800693bc680: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc- ffff8800693bc700: fc fc fc fc fb fb fb fb fb fb fb fb fb fb fb fb- ffff8800693bc780: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb- ffff8800693bc800: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb+ ffff8800696f3c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fc+ ffff8800696f3c80: fc fc fc fc fc fc fc fc 00 00 00 00 00 00 00 00+ >ffff8800696f3d00: 00 00 00 00 00 00 00 03 fc fc fc fc fc fc fc fc+ ^+ ffff8800696f3d80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fc fc+ ffff8800696f3e00: fc fc fc fc fc fc fc fc fb fb fb fb fb fb fb fb ==================================================================-The header of the report discribe what kind of bug happened and what kind of-access caused it. It's followed by the description of the accessed slub object-(see 'SLUB Debug output' section in Documentation/vm/slub.rst for details) and-the description of the accessed memory page.+The header of the report provides a short summary of what kind of bug happened+and what kind of access caused it. It's followed by a stack trace of the bad+access, a stack trace of where the accessed memory was allocated (in case bad+access happens on a slab object), and a stack trace of where the object was+freed (in case of a use-after-free bug report). Next comes a description of+the accessed slab object and information about the accessed memory page. In the last section the report shows memory state around the accessed address. Reading this part requires some understanding of how KASAN works.
@@ -138,18 +143,24 @@ inaccessible memory like redzones or freed memory (see mm/kasan/kasan.h). In the report above the arrows point to the shadow byte 03, which means that the accessed address is partially accessible.+For KHWASAN this last report section shows the memory tags around the accessed+address (see Implementation details section).+ Implementation details ----------------------+Classic KASAN+~~~~~~~~~~~~~+ From a high level, our approach to memory error detection is similar to that of kmemcheck: use shadow memory to record whether each byte of memory is safe-to access, and use compile-time instrumentation to check shadow memory on each-memory access.+to access, and use compile-time instrumentation to insert checks of shadow+memory on each memory access.-AddressSanitizer dedicates 1/8 of kernel memory to its shadow memory-(e.g. 16TB to cover 128TB on x86_64) and uses direct mapping with a scale and-offset to translate a memory address to its corresponding shadow address.+Classic KASAN dedicates 1/8th of kernel memory to its shadow memory (e.g. 16TB+to cover 128TB on x86_64) and uses direct mapping with a scale and offset to+translate a memory address to its corresponding shadow address. Here is the function which translates an address to its corresponding shadow address::
@@ -162,12 +173,34 @@ address:: where ``KASAN_SHADOW_SCALE_SHIFT = 3``.-Compile-time instrumentation used for checking memory accesses. Compiler inserts-function calls (__asan_load*(addr), __asan_store*(addr)) before each memory-access of size 1, 2, 4, 8 or 16. These functions check whether memory access is-valid or not by checking corresponding shadow memory.+Compile-time instrumentation is used to insert memory access checks. Compiler+inserts function calls (__asan_load*(addr), __asan_store*(addr)) before each+memory access of size 1, 2, 4, 8 or 16. These functions check whether memory+access is valid or not by checking corresponding shadow memory. GCC 5.0 has possibility to perform inline instrumentation. Instead of making function calls GCC directly inserts the code to check the shadow memory. This option significantly enlarges kernel but it gives x1.1-x2 performance boost over outline instrumented kernel.++KHWASAN+~~~~~~~++KHWASAN uses the Top Byte Ignore (TBI) feature of modern arm64 CPUs to store+a pointer tag in the top byte of kernel pointers. KHWASAN also uses shadow+memory to store memory tags associated with each 16-byte memory cell (therefore+it dedicates 1/16th of the kernel memory for shadow memory).++On each memory allocation KHWASAN generates a random tag, tags allocated memory+with this tag, and embeds this tag into the returned pointer. KHWASAN uses+compile-time instrumentation to insert checks before each memory access. These+checks make sure that tag of the memory that is being accessed is equal to tag+of the pointer that is used to access this memory. In case of a tag mismatch+KHWASAN prints a bug report.++KHWASAN also has two instrumentation modes (outline, that emits callbacks to+check memory accesses; and inline, that performs the shadow memory checks+inline). With outline instrumentation mode, a bug report is simply printed+from the function that performs the access check. With inline instrumentation+a brk instruction is emitted by the compiler, and a dedicated brk handler is+used to print KHWASAN reports.
KWHASAN doesn't check memory accesses through pointers tagged with 0xff.
When page_address is used to get pointer to memory that corresponds to
some page, the tag of the resulting pointer gets set to 0xff, even though
the allocated memory might have been tagged differently.
For slab pages it's impossible to recover the correct tag to return from
page_address, since the page might contain multiple slab objects tagged
with different values, and we can't know in advance which one of them is
going to get accessed. For non slab pages however, we can recover the tag
in page_address, since the whole page was marked with the same tag.
This patch adds tagging to non slab memory allocated with pagealloc. To
set the tag of the pointer returned from page_address, the tag gets stored
to page->flags when the memory gets allocated.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/include/asm/memory.h | 10 ++++++++++
include/linux/mm.h | 29 +++++++++++++++++++++++++++++
include/linux/page-flags-layout.h | 10 ++++++++++
mm/cma.c | 11 +++++++++++
mm/kasan/common.c | 17 +++++++++++++++--
mm/page_alloc.c | 1 +
6 files changed, 76 insertions(+), 2 deletions(-)
@@ -484,7 +497,7 @@ void kasan_poison_kfree(void *ptr, unsigned long ip)page=virt_to_head_page(ptr);if(unlikely(!PageSlab(page))){-if(reset_tag(ptr)!=page_address(page)){+if(ptr!=page_address(page)){kasan_report_invalid_free(ptr,ip);return;}
@@ -497,7 +510,7 @@ void kasan_poison_kfree(void *ptr, unsigned long ip)voidkasan_kfree_large(void*ptr,unsignedlongip){-if(reset_tag(ptr)!=page_address(virt_to_head_page(ptr)))+if(ptr!=page_address(virt_to_head_page(ptr)))kasan_report_invalid_free(ptr,ip);/* The object will be poisoned by page_alloc. */}
KHWASAN uses the Top Byte Ignore feature of arm64 CPUs to store a pointer
tag in the top byte of each pointer. This commit enables the TCR_TBI1 bit,
which enables Top Byte Ignore for the kernel, when KHWASAN is used.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/include/asm/pgtable-hwdef.h | 1 +
arch/arm64/mm/proc.S | 8 +++++++-
2 files changed, 8 insertions(+), 1 deletion(-)
This commit adds a few helper functions, that are meant to be used to
work with tags embedded in the top byte of kernel pointers: to set, to
get or to reset (set to 0xff) the top byte.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/mm/kasan_init.c | 2 ++
include/linux/kasan.h | 29 +++++++++++++++++
mm/kasan/kasan.h | 55 ++++++++++++++++++++++++++++++++
mm/kasan/khwasan.c | 65 ++++++++++++++++++++++++++++++++++++++
4 files changed, 151 insertions(+)
@@ -250,6 +250,8 @@ void __init kasan_init(void)memset(kasan_zero_page,KASAN_SHADOW_INIT,PAGE_SIZE);cpu_replace_ttbr1(lm_alias(swapper_pg_dir));+khwasan_init();+/* At this point kasan is fully initialized. Enable error messages */init_task.kasan_depth=0;pr_info("KernelAddressSanitizer initialized\n");
KWHASAN uses 1 shadow byte for 16 bytes of kernel memory, so it requires
1/16th of the kernel virtual address space for the shadow memory.
This commit sets KASAN_SHADOW_SCALE_SHIFT to 4 when KHWASAN is enabled.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/Makefile | 2 +-
arch/arm64/include/asm/memory.h | 13 +++++++++----
2 files changed, 10 insertions(+), 5 deletions(-)
KHWASAN will change the value of the top byte of pointers returned from the
kernel allocation functions (such as kmalloc). This patch updates KASAN
hooks signatures and their usage in SLAB and SLUB code to reflect that.
Signed-off-by: Andrey Konovalov <redacted>
---
include/linux/kasan.h | 34 +++++++++++++++++++++++-----------
mm/kasan/kasan.c | 24 ++++++++++++++----------
mm/slab.c | 12 ++++++------
mm/slab.h | 2 +-
mm/slab_common.c | 4 ++--
mm/slub.c | 15 +++++++--------
6 files changed, 53 insertions(+), 38 deletions(-)
From: Andrew Morton <akpm@linux-foundation.org> Date: 2018-09-05 21:10:38
On Wed, 29 Aug 2018 13:35:04 +0200 Andrey Konovalov [off-list ref] wrote:
This patchset adds a new mode to KASAN [1], which is called KHWASAN
(Kernel HardWare assisted Address SANitizer).
We're at v6 and there are no reviewed-by's or acked-by's to be seen.
Is that a fair commentary on what has been happening, or have people
been remiss in sending and gathering such things?
From: Nick Desaulniers <ndesaulniers@google.com> Date: 2018-09-05 21:56:01
On Wed, Sep 5, 2018 at 2:10 PM Andrew Morton [off-list ref] wrote:
On Wed, 29 Aug 2018 13:35:04 +0200 Andrey Konovalov [off-list ref] wrote:
quoted
This patchset adds a new mode to KASAN [1], which is called KHWASAN
(Kernel HardWare assisted Address SANitizer).
We're at v6 and there are no reviewed-by's or acked-by's to be seen.
Is that a fair commentary on what has been happening, or have people
been remiss in sending and gathering such things?
I'm anxious to use these for Pixel Android devices. Looks like the
series has been aggregating changes from valuable feedback. Maybe if
the ARM maintainers and KASAN maintainers could Ack or Nack these, we
could decide to merge these or what needs more work?
--
Thanks,
~Nick Desaulniers
From: Will Deacon <hidden> Date: 2018-09-06 10:05:31
On Wed, Sep 05, 2018 at 02:10:32PM -0700, Andrew Morton wrote:
On Wed, 29 Aug 2018 13:35:04 +0200 Andrey Konovalov [off-list ref] wrote:
quoted
This patchset adds a new mode to KASAN [1], which is called KHWASAN
(Kernel HardWare assisted Address SANitizer).
We're at v6 and there are no reviewed-by's or acked-by's to be seen.
Is that a fair commentary on what has been happening, or have people
been remiss in sending and gathering such things?
I still have concerns about the consequences of merging this as anything
other than a debug option [1]. Unfortunately, merging it as a debug option
defeats the whole point, so I think we need to spend more effort on developing
tools that can help us to find and fix the subtle bugs which will arise from
enabling tagged pointers in the kernel.
Will
[1] http://lists.infradead.org/pipermail/linux-arm-kernel/2018-August/596077.html
On Thu, Sep 6, 2018 at 12:05 PM, Will Deacon [off-list ref] wrote:
On Wed, Sep 05, 2018 at 02:10:32PM -0700, Andrew Morton wrote:
quoted
On Wed, 29 Aug 2018 13:35:04 +0200 Andrey Konovalov [off-list ref] wrote:
quoted
This patchset adds a new mode to KASAN [1], which is called KHWASAN
(Kernel HardWare assisted Address SANitizer).
We're at v6 and there are no reviewed-by's or acked-by's to be seen.
Is that a fair commentary on what has been happening, or have people
been remiss in sending and gathering such things?
I still have concerns about the consequences of merging this as anything
other than a debug option [1]. Unfortunately, merging it as a debug option
defeats the whole point, so I think we need to spend more effort on developing
tools that can help us to find and fix the subtle bugs which will arise from
enabling tagged pointers in the kernel.
I totally don't mind calling it a debug option. Do I need to somehow
specify it somewhere?
Why does it defeat the point? The point is to ease KASAN-like testing
on devices with limited memory.
From: Nick Desaulniers <ndesaulniers@google.com> Date: 2018-09-06 16:40:06
On Thu, Sep 6, 2018 at 4:06 AM Andrey Konovalov [off-list ref] wrote:
On Thu, Sep 6, 2018 at 12:05 PM, Will Deacon [off-list ref] wrote:
quoted
On Wed, Sep 05, 2018 at 02:10:32PM -0700, Andrew Morton wrote:
quoted
On Wed, 29 Aug 2018 13:35:04 +0200 Andrey Konovalov [off-list ref] wrote:
quoted
This patchset adds a new mode to KASAN [1], which is called KHWASAN
(Kernel HardWare assisted Address SANitizer).
We're at v6 and there are no reviewed-by's or acked-by's to be seen.
Is that a fair commentary on what has been happening, or have people
been remiss in sending and gathering such things?
I still have concerns about the consequences of merging this as anything
other than a debug option [1]. Unfortunately, merging it as a debug option
defeats the whole point, so I think we need to spend more effort on developing
tools that can help us to find and fix the subtle bugs which will arise from
enabling tagged pointers in the kernel.
I totally don't mind calling it a debug option. Do I need to somehow
specify it somewhere?
Why does it defeat the point? The point is to ease KASAN-like testing
on devices with limited memory.
I don't disagree with using it strictly for debug. When I say I want
the series for Pixel phones, I should have been clearer that my intent
is for a limited pool of internal testers to walk around with KHWASAN
enabled devices; not general end users. It's hard enough today to get
anyone to test KASAN/ASAN on their "daily driver" due to the memory
usage and resulting performance.
We don't ship KASAN or KUBSAN on by default to end users (nor plan
to); it's used strictly for fuzzing through syzkaller (or by brave
"dogfooders" on the internal kernel teams). KHWASAN would let these
dogfooders go from being brave to fearless.
--
Thanks,
~Nick Desaulniers
Andrey Konovalov (18):
khwasan, mm: change kasan hooks signatures
khwasan: move common kasan and khwasan code to common.c
khwasan: add CONFIG_KASAN_GENERIC and CONFIG_KASAN_HW
khwasan, arm64: adjust shadow size for CONFIG_KASAN_HW
khwasan: initialize shadow to 0xff
khwasan, arm64: untag virt address in __kimg_to_phys and
_virt_addr_is_linear
khwasan: add tag related helper functions
khwasan: preassign tags to objects with ctors or SLAB_TYPESAFE_BY_RCU
khwasan, arm64: fix up fault handling logic
khwasan, arm64: enable top byte ignore for the kernel
khwasan, mm: perform untagged pointers comparison in krealloc
khwasan: split out kasan_report.c from report.c
khwasan: add bug reporting routines
khwasan: add hooks implementation
khwasan, arm64: add brk handler for inline instrumentation
khwasan, mm, arm64: tag non slab memory allocated via pagealloc
khwasan: update kasan documentation
kasan: add SPDX-License-Identifier mark to source files
Aside from nit in 16/18 patch looks fine for me.
Reviewed-by: Andrey Ryabinin <redacted>
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
quoted hunk
This commit splits the current CONFIG_KASAN config option into two:
1. CONFIG_KASAN_GENERIC, that enables the generic software-only KASAN
version (the one that exists now);
2. CONFIG_KASAN_HW, that enables KHWASAN.
With CONFIG_KASAN_HW enabled, compiler options are changed to instrument
kernel files wiht -fsantize=hwaddress (except the ones for which
KASAN_SANITIZE := n is set).
Both CONFIG_KASAN_GENERIC and CONFIG_KASAN_HW support both
CONFIG_KASAN_INLINE and CONFIG_KASAN_OUTLINE instrumentation modes.
This commit also adds empty placeholder (for now) implementation of
KHWASAN specific hooks inserted by the compiler and adjusts common hooks
implementation to compile correctly with each of the config options.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/Kconfig | 1 +
include/linux/compiler-clang.h | 3 +-
include/linux/compiler-gcc.h | 4 ++
include/linux/compiler.h | 3 +-
include/linux/kasan.h | 16 +++++--
lib/Kconfig.kasan | 77 ++++++++++++++++++++++++++--------
mm/kasan/Makefile | 6 ++-
mm/kasan/kasan.h | 3 +-
mm/kasan/khwasan.c | 75 +++++++++++++++++++++++++++++++++
mm/slub.c | 2 +-
scripts/Makefile.kasan | 27 +++++++++++-
11 files changed, 188 insertions(+), 29 deletions(-)
create mode 100644 mm/kasan/khwasan.c
It seems that it would be better to have just 1 attribute for both types.
Currently __no_sanitize_address is used just in a single place. But if
it ever used more, people will need to always spell both which looks
unnecessary, or, worse will only fix asan but forget about khwasan.
If we do just:
#define __no_sanitize_address __attribute__((no_sanitize("address",
"hwaddress")))
Then we don't need any changes in compiler-gcc.h nor in compiler.h,
and no chance or forgetting one of them.
quoted hunk
/*
* Not all versions of clang implement the the type-generic versions
Perhaps also give link to Documentation/dev-tools/kasan.rst while we are here.
+
+choice
+ prompt "KASAN mode"
+ depends on KASAN
+ default KASAN_GENERIC
+ help
+ KASAN has two modes: KASAN (a classic version, similar to userspace
In these few sentences we call the old mode with 3 different terms:
"generic", "classic" and "KASAN" :)
This is somewhat confusing. Let's call it "generic" throughout (here
and in the docs patch). "Generic" as in "supported on multiple arch
and not-dependent on hardware features". "Classic" makes sense for
people who knew KASAN before, but for future readers in won't make
sense.
+ ASan, enabled with CONFIG_KASAN_GENERIC) and KHWASAN (a version
+ based on pointer tagging, only for arm64, similar to userspace
+ HWASan, enabled with CONFIG_KASAN_HW).
+
+config KASAN_GENERIC
+ bool "KASAN: the generic mode"
depends on (SLUB && SYSFS) || (SLAB && !DEBUG_SLAB)
select SLUB_DEBUG if SLUB
select CONSTRUCTORS
select STACKDEPOT
help
- Enables kernel address sanitizer - runtime memory debugger,
- designed to find out-of-bounds accesses and use-after-free bugs.
- This is strictly a debugging feature and it requires a gcc version
- of 4.9.2 or later. Detection of out of bounds accesses to stack or
- global variables requires gcc 5.0 or later.
- This feature consumes about 1/8 of available memory and brings about
- ~x3 performance slowdown.
+ Enables the generic mode of KASAN.
+ This is strictly a debugging feature and it requires a GCC version
+ of 4.9.2 or later. Detection of out-of-bounds accesses to stack or
+ global variables requires GCC 5.0 or later.
+ This mode consumes about 1/8 of available memory at kernel start
+ and introduces an overhead of ~x1.5 for the rest of the allocations.
+ The performance slowdown is ~x3.
For better error detection enable CONFIG_STACKTRACE.
- Currently CONFIG_KASAN doesn't work with CONFIG_DEBUG_SLAB
+ Currently CONFIG_KASAN_GENERIC doesn't work with CONFIG_DEBUG_SLAB
(the resulting kernel does not boot).
+if HAVE_ARCH_KASAN_HW
This choice looks somewhat weird on non-arm64. It's kinda a choice
menu, but one can't really choose anything. Should we put the whole
choice under HAVE_ARCH_KASAN_HW, and just select KASAN_GENERIC
otherwise? I don't know what't the practice here. Andrey R?
quoted hunk
+config KASAN_HW
+ bool "KHWASAN: the hardware assisted mode"
+ depends on (SLUB && SYSFS) || (SLAB && !DEBUG_SLAB)
+ select SLUB_DEBUG if SLUB
+ select CONSTRUCTORS
+ select STACKDEPOT
+ help
+ Enabled KHWASAN (KASAN mode based on pointer tagging).
+ This mode requires Top Byte Ignore support by the CPU and therefore
+ only supported for arm64.
+ This feature requires clang revision 330044 or later.
+ This mode consumes about 1/16 of available memory at kernel start
+ and introduces an overhead of ~20% for the rest of the allocations.
+ For better error detection enable CONFIG_STACKTRACE.
+ Currently CONFIG_KASAN_HW doesn't work with CONFIG_DEBUG_SLAB
+ (the resulting kernel does not boot).
+
+endif
+
+endchoice
+
config KASAN_EXTRA
- bool "KAsan: extra checks"
- depends on KASAN && DEBUG_KERNEL && !COMPILE_TEST
+ bool "KASAN: extra checks"
+ depends on KASAN_GENERIC && DEBUG_KERNEL && !COMPILE_TEST
help
- This enables further checks in the kernel address sanitizer, for now
- it only includes the address-use-after-scope check that can lead
- to excessive kernel stack usage, frame size warnings and longer
- compile time.
+ This enables further checks in KASAN, for now it only includes the
+ address-use-after-scope check that can lead to excessive kernel
+ stack usage, frame size warnings and longer compile time.
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81715 has more
@@ -53,16 +94,16 @@ config KASAN_INLINE memory accesses. This is faster than outline (in some workloads it gives about x2 boost over outline instrumentation), but make kernel's .text size much bigger.- This requires a gcc version of 5.0 or later.+ For CONFIG_KASAN_GENERIC this requires GCC 5.0 or later. endchoice config TEST_KASAN- tristate "Module for testing kasan for bug detection"+ tristate "Module for testing KASAN for bug detection" depends on m && KASAN help This is a test module doing various nasty things like out of bounds accesses, use after free. It is useful for testing- kernel debugging features like kernel address sanitizer.+ kernel debugging features like KASAN. endif
On Wed, Sep 12, 2018 at 4:47 PM, Dmitry Vyukov [off-list ref] wrote:
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
quoted
This commit splits the current CONFIG_KASAN config option into two:
1. CONFIG_KASAN_GENERIC, that enables the generic software-only KASAN
version (the one that exists now);
2. CONFIG_KASAN_HW, that enables KHWASAN.
With CONFIG_KASAN_HW enabled, compiler options are changed to instrument
kernel files wiht -fsantize=hwaddress (except the ones for which
KASAN_SANITIZE := n is set).
Both CONFIG_KASAN_GENERIC and CONFIG_KASAN_HW support both
CONFIG_KASAN_INLINE and CONFIG_KASAN_OUTLINE instrumentation modes.
This commit also adds empty placeholder (for now) implementation of
KHWASAN specific hooks inserted by the compiler and adjusts common hooks
implementation to compile correctly with each of the config options.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/Kconfig | 1 +
include/linux/compiler-clang.h | 3 +-
include/linux/compiler-gcc.h | 4 ++
include/linux/compiler.h | 3 +-
include/linux/kasan.h | 16 +++++--
lib/Kconfig.kasan | 77 ++++++++++++++++++++++++++--------
mm/kasan/Makefile | 6 ++-
mm/kasan/kasan.h | 3 +-
mm/kasan/khwasan.c | 75 +++++++++++++++++++++++++++++++++
mm/slub.c | 2 +-
scripts/Makefile.kasan | 27 +++++++++++-
11 files changed, 188 insertions(+), 29 deletions(-)
create mode 100644 mm/kasan/khwasan.c
It seems that it would be better to have just 1 attribute for both types.
Currently __no_sanitize_address is used just in a single place. But if
it ever used more, people will need to always spell both which looks
unnecessary, or, worse will only fix asan but forget about khwasan.
If we do just:
#define __no_sanitize_address __attribute__((no_sanitize("address",
"hwaddress")))
Then we don't need any changes in compiler-gcc.h nor in compiler.h,
and no chance or forgetting one of them.
quoted
/*
* Not all versions of clang implement the the type-generic versions
Perhaps also give link to Documentation/dev-tools/kasan.rst while we are here.
quoted
+
+choice
+ prompt "KASAN mode"
+ depends on KASAN
+ default KASAN_GENERIC
+ help
+ KASAN has two modes: KASAN (a classic version, similar to userspace
In these few sentences we call the old mode with 3 different terms:
"generic", "classic" and "KASAN" :)
This is somewhat confusing. Let's call it "generic" throughout (here
and in the docs patch). "Generic" as in "supported on multiple arch
and not-dependent on hardware features". "Classic" makes sense for
people who knew KASAN before, but for future readers in won't make
sense.
quoted
+ ASan, enabled with CONFIG_KASAN_GENERIC) and KHWASAN (a version
+ based on pointer tagging, only for arm64, similar to userspace
+ HWASan, enabled with CONFIG_KASAN_HW).
+
+config KASAN_GENERIC
+ bool "KASAN: the generic mode"
depends on (SLUB && SYSFS) || (SLAB && !DEBUG_SLAB)
select SLUB_DEBUG if SLUB
select CONSTRUCTORS
select STACKDEPOT
help
- Enables kernel address sanitizer - runtime memory debugger,
- designed to find out-of-bounds accesses and use-after-free bugs.
- This is strictly a debugging feature and it requires a gcc version
- of 4.9.2 or later. Detection of out of bounds accesses to stack or
- global variables requires gcc 5.0 or later.
- This feature consumes about 1/8 of available memory and brings about
- ~x3 performance slowdown.
+ Enables the generic mode of KASAN.
+ This is strictly a debugging feature and it requires a GCC version
+ of 4.9.2 or later. Detection of out-of-bounds accesses to stack or
+ global variables requires GCC 5.0 or later.
+ This mode consumes about 1/8 of available memory at kernel start
+ and introduces an overhead of ~x1.5 for the rest of the allocations.
+ The performance slowdown is ~x3.
For better error detection enable CONFIG_STACKTRACE.
- Currently CONFIG_KASAN doesn't work with CONFIG_DEBUG_SLAB
+ Currently CONFIG_KASAN_GENERIC doesn't work with CONFIG_DEBUG_SLAB
(the resulting kernel does not boot).
+if HAVE_ARCH_KASAN_HW
This choice looks somewhat weird on non-arm64. It's kinda a choice
menu, but one can't really choose anything. Should we put the whole
choice under HAVE_ARCH_KASAN_HW, and just select KASAN_GENERIC
otherwise? I don't know what't the practice here. Andrey R?
quoted
+config KASAN_HW
+ bool "KHWASAN: the hardware assisted mode"
Do we need a hyphen here? hardware-assisted?
quoted
+ depends on (SLUB && SYSFS) || (SLAB && !DEBUG_SLAB)
+ select SLUB_DEBUG if SLUB
+ select CONSTRUCTORS
+ select STACKDEPOT
+ help
+ Enabled KHWASAN (KASAN mode based on pointer tagging).
+ This mode requires Top Byte Ignore support by the CPU and therefore
+ only supported for arm64.
+ This feature requires clang revision 330044 or later.
+ This mode consumes about 1/16 of available memory at kernel start
+ and introduces an overhead of ~20% for the rest of the allocations.
+ For better error detection enable CONFIG_STACKTRACE.
+ Currently CONFIG_KASAN_HW doesn't work with CONFIG_DEBUG_SLAB
+ (the resulting kernel does not boot).
+
+endif
+
+endchoice
+
config KASAN_EXTRA
- bool "KAsan: extra checks"
- depends on KASAN && DEBUG_KERNEL && !COMPILE_TEST
+ bool "KASAN: extra checks"
+ depends on KASAN_GENERIC && DEBUG_KERNEL && !COMPILE_TEST
help
- This enables further checks in the kernel address sanitizer, for now
- it only includes the address-use-after-scope check that can lead
- to excessive kernel stack usage, frame size warnings and longer
- compile time.
+ This enables further checks in KASAN, for now it only includes the
+ address-use-after-scope check that can lead to excessive kernel
+ stack usage, frame size warnings and longer compile time.
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81715 has more
@@ -53,16 +94,16 @@ config KASAN_INLINE memory accesses. This is faster than outline (in some workloads it gives about x2 boost over outline instrumentation), but make kernel's .text size much bigger.- This requires a gcc version of 5.0 or later.+ For CONFIG_KASAN_GENERIC this requires GCC 5.0 or later. endchoice config TEST_KASAN- tristate "Module for testing kasan for bug detection"+ tristate "Module for testing KASAN for bug detection" depends on m && KASAN help This is a test module doing various nasty things like out of bounds accesses, use after free. It is useful for testing- kernel debugging features like kernel address sanitizer.+ kernel debugging features like KASAN. endif
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
quoted hunk
KWHASAN uses 1 shadow byte for 16 bytes of kernel memory, so it requires
1/16th of the kernel virtual address space for the shadow memory.
This commit sets KASAN_SHADOW_SCALE_SHIFT to 4 when KHWASAN is enabled.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/Makefile | 2 +-
arch/arm64/include/asm/memory.h | 13 +++++++++----
2 files changed, 10 insertions(+), 5 deletions(-)
I am somewhat confused by the terminology.
"KASAN" is not actually "CONFIG_KASAN" below, it is actually
"CONFIG_KASAN_GENERIC". While "KHWASAN" translates to "KASAN_HW" few
lines later.
I think we need some consistent terminology for comments and config
names until it's too late.
+ * space for the shadow region respectively. They can bloat the stack
+ * significantly, so double the (minimum) stack size when they are in use.
*/
-#ifdef CONFIG_KASAN
+#ifdef CONFIG_KASAN_GENERIC
#define KASAN_SHADOW_SCALE_SHIFT 3
+#endif
+#ifdef CONFIG_KASAN_HW
+#define KASAN_SHADOW_SCALE_SHIFT 4
+#endif
+#ifdef CONFIG_KASAN
#define KASAN_SHADOW_SIZE (UL(1) << (VA_BITS - KASAN_SHADOW_SCALE_SHIFT))
#define KASAN_THREAD_SHIFT 1
#else
--
2.19.0.rc0.228.g281dcd1b4d0-goog
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
quoted hunk
This commit adds a few helper functions, that are meant to be used to
work with tags embedded in the top byte of kernel pointers: to set, to
get or to reset (set to 0xff) the top byte.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/mm/kasan_init.c | 2 ++
include/linux/kasan.h | 29 +++++++++++++++++
mm/kasan/kasan.h | 55 ++++++++++++++++++++++++++++++++
mm/kasan/khwasan.c | 65 ++++++++++++++++++++++++++++++++++++++
4 files changed, 151 insertions(+)
@@ -250,6 +250,8 @@ void __init kasan_init(void)memset(kasan_zero_page,KASAN_SHADOW_INIT,PAGE_SIZE);cpu_replace_ttbr1(lm_alias(swapper_pg_dir));+khwasan_init();+/* At this point kasan is fully initialized. Enable error messages */init_task.kasan_depth=0;pr_info("KernelAddressSanitizer initialized\n");
Can't we do this in the existing kasan_init_slab_obj() hook? It looks
like it should do exactly this -- allow any one-time initialization
for objects. We could extend it to accept index and return a new
pointer.
If that does not work for some reason, I would try to at least unify
the hook for slab/slub, e.g. pass idx=-1 from slub and then use
random_tag().
It also seems that we do preset tag for slab multiple times (from
slab_get_obj()). Using kasan_init_slab_obj() should resolve this too
(hopefully we don't call it multiple times).
+{
+ /*
+ * Since it's desirable to only call object contructors ones during
+ * slab allocation, we preassign tags to all such objects.
+ * Also preassign tags for SLAB_TYPESAFE_BY_RCU slabs to avoid
+ * use-after-free reports.
+ */
+ if (cache->ctor || cache->flags & SLAB_TYPESAFE_BY_RCU)
+ return set_tag(addr, random_tag());
+ return (void *)addr;
+}
+
+void *khwasan_preset_slab_tag(struct kmem_cache *cache, unsigned int idx,
+ const void *addr)
+{
+ /*
+ * See comment in khwasan_preset_slub_tag.
+ * For SLAB allocator we can't preassign tags randomly since the
+ * freelist is stored as an array of indexes instead of a linked
+ * list. Assign tags based on objects indexes, so that objects that
+ * are next to each other get different tags.
+ */
+ if (cache->ctor || cache->flags & SLAB_TYPESAFE_BY_RCU)
+ return set_tag(addr, (u8)idx);
+ return (void *)addr;
+}
+
void check_memory_region(unsigned long addr, size_t size, bool write,
unsigned long ret_ip)
{
--
2.19.0.rc0.228.g281dcd1b4d0-goog
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
quoted hunk
__kimg_to_phys (which is used by virt_to_phys) and _virt_addr_is_linear
(which is used by virt_addr_valid) assume that the top byte of the address
is 0xff, which isn't always the case with KHWASAN enabled.
The solution is to reset the tag in those macros.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/include/asm/memory.h | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
Wouldn't it be better to
#define KASAN_RESET_TAG(addr) addr
when CONFIG_KASAN_HW is not enabled, and then not duplicate the macros
below? That's what we do in kasan.h for all hooks.
I see that a subsequent patch duplicates yet another macro in this
file. While we could use:
#define __kimg_to_phys(addr) (KASAN_RESET_TAG(addr) - kimage_voffset)
with and without kasan. Duplicating them increases risk that somebody
will change only the non-kasan version but forget kasan version.
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
quoted hunk
An object constructor can initialize pointers within this objects based on
the address of the object. Since the object address might be tagged, we
need to assign a tag before calling constructor.
The implemented approach is to assign tags to objects with constructors
when a slab is allocated and call constructors once as usual. The
downside is that such object would always have the same tag when it is
reallocated, so we won't catch use-after-frees on it.
Also pressign tags for objects from SLAB_TYPESAFE_BY_RCU caches, since
they can be validy accessed after having been freed.
Signed-off-by: Andrey Konovalov <redacted>
---
mm/slab.c | 6 +++++-
mm/slub.c | 4 ++++
2 files changed, 9 insertions(+), 1 deletion(-)
@@ -1531,12 +1531,14 @@ static bool shuffle_freelist(struct kmem_cache *s, struct page *page)/* First entry is used as the base of the freelist */cur=next_freelist_entry(s,page,&pos,start,page_limit,freelist_count);+cur=khwasan_preset_slub_tag(s,cur);page->freelist=cur;for(idx=1;idx<page->objects;idx++){setup_object(s,page,cur);next=next_freelist_entry(s,page,&pos,start,page_limit,freelist_count);+next=khwasan_preset_slub_tag(s,next);set_freepointer(s,cur,next);cur=next;}
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
quoted hunk
KHWASAN inline instrumentation mode (which embeds checks of shadow memory
into the generated code, instead of inserting a callback) generates a brk
instruction when a tag mismatch is detected.
This commit add a KHWASAN brk handler, that decodes the immediate value
passed to the brk instructions (to extract information about the memory
access that triggered the mismatch), reads the register values (x0 contains
the guilty address) and reports the bug.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/include/asm/brk-imm.h | 2 +
arch/arm64/kernel/traps.c | 69 +++++++++++++++++++++++++++++++-
2 files changed, 69 insertions(+), 2 deletions(-)
@@ -775,7 +780,7 @@ static int bug_handler(struct pt_regs *regs, unsigned int esr)}/* If thread survives, skip over the BUG instruction and continue: */-arm64_skip_faulting_instruction(regs,AARCH64_INSN_SIZE);+__arm64_skip_faulting_instruction(regs,AARCH64_INSN_SIZE);returnDBG_HOOK_HANDLED;}
I am not following this part.
Slab accesses metadata. OK.
This is detected as bad access. OK.
Report is not printed. OK.
We skip BRK and resume execution.
What is the problem?
quoted hunk
+ *
+ * This is something that might be fixed at some point in the future.
+ */
+ if (!recover)
+ die("Oops - KHWASAN", regs, 0);
+
+ /* If thread survives, skip over the brk instruction and continue: */
+ __arm64_skip_faulting_instruction(regs, AARCH64_INSN_SIZE);
+ return DBG_HOOK_HANDLED;
+}
+
+#define KHWASAN_ESR_VAL (0xf2000000 | KHWASAN_BRK_IMM)
+#define KHWASAN_ESR_MASK 0xffffff00
+
+static struct break_hook khwasan_break_hook = {
+ .esr_val = KHWASAN_ESR_VAL,
+ .esr_mask = KHWASAN_ESR_MASK,
+ .fn = khwasan_handler,
+};
+#endif
+
/*
* Initial handler for AArch64 BRK exceptions
* This handler only used until debug_traps_init().
@@ -792,6 +850,10 @@ static struct break_hook bug_break_hook = { int __init early_brk64(unsigned long addr, unsigned int esr, struct pt_regs *regs) {+#ifdef CONFIG_KASAN_HW+ if ((esr & KHWASAN_ESR_MASK) == KHWASAN_ESR_VAL)+ return khwasan_handler(regs, esr) != DBG_HOOK_HANDLED;+#endif return bug_handler(regs, esr) != DBG_HOOK_HANDLED; }
@@ -799,4 +861,7 @@ int __init early_brk64(unsigned long addr, unsigned int esr, void __init trap_init(void) { register_break_hook(&bug_break_hook);+#ifdef CONFIG_KASAN_HW+ register_break_hook(&khwasan_break_hook);+#endif }--
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
quoted hunk
KHWASAN inline instrumentation mode (which embeds checks of shadow memory
into the generated code, instead of inserting a callback) generates a brk
instruction when a tag mismatch is detected.
This commit add a KHWASAN brk handler, that decodes the immediate value
passed to the brk instructions (to extract information about the memory
access that triggered the mismatch), reads the register values (x0 contains
the guilty address) and reports the bug.
Signed-off-by: Andrey Konovalov <redacted>
---
arch/arm64/include/asm/brk-imm.h | 2 +
arch/arm64/kernel/traps.c | 69 +++++++++++++++++++++++++++++++-
2 files changed, 69 insertions(+), 2 deletions(-)
@@ -775,7 +780,7 @@ static int bug_handler(struct pt_regs *regs, unsigned int esr)}/* If thread survives, skip over the BUG instruction and continue: */-arm64_skip_faulting_instruction(regs,AARCH64_INSN_SIZE);+__arm64_skip_faulting_instruction(regs,AARCH64_INSN_SIZE);returnDBG_HOOK_HANDLED;}
On Wed, Sep 12, 2018 at 7:16 PM Dmitry Vyukov [off-list ref] wrote:
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
[...]
quoted
+static int khwasan_handler(struct pt_regs *regs, unsigned int esr)
+{
+ bool recover = esr & KHWASAN_ESR_RECOVER;
+ bool write = esr & KHWASAN_ESR_WRITE;
+ size_t size = KHWASAN_ESR_SIZE(esr);
+ u64 addr = regs->regs[0];
+ u64 pc = regs->pc;
+
+ if (user_mode(regs))
+ return DBG_HOOK_ERROR;
+
+ kasan_report(addr, size, write, pc);
+
+ /*
+ * The instrumentation allows to control whether we can proceed after
+ * a crash was detected. This is done by passing the -recover flag to
+ * the compiler. Disabling recovery allows to generate more compact
+ * code.
+ *
+ * Unfortunately disabling recovery doesn't work for the kernel right
+ * now. KHWASAN reporting is disabled in some contexts (for example when
+ * the allocator accesses slab object metadata; same is true for KASAN;
+ * this is controlled by current->kasan_depth). All these accesses are
+ * detected by the tool, even though the reports for them are not
+ * printed.
+ *
+ * This is something that might be fixed at some point in the future.
+ */
+ if (!recover)
+ die("Oops - KHWASAN", regs, 0);
Why die and not panic? Die seems to be much less used function, and it
calls panic anyway, and we call panic in kasan_report if panic_on_warn
is set.
die() is vaguely equivalent to BUG(); die() and BUG() normally only
terminate the current process, which may or may not leave the system
somewhat usable, while panic() always brings down the whole system.
AFAIK panic() shouldn't be used unless you're in some very low-level
code where you know that trying to just kill the current process can't
work and the entire system is broken beyond repair.
If KASAN traps on some random memory access, there's a good chance
that just killing the current process will allow at least parts of the
system to continue. I'm not sure whether BUG() or die() is more
appropriate here, but I think it definitely should not be a panic().
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
quoted hunk
This commit adds rountines, that print KHWASAN error reports. Those are
quite similar to KASAN, the difference is:
1. The way KHWASAN finds the first bad shadow cell (with a mismatching
tag). KHWASAN compares memory tags from the shadow memory to the pointer
tag.
2. KHWASAN reports all bugs with the "KASAN: invalid-access" header. This
is done, so various external tools that already parse the kernel logs
looking for KASAN reports wouldn't need to be changed.
Signed-off-by: Andrey Konovalov <redacted>
---
include/linux/kasan.h | 3 +++
mm/kasan/kasan.h | 7 +++++
mm/kasan/kasan_report.c | 7 ++---
mm/kasan/khwasan_report.c | 21 +++++++++++++++
mm/kasan/report.c | 57 +++++++++++++++++++++------------------
5 files changed, 64 insertions(+), 31 deletions(-)
We already have #ifdef CONFIG_KASAN_HW section below with additional
functions for KASAN_HW and empty stubs otherwise. I would add this one
there as well.
I think it's better to check that are within bounds before accessing
shadow. Otherwise it's kinda potential out-of-bounds access ;)
I know that we _should_ not do an oob here, but still.
Also feels that this function can be shortened to something like:
u8 tag = get_tag(addr);
void *p = reset_tag(addr);
void *end = p + size;
while (p < end && tag == *(u8 *)kasan_mem_to_shadow(p))
p += KASAN_SHADOW_SCALE_SIZE;
return p;
@@ -64,11 +64,10 @@ static int __init kasan_set_multi_shot(char *str)}__setup("kasan_multi_shot",kasan_set_multi_shot);-staticvoidprint_error_description(structkasan_access_info*info,-constchar*bug_type)+staticvoidprint_error_description(structkasan_access_info*info){pr_err("BUG: KASAN: %s in %pS\n",-bug_type,(void*)info->ip);+get_bug_type(info),(void*)info->ip);pr_err("%s of size %zu at addr %px by task %s/%d\n",info->is_write?"Write":"Read",info->access_size,info->access_addr,current->comm,task_pid_nr(current));
@@ -272,6 +271,8 @@ void kasan_report_invalid_free(void *object, unsigned long ip)start_report(&flags);pr_err("BUG: KASAN: double-free or invalid-free in %pS\n",(void*)ip);+print_tags(get_tag(object),reset_tag(object));+object=reset_tag(object);pr_err("\n");print_address_description(object);pr_err("\n");
@@ -279,41 +280,45 @@ void kasan_report_invalid_free(void *object, unsigned long ip)end_report(&flags);}-staticvoidkasan_report_error(structkasan_access_info*info)-{-unsignedlongflags;--start_report(&flags);--print_error_description(info,get_bug_type(info));-pr_err("\n");--if(!addr_has_shadow(info->access_addr)){-dump_stack();-}else{-print_address_description((void*)info->access_addr);-pr_err("\n");-print_shadow_for_address(info->first_bad_addr);-}--end_report(&flags);-}-voidkasan_report(unsignedlongaddr,size_tsize,boolis_write,unsignedlongip){structkasan_access_infoinfo;+void*tagged_addr;+void*untagged_addr;+unsignedlongflags;if(likely(!report_enabled()))return;disable_trace_on_warning();-info.access_addr=(void*)addr;-info.first_bad_addr=(void*)addr;+tagged_addr=(void*)addr;+untagged_addr=reset_tag(tagged_addr);++info.access_addr=tagged_addr;+if(addr_has_shadow(untagged_addr))+info.first_bad_addr=find_first_bad_addr(tagged_addr,size);+else+info.first_bad_addr=untagged_addr;info.access_size=size;info.is_write=is_write;info.ip=ip;-kasan_report_error(&info);+start_report(&flags);++print_error_description(&info);+if(addr_has_shadow(untagged_addr))+print_tags(get_tag(tagged_addr),info.first_bad_addr);+pr_err("\n");++if(addr_has_shadow(untagged_addr)){+print_address_description(untagged_addr);+pr_err("\n");+print_shadow_for_address(info.first_bad_addr);+}else{+dump_stack();+}++end_report(&flags);}--
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
quoted hunk
This commit adds KHWASAN specific hooks implementation and adjusts
common KASAN and KHWASAN ones.
1. When a new slab cache is created, KHWASAN rounds up the size of the
objects in this cache to KASAN_SHADOW_SCALE_SIZE (== 16).
2. On each kmalloc KHWASAN generates a random tag, sets the shadow memory,
that corresponds to this object to this tag, and embeds this tag value
into the top byte of the returned pointer.
3. On each kfree KHWASAN poisons the shadow memory with a random tag to
allow detection of use-after-free bugs.
The rest of the logic of the hook implementation is very much similar to
the one provided by KASAN. KHWASAN saves allocation and free stack metadata
to the slab object the same was KASAN does this.
Signed-off-by: Andrey Konovalov <redacted>
---
mm/kasan/common.c | 82 +++++++++++++++++++++++++++++++++++-----------
mm/kasan/kasan.h | 8 +++++
mm/kasan/khwasan.c | 40 ++++++++++++++++++++++
3 files changed, 111 insertions(+), 19 deletions(-)
The comment is not super-useful. It would be more useful to say why we
need to do this.
Most callers explicitly untag pointer passed to this function, for
some it's unclear if the pointer contains tag or not.
For example, __hwasan_tag_memory -- what does it accept? Tagged or untagged?
It seems that this function is just different for kasan and khwasan.
Currently for kasan we have:
kasan_poison_shadow(address, size, tag);
if (size & KASAN_SHADOW_MASK) {
u8 *shadow = (u8 *)kasan_mem_to_shadow(address + size);
*shadow = size & KASAN_SHADOW_MASK;
}
But what we want to say for khwasan is:
kasan_poison_shadow(address, round_up(size, KASAN_SHADOW_SCALE_SIZE),
get_tag(address));
Not sure if we want to keep a common implementation or just have
separate implementations...
quoted hunk
@@ -200,8 +212,9 @@ void kasan_unpoison_stack_above_sp_to(const void *watermark) void kasan_alloc_pages(struct page *page, unsigned int order) {- if (likely(!PageHighMem(page)))- kasan_unpoison_shadow(page_address(page), PAGE_SIZE << order);+ if (unlikely(PageHighMem(page)))+ return;+ kasan_unpoison_shadow(page_address(page), PAGE_SIZE << order); } void kasan_free_pages(struct page *page, unsigned int order)
@@ -235,6 +248,7 @@ void kasan_cache_create(struct kmem_cache *cache, unsigned int *size, slab_flags_t *flags) { unsigned int orig_size = *size;+ unsigned int redzone_size = 0;
This variable seems to be always initialized below. We don't general
initialize local variables in this case.
@@ -371,6 +404,7 @@ void *kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size, { unsigned long redzone_start; unsigned long redzone_end;+ u8 tag; if (gfpflags_allow_blocking(flags)) quarantine_reduce();
@@ -383,14 +417,24 @@ void *kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size, redzone_end = round_up((unsigned long)object + cache->object_size, KASAN_SHADOW_SCALE_SIZE);- kasan_unpoison_shadow(object, size);+ /*+ * Objects with contructors and objects from SLAB_TYPESAFE_BY_RCU slabs+ * have tags preassigned and are already tagged.+ */+ if (IS_ENABLED(CONFIG_KASAN_HW) &&+ (cache->ctor || cache->flags & SLAB_TYPESAFE_BY_RCU))+ tag = get_tag(object);+ else+ tag = random_tag();++ kasan_unpoison_shadow(set_tag(object, tag), size); kasan_poison_shadow((void *)redzone_start, redzone_end - redzone_start, KASAN_KMALLOC_REDZONE); if (cache->flags & SLAB_KASAN) set_track(&get_alloc_info(cache, object)->alloc_track, flags);- return (void *)object;+ return set_tag(object, tag); } EXPORT_SYMBOL(kasan_kmalloc);
@@ -440,7 +484,7 @@ void kasan_poison_kfree(void *ptr, unsigned long ip) page = virt_to_head_page(ptr); if (unlikely(!PageSlab(page))) {- if (ptr != page_address(page)) {+ if (reset_tag(ptr) != page_address(page)) { kasan_report_invalid_free(ptr, ip); return; }
@@ -453,7 +497,7 @@ void kasan_poison_kfree(void *ptr, unsigned long ip) void kasan_kfree_large(void *ptr, unsigned long ip) {- if (ptr != page_address(virt_to_head_page(ptr)))+ if (reset_tag(ptr) != page_address(virt_to_head_page(ptr))) kasan_report_invalid_free(ptr, ip); /* The object will be poisoned by page_alloc. */ }
@@ -106,15 +106,52 @@ void *khwasan_preset_slab_tag(struct kmem_cache *cache, unsigned int idx,voidcheck_memory_region(unsignedlongaddr,size_tsize,boolwrite,unsignedlongret_ip){+u8tag;+u8*shadow_first,*shadow_last,*shadow;+void*untagged_addr;++tag=get_tag((constvoid*)addr);++/* Ignore accesses for pointers tagged with 0xff (native kernel
/* on a separate line
+ * pointer tag) to suppress false positives caused by kmap.
+ *
+ * Some kernel code was written to account for archs that don't keep
+ * high memory mapped all the time, but rather map and unmap particular
+ * pages when needed. Instead of storing a pointer to the kernel memory,
+ * this code saves the address of the page structure and offset within
+ * that page for later use. Those pages are then mapped and unmapped
+ * with kmap/kunmap when necessary and virt_to_page is used to get the
+ * virtual address of the page. For arm64 (that keeps the high memory
+ * mapped all the time), kmap is turned into a page_address call.
+
+ * The issue is that with use of the page_address + virt_to_page
+ * sequence the top byte value of the original pointer gets lost (gets
+ * set to KHWASAN_TAG_KERNEL (0xFF).
@@ -8,11 +8,19 @@ KernelAddressSANitizer (KASAN) is a dynamic memory error detector. It provides a fast and comprehensive solution for finding use-after-free and out-of-bounds bugs.-KASAN uses compile-time instrumentation for checking every memory access,-therefore you will need a GCC version 4.9.2 or later. GCC 5.0 or later is-required for detection of out-of-bounds accesses to stack or global variables.+KASAN has two modes: classic KASAN (a classic version, similar to user space+ASan) and KHWASAN (a version based on memory tagging, similar to user space+HWASan).-Currently KASAN is supported only for the x86_64 and arm64 architectures.+KASAN uses compile-time instrumentation to insert validity checks before every+memory access, and therefore requires a compiler version that supports that.+For classic KASAN you need GCC version 4.9.2 or later. GCC 5.0 or later is+required for detection of out-of-bounds accesses on stack and global variables.+KHWASAN in turns is only supported in clang and requires revision 330044 or
in turn?
quoted hunk
+later.
+
+Currently classic KASAN is supported for the x86_64, arm64 and xtensa
+architectures, and KHWASAN is supported only for arm64.
Usage
-----
@@ -21,12 +29,14 @@ To enable KASAN configure kernel with:: CONFIG_KASAN = y-and choose between CONFIG_KASAN_OUTLINE and CONFIG_KASAN_INLINE. Outline and-inline are compiler instrumentation types. The former produces smaller binary-the latter is 1.1 - 2 times faster. Inline instrumentation requires a GCC+and choose between CONFIG_KASAN_GENERIC (to enable classic KASAN) and+CONFIG_KASAN_HW (to enabled KHWASAN). You also need to choose choose between
to enable
quoted hunk
+CONFIG_KASAN_OUTLINE and CONFIG_KASAN_INLINE. Outline and inline are compiler
+instrumentation types. The former produces smaller binary while the latter is
+1.1 - 2 times faster. For classic KASAN inline instrumentation requires GCC
version 5.0 or later.
-KASAN works with both SLUB and SLAB memory allocators.
+Both KASAN modes work with both SLUB and SLAB memory allocators.
For better bug detection and nicer reporting, enable CONFIG_STACKTRACE.
To disable instrumentation for specific files or directories, add a line
KASAN does not print line numbers per se.
I think we need to show unmodified output to not confuse readers
(probably remove the useless ? lines).
quoted hunk
+ kasan_report_error mm/kasan/report.c:352
+ kasan_report+0x10e/0x220 mm/kasan/report.c:410
+ __asan_report_store1_noabort+0x17/0x20 mm/kasan/report.c:505
+ kmalloc_oob_right+0xa8/0xbc [test_kasan] lib/test_kasan.c:42
+ kmalloc_tests_init+0x16/0x769 [test_kasan]
+ do_one_initcall+0x9e/0x240 init/main.c:832
+ do_init_module+0x1b6/0x542 kernel/module.c:3462
+ load_module+0x6042/0x9030 kernel/module.c:3786
+ SYSC_init_module+0x18f/0x1c0 kernel/module.c:3858
+ SyS_init_module+0x9/0x10 kernel/module.c:3841
+ do_syscall_64+0x198/0x480 arch/x86/entry/common.c:287
+ entry_SYSCALL_64_after_hwframe+0x21/0x86 arch/x86/entry/entry_64.S:251
+ RIP: 0033:0x7fdd79df99da
+ RSP: 002b:00007fff2229bdf8 EFLAGS: 00000202 ORIG_RAX: 00000000000000af
+ RAX: ffffffffffffffda RBX: 000055c408121190 RCX: 00007fdd79df99da
+ RDX: 00007fdd7a0b8f88 RSI: 0000000000055670 RDI: 00007fdd7a47e000
+ RBP: 000055c4081200b0 R08: 0000000000000003 R09: 0000000000000000
+ R10: 00007fdd79df5d0a R11: 0000000000000202 R12: 00007fdd7a0b8f88
+ R13: 000055c408120090 R14: 0000000000000000 R15: 0000000000000000
+
+ Allocated by task 2734:
+ save_stack+0x43/0xd0 mm/kasan/common.c:176
+ set_track+0x20/0x30 mm/kasan/common.c:188
+ kasan_kmalloc+0x9a/0xc0 mm/kasan/kasan.c:372
+ kmem_cache_alloc_trace+0xcd/0x1a0 mm/slub.c:2761
+ kmalloc ./include/linux/slab.h:512
+ kmalloc_oob_right+0x56/0xbc [test_kasan] lib/test_kasan.c:36
+ kmalloc_tests_init+0x16/0x769 [test_kasan]
+ do_one_initcall+0x9e/0x240 init/main.c:832
+ do_init_module+0x1b6/0x542 kernel/module.c:3462
+ load_module+0x6042/0x9030 kernel/module.c:3786
+ SYSC_init_module+0x18f/0x1c0 kernel/module.c:3858
+ SyS_init_module+0x9/0x10 kernel/module.c:3841
+ do_syscall_64+0x198/0x480 arch/x86/entry/common.c:287
+ entry_SYSCALL_64_after_hwframe+0x21/0x86 arch/x86/entry/entry_64.S:251
+
+ The buggy address belongs to the object at ffff8800696f3cc0
+ which belongs to the cache kmalloc-128 of size 128
+ The buggy address is located 123 bytes inside of
+ 128-byte region [ffff8800696f3cc0, ffff8800696f3d40)
+ The buggy address belongs to the page:
+ page:ffffea0001a5bcc0 count:1 mapcount:0 mapping: (null) index:0x0
+ flags: 0x100000000000100(slab)
+ raw: 0100000000000100 0000000000000000 0000000000000000 0000000180150015
+ raw: ffffea0001a8ce40 0000000300000003 ffff88006d001640 0000000000000000
+ page dumped because: kasan: bad access detected
+
Memory state around the buggy address:
- ffff8800693bc300: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
- ffff8800693bc380: fc fc 00 00 00 00 00 00 00 00 00 00 00 00 00 fc
- ffff8800693bc400: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
- ffff8800693bc480: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
- ffff8800693bc500: fc fc fc fc fc fc fc fc fc fc fc 00 00 00 00 00
- >ffff8800693bc580: 00 00 00 00 00 00 00 00 00 00 03 fc fc fc fc fc
- ^
- ffff8800693bc600: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
- ffff8800693bc680: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
- ffff8800693bc700: fc fc fc fc fb fb fb fb fb fb fb fb fb fb fb fb
- ffff8800693bc780: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
- ffff8800693bc800: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
+ ffff8800696f3c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fc
+ ffff8800696f3c80: fc fc fc fc fc fc fc fc 00 00 00 00 00 00 00 00
+ >ffff8800696f3d00: 00 00 00 00 00 00 00 03 fc fc fc fc fc fc fc fc
+ ^
+ ffff8800696f3d80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fc fc
+ ffff8800696f3e00: fc fc fc fc fc fc fc fc fb fb fb fb fb fb fb fb
==================================================================
-The header of the report discribe what kind of bug happened and what kind of
-access caused it. It's followed by the description of the accessed slub object
-(see 'SLUB Debug output' section in Documentation/vm/slub.rst for details) and
-the description of the accessed memory page.
+The header of the report provides a short summary of what kind of bug happened
+and what kind of access caused it. It's followed by a stack trace of the bad
+access, a stack trace of where the accessed memory was allocated (in case bad
+access happens on a slab object), and a stack trace of where the object was
+freed (in case of a use-after-free bug report). Next comes a description of
+the accessed slab object and information about the accessed memory page.
In the last section the report shows memory state around the accessed address.
Reading this part requires some understanding of how KASAN works.
@@ -138,18 +143,24 @@ inaccessible memory like redzones or freed memory (see mm/kasan/kasan.h). In the report above the arrows point to the shadow byte 03, which means that the accessed address is partially accessible.+For KHWASAN this last report section shows the memory tags around the accessed+address (see Implementation details section). Implementation details ----------------------+Classic KASAN+~~~~~~~~~~~~~+ From a high level, our approach to memory error detection is similar to that of kmemcheck: use shadow memory to record whether each byte of memory is safe-to access, and use compile-time instrumentation to check shadow memory on each-memory access.+to access, and use compile-time instrumentation to insert checks of shadow+memory on each memory access.-AddressSanitizer dedicates 1/8 of kernel memory to its shadow memory-(e.g. 16TB to cover 128TB on x86_64) and uses direct mapping with a scale and-offset to translate a memory address to its corresponding shadow address.+Classic KASAN dedicates 1/8th of kernel memory to its shadow memory (e.g. 16TB+to cover 128TB on x86_64) and uses direct mapping with a scale and offset to+translate a memory address to its corresponding shadow address. Here is the function which translates an address to its corresponding shadow address::
@@ -162,12 +173,34 @@ address:: where ``KASAN_SHADOW_SCALE_SHIFT = 3``.-Compile-time instrumentation used for checking memory accesses. Compiler inserts-function calls (__asan_load*(addr), __asan_store*(addr)) before each memory-access of size 1, 2, 4, 8 or 16. These functions check whether memory access is-valid or not by checking corresponding shadow memory.+Compile-time instrumentation is used to insert memory access checks. Compiler+inserts function calls (__asan_load*(addr), __asan_store*(addr)) before each+memory access of size 1, 2, 4, 8 or 16. These functions check whether memory+access is valid or not by checking corresponding shadow memory. GCC 5.0 has possibility to perform inline instrumentation. Instead of making function calls GCC directly inserts the code to check the shadow memory. This option significantly enlarges kernel but it gives x1.1-x2 performance boost over outline instrumented kernel.++KHWASAN+~~~~~~~++KHWASAN uses the Top Byte Ignore (TBI) feature of modern arm64 CPUs to store+a pointer tag in the top byte of kernel pointers. KHWASAN also uses shadow+memory to store memory tags associated with each 16-byte memory cell (therefore+it dedicates 1/16th of the kernel memory for shadow memory).++On each memory allocation KHWASAN generates a random tag, tags allocated memory+with this tag, and embeds this tag into the returned pointer. KHWASAN uses+compile-time instrumentation to insert checks before each memory access. These+checks make sure that tag of the memory that is being accessed is equal to tag+of the pointer that is used to access this memory. In case of a tag mismatch+KHWASAN prints a bug report.++KHWASAN also has two instrumentation modes (outline, that emits callbacks to+check memory accesses; and inline, that performs the shadow memory checks+inline). With outline instrumentation mode, a bug report is simply printed+from the function that performs the access check. With inline instrumentation+a brk instruction is emitted by the compiler, and a dedicated brk handler is+used to print KHWASAN reports.--
On Wed, Sep 12, 2018 at 7:39 PM, Jann Horn [off-list ref] wrote:
On Wed, Sep 12, 2018 at 7:16 PM Dmitry Vyukov [off-list ref] wrote:
quoted
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
[...]
quoted
quoted
+static int khwasan_handler(struct pt_regs *regs, unsigned int esr)
+{
+ bool recover = esr & KHWASAN_ESR_RECOVER;
+ bool write = esr & KHWASAN_ESR_WRITE;
+ size_t size = KHWASAN_ESR_SIZE(esr);
+ u64 addr = regs->regs[0];
+ u64 pc = regs->pc;
+
+ if (user_mode(regs))
+ return DBG_HOOK_ERROR;
+
+ kasan_report(addr, size, write, pc);
+
+ /*
+ * The instrumentation allows to control whether we can proceed after
+ * a crash was detected. This is done by passing the -recover flag to
+ * the compiler. Disabling recovery allows to generate more compact
+ * code.
+ *
+ * Unfortunately disabling recovery doesn't work for the kernel right
+ * now. KHWASAN reporting is disabled in some contexts (for example when
+ * the allocator accesses slab object metadata; same is true for KASAN;
+ * this is controlled by current->kasan_depth). All these accesses are
+ * detected by the tool, even though the reports for them are not
+ * printed.
+ *
+ * This is something that might be fixed at some point in the future.
+ */
+ if (!recover)
+ die("Oops - KHWASAN", regs, 0);
Why die and not panic? Die seems to be much less used function, and it
calls panic anyway, and we call panic in kasan_report if panic_on_warn
is set.
die() is vaguely equivalent to BUG(); die() and BUG() normally only
terminate the current process, which may or may not leave the system
somewhat usable, while panic() always brings down the whole system.
AFAIK panic() shouldn't be used unless you're in some very low-level
code where you know that trying to just kill the current process can't
work and the entire system is broken beyond repair.
If KASAN traps on some random memory access, there's a good chance
that just killing the current process will allow at least parts of the
system to continue. I'm not sure whether BUG() or die() is more
appropriate here, but I think it definitely should not be a panic().
Nick, do you know if die() will be enough to catch problems on Android
phones? panic_on_warn would turn this into panic, but I guess one does
not want panic_on_warn on a canary phone.
From: Nick Desaulniers <ndesaulniers@google.com> Date: 2018-09-13 18:09:55
On Thu, Sep 13, 2018 at 1:37 AM Dmitry Vyukov [off-list ref] wrote:
On Wed, Sep 12, 2018 at 7:39 PM, Jann Horn [off-list ref] wrote:
quoted
On Wed, Sep 12, 2018 at 7:16 PM Dmitry Vyukov [off-list ref] wrote:
quoted
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
[...]
quoted
quoted
+static int khwasan_handler(struct pt_regs *regs, unsigned int esr)
+{
+ bool recover = esr & KHWASAN_ESR_RECOVER;
+ bool write = esr & KHWASAN_ESR_WRITE;
+ size_t size = KHWASAN_ESR_SIZE(esr);
+ u64 addr = regs->regs[0];
+ u64 pc = regs->pc;
+
+ if (user_mode(regs))
+ return DBG_HOOK_ERROR;
+
+ kasan_report(addr, size, write, pc);
+
+ /*
+ * The instrumentation allows to control whether we can proceed after
+ * a crash was detected. This is done by passing the -recover flag to
+ * the compiler. Disabling recovery allows to generate more compact
+ * code.
+ *
+ * Unfortunately disabling recovery doesn't work for the kernel right
+ * now. KHWASAN reporting is disabled in some contexts (for example when
+ * the allocator accesses slab object metadata; same is true for KASAN;
+ * this is controlled by current->kasan_depth). All these accesses are
+ * detected by the tool, even though the reports for them are not
+ * printed.
+ *
+ * This is something that might be fixed at some point in the future.
+ */
+ if (!recover)
+ die("Oops - KHWASAN", regs, 0);
Why die and not panic? Die seems to be much less used function, and it
calls panic anyway, and we call panic in kasan_report if panic_on_warn
is set.
die() is vaguely equivalent to BUG(); die() and BUG() normally only
terminate the current process, which may or may not leave the system
somewhat usable, while panic() always brings down the whole system.
AFAIK panic() shouldn't be used unless you're in some very low-level
code where you know that trying to just kill the current process can't
work and the entire system is broken beyond repair.
If KASAN traps on some random memory access, there's a good chance
that just killing the current process will allow at least parts of the
system to continue. I'm not sure whether BUG() or die() is more
appropriate here, but I think it definitely should not be a panic().
Nick, do you know if die() will be enough to catch problems on Android
phones? panic_on_warn would turn this into panic, but I guess one does
not want panic_on_warn on a canary phone.
die() has arch specific implementations, so looking at:
arch/arm64/kernel/traps.c:196#die
it looks like panic is invoked if in_interrupt() or panic_on_oops(),
which is a configure option. So maybe the config for KHWASAN should
also enable that? Otherwise seems easy to forget. But maybe that
should remain configurable separately?
Looking at the kernel configs for the Pixel 2, it does seem like
CONFIG_PANIC_ON_OOPS=y is already enabled.
https://android.googlesource.com/kernel/msm/+/android-msm-wahoo-4.4-pie/arch/arm64/configs/wahoo_defconfig#746
Specifically to catch problems on Android, our internal debug builds
can report on panics, but not oops, IIUC.
--
Thanks,
~Nick Desaulniers
On Thu, Sep 13, 2018 at 8:09 PM Nick Desaulniers
[off-list ref] wrote:
On Thu, Sep 13, 2018 at 1:37 AM Dmitry Vyukov [off-list ref] wrote:
quoted
On Wed, Sep 12, 2018 at 7:39 PM, Jann Horn [off-list ref] wrote:
quoted
On Wed, Sep 12, 2018 at 7:16 PM Dmitry Vyukov [off-list ref] wrote:
quoted
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
[...]
quoted
quoted
+static int khwasan_handler(struct pt_regs *regs, unsigned int esr)
+{
+ bool recover = esr & KHWASAN_ESR_RECOVER;
+ bool write = esr & KHWASAN_ESR_WRITE;
+ size_t size = KHWASAN_ESR_SIZE(esr);
+ u64 addr = regs->regs[0];
+ u64 pc = regs->pc;
+
+ if (user_mode(regs))
+ return DBG_HOOK_ERROR;
+
+ kasan_report(addr, size, write, pc);
+
+ /*
+ * The instrumentation allows to control whether we can proceed after
+ * a crash was detected. This is done by passing the -recover flag to
+ * the compiler. Disabling recovery allows to generate more compact
+ * code.
+ *
+ * Unfortunately disabling recovery doesn't work for the kernel right
+ * now. KHWASAN reporting is disabled in some contexts (for example when
+ * the allocator accesses slab object metadata; same is true for KASAN;
+ * this is controlled by current->kasan_depth). All these accesses are
+ * detected by the tool, even though the reports for them are not
+ * printed.
+ *
+ * This is something that might be fixed at some point in the future.
+ */
+ if (!recover)
+ die("Oops - KHWASAN", regs, 0);
Why die and not panic? Die seems to be much less used function, and it
calls panic anyway, and we call panic in kasan_report if panic_on_warn
is set.
die() is vaguely equivalent to BUG(); die() and BUG() normally only
terminate the current process, which may or may not leave the system
somewhat usable, while panic() always brings down the whole system.
AFAIK panic() shouldn't be used unless you're in some very low-level
code where you know that trying to just kill the current process can't
work and the entire system is broken beyond repair.
If KASAN traps on some random memory access, there's a good chance
that just killing the current process will allow at least parts of the
system to continue. I'm not sure whether BUG() or die() is more
appropriate here, but I think it definitely should not be a panic().
Nick, do you know if die() will be enough to catch problems on Android
phones? panic_on_warn would turn this into panic, but I guess one does
not want panic_on_warn on a canary phone.
die() has arch specific implementations, so looking at:
arch/arm64/kernel/traps.c:196#die
it looks like panic is invoked if in_interrupt() or panic_on_oops(),
which is a configure option. So maybe the config for KHWASAN should
also enable that? Otherwise seems easy to forget. But maybe that
should remain configurable separately?
In the upstream kernel, it is desirable to be able to discover bugs
and debug them from inside the running system. When you detect a bug
that makes it impossible for the current task to continue safely,
you're supposed to use something like BUG() to terminate the task; if
you can continue safely, you're supposed to use WARN(). Either way,
you should *not* just shoot down the whole kernel unless the system is
corrupted so badly that trying to keep running would be pointless.
You can configure the kernel such that it crashes the device when such
an event occurs, and doing so can sometimes be beneficial; but please
don't hardcode such policies into the upstream kernel.
On Thu, Sep 13, 2018 at 8:09 PM, 'Nick Desaulniers' via kasan-dev
[off-list ref] wrote:
On Thu, Sep 13, 2018 at 1:37 AM Dmitry Vyukov [off-list ref] wrote:
quoted
On Wed, Sep 12, 2018 at 7:39 PM, Jann Horn [off-list ref] wrote:
quoted
On Wed, Sep 12, 2018 at 7:16 PM Dmitry Vyukov [off-list ref] wrote:
quoted
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
[...]
quoted
quoted
+static int khwasan_handler(struct pt_regs *regs, unsigned int esr)
+{
+ bool recover = esr & KHWASAN_ESR_RECOVER;
+ bool write = esr & KHWASAN_ESR_WRITE;
+ size_t size = KHWASAN_ESR_SIZE(esr);
+ u64 addr = regs->regs[0];
+ u64 pc = regs->pc;
+
+ if (user_mode(regs))
+ return DBG_HOOK_ERROR;
+
+ kasan_report(addr, size, write, pc);
+
+ /*
+ * The instrumentation allows to control whether we can proceed after
+ * a crash was detected. This is done by passing the -recover flag to
+ * the compiler. Disabling recovery allows to generate more compact
+ * code.
+ *
+ * Unfortunately disabling recovery doesn't work for the kernel right
+ * now. KHWASAN reporting is disabled in some contexts (for example when
+ * the allocator accesses slab object metadata; same is true for KASAN;
+ * this is controlled by current->kasan_depth). All these accesses are
+ * detected by the tool, even though the reports for them are not
+ * printed.
+ *
+ * This is something that might be fixed at some point in the future.
+ */
+ if (!recover)
+ die("Oops - KHWASAN", regs, 0);
Why die and not panic? Die seems to be much less used function, and it
calls panic anyway, and we call panic in kasan_report if panic_on_warn
is set.
die() is vaguely equivalent to BUG(); die() and BUG() normally only
terminate the current process, which may or may not leave the system
somewhat usable, while panic() always brings down the whole system.
AFAIK panic() shouldn't be used unless you're in some very low-level
code where you know that trying to just kill the current process can't
work and the entire system is broken beyond repair.
If KASAN traps on some random memory access, there's a good chance
that just killing the current process will allow at least parts of the
system to continue. I'm not sure whether BUG() or die() is more
appropriate here, but I think it definitely should not be a panic().
Nick, do you know if die() will be enough to catch problems on Android
phones? panic_on_warn would turn this into panic, but I guess one does
not want panic_on_warn on a canary phone.
die() has arch specific implementations, so looking at:
arch/arm64/kernel/traps.c:196#die
it looks like panic is invoked if in_interrupt() or panic_on_oops(),
which is a configure option. So maybe the config for KHWASAN should
also enable that? Otherwise seems easy to forget. But maybe that
should remain configurable separately?
Looking at the kernel configs for the Pixel 2, it does seem like
CONFIG_PANIC_ON_OOPS=y is already enabled.
https://android.googlesource.com/kernel/msm/+/android-msm-wahoo-4.4-pie/arch/arm64/configs/wahoo_defconfig#746
Then I think we are good here.
Specifically to catch problems on Android, our internal debug builds
can report on panics, but not oops, IIUC.
From: Will Deacon <hidden> Date: 2018-09-14 15:28:11
On Thu, Sep 06, 2018 at 01:06:23PM +0200, Andrey Konovalov wrote:
On Thu, Sep 6, 2018 at 12:05 PM, Will Deacon [off-list ref] wrote:
quoted
On Wed, Sep 05, 2018 at 02:10:32PM -0700, Andrew Morton wrote:
quoted
On Wed, 29 Aug 2018 13:35:04 +0200 Andrey Konovalov [off-list ref] wrote:
quoted
This patchset adds a new mode to KASAN [1], which is called KHWASAN
(Kernel HardWare assisted Address SANitizer).
We're at v6 and there are no reviewed-by's or acked-by's to be seen.
Is that a fair commentary on what has been happening, or have people
been remiss in sending and gathering such things?
I still have concerns about the consequences of merging this as anything
other than a debug option [1]. Unfortunately, merging it as a debug option
defeats the whole point, so I think we need to spend more effort on developing
tools that can help us to find and fix the subtle bugs which will arise from
enabling tagged pointers in the kernel.
I totally don't mind calling it a debug option. Do I need to somehow
specify it somewhere?
Ok, sorry, I completely misunderstood you earlier on then! For some reason
I thought you wanted this on by default.
In which case, I'm ok with the overall idea as long as we make the caveats
clear in the Kconfig text. In particular, that enabling this option may
introduce problems relating to pointer casting and comparison, but can
offer better coverage and lower memory consumption than a fully
software-based KASAN solution.
Will
It seems that it would be better to have just 1 attribute for both types.
Currently __no_sanitize_address is used just in a single place. But if
it ever used more, people will need to always spell both which looks
unnecessary, or, worse will only fix asan but forget about khwasan.
If we do just:
#define __no_sanitize_address __attribute__((no_sanitize("address",
"hwaddress")))
Then we don't need any changes in compiler-gcc.h nor in compiler.h,
and no chance or forgetting one of them.
Will do in v7.
quoted
config KASAN
- bool "KASan: runtime memory debugger"
+ bool "KASAN: runtime memory debugger"
+ help
+ Enables KASAN (KernelAddressSANitizer) - runtime memory debugger,
+ designed to find out-of-bounds accesses and use-after-free bugs.
Perhaps also give link to Documentation/dev-tools/kasan.rst while we are here.
Will do in v7.
quoted
+
+choice
+ prompt "KASAN mode"
+ depends on KASAN
+ default KASAN_GENERIC
+ help
+ KASAN has two modes: KASAN (a classic version, similar to userspace
In these few sentences we call the old mode with 3 different terms:
"generic", "classic" and "KASAN" :)
This is somewhat confusing. Let's call it "generic" throughout (here
and in the docs patch). "Generic" as in "supported on multiple arch
and not-dependent on hardware features". "Classic" makes sense for
people who knew KASAN before, but for future readers in won't make
sense.
Will use "generic" in v7.
quoted
+if HAVE_ARCH_KASAN_HW
This choice looks somewhat weird on non-arm64. It's kinda a choice
menu, but one can't really choose anything. Should we put the whole
choice under HAVE_ARCH_KASAN_HW, and just select KASAN_GENERIC
otherwise? I don't know what't the practice here. Andrey R?
I think having one option that is auto selected is fine.
quoted
+config KASAN_HW
+ bool "KHWASAN: the hardware assisted mode"
Do we need a hyphen here? hardware-assisted?
Yes, will fix in v7.
Can't we do this in the existing kasan_init_slab_obj() hook? It looks
like it should do exactly this -- allow any one-time initialization
for objects. We could extend it to accept index and return a new
pointer.
If that does not work for some reason, I would try to at least unify
the hook for slab/slub, e.g. pass idx=-1 from slub and then use
random_tag().
It also seems that we do preset tag for slab multiple times (from
slab_get_obj()). Using kasan_init_slab_obj() should resolve this too
(hopefully we don't call it multiple times).
The issue is that SLAB stores freelist as an array of indexes instead
of using an actual linked list like SLUB. So you can't store the tag
in the pointer while the object is in the freelist, since there's no
pointer. And, technically, we don't preset tags for SLAB, we just use
the id as the tag every time a pointer is used, so perhaps we should
rename the callback. As to unifying the callbacks, sure, we can do
that.
On Wed, Sep 12, 2018 at 7:13 PM, Dmitry Vyukov [off-list ref] wrote:
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
quoted
+static int khwasan_handler(struct pt_regs *regs, unsigned int esr)
+{
+ bool recover = esr & KHWASAN_ESR_RECOVER;
+ bool write = esr & KHWASAN_ESR_WRITE;
+ size_t size = KHWASAN_ESR_SIZE(esr);
+ u64 addr = regs->regs[0];
+ u64 pc = regs->pc;
+
+ if (user_mode(regs))
+ return DBG_HOOK_ERROR;
+
+ kasan_report(addr, size, write, pc);
+
+ /*
+ * The instrumentation allows to control whether we can proceed after
+ * a crash was detected. This is done by passing the -recover flag to
+ * the compiler. Disabling recovery allows to generate more compact
+ * code.
+ *
+ * Unfortunately disabling recovery doesn't work for the kernel right
+ * now. KHWASAN reporting is disabled in some contexts (for example when
+ * the allocator accesses slab object metadata; same is true for KASAN;
+ * this is controlled by current->kasan_depth). All these accesses are
+ * detected by the tool, even though the reports for them are not
+ * printed.
I am not following this part.
Slab accesses metadata. OK.
This is detected as bad access. OK.
Report is not printed. OK.
We skip BRK and resume execution.
What is the problem?
When the kernel is compiled with -fsanitize=kernel-hwaddress without
any additional flags (like it's done now with KASAN_HW) everything
works as you described and there's no problem. However if one were to
recompile the kernel with hwasan recovery disabled, KHWASAN wouldn't
work due to the reasons described in the comment. Should I make it
more clear?
quoted
+ *
+ * This is something that might be fixed at some point in the future.
+ */
+ if (!recover)
+ die("Oops - KHWASAN", regs, 0);
Can't we do this in the existing kasan_init_slab_obj() hook? It looks
like it should do exactly this -- allow any one-time initialization
for objects. We could extend it to accept index and return a new
pointer.
If that does not work for some reason, I would try to at least unify
the hook for slab/slub, e.g. pass idx=-1 from slub and then use
random_tag().
It also seems that we do preset tag for slab multiple times (from
slab_get_obj()). Using kasan_init_slab_obj() should resolve this too
(hopefully we don't call it multiple times).
The issue is that SLAB stores freelist as an array of indexes instead
of using an actual linked list like SLUB. So you can't store the tag
in the pointer while the object is in the freelist, since there's no
pointer. And, technically, we don't preset tags for SLAB, we just use
the id as the tag every time a pointer is used, so perhaps we should
rename the callback. As to unifying the callbacks, sure, we can do
that.
As per offline discussion: potentially we can use
kasan_init_slab_obj() if we add tag in kmalloc hook by using
obj_to_idx().
Wouldn't it be better to
#define KASAN_RESET_TAG(addr) addr
when CONFIG_KASAN_HW is not enabled, and then not duplicate the macros
below? That's what we do in kasan.h for all hooks.
I see that a subsequent patch duplicates yet another macro in this
file. While we could use:
#define __kimg_to_phys(addr) (KASAN_RESET_TAG(addr) - kimage_voffset)
with and without kasan. Duplicating them increases risk that somebody
will change only the non-kasan version but forget kasan version.
On Wed, Sep 12, 2018 at 7:50 PM, Dmitry Vyukov [off-list ref] wrote:
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
quoted
+#ifdef CONFIG_KASAN_HW
We already have #ifdef CONFIG_KASAN_HW section below with additional
functions for KASAN_HW and empty stubs otherwise. I would add this one
there as well.
I think it's better to check that are within bounds before accessing
shadow. Otherwise it's kinda potential out-of-bounds access ;)
I know that we _should_ not do an oob here, but still.
Also feels that this function can be shortened to something like:
u8 tag = get_tag(addr);
void *p = reset_tag(addr);
void *end = p + size;
while (p < end && tag == *(u8 *)kasan_mem_to_shadow(p))
p += KASAN_SHADOW_SCALE_SIZE;
return p;
On Wed, Sep 12, 2018 at 8:39 PM, Dmitry Vyukov [off-list ref] wrote:
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
quoted
This patch updates KASAN documentation to reflect the addition of KHWASAN.
quoted
-Currently KASAN is supported only for the x86_64 and arm64 architectures.
+KASAN uses compile-time instrumentation to insert validity checks before every
+memory access, and therefore requires a compiler version that supports that.
+For classic KASAN you need GCC version 4.9.2 or later. GCC 5.0 or later is
+required for detection of out-of-bounds accesses on stack and global variables.
+KHWASAN in turns is only supported in clang and requires revision 330044 or
in turn?
quoted
-and choose between CONFIG_KASAN_OUTLINE and CONFIG_KASAN_INLINE. Outline and
-inline are compiler instrumentation types. The former produces smaller binary
-the latter is 1.1 - 2 times faster. Inline instrumentation requires a GCC
+and choose between CONFIG_KASAN_GENERIC (to enable classic KASAN) and
+CONFIG_KASAN_HW (to enabled KHWASAN). You also need to choose choose between
On Wed, Sep 12, 2018 at 8:30 PM, Dmitry Vyukov [off-list ref] wrote:
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
quoted
void kasan_unpoison_shadow(const void *address, size_t size)
{
- kasan_poison_shadow(address, size, 0);
+ u8 tag = get_tag(address);
+
+ /* Perform shadow offset calculation based on untagged address */
The comment is not super-useful. It would be more useful to say why we
need to do this.
Most callers explicitly untag pointer passed to this function, for
some it's unclear if the pointer contains tag or not.
For example, __hwasan_tag_memory -- what does it accept? Tagged or untagged?
There are some callers that pass tagged pointers to this functions,
e.g. ksize or kasan_unpoison_object_data. I'll expand the comment.
It seems that this function is just different for kasan and khwasan.
Currently for kasan we have:
kasan_poison_shadow(address, size, tag);
if (size & KASAN_SHADOW_MASK) {
u8 *shadow = (u8 *)kasan_mem_to_shadow(address + size);
*shadow = size & KASAN_SHADOW_MASK;
}
But what we want to say for khwasan is:
kasan_poison_shadow(address, round_up(size, KASAN_SHADOW_SCALE_SIZE),
get_tag(address));
Not sure if we want to keep a common implementation or just have
separate implementations...
As per offline discussion leaving as is.
quoted
void kasan_free_pages(struct page *page, unsigned int order)
@@ -235,6 +248,7 @@ void kasan_cache_create(struct kmem_cache *cache, unsigned int *size, slab_flags_t *flags) { unsigned int orig_size = *size;+ unsigned int redzone_size = 0;
This variable seems to be always initialized below. We don't general
initialize local variables in this case.
Will fix in v7.
quoted
void check_memory_region(unsigned long addr, size_t size, bool write,
unsigned long ret_ip)
{
+ u8 tag;
+ u8 *shadow_first, *shadow_last, *shadow;
+ void *untagged_addr;
+
+ tag = get_tag((const void *)addr);
+
+ /* Ignore accesses for pointers tagged with 0xff (native kernel
/* on a separate line
Will fix in v7.
quoted
+ * pointer tag) to suppress false positives caused by kmap.
+ *
+ * Some kernel code was written to account for archs that don't keep
+ * high memory mapped all the time, but rather map and unmap particular
+ * pages when needed. Instead of storing a pointer to the kernel memory,
+ * this code saves the address of the page structure and offset within
+ * that page for later use. Those pages are then mapped and unmapped
+ * with kmap/kunmap when necessary and virt_to_page is used to get the
+ * virtual address of the page. For arm64 (that keeps the high memory
+ * mapped all the time), kmap is turned into a page_address call.
+
+ * The issue is that with use of the page_address + virt_to_page
+ * sequence the top byte value of the original pointer gets lost (gets
+ * set to KHWASAN_TAG_KERNEL (0xFF).
On Wed, Sep 12, 2018 at 4:54 PM, Dmitry Vyukov [off-list ref] wrote:
On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov [off-list ref] wrote:
quoted
/*
- * KASAN requires 1/8th of the kernel virtual address space for the shadow
- * region. KASAN can bloat the stack significantly, so double the (minimum)
- * stack size when KASAN is in use.
+ * KASAN and KHWASAN require 1/8th and 1/16th of the kernel virtual address
I am somewhat confused by the terminology.
"KASAN" is not actually "CONFIG_KASAN" below, it is actually
"CONFIG_KASAN_GENERIC". While "KHWASAN" translates to "KASAN_HW" few
lines later.
I think we need some consistent terminology for comments and config
names until it's too late.
On Fri, Sep 14, 2018 at 5:28 PM, Will Deacon [off-list ref] wrote:
On Thu, Sep 06, 2018 at 01:06:23PM +0200, Andrey Konovalov wrote:
quoted
On Thu, Sep 6, 2018 at 12:05 PM, Will Deacon [off-list ref] wrote:
quoted
On Wed, Sep 05, 2018 at 02:10:32PM -0700, Andrew Morton wrote:
quoted
On Wed, 29 Aug 2018 13:35:04 +0200 Andrey Konovalov [off-list ref] wrote:
quoted
This patchset adds a new mode to KASAN [1], which is called KHWASAN
(Kernel HardWare assisted Address SANitizer).
We're at v6 and there are no reviewed-by's or acked-by's to be seen.
Is that a fair commentary on what has been happening, or have people
been remiss in sending and gathering such things?
I still have concerns about the consequences of merging this as anything
other than a debug option [1]. Unfortunately, merging it as a debug option
defeats the whole point, so I think we need to spend more effort on developing
tools that can help us to find and fix the subtle bugs which will arise from
enabling tagged pointers in the kernel.
I totally don't mind calling it a debug option. Do I need to somehow
specify it somewhere?
Ok, sorry, I completely misunderstood you earlier on then! For some reason
I thought you wanted this on by default.
In which case, I'm ok with the overall idea as long as we make the caveats
clear in the Kconfig text. In particular, that enabling this option may
introduce problems relating to pointer casting and comparison, but can
offer better coverage and lower memory consumption than a fully
software-based KASAN solution.
Great! I'll explicitly call it debug feature and mention the caveats
in v7. Thanks!