[RFC PATCH v2 00/15] khwasan: kernel hardware assisted address sanitizer

19 views
Skip to first unread message

Andrey Konovalov

unread,
Mar 23, 2018, 2:05:58 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
Hi! This is the 2nd RFC version of the patchset.

This patchset adds a new mode to KASAN [1], which is called KHWASAN
(Kernel HardWare assisted Address SANitizer). There's still some work to
do and there are a few TODOs in the code, so I'm publishing this as an RFC
to collect some initial feedback.

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


====== 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).


====== Benchmarks

The following numbers were collected on Odroid C2 board. Both KASAN and
KHWASAN were used in inline instrumentation mode. These are the numbers
I got with the current prototype and they might change.

Boot time [1]:
* ~4.5 sec for clean kernel
* ~5.0 sec for KASAN
* ~5.1 sec for KHWASAN

Slab memory usage after boot [2]:
* ~32 kb for clean kernel
* ~95 kb + 1/8th shadow ~= 107 kb for KASAN
* ~38 kb + 1/16th shadow ~= 40 kb for KHWASAN

Network performance [3]:
* 11.9 Gbits/sec for clean kernel
* 3.08 Gbits/sec for KASAN
* 3.02 Gbits/sec for KHWASAN

Note, that KHWASAN (compared to KASAN) doesn't require quarantine.

[1] Time before the ext4 driver is initialized.
[2] Measured as `cat /proc/meminfo | grep Slab`.
[3] Measured as `iperf -s & iperf -c 127.0.0.1 -t 30`.


====== 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 (r325711 or later) with this
patch applied: https://reviews.llvm.org/D44827.

3. Stack instrumentation is not supported yet (in progress).

4. There are still a few TODOs in the code, that need to be addressed.


====== Changes

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.

Andrey Konovalov (15):
khwasan, mm: change kasan hooks signatures
khwasan: move common kasan and khwasan code to common.c
khwasan: add CONFIG_KASAN_CLASSIC and CONFIG_KASAN_TAGS
khwasan, arm64: adjust shadow size for CONFIG_KASAN_TAGS
khwasan: initialize shadow to 0xff
khwasan, arm64: untag virt address in __kimg_to_phys
khwasan, arm64: fix up fault handling logic
khwasan: add tag related helper functions
khwasan, kvm: untag pointers in kern_hyp_va
khwasan, arm64: enable top byte ignore for the kernel
khwasan, mm: perform untagged pointers comparison in krealloc
khwasan: add bug reporting routines
khwasan: add hooks implementation
khwasan, arm64: add brk handler for inline instrumentation
khwasan: update kasan documentation

Documentation/dev-tools/kasan.rst | 212 ++++++++------
arch/arm64/Kconfig | 1 +
arch/arm64/Makefile | 2 +-
arch/arm64/include/asm/brk-imm.h | 2 +
arch/arm64/include/asm/kvm_mmu.h | 8 +
arch/arm64/include/asm/memory.h | 22 +-
arch/arm64/include/asm/pgtable-hwdef.h | 1 +
arch/arm64/kernel/traps.c | 61 ++++
arch/arm64/mm/fault.c | 3 +
arch/arm64/mm/kasan_init.c | 13 +-
arch/arm64/mm/proc.S | 9 +-
include/linux/compiler-clang.h | 9 +-
include/linux/compiler-gcc.h | 4 +
include/linux/compiler.h | 3 +-
include/linux/kasan.h | 84 +++++-
lib/Kconfig.kasan | 68 +++--
mm/kasan/Makefile | 9 +-
mm/kasan/common.c | 325 +++++++++++++++++++++
mm/kasan/kasan.c | 302 +-------------------
mm/kasan/kasan.h | 33 +++
mm/kasan/khwasan.c | 372 +++++++++++++++++++++++++
mm/kasan/report.c | 88 +++++-
mm/slab.c | 12 +-
mm/slab.h | 2 +-
mm/slab_common.c | 6 +-
mm/slub.c | 18 +-
scripts/Makefile.kasan | 32 ++-
virt/kvm/arm/mmu.c | 20 +-
28 files changed, 1266 insertions(+), 455 deletions(-)
create mode 100644 mm/kasan/common.c
create mode 100644 mm/kasan/khwasan.c

--
2.17.0.rc0.231.g781580f067-goog

Andrey Konovalov

unread,
Mar 23, 2018, 2:06:01 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
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 <andre...@google.com>
---
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 | 16 ++++++++--------
6 files changed, 54 insertions(+), 38 deletions(-)

diff --git a/include/linux/kasan.h b/include/linux/kasan.h
index adc13474a53b..3bfebcf7ad2b 100644
--- a/include/linux/kasan.h
+++ b/include/linux/kasan.h
@@ -53,14 +53,14 @@ void kasan_unpoison_object_data(struct kmem_cache *cache, void *object);
void kasan_poison_object_data(struct kmem_cache *cache, void *object);
void kasan_init_slab_obj(struct kmem_cache *cache, const void *object);

-void kasan_kmalloc_large(const void *ptr, size_t size, gfp_t flags);
+void *kasan_kmalloc_large(const void *ptr, size_t size, gfp_t flags);
void kasan_kfree_large(void *ptr, unsigned long ip);
void kasan_poison_kfree(void *ptr, unsigned long ip);
-void kasan_kmalloc(struct kmem_cache *s, const void *object, size_t size,
+void *kasan_kmalloc(struct kmem_cache *s, const void *object, size_t size,
gfp_t flags);
-void kasan_krealloc(const void *object, size_t new_size, gfp_t flags);
+void *kasan_krealloc(const void *object, size_t new_size, gfp_t flags);

-void kasan_slab_alloc(struct kmem_cache *s, void *object, gfp_t flags);
+void *kasan_slab_alloc(struct kmem_cache *s, void *object, gfp_t flags);
bool kasan_slab_free(struct kmem_cache *s, void *object, unsigned long ip);

struct kasan_cache {
@@ -105,16 +105,28 @@ static inline void kasan_poison_object_data(struct kmem_cache *cache,
static inline void kasan_init_slab_obj(struct kmem_cache *cache,
const void *object) {}

-static inline void kasan_kmalloc_large(void *ptr, size_t size, gfp_t flags) {}
+static inline void *kasan_kmalloc_large(void *ptr, size_t size, gfp_t flags)
+{
+ return ptr;
+}
static inline void kasan_kfree_large(void *ptr, unsigned long ip) {}
static inline void kasan_poison_kfree(void *ptr, unsigned long ip) {}
-static inline void kasan_kmalloc(struct kmem_cache *s, const void *object,
- size_t size, gfp_t flags) {}
-static inline void kasan_krealloc(const void *object, size_t new_size,
- gfp_t flags) {}
+static inline void *kasan_kmalloc(struct kmem_cache *s, const void *object,
+ size_t size, gfp_t flags)
+{
+ return (void *)object;
+}
+static inline void *kasan_krealloc(const void *object, size_t new_size,
+ gfp_t flags)
+{
+ return (void *)object;
+}

-static inline void kasan_slab_alloc(struct kmem_cache *s, void *object,
- gfp_t flags) {}
+static inline void *kasan_slab_alloc(struct kmem_cache *s, void *object,
+ gfp_t flags)
+{
+ return object;
+}
static inline bool kasan_slab_free(struct kmem_cache *s, void *object,
unsigned long ip)
{
diff --git a/mm/kasan/kasan.c b/mm/kasan/kasan.c
index e13d911251e7..d8cb63bd1ecc 100644
--- a/mm/kasan/kasan.c
+++ b/mm/kasan/kasan.c
@@ -484,9 +484,9 @@ void kasan_init_slab_obj(struct kmem_cache *cache, const void *object)
__memset(alloc_info, 0, sizeof(*alloc_info));
}

-void kasan_slab_alloc(struct kmem_cache *cache, void *object, gfp_t flags)
+void *kasan_slab_alloc(struct kmem_cache *cache, void *object, gfp_t flags)
{
- kasan_kmalloc(cache, object, cache->object_size, flags);
+ return kasan_kmalloc(cache, object, cache->object_size, flags);
}

static bool __kasan_slab_free(struct kmem_cache *cache, void *object,
@@ -527,7 +527,7 @@ bool kasan_slab_free(struct kmem_cache *cache, void *object, unsigned long ip)
return __kasan_slab_free(cache, object, ip, true);
}

-void kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size,
+void *kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size,
gfp_t flags)
{
unsigned long redzone_start;
@@ -537,7 +537,7 @@ void kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size,
quarantine_reduce();

if (unlikely(object == NULL))
- return;
+ return NULL;

redzone_start = round_up((unsigned long)(object + size),
KASAN_SHADOW_SCALE_SIZE);
@@ -550,10 +550,12 @@ void kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size,

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(const void *ptr, size_t size, gfp_t flags)
+void *kasan_kmalloc_large(const void *ptr, size_t size, gfp_t flags)
{
struct page *page;
unsigned long redzone_start;
@@ -563,7 +565,7 @@ void kasan_kmalloc_large(const void *ptr, size_t size, gfp_t flags)
quarantine_reduce();

if (unlikely(ptr == NULL))
- return;
+ return NULL;

page = virt_to_page(ptr);
redzone_start = round_up((unsigned long)(ptr + size),
@@ -573,21 +575,23 @@ void kasan_kmalloc_large(const void *ptr, size_t size, gfp_t flags)
kasan_unpoison_shadow(ptr, size);
kasan_poison_shadow((void *)redzone_start, redzone_end - redzone_start,
KASAN_PAGE_REDZONE);
+
+ return (void *)ptr;
}

-void kasan_krealloc(const void *object, size_t size, gfp_t flags)
+void *kasan_krealloc(const void *object, size_t size, gfp_t flags)
{
struct page *page;

if (unlikely(object == ZERO_SIZE_PTR))
- return;
+ return ZERO_SIZE_PTR;

page = virt_to_head_page(object);

if (unlikely(!PageSlab(page)))
- kasan_kmalloc_large(object, size, flags);
+ return kasan_kmalloc_large(object, size, flags);
else
- kasan_kmalloc(page->slab_cache, object, size, flags);
+ return kasan_kmalloc(page->slab_cache, object, size, flags);
}

void kasan_poison_kfree(void *ptr, unsigned long ip)
diff --git a/mm/slab.c b/mm/slab.c
index 324446621b3e..ec6a9e8696ab 100644
--- a/mm/slab.c
+++ b/mm/slab.c
@@ -3538,7 +3538,7 @@ void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
void *ret = slab_alloc(cachep, flags, _RET_IP_);

- kasan_slab_alloc(cachep, ret, flags);
+ ret = kasan_slab_alloc(cachep, ret, flags);
trace_kmem_cache_alloc(_RET_IP_, ret,
cachep->object_size, cachep->size, flags);

@@ -3604,7 +3604,7 @@ kmem_cache_alloc_trace(struct kmem_cache *cachep, gfp_t flags, size_t size)

ret = slab_alloc(cachep, flags, _RET_IP_);

- kasan_kmalloc(cachep, ret, size, flags);
+ ret = kasan_kmalloc(cachep, ret, size, flags);
trace_kmalloc(_RET_IP_, ret,
size, cachep->size, flags);
return ret;
@@ -3628,7 +3628,7 @@ void *kmem_cache_alloc_node(struct kmem_cache *cachep, gfp_t flags, int nodeid)
{
void *ret = slab_alloc_node(cachep, flags, nodeid, _RET_IP_);

- kasan_slab_alloc(cachep, ret, flags);
+ ret = kasan_slab_alloc(cachep, ret, flags);
trace_kmem_cache_alloc_node(_RET_IP_, ret,
cachep->object_size, cachep->size,
flags, nodeid);
@@ -3647,7 +3647,7 @@ void *kmem_cache_alloc_node_trace(struct kmem_cache *cachep,

ret = slab_alloc_node(cachep, flags, nodeid, _RET_IP_);

- kasan_kmalloc(cachep, ret, size, flags);
+ ret = kasan_kmalloc(cachep, ret, size, flags);
trace_kmalloc_node(_RET_IP_, ret,
size, cachep->size,
flags, nodeid);
@@ -3666,7 +3666,7 @@ __do_kmalloc_node(size_t size, gfp_t flags, int node, unsigned long caller)
if (unlikely(ZERO_OR_NULL_PTR(cachep)))
return cachep;
ret = kmem_cache_alloc_node_trace(cachep, flags, node, size);
- kasan_kmalloc(cachep, ret, size, flags);
+ ret = kasan_kmalloc(cachep, ret, size, flags);

return ret;
}
@@ -3702,7 +3702,7 @@ static __always_inline void *__do_kmalloc(size_t size, gfp_t flags,
return cachep;
ret = slab_alloc(cachep, flags, caller);

- kasan_kmalloc(cachep, ret, size, flags);
+ ret = kasan_kmalloc(cachep, ret, size, flags);
trace_kmalloc(caller, ret,
size, cachep->size, flags);

diff --git a/mm/slab.h b/mm/slab.h
index 51813236e773..8a588d9d89a0 100644
--- a/mm/slab.h
+++ b/mm/slab.h
@@ -440,7 +440,7 @@ static inline void slab_post_alloc_hook(struct kmem_cache *s, gfp_t flags,

kmemleak_alloc_recursive(object, s->object_size, 1,
s->flags, flags);
- kasan_slab_alloc(s, object, flags);
+ p[i] = kasan_slab_alloc(s, object, flags);
}

if (memcg_kmem_enabled())
diff --git a/mm/slab_common.c b/mm/slab_common.c
index 10f127b2de7c..a33e61315ca6 100644
--- a/mm/slab_common.c
+++ b/mm/slab_common.c
@@ -1164,7 +1164,7 @@ void *kmalloc_order(size_t size, gfp_t flags, unsigned int order)
page = alloc_pages(flags, order);
ret = page ? page_address(page) : NULL;
kmemleak_alloc(ret, size, 1, flags);
- kasan_kmalloc_large(ret, size, flags);
+ ret = kasan_kmalloc_large(ret, size, flags);
return ret;
}
EXPORT_SYMBOL(kmalloc_order);
@@ -1442,7 +1442,7 @@ static __always_inline void *__do_krealloc(const void *p, size_t new_size,
ks = ksize(p);

if (ks >= new_size) {
- kasan_krealloc((void *)p, new_size, flags);
+ p = kasan_krealloc((void *)p, new_size, flags);
return (void *)p;
}

diff --git a/mm/slub.c b/mm/slub.c
index f111c2a908b9..4a856512f225 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -1350,10 +1350,10 @@ static inline void dec_slabs_node(struct kmem_cache *s, int node,
* Hooks for other subsystems that check memory allocations. In a typical
* production configuration these hooks all should produce no code at all.
*/
-static inline void kmalloc_large_node_hook(void *ptr, size_t size, gfp_t flags)
+static inline void kmalloc_large_node_hook(void **ptr, size_t size, gfp_t flags)
{
- kmemleak_alloc(ptr, size, 1, flags);
- kasan_kmalloc_large(ptr, size, flags);
+ kmemleak_alloc(*ptr, size, 1, flags);
+ *ptr = kasan_kmalloc_large(*ptr, size, flags);
}

static __always_inline void kfree_hook(void *x)
@@ -2758,7 +2758,7 @@ void *kmem_cache_alloc_trace(struct kmem_cache *s, gfp_t gfpflags, size_t size)
{
void *ret = slab_alloc(s, gfpflags, _RET_IP_);
trace_kmalloc(_RET_IP_, ret, size, s->size, gfpflags);
- kasan_kmalloc(s, ret, size, gfpflags);
+ ret = kasan_kmalloc(s, ret, size, gfpflags);
return ret;
}
EXPORT_SYMBOL(kmem_cache_alloc_trace);
@@ -2786,7 +2786,7 @@ void *kmem_cache_alloc_node_trace(struct kmem_cache *s,
trace_kmalloc_node(_RET_IP_, ret,
size, s->size, gfpflags, node);

- kasan_kmalloc(s, ret, size, gfpflags);
+ ret = kasan_kmalloc(s, ret, size, gfpflags);
return ret;
}
EXPORT_SYMBOL(kmem_cache_alloc_node_trace);
@@ -3767,7 +3767,7 @@ void *__kmalloc(size_t size, gfp_t flags)

trace_kmalloc(_RET_IP_, ret, size, s->size, flags);

- kasan_kmalloc(s, ret, size, flags);
+ ret = kasan_kmalloc(s, ret, size, flags);

return ret;
}
@@ -3784,7 +3784,7 @@ static void *kmalloc_large_node(size_t size, gfp_t flags, int node)
if (page)
ptr = page_address(page);

- kmalloc_large_node_hook(ptr, size, flags);
+ kmalloc_large_node_hook(&ptr, size, flags);
return ptr;
}

@@ -3812,7 +3812,7 @@ void *__kmalloc_node(size_t size, gfp_t flags, int node)

trace_kmalloc_node(_RET_IP_, ret, size, s->size, flags, node);

- kasan_kmalloc(s, ret, size, flags);
+ ret = kasan_kmalloc(s, ret, size, flags);

return ret;
}
--
2.17.0.rc0.231.g781580f067-goog

Andrey Konovalov

unread,
Mar 23, 2018, 2:06:04 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
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 <andre...@google.com>
---
mm/kasan/Makefile | 5 +-
mm/kasan/common.c | 318 ++++++++++++++++++++++++++++++++++++++++++++++
mm/kasan/kasan.c | 288 +----------------------------------------
mm/kasan/kasan.h | 4 +
4 files changed, 330 insertions(+), 285 deletions(-)
create mode 100644 mm/kasan/common.c

diff --git a/mm/kasan/Makefile b/mm/kasan/Makefile
index 3289db38bc87..a6df14bffb6b 100644
--- a/mm/kasan/Makefile
+++ b/mm/kasan/Makefile
@@ -1,11 +1,14 @@
# SPDX-License-Identifier: GPL-2.0
KASAN_SANITIZE := n
+UBSAN_SANITIZE_common.o := n
UBSAN_SANITIZE_kasan.o := n
KCOV_INSTRUMENT := n

CFLAGS_REMOVE_kasan.o = -pg
# Function splitter causes unnecessary splits in __asan_load1/__asan_store1
# see: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63533
+
+CFLAGS_common.o := $(call cc-option, -fno-conserve-stack -fno-stack-protector)
CFLAGS_kasan.o := $(call cc-option, -fno-conserve-stack -fno-stack-protector)

-obj-y := kasan.o report.o kasan_init.o quarantine.o
+obj-y := common.o kasan.o report.o kasan_init.o quarantine.o
diff --git a/mm/kasan/common.c b/mm/kasan/common.c
new file mode 100644
index 000000000000..08f6c8cb9f84
--- /dev/null
+++ b/mm/kasan/common.c
@@ -0,0 +1,318 @@
+/*
+ * This file contains common KASAN and KHWASAN code.
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Author: Andrey Ryabinin <ryabin...@gmail.com>
+ *
+ * Some code borrowed from https://github.com/xairy/kasan-prototype by
+ * Andrey Konovalov <andre...@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#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"
+
+void kasan_enable_current(void)
+{
+ current->kasan_depth++;
+}
+
+void kasan_disable_current(void)
+{
+ current->kasan_depth--;
+}
+
+static void __kasan_unpoison_stack(struct task_struct *task, const void *sp)
+{
+ void *base = task_stack_page(task);
+ size_t size = sp - base;
+
+ kasan_unpoison_shadow(base, size);
+}
+
+/* Unpoison the entire stack for a task. */
+void kasan_unpoison_task_stack(struct task_struct *task)
+{
+ __kasan_unpoison_stack(task, task_stack_page(task) + THREAD_SIZE);
+}
+
+/* Unpoison the stack for the current task beyond a watermark sp value. */
+asmlinkage void kasan_unpoison_task_stack_below(const void *watermark)
+{
+ /*
+ * Calculate the task stack base address. Avoid using 'current'
+ * because this function is called by early resume code which hasn't
+ * yet set up the percpu register (%gs).
+ */
+ void *base = (void *)((unsigned long)watermark & ~(THREAD_SIZE - 1));
+
+ kasan_unpoison_shadow(base, watermark - base);
+}
+
+/*
+ * Clear all poison for the region between the current SP and a provided
+ * watermark value, as is sometimes required prior to hand-crafted asm function
+ * returns in the middle of functions.
+ */
+void kasan_unpoison_stack_above_sp_to(const void *watermark)
+{
+ const void *sp = __builtin_frame_address(0);
+ size_t size = watermark - sp;
+
+ if (WARN_ON(sp > watermark))
+ return;
+ kasan_unpoison_shadow(sp, size);
+}
+
+void kasan_check_read(const volatile void *p, unsigned int size)
+{
+ check_memory_region((unsigned long)p, size, false, _RET_IP_);
+}
+EXPORT_SYMBOL(kasan_check_read);
+
+void kasan_check_write(const volatile void *p, unsigned int size)
+{
+ check_memory_region((unsigned long)p, size, true, _RET_IP_);
+}
+EXPORT_SYMBOL(kasan_check_write);
+
+#undef memset
+void *memset(void *addr, int c, size_t len)
+{
+ check_memory_region((unsigned long)addr, len, true, _RET_IP_);
+
+ return __memset(addr, c, len);
+}
+
+#undef memmove
+void *memmove(void *dest, const void *src, size_t len)
+{
+ check_memory_region((unsigned long)src, len, false, _RET_IP_);
+ check_memory_region((unsigned long)dest, len, true, _RET_IP_);
+
+ return __memmove(dest, src, len);
+}
+
+#undef memcpy
+void *memcpy(void *dest, const void *src, size_t len)
+{
+ check_memory_region((unsigned long)src, len, false, _RET_IP_);
+ check_memory_region((unsigned long)dest, len, true, _RET_IP_);
+
+ return __memcpy(dest, src, len);
+}
+
+void kasan_alloc_pages(struct page *page, unsigned int order)
+{
+ if (likely(!PageHighMem(page)))
+ kasan_unpoison_shadow(page_address(page), PAGE_SIZE << order);
+}
+
+size_t kasan_metadata_size(struct kmem_cache *cache)
+{
+ return (cache->kasan_info.alloc_meta_offset ?
+ sizeof(struct kasan_alloc_meta) : 0) +
+ (cache->kasan_info.free_meta_offset ?
+ sizeof(struct kasan_free_meta) : 0);
+}
+
+void kasan_unpoison_object_data(struct kmem_cache *cache, void *object)
+{
+ kasan_unpoison_shadow(object, cache->object_size);
+}
+
+static inline int in_irqentry_text(unsigned long ptr)
+{
+ return (ptr >= (unsigned long)&__irqentry_text_start &&
+ ptr < (unsigned long)&__irqentry_text_end) ||
+ (ptr >= (unsigned long)&__softirqentry_text_start &&
+ ptr < (unsigned long)&__softirqentry_text_end);
+}
+
+static inline void filter_irq_stacks(struct stack_trace *trace)
+{
+ int i;
+
+ 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;
+ }
+}
+
+static inline depot_stack_handle_t save_stack(gfp_t flags)
+{
+ unsigned long entries[KASAN_STACK_DEPTH];
+ struct stack_trace trace = {
+ .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--;
+
+ return depot_save_stack(&trace, flags);
+}
+
+void set_track(struct kasan_track *track, gfp_t flags)
+{
+ track->pid = current->pid;
+ track->stack = save_stack(flags);
+}
+
+struct kasan_alloc_meta *get_alloc_info(struct kmem_cache *cache,
+ const void *object)
+{
+ BUILD_BUG_ON(sizeof(struct kasan_alloc_meta) > 32);
+ return (void *)object + cache->kasan_info.alloc_meta_offset;
+}
+
+struct kasan_free_meta *get_free_info(struct kmem_cache *cache,
+ const void *object)
+{
+ BUILD_BUG_ON(sizeof(struct kasan_free_meta) > 32);
+ return (void *)object + cache->kasan_info.free_meta_offset;
+}
+
+void kasan_init_slab_obj(struct kmem_cache *cache, const void *object)
+{
+ struct kasan_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_krealloc(const void *object, size_t size, gfp_t flags)
+{
+ struct page *page;
+
+ if (unlikely(object == ZERO_SIZE_PTR))
+ return (void *)object;
+
+ page = virt_to_head_page(object);
+
+ if (unlikely(!PageSlab(page)))
+ return kasan_kmalloc_large(object, size, flags);
+ else
+ return kasan_kmalloc(page->slab_cache, object, size, flags);
+}
+
+int kasan_module_alloc(void *addr, size_t size)
+{
+ void *ret;
+ size_t shadow_size;
+ unsigned long shadow_start;
+
+ shadow_start = (unsigned long)kasan_mem_to_shadow(addr);
+ shadow_size = round_up(size >> KASAN_SHADOW_SCALE_SHIFT,
+ 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);
+ return 0;
+ }
+
+ return -ENOMEM;
+}
+
+void kasan_free_shadow(const struct vm_struct *vm)
+{
+ if (vm->flags & VM_KASAN)
+ vfree(kasan_mem_to_shadow(vm->addr));
+}
+
+#ifdef CONFIG_MEMORY_HOTPLUG
+static int __meminit kasan_mem_notifier(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct memory_notify *mem_data = data;
+ unsigned long nr_shadow_pages, start_kaddr, shadow_start;
+ unsigned long shadow_end, shadow_size;
+
+ nr_shadow_pages = mem_data->nr_pages >> KASAN_SHADOW_SCALE_SHIFT;
+ start_kaddr = (unsigned long)pfn_to_kaddr(mem_data->start_pfn);
+ shadow_start = (unsigned long)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)))
+ return NOTIFY_BAD;
+
+ switch (action) {
+ case MEM_GOING_ONLINE: {
+ void *ret;
+
+ 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)
+ return NOTIFY_BAD;
+
+ kmemleak_ignore(ret);
+ return NOTIFY_OK;
+ }
+ case MEM_OFFLINE:
+ vfree((void *)shadow_start);
+ }
+
+ return NOTIFY_OK;
+}
+
+static int __init kasan_memhotplug_init(void)
+{
+ hotplug_memory_notifier(kasan_mem_notifier, 0);
+
+ return 0;
+}
+
+module_init(kasan_memhotplug_init);
+#endif
diff --git a/mm/kasan/kasan.c b/mm/kasan/kasan.c
index d8cb63bd1ecc..d026286de750 100644
--- a/mm/kasan/kasan.c
+++ b/mm/kasan/kasan.c
@@ -1,5 +1,6 @@
/*
- * This file contains shadow memory manipulation code.
+ * This file contains core KASAN code, including shadow memory manipulation
+ * code, implementation of KASAN hooks and compiler inserted callbacks, etc.
*
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
* Author: Andrey Ryabinin <ryabin...@gmail.com>
@@ -40,21 +41,11 @@
#include "kasan.h"
#include "../slab.h"

-void kasan_enable_current(void)
-{
- current->kasan_depth++;
-}
-
-void kasan_disable_current(void)
-{
- current->kasan_depth--;
-}
-
/*
* Poisons the shadow memory for 'size' bytes starting from 'addr'.
* Memory addresses should be aligned to KASAN_SHADOW_SCALE_SIZE.
*/
-static void kasan_poison_shadow(const void *address, size_t size, u8 value)
+void kasan_poison_shadow(const void *address, size_t size, u8 value)
{
void *shadow_start, *shadow_end;

@@ -74,48 +65,6 @@ void kasan_unpoison_shadow(const void *address, size_t size)
}
}

-static void __kasan_unpoison_stack(struct task_struct *task, const void *sp)
-{
- void *base = task_stack_page(task);
- size_t size = sp - base;
-
- kasan_unpoison_shadow(base, size);
-}
-
-/* Unpoison the entire stack for a task. */
-void kasan_unpoison_task_stack(struct task_struct *task)
-{
- __kasan_unpoison_stack(task, task_stack_page(task) + THREAD_SIZE);
-}
-
-/* Unpoison the stack for the current task beyond a watermark sp value. */
-asmlinkage void kasan_unpoison_task_stack_below(const void *watermark)
-{
- /*
- * Calculate the task stack base address. Avoid using 'current'
- * because this function is called by early resume code which hasn't
- * yet set up the percpu register (%gs).
- */
- void *base = (void *)((unsigned long)watermark & ~(THREAD_SIZE - 1));
-
- kasan_unpoison_shadow(base, watermark - base);
-}
-
-/*
- * Clear all poison for the region between the current SP and a provided
- * watermark value, as is sometimes required prior to hand-crafted asm function
- * returns in the middle of functions.
- */
-void kasan_unpoison_stack_above_sp_to(const void *watermark)
-{
- const void *sp = __builtin_frame_address(0);
- size_t size = watermark - sp;
-
- if (WARN_ON(sp > watermark))
- return;
- kasan_unpoison_shadow(sp, size);
-}
-
/*
* All functions below always inlined so compiler could
* perform better optimizations in each of __asan_loadX/__assn_storeX
@@ -260,57 +209,12 @@ static __always_inline void check_memory_region_inline(unsigned long addr,
kasan_report(addr, size, write, ret_ip);
}

-static void check_memory_region(unsigned long addr,
- size_t size, bool write,
+void check_memory_region(unsigned long addr, size_t size, bool write,
unsigned long ret_ip)
{
check_memory_region_inline(addr, size, write, ret_ip);
}

-void kasan_check_read(const volatile void *p, unsigned int size)
-{
- check_memory_region((unsigned long)p, size, false, _RET_IP_);
-}
-EXPORT_SYMBOL(kasan_check_read);
-
-void kasan_check_write(const volatile void *p, unsigned int size)
-{
- check_memory_region((unsigned long)p, size, true, _RET_IP_);
-}
-EXPORT_SYMBOL(kasan_check_write);
-
-#undef memset
-void *memset(void *addr, int c, size_t len)
-{
- check_memory_region((unsigned long)addr, len, true, _RET_IP_);
-
- return __memset(addr, c, len);
-}
-
-#undef memmove
-void *memmove(void *dest, const void *src, size_t len)
-{
- check_memory_region((unsigned long)src, len, false, _RET_IP_);
- check_memory_region((unsigned long)dest, len, true, _RET_IP_);
-
- return __memmove(dest, src, len);
-}
-
-#undef memcpy
-void *memcpy(void *dest, const void *src, size_t len)
-{
- check_memory_region((unsigned long)src, len, false, _RET_IP_);
- check_memory_region((unsigned long)dest, len, true, _RET_IP_);
-
- return __memcpy(dest, src, len);
-}
-
-void kasan_alloc_pages(struct page *page, unsigned int order)
-{
- if (likely(!PageHighMem(page)))
- kasan_unpoison_shadow(page_address(page), PAGE_SIZE << order);
-}
-
void kasan_free_pages(struct page *page, unsigned int order)
{
if (likely(!PageHighMem(page)))
@@ -385,14 +289,6 @@ void kasan_cache_shutdown(struct kmem_cache *cache)
quarantine_remove_cache(cache);
}

-size_t kasan_metadata_size(struct kmem_cache *cache)
-{
- return (cache->kasan_info.alloc_meta_offset ?
- sizeof(struct kasan_alloc_meta) : 0) +
- (cache->kasan_info.free_meta_offset ?
- sizeof(struct kasan_free_meta) : 0);
-}
-
void kasan_poison_slab(struct page *page)
{
kasan_poison_shadow(page_address(page),
@@ -400,11 +296,6 @@ void kasan_poison_slab(struct page *page)
KASAN_KMALLOC_REDZONE);
}

-void kasan_unpoison_object_data(struct kmem_cache *cache, void *object)
-{
- kasan_unpoison_shadow(object, cache->object_size);
-}
-
void kasan_poison_object_data(struct kmem_cache *cache, void *object)
{
kasan_poison_shadow(object,
@@ -412,78 +303,6 @@ void kasan_poison_object_data(struct kmem_cache *cache, void *object)
KASAN_KMALLOC_REDZONE);
}

-static inline int in_irqentry_text(unsigned long ptr)
-{
- return (ptr >= (unsigned long)&__irqentry_text_start &&
- ptr < (unsigned long)&__irqentry_text_end) ||
- (ptr >= (unsigned long)&__softirqentry_text_start &&
- ptr < (unsigned long)&__softirqentry_text_end);
-}
-
-static inline void filter_irq_stacks(struct stack_trace *trace)
-{
- int i;
-
- 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;
- }
-}
-
-static inline depot_stack_handle_t save_stack(gfp_t flags)
-{
- unsigned long entries[KASAN_STACK_DEPTH];
- struct stack_trace trace = {
- .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--;
-
- return depot_save_stack(&trace, flags);
-}
-
-static inline void set_track(struct kasan_track *track, gfp_t flags)
-{
- track->pid = current->pid;
- track->stack = save_stack(flags);
-}
-
-struct kasan_alloc_meta *get_alloc_info(struct kmem_cache *cache,
- const void *object)
-{
- BUILD_BUG_ON(sizeof(struct kasan_alloc_meta) > 32);
- return (void *)object + cache->kasan_info.alloc_meta_offset;
-}
-
-struct kasan_free_meta *get_free_info(struct kmem_cache *cache,
- const void *object)
-{
- BUILD_BUG_ON(sizeof(struct kasan_free_meta) > 32);
- return (void *)object + cache->kasan_info.free_meta_offset;
-}
-
-void kasan_init_slab_obj(struct kmem_cache *cache, const void *object)
-{
- struct kasan_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(struct kmem_cache *cache, void *object, gfp_t flags)
{
return kasan_kmalloc(cache, object, cache->object_size, flags);
@@ -579,21 +398,6 @@ void *kasan_kmalloc_large(const void *ptr, size_t size, gfp_t flags)
return (void *)ptr;
}

-void *kasan_krealloc(const void *object, size_t size, gfp_t flags)
-{
- struct page *page;
-
- if (unlikely(object == ZERO_SIZE_PTR))
- return ZERO_SIZE_PTR;
-
- page = virt_to_head_page(object);
-
- if (unlikely(!PageSlab(page)))
- return kasan_kmalloc_large(object, size, flags);
- else
- return kasan_kmalloc(page->slab_cache, object, size, flags);
-}
-
void kasan_poison_kfree(void *ptr, unsigned long ip)
{
struct page *page;
@@ -619,40 +423,6 @@ void kasan_kfree_large(void *ptr, unsigned long ip)
/* The object will be poisoned by page_alloc. */
}

-int kasan_module_alloc(void *addr, size_t size)
-{
- void *ret;
- size_t shadow_size;
- unsigned long shadow_start;
-
- shadow_start = (unsigned long)kasan_mem_to_shadow(addr);
- shadow_size = round_up(size >> KASAN_SHADOW_SCALE_SHIFT,
- 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);
- return 0;
- }
-
- return -ENOMEM;
-}
-
-void kasan_free_shadow(const struct vm_struct *vm)
-{
- if (vm->flags & VM_KASAN)
- vfree(kasan_mem_to_shadow(vm->addr));
-}
-
static void register_global(struct kasan_global *global)
{
size_t aligned_size = round_up(global->size, KASAN_SHADOW_SCALE_SIZE);
@@ -793,53 +563,3 @@ DEFINE_ASAN_SET_SHADOW(f2);
DEFINE_ASAN_SET_SHADOW(f3);
DEFINE_ASAN_SET_SHADOW(f5);
DEFINE_ASAN_SET_SHADOW(f8);
-
-#ifdef CONFIG_MEMORY_HOTPLUG
-static int __meminit kasan_mem_notifier(struct notifier_block *nb,
- unsigned long action, void *data)
-{
- struct memory_notify *mem_data = data;
- unsigned long nr_shadow_pages, start_kaddr, shadow_start;
- unsigned long shadow_end, shadow_size;
-
- nr_shadow_pages = mem_data->nr_pages >> KASAN_SHADOW_SCALE_SHIFT;
- start_kaddr = (unsigned long)pfn_to_kaddr(mem_data->start_pfn);
- shadow_start = (unsigned long)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)))
- return NOTIFY_BAD;
-
- switch (action) {
- case MEM_GOING_ONLINE: {
- void *ret;
-
- 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)
- return NOTIFY_BAD;
-
- kmemleak_ignore(ret);
- return NOTIFY_OK;
- }
- case MEM_OFFLINE:
- vfree((void *)shadow_start);
- }
-
- return NOTIFY_OK;
-}
-
-static int __init kasan_memhotplug_init(void)
-{
- hotplug_memory_notifier(kasan_mem_notifier, 0);
-
- return 0;
-}
-
-module_init(kasan_memhotplug_init);
-#endif
diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h
index c12dcfde2ebd..2be31754278e 100644
--- a/mm/kasan/kasan.h
+++ b/mm/kasan/kasan.h
@@ -94,6 +94,7 @@ struct kasan_free_meta {
struct qlist_node quarantine_link;
};

+void set_track(struct kasan_track *track, gfp_t flags);
struct kasan_alloc_meta *get_alloc_info(struct kmem_cache *cache,
const void *object);
struct kasan_free_meta *get_free_info(struct kmem_cache *cache,
@@ -105,6 +106,9 @@ static inline const void *kasan_shadow_to_mem(const void *shadow_addr)
<< KASAN_SHADOW_SCALE_SHIFT);
}

+void check_memory_region(unsigned long addr, size_t size, bool write,
+ unsigned long ret_ip);
+
void kasan_report(unsigned long addr, size_t size,
bool is_write, unsigned long ip);
void kasan_report_invalid_free(void *object, unsigned long ip);
--
2.17.0.rc0.231.g781580f067-goog

Andrey Konovalov

unread,
Mar 23, 2018, 2:06:07 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
This commit splits the current CONFIG_KASAN config option into two:
1. CONFIG_KASAN_CLASSIC, that enables the classic KASAN version (the one
that exists now);
2. CONFIG_KASAN_TAGS, that enables KHWASAN.

With CONFIG_KASAN_TAGS 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_CLASSIC and CONFIG_KASAN_CLASSIC support both
CONFIG_KASAN_INLINE and CONFIG_KASAN_OUTLINE instrumentation modes.

This commit also adds empty placeholder (for now) KHWASAN implementation
of KASAN hooks (which KHWASAN reuses) and placeholder implementation
of KHWASAN specific hooks inserted by the compiler.

Signed-off-by: Andrey Konovalov <andre...@google.com>
---
arch/arm64/Kconfig | 1 +
include/linux/compiler-clang.h | 9 ++-
include/linux/compiler-gcc.h | 4 ++
include/linux/compiler.h | 3 +-
include/linux/kasan.h | 16 +++--
lib/Kconfig.kasan | 68 +++++++++++++-----
mm/kasan/Makefile | 6 +-
mm/kasan/khwasan.c | 127 +++++++++++++++++++++++++++++++++
mm/slub.c | 2 +-
scripts/Makefile.kasan | 32 ++++++++-
10 files changed, 242 insertions(+), 26 deletions(-)
create mode 100644 mm/kasan/khwasan.c

diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index 7381eeb7ef8e..759871510f87 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -88,6 +88,7 @@ config ARM64
select HAVE_ARCH_HUGE_VMAP
select HAVE_ARCH_JUMP_LABEL
select HAVE_ARCH_KASAN if !(ARM64_16K_PAGES && ARM64_VA_BITS_48)
+ select HAVE_ARCH_KASAN_TAGS if !(ARM64_16K_PAGES && ARM64_VA_BITS_48)
select HAVE_ARCH_KGDB
select HAVE_ARCH_MMAP_RND_BITS
select HAVE_ARCH_MMAP_RND_COMPAT_BITS if COMPAT
diff --git a/include/linux/compiler-clang.h b/include/linux/compiler-clang.h
index c8f4eea6a5f3..fccebb963ee3 100644
--- a/include/linux/compiler-clang.h
+++ b/include/linux/compiler-clang.h
@@ -24,15 +24,20 @@
#define KASAN_ABI_VERSION 5

/* emulate gcc's __SANITIZE_ADDRESS__ flag */
-#if __has_feature(address_sanitizer)
+#if __has_feature(address_sanitizer) || __has_feature(hwaddress_sanitizer)
#define __SANITIZE_ADDRESS__
#endif

-#ifdef CONFIG_KASAN
+#ifdef CONFIG_KASAN_CLASSIC
#undef __no_sanitize_address
#define __no_sanitize_address __attribute__((no_sanitize("kernel-address")))
#endif

+#ifdef CONFIG_KASAN_TAGS
+#undef __no_sanitize_hwaddress
+#define __no_sanitize_hwaddress __attribute__((no_sanitize("hwaddress")))
+#endif
+
/* Clang doesn't have a way to turn it off per-function, yet. */
#ifdef __noretpoline
#undef __noretpoline
diff --git a/include/linux/compiler-gcc.h b/include/linux/compiler-gcc.h
index e2c7f4369eff..e9bc985c1227 100644
--- a/include/linux/compiler-gcc.h
+++ b/include/linux/compiler-gcc.h
@@ -344,6 +344,10 @@
#define __no_sanitize_address
#endif

+#if !defined(__no_sanitize_hwaddress)
+#define __no_sanitize_hwaddress /* gcc doesn't support KHWASAN */
+#endif
+
/*
* A trick to suppress uninitialized variable warning without generating any
* code
diff --git a/include/linux/compiler.h b/include/linux/compiler.h
index ab4711c63601..6142bae513e8 100644
--- a/include/linux/compiler.h
+++ b/include/linux/compiler.h
@@ -195,7 +195,8 @@ void __read_once_size(const volatile void *p, void *res, int size)
* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67368
* '__maybe_unused' allows us to avoid defined-but-not-used warnings.
*/
-# define __no_kasan_or_inline __no_sanitize_address __maybe_unused
+# define __no_kasan_or_inline __no_sanitize_address __no_sanitize_hwaddress \
+ __maybe_unused
#else
# define __no_kasan_or_inline __always_inline
#endif
diff --git a/include/linux/kasan.h b/include/linux/kasan.h
index 3bfebcf7ad2b..3c45e273a936 100644
--- a/include/linux/kasan.h
+++ b/include/linux/kasan.h
@@ -45,8 +45,6 @@ void kasan_free_pages(struct page *page, unsigned int order);

void kasan_cache_create(struct kmem_cache *cache, size_t *size,
slab_flags_t *flags);
-void kasan_cache_shrink(struct kmem_cache *cache);
-void kasan_cache_shutdown(struct kmem_cache *cache);

void kasan_poison_slab(struct page *page);
void kasan_unpoison_object_data(struct kmem_cache *cache, void *object);
@@ -94,8 +92,6 @@ static inline void kasan_free_pages(struct page *page, unsigned int order) {}
static inline void kasan_cache_create(struct kmem_cache *cache,
size_t *size,
slab_flags_t *flags) {}
-static inline void kasan_cache_shrink(struct kmem_cache *cache) {}
-static inline void kasan_cache_shutdown(struct kmem_cache *cache) {}

static inline void kasan_poison_slab(struct page *page) {}
static inline void kasan_unpoison_object_data(struct kmem_cache *cache,
@@ -141,4 +137,16 @@ static inline size_t kasan_metadata_size(struct kmem_cache *cache) { return 0; }

#endif /* CONFIG_KASAN */

+#ifdef CONFIG_KASAN_CLASSIC
+
+void kasan_cache_shrink(struct kmem_cache *cache);
+void kasan_cache_shutdown(struct kmem_cache *cache);
+
+#else /* CONFIG_KASAN_CLASSIC */
+
+static inline void kasan_cache_shrink(struct kmem_cache *cache) {}
+static inline void kasan_cache_shutdown(struct kmem_cache *cache) {}
+
+#endif /* CONFIG_KASAN_CLASSIC */
+
#endif /* LINUX_KASAN_H */
diff --git a/lib/Kconfig.kasan b/lib/Kconfig.kasan
index 3d35d062970d..ab34e7d7d3a7 100644
--- a/lib/Kconfig.kasan
+++ b/lib/Kconfig.kasan
@@ -1,33 +1,69 @@
config HAVE_ARCH_KASAN
bool

+config HAVE_ARCH_KASAN_TAGS
+ bool
+
if HAVE_ARCH_KASAN

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.
+ KASAN has two modes: KASAN (a classic version, similar to userspace
+ ASan, enabled with CONFIG_KASAN_CLASSIC) and KHWASAN (a version
+ based on pointer tagging, only for arm64, similar to userspace
+ HWASan, enabled with CONFIG_KASAN_TAGS).
+
+choice
+ prompt "KASAN mode"
+ depends on KASAN
+ default KASAN_CLASSIC
+
+config KASAN_CLASSIC
+ bool "KASAN: the classic mode"
depends on SLUB || (SLAB && !DEBUG_SLAB)
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.
+ Enables the classic 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 feature consumes about 1/8 of available memory and brings about
~x3 performance slowdown.
For better error detection enable CONFIG_STACKTRACE.
- Currently CONFIG_KASAN doesn't work with CONFIG_DEBUG_SLAB
+ Currently CONFIG_KASAN_CLASSIC doesn't work with CONFIG_DEBUG_SLAB
(the resulting kernel does not boot).

+if HAVE_ARCH_KASAN_TAGS
+
+config KASAN_TAGS
+ bool "KHWASAN: the tagged pointers mode"
+ depends on SLUB || (SLAB && !DEBUG_SLAB)
+ 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.
+ TODO: clang version, slowdown, memory usage
+ For better error detection enable CONFIG_STACKTRACE.
+ Currently CONFIG_KASAN_TAGS 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_CLASSIC && 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


@@ -52,16 +88,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_CLASSIC 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
diff --git a/mm/kasan/Makefile b/mm/kasan/Makefile
index a6df14bffb6b..d930575e6d55 100644
--- a/mm/kasan/Makefile
+++ b/mm/kasan/Makefile
@@ -2,6 +2,7 @@
KASAN_SANITIZE := n
UBSAN_SANITIZE_common.o := n
UBSAN_SANITIZE_kasan.o := n
+UBSAN_SANITIZE_khwasan.o := n
KCOV_INSTRUMENT := n

CFLAGS_REMOVE_kasan.o = -pg
@@ -10,5 +11,8 @@ CFLAGS_REMOVE_kasan.o = -pg

CFLAGS_common.o := $(call cc-option, -fno-conserve-stack -fno-stack-protector)
CFLAGS_kasan.o := $(call cc-option, -fno-conserve-stack -fno-stack-protector)
+CFLAGS_khwasan.o := $(call cc-option, -fno-conserve-stack -fno-stack-protector)

-obj-y := common.o kasan.o report.o kasan_init.o quarantine.o
+obj-$(CONFIG_KASAN) := common.o kasan_init.o report.o
+obj-$(CONFIG_KASAN_CLASSIC) += kasan.o quarantine.o
+obj-$(CONFIG_KASAN_TAGS) += khwasan.o
diff --git a/mm/kasan/khwasan.c b/mm/kasan/khwasan.c
new file mode 100644
index 000000000000..24d75245e9d0
--- /dev/null
+++ b/mm/kasan/khwasan.c
@@ -0,0 +1,127 @@
+/*
+ * This file contains core KHWASAN code, including shadow memory manipulation
+ * code, implementation of KHWASAN hooks and compiler inserted callbacks, etc.
+ *
+ * Copyright (c) 2018 Google, Inc.
+ * Author: Andrey Konovalov <andre...@google.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#define DISABLE_BRANCH_PROFILING
+
+#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/random.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"
+
+void kasan_unpoison_shadow(const void *address, size_t size)
+{
+}
+
+void check_memory_region(unsigned long addr, size_t size, bool write,
+ unsigned long ret_ip)
+{
+}
+
+void kasan_free_pages(struct page *page, unsigned int order)
+{
+}
+
+void kasan_cache_create(struct kmem_cache *cache, size_t *size,
+ slab_flags_t *flags)
+{
+}
+
+void kasan_poison_slab(struct page *page)
+{
+}
+
+void kasan_poison_object_data(struct kmem_cache *cache, void *object)
+{
+}
+
+void *kasan_slab_alloc(struct kmem_cache *cache, void *object, gfp_t flags)
+{
+ return object;
+}
+
+bool kasan_slab_free(struct kmem_cache *cache, void *object, unsigned long ip)
+{
+ return false;
+}
+
+void *kasan_kmalloc(struct kmem_cache *cache, const void *object,
+ size_t size, gfp_t flags)
+{
+ return (void *)object;
+}
+EXPORT_SYMBOL(kasan_kmalloc);
+
+void *kasan_kmalloc_large(const void *ptr, size_t size, gfp_t flags)
+{
+ return (void *)ptr;
+}
+
+void kasan_poison_kfree(void *ptr, unsigned long ip)
+{
+}
+
+void kasan_kfree_large(void *ptr, unsigned long ip)
+{
+}
+
+#define DEFINE_HWASAN_LOAD_STORE(size) \
+ void __hwasan_load##size##_noabort(unsigned long addr) \
+ { \
+ } \
+ EXPORT_SYMBOL(__hwasan_load##size##_noabort); \
+ void __hwasan_store##size##_noabort(unsigned long addr) \
+ { \
+ } \
+ EXPORT_SYMBOL(__hwasan_store##size##_noabort)
+
+DEFINE_HWASAN_LOAD_STORE(1);
+DEFINE_HWASAN_LOAD_STORE(2);
+DEFINE_HWASAN_LOAD_STORE(4);
+DEFINE_HWASAN_LOAD_STORE(8);
+DEFINE_HWASAN_LOAD_STORE(16);
+
+void __hwasan_loadN_noabort(unsigned long addr, unsigned long size)
+{
+}
+EXPORT_SYMBOL(__hwasan_loadN_noabort);
+
+void __hwasan_storeN_noabort(unsigned long addr, unsigned long size)
+{
+}
+EXPORT_SYMBOL(__hwasan_storeN_noabort);
+
+void __hwasan_tag_memory(unsigned long addr, u8 tag, unsigned long size)
+{
+}
+EXPORT_SYMBOL(__hwasan_tag_memory);
diff --git a/mm/slub.c b/mm/slub.c
index 4a856512f225..a00bf24d668e 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -2983,7 +2983,7 @@ static __always_inline void slab_free(struct kmem_cache *s, struct page *page,
do_slab_free(s, page, head, tail, cnt, addr);
}

-#ifdef CONFIG_KASAN
+#ifdef CONFIG_KASAN_CLASSIC
void ___cache_free(struct kmem_cache *cache, void *x, unsigned long addr)
{
do_slab_free(cache, virt_to_head_page(x), x, NULL, 1, addr);
diff --git a/scripts/Makefile.kasan b/scripts/Makefile.kasan
index 69552a39951d..7661ee46ee15 100644
--- a/scripts/Makefile.kasan
+++ b/scripts/Makefile.kasan
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
-ifdef CONFIG_KASAN
+ifdef CONFIG_KASAN_CLASSIC
ifdef CONFIG_KASAN_INLINE
call_threshold := 10000
else
@@ -45,3 +45,33 @@ endif
CFLAGS_KASAN_NOSANITIZE := -fno-builtin

endif
+
+ifdef CONFIG_KASAN_TAGS
+
+ifdef CONFIG_KASAN_INLINE
+ instrumentation_flags := -mllvm -hwasan-mapping-offset=$(KASAN_SHADOW_OFFSET)
+else
+ instrumentation_flags := -mllvm -hwasan-instrument-with-calls=1
+endif
+
+CFLAGS_KASAN_MINIMAL := -fsanitize=hwaddress
+
+# TODO: implement in clang and use -fsanitize=kernel-hwaddress
+# TODO: fix stack intrumentation and remove -hwasan-instrument-stack=0
+
+ifeq ($(call cc-option, $(CFLAGS_KASAN_MINIMAL) -Werror),)
+ ifneq ($(CONFIG_COMPILE_TEST),y)
+ $(warning Cannot use CONFIG_KASAN_TAGS: \
+ -fsanitize=hwaddress is not supported by compiler)
+ endif
+else
+ CFLAGS_KASAN := $(call cc-option, -fsanitize=hwaddress \
+ -mllvm -hwasan-kernel=1 \
+ -mllvm -hwasan-instrument-stack=0 \
+ -mllvm -hwasan-recover=1 \
+ $(instrumentation_flags))
+endif
+
+CFLAGS_KASAN_NOSANITIZE := -fno-builtin
+
+endif
--
2.17.0.rc0.231.g781580f067-goog

Andrey Konovalov

unread,
Mar 23, 2018, 2:06:08 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
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 <andre...@google.com>
---
arch/arm64/Makefile | 2 +-
arch/arm64/include/asm/memory.h | 13 +++++++++----
2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/arch/arm64/Makefile b/arch/arm64/Makefile
index 4bb18aee4846..23e9fe816cb4 100644
--- a/arch/arm64/Makefile
+++ b/arch/arm64/Makefile
@@ -100,7 +100,7 @@ endif
# KASAN_SHADOW_OFFSET = VA_START + (1 << (VA_BITS - KASAN_SHADOW_SCALE_SHIFT))
# - (1 << (64 - KASAN_SHADOW_SCALE_SHIFT))
# in 32-bit arithmetic
-KASAN_SHADOW_SCALE_SHIFT := 3
+KASAN_SHADOW_SCALE_SHIFT := $(if $(CONFIG_KASAN_TAGS), 4, 3)
KASAN_SHADOW_OFFSET := $(shell printf "0x%08x00000000\n" $$(( \
(0xffffffff & (-1 << ($(CONFIG_ARM64_VA_BITS) - 32))) \
+ (1 << ($(CONFIG_ARM64_VA_BITS) - 32 - $(KASAN_SHADOW_SCALE_SHIFT))) \
diff --git a/arch/arm64/include/asm/memory.h b/arch/arm64/include/asm/memory.h
index 50fa96a49792..febd54ff3354 100644
--- a/arch/arm64/include/asm/memory.h
+++ b/arch/arm64/include/asm/memory.h
@@ -80,12 +80,17 @@
#define KERNEL_END _end

/*
- * 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
+ * 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_CLASSIC
#define KASAN_SHADOW_SCALE_SHIFT 3
+#endif
+#ifdef CONFIG_KASAN_TAGS
+#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.17.0.rc0.231.g781580f067-goog

Andrey Konovalov

unread,
Mar 23, 2018, 2:06:10 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
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 <andre...@google.com>
---
arch/arm64/mm/kasan_init.c | 11 ++++++++++-
include/linux/kasan.h | 8 ++++++++
mm/kasan/common.c | 7 +++++++
3 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/mm/kasan_init.c b/arch/arm64/mm/kasan_init.c
index dabfc1ecda3d..d4bceba60010 100644
--- a/arch/arm64/mm/kasan_init.c
+++ b/arch/arm64/mm/kasan_init.c
@@ -90,6 +90,10 @@ static void __init kasan_pte_populate(pmd_t *pmdp, unsigned long addr,
do {
phys_addr_t page_phys = early ? __pa_symbol(kasan_zero_page)
: kasan_alloc_zeroed_page(node);
+#if KASAN_SHADOW_INIT != 0
+ if (!early)
+ memset(__va(page_phys), KASAN_SHADOW_INIT, PAGE_SIZE);
+#endif
next = addr + PAGE_SIZE;
set_pte(ptep, pfn_pte(__phys_to_pfn(page_phys), PAGE_KERNEL));
} while (ptep++, addr = next, addr != end && pte_none(READ_ONCE(*ptep)));
@@ -139,6 +143,11 @@ asmlinkage void __init kasan_early_init(void)
KASAN_SHADOW_END - (1UL << (64 - KASAN_SHADOW_SCALE_SHIFT)));
BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_START, PGDIR_SIZE));
BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_END, PGDIR_SIZE));
+
+#if KASAN_SHADOW_INIT != 0
+ memset(kasan_zero_page, KASAN_SHADOW_INIT, PAGE_SIZE);
+#endif
+
kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE,
true);
}
@@ -235,7 +244,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 */
diff --git a/include/linux/kasan.h b/include/linux/kasan.h
index 3c45e273a936..700734dff218 100644
--- a/include/linux/kasan.h
+++ b/include/linux/kasan.h
@@ -139,6 +139,8 @@ static inline size_t kasan_metadata_size(struct kmem_cache *cache) { return 0; }

#ifdef CONFIG_KASAN_CLASSIC

+#define KASAN_SHADOW_INIT 0
+
void kasan_cache_shrink(struct kmem_cache *cache);
void kasan_cache_shutdown(struct kmem_cache *cache);

@@ -149,4 +151,10 @@ static inline void kasan_cache_shutdown(struct kmem_cache *cache) {}

#endif /* CONFIG_KASAN_CLASSIC */

+#ifdef CONFIG_KASAN_TAGS
+
+#define KASAN_SHADOW_INIT 0xFF
+
+#endif /* CONFIG_KASAN_TAGS */
+
#endif /* LINUX_KASAN_H */
diff --git a/mm/kasan/common.c b/mm/kasan/common.c
index 08f6c8cb9f84..f4ccb9425655 100644
--- a/mm/kasan/common.c
+++ b/mm/kasan/common.c
@@ -253,6 +253,9 @@ int kasan_module_alloc(void *addr, size_t size)
__builtin_return_address(0));

if (ret) {
+#if KASAN_SHADOW_INIT != 0
+ __memset(ret, KASAN_SHADOW_INIT, shadow_size);
+#endif
find_vm_area(addr)->flags |= VM_KASAN;
kmemleak_ignore(ret);
return 0;
@@ -297,6 +300,10 @@ static int __meminit kasan_mem_notifier(struct notifier_block *nb,
if (!ret)
return NOTIFY_BAD;

+#if KASAN_SHADOW_INIT != 0
+ __memset(ret, KASAN_SHADOW_INIT, shadow_end - shadow_start);
+#endif
+
kmemleak_ignore(ret);
return NOTIFY_OK;
}
--
2.17.0.rc0.231.g781580f067-goog

Andrey Konovalov

unread,
Mar 23, 2018, 2:06:11 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
__kimg_to_phys (which is used by virt_to_phys) assumes 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 __kimg_to_phys.

__lm_to_phys doesn't require any fixups, as it zeroes out the top byte
with the current implementation.

Signed-off-by: Andrey Konovalov <andre...@google.com>
---
arch/arm64/include/asm/memory.h | 9 +++++++++
1 file changed, 9 insertions(+)

diff --git a/arch/arm64/include/asm/memory.h b/arch/arm64/include/asm/memory.h
index febd54ff3354..c13b89257352 100644
--- a/arch/arm64/include/asm/memory.h
+++ b/arch/arm64/include/asm/memory.h
@@ -98,6 +98,10 @@
#define KASAN_THREAD_SHIFT 0
#endif

+#ifdef CONFIG_KASAN_TAGS
+#define KASAN_PTR_TAG_MASK (UL(0xff) << 56)
+#endif
+
#define MIN_THREAD_SHIFT (14 + KASAN_THREAD_SHIFT)

/*
@@ -231,7 +235,12 @@ static inline unsigned long kaslr_offset(void)
#define __is_lm_address(addr) (!!((addr) & BIT(VA_BITS - 1)))

#define __lm_to_phys(addr) (((addr) & ~PAGE_OFFSET) + PHYS_OFFSET)
+
+#ifdef CONFIG_KASAN_TAGS
+#define __kimg_to_phys(addr) (((addr) | KASAN_PTR_TAG_MASK) - kimage_voffset)
+#else
#define __kimg_to_phys(addr) ((addr) - kimage_voffset)
+#endif

#define __virt_to_phys_nodebug(x) ({ \
phys_addr_t __x = (phys_addr_t)(x); \
--
2.17.0.rc0.231.g781580f067-goog

Andrey Konovalov

unread,
Mar 23, 2018, 2:06:13 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
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 <andre...@google.com>
---
arch/arm64/mm/fault.c | 3 +++
1 file changed, 3 insertions(+)

diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
index bff11553eb05..234613777f2a 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -32,6 +32,7 @@
#include <linux/perf_event.h>
#include <linux/preempt.h>
#include <linux/hugetlb.h>
+#include <linux/kasan.h>

#include <asm/bug.h>
#include <asm/cmpxchg.h>
@@ -133,6 +134,8 @@ void show_pte(unsigned long addr)
pgd_t *pgdp;
pgd_t pgd;

+ addr = (unsigned long)khwasan_reset_tag((void *)addr);
+
if (addr < TASK_SIZE) {
/* TTBR0 */
mm = current->active_mm;
--
2.17.0.rc0.231.g781580f067-goog

Andrey Konovalov

unread,
Mar 23, 2018, 2:06:15 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
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 <andre...@google.com>
---
arch/arm64/mm/kasan_init.c | 2 ++
include/linux/kasan.h | 23 +++++++++++++++++
mm/kasan/kasan.h | 29 ++++++++++++++++++++++
mm/kasan/khwasan.c | 51 ++++++++++++++++++++++++++++++++++++++
4 files changed, 105 insertions(+)

diff --git a/arch/arm64/mm/kasan_init.c b/arch/arm64/mm/kasan_init.c
index d4bceba60010..7fd9aee88069 100644
--- a/arch/arm64/mm/kasan_init.c
+++ b/arch/arm64/mm/kasan_init.c
@@ -247,6 +247,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");
diff --git a/include/linux/kasan.h b/include/linux/kasan.h
index 700734dff218..a2869464a8be 100644
--- a/include/linux/kasan.h
+++ b/include/linux/kasan.h
@@ -155,6 +155,29 @@ static inline void kasan_cache_shutdown(struct kmem_cache *cache) {}

#define KASAN_SHADOW_INIT 0xFF

+void khwasan_init(void);
+
+void *khwasan_set_tag(const void *addr, u8 tag);
+u8 khwasan_get_tag(const void *addr);
+void *khwasan_reset_tag(const void *ptr);
+
+#else /* CONFIG_KASAN_TAGS */
+
+static inline void khwasan_init(void) { }
+
+static inline void *khwasan_set_tag(const void *addr, u8 tag)
+{
+ return (void *)addr;
+}
+static inline u8 khwasan_get_tag(const void *addr)
+{
+ return 0xFF;
+}
+static inline void *khwasan_reset_tag(const void *ptr)
+{
+ return (void *)ptr;
+}
+
#endif /* CONFIG_KASAN_TAGS */

#endif /* LINUX_KASAN_H */
diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h
index 2be31754278e..c715b44c4780 100644
--- a/mm/kasan/kasan.h
+++ b/mm/kasan/kasan.h
@@ -113,6 +113,35 @@ void kasan_report(unsigned long addr, size_t size,
bool is_write, unsigned long ip);
void kasan_report_invalid_free(void *object, unsigned long ip);

+#define KHWASAN_TAG_KERNEL 0xFF /* native kernel pointers tag */
+#define KHWASAN_TAG_INVALID 0xFE /* redzone or free memory tag */
+#define KHWASAN_TAG_MAX 0xFD /* maximum value for random tags */
+
+#define KHWASAN_TAG_SHIFT 56
+#define KHWASAN_TAG_MASK (0xFFUL << KHWASAN_TAG_SHIFT)
+
+static inline void *set_tag(const void *addr, u8 tag)
+{
+ u64 a = (u64)addr;
+
+ a &= ~KHWASAN_TAG_MASK;
+ a |= ((u64)tag << KHWASAN_TAG_SHIFT);
+
+ return (void *)a;
+}
+
+static inline u8 get_tag(const void *addr)
+{
+ return (u8)((u64)addr >> KHWASAN_TAG_SHIFT);
+}
+
+static inline void *reset_tag(const void *addr)
+{
+ return set_tag(addr, KHWASAN_TAG_KERNEL);
+}
+
+void khwasan_report_invalid_free(void *object, unsigned long ip);
+
#if defined(CONFIG_SLAB) || defined(CONFIG_SLUB)
void quarantine_put(struct kasan_free_meta *info, struct kmem_cache *cache);
void quarantine_reduce(void);
diff --git a/mm/kasan/khwasan.c b/mm/kasan/khwasan.c
index 24d75245e9d0..da4b17997c71 100644
--- a/mm/kasan/khwasan.c
+++ b/mm/kasan/khwasan.c
@@ -39,6 +39,57 @@
#include "kasan.h"
#include "../slab.h"

+int khwasan_enabled;
+
+static DEFINE_PER_CPU(u32, prng_state);
+
+void khwasan_init(void)
+{
+ int cpu;
+
+ for_each_possible_cpu(cpu) {
+ per_cpu(prng_state, cpu) = get_random_u32();
+ }
+ WRITE_ONCE(khwasan_enabled, 1);
+}
+
+/*
+ * If a preemption happens between this_cpu_read and this_cpu_write, the only
+ * side effect is that we'll give a few allocated in different contexts objects
+ * the same tag. Since KHWASAN is meant to be used a probabilistic bug-detection
+ * debug feature, this doesn’t have significant negative impact.
+ *
+ * Ideally the tags use strong randomness to prevent any attempts to predict
+ * them during explicit exploit attempts. But strong randomness is expensive,
+ * and we did an intentional trade-off to use a PRNG. This non-atomic RMW
+ * sequence has in fact positive effect, since interrupts that randomly skew
+ * PRNG at unpredictable points do only good.
+ */
+static inline u8 khwasan_random_tag(void)
+{
+ u32 state = this_cpu_read(prng_state);
+
+ state = 1664525 * state + 1013904223;
+ this_cpu_write(prng_state, state);
+
+ return (u8)state % (KHWASAN_TAG_MAX + 1);
+}
+
+void *khwasan_set_tag(const void *addr, u8 tag)
+{
+ return set_tag(addr, tag);
+}
+
+u8 khwasan_get_tag(const void *addr)
+{
+ return get_tag(addr);
+}
+
+void *khwasan_reset_tag(const void *addr)
+{
+ return reset_tag(addr);
+}
+
void kasan_unpoison_shadow(const void *address, size_t size)
{
}
--
2.17.0.rc0.231.g781580f067-goog

Andrey Konovalov

unread,
Mar 23, 2018, 2:06:18 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
kern_hyp_va that converts kernel VA into a HYP VA relies on the top byte
of kernel pointers being 0xff. Untag pointers passed to it with KHWASAN
enabled.

Also fix create_hyp_mappings() and create_hyp_io_mappings(), to use the
untagged kernel pointers for address computations.

Signed-off-by: Andrey Konovalov <andre...@google.com>
---
arch/arm64/include/asm/kvm_mmu.h | 8 ++++++++
virt/kvm/arm/mmu.c | 20 +++++++++++++++-----
2 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_mmu.h b/arch/arm64/include/asm/kvm_mmu.h
index 7faed6e48b46..5149ff83b4c4 100644
--- a/arch/arm64/include/asm/kvm_mmu.h
+++ b/arch/arm64/include/asm/kvm_mmu.h
@@ -97,6 +97,9 @@
* Should be completely invisible on any viable CPU.
*/
.macro kern_hyp_va reg
+#ifdef CONFIG_KASAN_TAGS
+ orr \reg, \reg, #KASAN_PTR_TAG_MASK
+#endif
alternative_if_not ARM64_HAS_VIRT_HOST_EXTN
and \reg, \reg, #HYP_PAGE_OFFSET_HIGH_MASK
alternative_else_nop_endif
@@ -115,6 +118,11 @@ alternative_else_nop_endif

static inline unsigned long __kern_hyp_va(unsigned long v)
{
+#ifdef CONFIG_KASAN_TAGS
+ asm volatile("orr %0, %0, %1"
+ : "+r" (v)
+ : "i" (KASAN_PTR_TAG_MASK));
+#endif
asm volatile(ALTERNATIVE("and %0, %0, %1",
"nop",
ARM64_HAS_VIRT_HOST_EXTN)
diff --git a/virt/kvm/arm/mmu.c b/virt/kvm/arm/mmu.c
index b960acdd0c05..3dba9b60e0a0 100644
--- a/virt/kvm/arm/mmu.c
+++ b/virt/kvm/arm/mmu.c
@@ -21,6 +21,7 @@
#include <linux/io.h>
#include <linux/hugetlb.h>
#include <linux/sched/signal.h>
+#include <linux/kasan.h>
#include <trace/events/kvm.h>
#include <asm/pgalloc.h>
#include <asm/cacheflush.h>
@@ -683,9 +684,13 @@ static phys_addr_t kvm_kaddr_to_phys(void *kaddr)
int create_hyp_mappings(void *from, void *to, pgprot_t prot)
{
phys_addr_t phys_addr;
- unsigned long virt_addr;
- unsigned long start = kern_hyp_va((unsigned long)from);
- unsigned long end = kern_hyp_va((unsigned long)to);
+ unsigned long virt_addr, start, end;
+
+ from = khwasan_reset_tag(from);
+ to = khwasan_reset_tag(to);
+
+ start = kern_hyp_va((unsigned long)from);
+ end = kern_hyp_va((unsigned long)to);

if (is_kernel_in_hyp_mode())
return 0;
@@ -719,8 +724,13 @@ int create_hyp_mappings(void *from, void *to, pgprot_t prot)
*/
int create_hyp_io_mappings(void *from, void *to, phys_addr_t phys_addr)
{
- unsigned long start = kern_hyp_va((unsigned long)from);
- unsigned long end = kern_hyp_va((unsigned long)to);
+ unsigned long start, end;
+
+ from = khwasan_reset_tag(from);
+ to = khwasan_reset_tag(to);
+
+ start = kern_hyp_va((unsigned long)from);
+ end = kern_hyp_va((unsigned long)to);

if (is_kernel_in_hyp_mode())
return 0;
--
2.17.0.rc0.231.g781580f067-goog

Andrey Konovalov

unread,
Mar 23, 2018, 2:06:19 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
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 <andre...@google.com>
---
arch/arm64/include/asm/pgtable-hwdef.h | 1 +
arch/arm64/mm/proc.S | 9 ++++++++-
2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/include/asm/pgtable-hwdef.h b/arch/arm64/include/asm/pgtable-hwdef.h
index cdfe3e657a9e..ae6b6405eacc 100644
--- a/arch/arm64/include/asm/pgtable-hwdef.h
+++ b/arch/arm64/include/asm/pgtable-hwdef.h
@@ -289,6 +289,7 @@
#define TCR_A1 (UL(1) << 22)
#define TCR_ASID16 (UL(1) << 36)
#define TCR_TBI0 (UL(1) << 37)
+#define TCR_TBI1 (UL(1) << 38)
#define TCR_HA (UL(1) << 39)
#define TCR_HD (UL(1) << 40)

diff --git a/arch/arm64/mm/proc.S b/arch/arm64/mm/proc.S
index c0af47617299..d64ce2ea40ec 100644
--- a/arch/arm64/mm/proc.S
+++ b/arch/arm64/mm/proc.S
@@ -41,6 +41,12 @@
/* PTWs cacheable, inner/outer WBWA */
#define TCR_CACHE_FLAGS TCR_IRGN_WBWA | TCR_ORGN_WBWA

+#ifdef CONFIG_KASAN_TAGS
+#define KASAN_TCR_FLAGS TCR_TBI1
+#else
+#define KASAN_TCR_FLAGS 0
+#endif
+
#define MAIR(attr, mt) ((attr) << ((mt) * 8))

/*
@@ -432,7 +438,8 @@ ENTRY(__cpu_setup)
* both user and kernel.
*/
ldr x10, =TCR_TxSZ(VA_BITS) | TCR_CACHE_FLAGS | TCR_SMP_FLAGS | \
- TCR_TG_FLAGS | TCR_ASID16 | TCR_TBI0 | TCR_A1
+ TCR_TG_FLAGS | TCR_ASID16 | TCR_TBI0 | TCR_A1 | \
+ KASAN_TCR_FLAGS
tcr_set_idmap_t0sz x10, x9

/*
--
2.17.0.rc0.231.g781580f067-goog

Andrey Konovalov

unread,
Mar 23, 2018, 2:06:21 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
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 <andre...@google.com>
---
mm/slab_common.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mm/slab_common.c b/mm/slab_common.c
index a33e61315ca6..5911f2194cf7 100644
--- a/mm/slab_common.c
+++ b/mm/slab_common.c
@@ -1494,7 +1494,7 @@ void *krealloc(const void *p, size_t new_size, gfp_t flags)
}

ret = __do_krealloc(p, new_size, flags);
- if (ret && p != ret)
+ if (ret && khwasan_reset_tag(p) != khwasan_reset_tag(ret))
kfree(p);

return ret;
--
2.17.0.rc0.231.g781580f067-goog

Andrey Konovalov

unread,
Mar 23, 2018, 2:06:23 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
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 <andre...@google.com>
---
include/linux/kasan.h | 3 ++
mm/kasan/report.c | 88 ++++++++++++++++++++++++++++++++++++++-----
2 files changed, 82 insertions(+), 9 deletions(-)

diff --git a/include/linux/kasan.h b/include/linux/kasan.h
index a2869464a8be..54e7c437dc8f 100644
--- a/include/linux/kasan.h
+++ b/include/linux/kasan.h
@@ -161,6 +161,9 @@ void *khwasan_set_tag(const void *addr, u8 tag);
u8 khwasan_get_tag(const void *addr);
void *khwasan_reset_tag(const void *ptr);

+void khwasan_report(unsigned long addr, size_t size, bool write,
+ unsigned long ip);
+
#else /* CONFIG_KASAN_TAGS */

static inline void khwasan_init(void) { }
diff --git a/mm/kasan/report.c b/mm/kasan/report.c
index 5c169aa688fd..ed17168a083e 100644
--- a/mm/kasan/report.c
+++ b/mm/kasan/report.c
@@ -51,10 +51,9 @@ static const void *find_first_bad_addr(const void *addr, size_t size)
return first_bad_addr;
}

-static bool addr_has_shadow(struct kasan_access_info *info)
+static bool addr_has_shadow(const void *addr)
{
- return (info->access_addr >=
- kasan_shadow_to_mem((void *)KASAN_SHADOW_START));
+ return (addr >= kasan_shadow_to_mem((void *)KASAN_SHADOW_START));
}

static const char *get_shadow_bug_type(struct kasan_access_info *info)
@@ -127,15 +126,14 @@ static const char *get_wild_bug_type(struct kasan_access_info *info)

static const char *get_bug_type(struct kasan_access_info *info)
{
- if (addr_has_shadow(info))
+ if (addr_has_shadow(info->access_addr))
return get_shadow_bug_type(info);
return get_wild_bug_type(info);
}

-static void print_error_description(struct kasan_access_info *info)
+static void print_error_description(struct kasan_access_info *info,
+ const char *bug_type)
{
- const char *bug_type = get_bug_type(info);
-
pr_err("BUG: KASAN: %s in %pS\n",
bug_type, (void *)info->ip);
pr_err("%s of size %zu at addr %px by task %s/%d\n",
@@ -345,10 +343,10 @@ static void kasan_report_error(struct kasan_access_info *info)

kasan_start_report(&flags);

- print_error_description(info);
+ print_error_description(info, get_bug_type(info));
pr_err("\n");

- if (!addr_has_shadow(info)) {
+ if (!addr_has_shadow(info->access_addr)) {
dump_stack();
} else {
print_address_description((void *)info->access_addr);
@@ -412,6 +410,78 @@ void kasan_report(unsigned long addr, size_t size,
kasan_report_error(&info);
}

+static inline void khwasan_print_tags(const void *addr)
+{
+ u8 addr_tag = get_tag(addr);
+ void *untagged_addr = reset_tag(addr);
+ u8 *shadow = (u8 *)kasan_mem_to_shadow(untagged_addr);
+
+ pr_err("Pointer tag: [%02x], memory tag: [%02x]\n", addr_tag, *shadow);
+}
+
+static const void *khwasan_find_first_bad_addr(const void *addr, size_t size)
+{
+ u8 tag = get_tag((void *)addr);
+ void *untagged_addr = reset_tag((void *)addr);
+ u8 *shadow = (u8 *)kasan_mem_to_shadow(untagged_addr);
+ const void *first_bad_addr = untagged_addr;
+
+ while (*shadow == tag && first_bad_addr < untagged_addr + size) {
+ first_bad_addr += KASAN_SHADOW_SCALE_SIZE;
+ shadow = (u8 *)kasan_mem_to_shadow(first_bad_addr);
+ }
+ return first_bad_addr;
+}
+
+void khwasan_report(unsigned long addr, size_t size, bool write,
+ unsigned long ip)
+{
+ struct kasan_access_info info;
+ unsigned long flags;
+ void *untagged_addr = reset_tag((void *)addr);
+
+ if (likely(!kasan_report_enabled()))
+ return;
+
+ disable_trace_on_warning();
+
+ info.access_addr = (void *)addr;
+ info.first_bad_addr = khwasan_find_first_bad_addr((void *)addr, size);
+ info.access_size = size;
+ info.is_write = write;
+ info.ip = ip;
+
+ kasan_start_report(&flags);
+
+ print_error_description(&info, "invalid-access");
+ khwasan_print_tags((void *)addr);
+ pr_err("\n");
+
+ if (!addr_has_shadow(untagged_addr)) {
+ dump_stack();
+ } else {
+ print_address_description(untagged_addr);
+ pr_err("\n");
+ print_shadow_for_address(info.first_bad_addr);
+ }
+
+ kasan_end_report(&flags);
+}
+
+void khwasan_report_invalid_free(void *object, unsigned long ip)
+{
+ unsigned long flags;
+ void *untagged_addr = reset_tag((void *)object);
+
+ kasan_start_report(&flags);
+ pr_err("BUG: KASAN: double-free or invalid-free in %pS\n", (void *)ip);
+ khwasan_print_tags(object);
+ pr_err("\n");
+ print_address_description(untagged_addr);
+ pr_err("\n");
+ print_shadow_for_address(untagged_addr);
+ kasan_end_report(&flags);
+}

#define DEFINE_ASAN_REPORT_LOAD(size) \
void __asan_report_load##size##_noabort(unsigned long addr) \
--
2.17.0.rc0.231.g781580f067-goog

Andrey Konovalov

unread,
Mar 23, 2018, 2:06:26 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
This commit adds KHWASAN hooks implementation.

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 <andre...@google.com>
---
mm/kasan/khwasan.c | 200 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 197 insertions(+), 3 deletions(-)

diff --git a/mm/kasan/khwasan.c b/mm/kasan/khwasan.c
index da4b17997c71..e8bed5a078c7 100644
--- a/mm/kasan/khwasan.c
+++ b/mm/kasan/khwasan.c
@@ -90,69 +90,260 @@ void *khwasan_reset_tag(const void *addr)
return reset_tag(addr);
}

+void kasan_poison_shadow(const void *address, size_t size, u8 value)
+{
+ void *shadow_start, *shadow_end;
+
+ /* Perform shadow offset calculation based on untagged address */
+ address = reset_tag(address);
+
+ shadow_start = kasan_mem_to_shadow(address);
+ shadow_end = kasan_mem_to_shadow(address + size);
+
+ memset(shadow_start, value, shadow_end - shadow_start);
+}
+
void kasan_unpoison_shadow(const void *address, size_t size)
{
+ /* KHWASAN only allows 16-byte granularity */
+ size = round_up(size, KASAN_SHADOW_SCALE_SIZE);
+ kasan_poison_shadow(address, size, get_tag(address));
}

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
+ * 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 0xff.
+ */
+ if (tag == 0xff)
+ return;
+
+ untagged_addr = reset_tag((const void *)addr);
+ shadow_first = kasan_mem_to_shadow(untagged_addr);
+ shadow_last = kasan_mem_to_shadow(untagged_addr + size - 1);
+
+ for (shadow = shadow_first; shadow <= shadow_last; shadow++) {
+ if (*shadow != tag) {
+ khwasan_report(addr, size, write, ret_ip);
+ return;
+ }
+ }
}

void kasan_free_pages(struct page *page, unsigned int order)
{
+ if (likely(!PageHighMem(page)))
+ kasan_poison_shadow(page_address(page),
+ PAGE_SIZE << order,
+ KHWASAN_TAG_INVALID);
}

void kasan_cache_create(struct kmem_cache *cache, size_t *size,
slab_flags_t *flags)
{
+ int orig_size = *size;
+
+ cache->kasan_info.alloc_meta_offset = *size;
+ *size += sizeof(struct kasan_alloc_meta);
+
+ if (*size % KASAN_SHADOW_SCALE_SIZE != 0)
+ *size = round_up(*size, KASAN_SHADOW_SCALE_SIZE);
+
+
+ if (*size > KMALLOC_MAX_SIZE) {
+ *size = orig_size;
+ return;
+ }
+
+ cache->align = round_up(cache->align, KASAN_SHADOW_SCALE_SIZE);
+
+ *flags |= SLAB_KASAN;
}

void kasan_poison_slab(struct page *page)
{
+ kasan_poison_shadow(page_address(page),
+ PAGE_SIZE << compound_order(page),
+ KHWASAN_TAG_INVALID);
}

void kasan_poison_object_data(struct kmem_cache *cache, void *object)
{
+ kasan_poison_shadow(object,
+ round_up(cache->object_size, KASAN_SHADOW_SCALE_SIZE),
+ KHWASAN_TAG_INVALID);
}

void *kasan_slab_alloc(struct kmem_cache *cache, void *object, gfp_t flags)
{
+ if (!READ_ONCE(khwasan_enabled))
+ return object;
+ object = kasan_kmalloc(cache, object, cache->object_size, flags);
+ if (unlikely(cache->ctor)) {
+ // Cache constructor might use object's pointer value to
+ // initialize some of its fields.
+ cache->ctor(object);
+ }
return object;
}

-bool kasan_slab_free(struct kmem_cache *cache, void *object, unsigned long ip)
+static bool __kasan_slab_free(struct kmem_cache *cache, void *object,
+ unsigned long ip)
{
+ u8 shadow_byte;
+ u8 tag;
+ unsigned long rounded_up_size;
+ void *untagged_addr = reset_tag(object);
+
+ if (unlikely(nearest_obj(cache, virt_to_head_page(untagged_addr),
+ untagged_addr) != untagged_addr)) {
+ khwasan_report_invalid_free(object, ip);
+ return true;
+ }
+
+ /* RCU slabs could be legally used after free within the RCU period */
+ if (unlikely(cache->flags & SLAB_TYPESAFE_BY_RCU))
+ return false;
+
+ shadow_byte = READ_ONCE(*(u8 *)kasan_mem_to_shadow(untagged_addr));
+ tag = get_tag(object);
+ if (tag != shadow_byte) {
+ khwasan_report_invalid_free(object, ip);
+ return true;
+ }
+
+ rounded_up_size = round_up(cache->object_size, KASAN_SHADOW_SCALE_SIZE);
+ kasan_poison_shadow(object, rounded_up_size, KHWASAN_TAG_INVALID);
+
+ if (unlikely(!(cache->flags & SLAB_KASAN)))
+ return false;
+
+ set_track(&get_alloc_info(cache, object)->free_track, GFP_NOWAIT);
return false;
}

+bool kasan_slab_free(struct kmem_cache *cache, void *object, unsigned long ip)
+{
+ return __kasan_slab_free(cache, object, ip);
+}
+
void *kasan_kmalloc(struct kmem_cache *cache, const void *object,
size_t size, gfp_t flags)
{
- return (void *)object;
+ unsigned long redzone_start, redzone_end;
+ u8 tag;
+
+ if (!READ_ONCE(khwasan_enabled))
+ return (void *)object;
+
+ if (unlikely(object == NULL))
+ return NULL;
+
+ redzone_start = round_up((unsigned long)(object + size),
+ KASAN_SHADOW_SCALE_SIZE);
+ redzone_end = round_up((unsigned long)(object + cache->object_size),
+ KASAN_SHADOW_SCALE_SIZE);
+
+ tag = khwasan_random_tag();
+ kasan_poison_shadow(object, redzone_start - (unsigned long)object, tag);
+ /* Redzone is deliberately poisoned with a different tag */
+ kasan_poison_shadow((void *)redzone_start, redzone_end - redzone_start,
+ KHWASAN_TAG_INVALID);
+
+ if (cache->flags & SLAB_KASAN)
+ set_track(&get_alloc_info(cache, object)->alloc_track, flags);
+
+ return set_tag(object, tag);
}
EXPORT_SYMBOL(kasan_kmalloc);

void *kasan_kmalloc_large(const void *ptr, size_t size, gfp_t flags)
{
- return (void *)ptr;
+ unsigned long redzone_start, redzone_end;
+ u8 tag;
+ struct page *page;
+
+ if (!READ_ONCE(khwasan_enabled))
+ return (void *)ptr;
+
+ if (unlikely(ptr == NULL))
+ return NULL;
+
+ page = virt_to_page(ptr);
+ redzone_start = round_up((unsigned long)(ptr + size),
+ KASAN_SHADOW_SCALE_SIZE);
+ redzone_end = (unsigned long)ptr + (PAGE_SIZE << compound_order(page));
+
+ tag = khwasan_random_tag();
+ kasan_poison_shadow(ptr, redzone_start - (unsigned long)ptr, tag);
+ /* Redzone is deliberately poisoned with a different tag */
+ kasan_poison_shadow((void *)redzone_start, redzone_end - redzone_start,
+ KHWASAN_TAG_INVALID);
+
+ return set_tag(ptr, tag);
}

void kasan_poison_kfree(void *ptr, unsigned long ip)
{
+ struct page *page;
+
+ page = virt_to_head_page(ptr);
+
+ if (unlikely(!PageSlab(page))) {
+ if (reset_tag(ptr) != page_address(page)) {
+ khwasan_report_invalid_free(ptr, ip);
+ return;
+ }
+ kasan_poison_shadow(ptr, PAGE_SIZE << compound_order(page),
+ KHWASAN_TAG_INVALID);
+ } else {
+ __kasan_slab_free(page->slab_cache, ptr, ip);
+ }
}

void kasan_kfree_large(void *ptr, unsigned long ip)
{
+ struct page *page = virt_to_page(ptr);
+ struct page *head_page = virt_to_head_page(ptr);
+
+ if (reset_tag(ptr) != page_address(head_page)) {
+ khwasan_report_invalid_free(ptr, ip);
+ return;
+ }
+
+ kasan_poison_shadow(ptr, PAGE_SIZE << compound_order(page),
+ KHWASAN_TAG_INVALID);
}

#define DEFINE_HWASAN_LOAD_STORE(size) \
void __hwasan_load##size##_noabort(unsigned long addr) \
{ \
+ check_memory_region(addr, size, false, _RET_IP_); \
} \
EXPORT_SYMBOL(__hwasan_load##size##_noabort); \
void __hwasan_store##size##_noabort(unsigned long addr) \
{ \
+ check_memory_region(addr, size, true, _RET_IP_); \
} \
EXPORT_SYMBOL(__hwasan_store##size##_noabort)

@@ -164,15 +355,18 @@ DEFINE_HWASAN_LOAD_STORE(16);

void __hwasan_loadN_noabort(unsigned long addr, unsigned long size)
{
+ check_memory_region(addr, size, false, _RET_IP_);
}
EXPORT_SYMBOL(__hwasan_loadN_noabort);

void __hwasan_storeN_noabort(unsigned long addr, unsigned long size)
{
+ check_memory_region(addr, size, true, _RET_IP_);
}
EXPORT_SYMBOL(__hwasan_storeN_noabort);

void __hwasan_tag_memory(unsigned long addr, u8 tag, unsigned long size)
{
+ kasan_poison_shadow((void *)addr, size, tag);
}
EXPORT_SYMBOL(__hwasan_tag_memory);
--
2.17.0.rc0.231.g781580f067-goog

Andrey Konovalov

unread,
Mar 23, 2018, 2:06:27 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
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 <andre...@google.com>
---
arch/arm64/include/asm/brk-imm.h | 2 ++
arch/arm64/kernel/traps.c | 61 ++++++++++++++++++++++++++++++++
2 files changed, 63 insertions(+)

diff --git a/arch/arm64/include/asm/brk-imm.h b/arch/arm64/include/asm/brk-imm.h
index ed693c5bcec0..e4a7013321dc 100644
--- a/arch/arm64/include/asm/brk-imm.h
+++ b/arch/arm64/include/asm/brk-imm.h
@@ -16,10 +16,12 @@
* 0x400: for dynamic BRK instruction
* 0x401: for compile time BRK instruction
* 0x800: kernel-mode BUG() and WARN() traps
+ * 0x9xx: KHWASAN trap (allowed values 0x900 - 0x9ff)
*/
#define FAULT_BRK_IMM 0x100
#define KGDB_DYN_DBG_BRK_IMM 0x400
#define KGDB_COMPILED_DBG_BRK_IMM 0x401
#define BUG_BRK_IMM 0x800
+#define KHWASAN_BRK_IMM 0x900

#endif
diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c
index eb2d15147e8d..9d9ca4eb6d2f 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -35,6 +35,7 @@
#include <linux/sizes.h>
#include <linux/syscalls.h>
#include <linux/mm_types.h>
+#include <linux/kasan.h>

#include <asm/atomic.h>
#include <asm/bug.h>
@@ -771,6 +772,59 @@ static struct break_hook bug_break_hook = {
.fn = bug_handler,
};

+#ifdef CONFIG_KASAN_TAGS
+
+#define KHWASAN_ESR_RECOVER 0x20
+#define KHWASAN_ESR_WRITE 0x10
+#define KHWASAN_ESR_SIZE_MASK 0x0f
+#define KHWASAN_ESR_SIZE(esr) (1 << ((esr) & KHWASAN_ESR_SIZE_MASK))
+
+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;
+
+ khwasan_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);
+
+ /* If thread survives, skip over the BUG 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().
@@ -778,6 +832,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_TAGS
+ 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;
}

@@ -785,4 +843,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_TAGS
+ register_break_hook(&khwasan_break_hook);
+#endif
}
--
2.17.0.rc0.231.g781580f067-goog

Andrey Konovalov

unread,
Mar 23, 2018, 2:06:30 PM3/23/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, GitAuthor : Andrey Konovalov, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
This patch updates KASAN documentation to reflect the addition of KHWASAN.

Signed-off-by: Andrey Konovalov <andre...@google.com>
---
Documentation/dev-tools/kasan.rst | 212 +++++++++++++++++-------------
1 file changed, 122 insertions(+), 90 deletions(-)

diff --git a/Documentation/dev-tools/kasan.rst b/Documentation/dev-tools/kasan.rst
index f7a18f274357..a817f4c4285c 100644
--- a/Documentation/dev-tools/kasan.rst
+++ b/Documentation/dev-tools/kasan.rst
@@ -8,11 +8,18 @@ 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.
+TODO: compiler requirements for KHWASAN
+
+Currently classic KASAN is supported for the x86_64, arm64 and xtensa
+architectures, and KHWASAN is supported only for arm64.

Usage
-----
@@ -21,12 +28,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_CLASSIC (to enable classic KASAN) and
+CONFIG_KASAN_TAGS (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 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 +52,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.txt 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 +142,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 +172,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 accesses 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
+ofthe 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.
--
2.17.0.rc0.231.g781580f067-goog

Ingo Molnar

unread,
Mar 24, 2018, 4:29:53 AM3/24/18
to Andrey Konovalov, Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
Small nit:

If 'reset' here means an all zeroes tag (upper byte) then khwasan_clear_tag()
might be a slightly easier to read primitive?

Thanks,

Ingo

Ingo Molnar

unread,
Mar 24, 2018, 4:43:38 AM3/24/18
to Andrey Konovalov, Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand

* Andrey Konovalov <andre...@google.com> wrote:

> This commit splits the current CONFIG_KASAN config option into two:
> 1. CONFIG_KASAN_CLASSIC, that enables the classic KASAN version (the one
> that exists now);
> 2. CONFIG_KASAN_TAGS, that enables KHWASAN.

Sorry, but this is pretty obscure naming scheme that doesn't explain the primary
difference between these KASAN models to users: that the first one is a pure
software implementation and the other is hardware-assisted.

Reminds me of the transparency of galactic buerocracy in "The Hitchhiker's Guide
to the Galaxy":

“But look, you found the notice, didn’t you?”
“Yes,” said Arthur, “yes I did. It was on display in the bottom of a locked filing
cabinet stuck in a disused lavatory with a sign on the door saying ‘Beware of the
Leopard.”

I'd suggest something more expressive, such as:

CONFIG_KASAN
CONFIG_KASAN_GENERIC
CONFIG_KASAN_HW_ASSIST

or so?

The 'generic' variant will basically run on any CPU. The 'hardware assisted' one
needs support from the CPU.

The following ones might also work:

CONFIG_KASAN_HWASSIST
CONFIG_KASAN_HW_TAGS
CONFIG_KASAN_HWTAGS

... or simply CONFIG_KASAN_SW/CONFIG_KASAN_HW.

If other types of KASAN hardware acceleration are implemented in the future then
the CONFIG_KASAN_HW namespace can be extended:

CONFIG_KASAN_HW_TAGS
CONFIG_KASAN_HW_KEYS
etc.

> Both CONFIG_KASAN_CLASSIC and CONFIG_KASAN_CLASSIC support both
> CONFIG_KASAN_INLINE and CONFIG_KASAN_OUTLINE instrumentation modes.

It would be very surprising if that wasn't so!

Or did you mean 'Both CONFIG_KASAN_CLASSIC and CONFIG_KASAN_TAGS'! ;-)

Thanks,

Ingo

Andrey Konovalov

unread,
Mar 27, 2018, 8:20:51 AM3/27/18
to Ingo Molnar, Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
'Reset' means to set the upper byte to the value that is native for
kernel pointers, and that is 0xFF. So it sets the tag to all ones, not
all zeroes. I can still rename it to khwasan_clear_tag(), if you think
that makes sense in this case as well.

Andrey Konovalov

unread,
Mar 27, 2018, 12:23:29 PM3/27/18
to Ingo Molnar, Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
How about these two:

CONFIG_KASAN_GENERIC
CONFIG_KASAN_HW

?

Shorter config name looks better to me and I think it makes sense to
name the new config just HW, as there's only one HW implementation
right now. When (and if) there are more, we can expand the config name
as you suggested (CONFIG_KASAN_HW_TAGS, CONFIG_KASAN_HW_KEYS, etc).

Ingo Molnar

unread,
Mar 27, 2018, 4:01:56 PM3/27/18
to Andrey Konovalov, Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
Ok, if it's not 0 then I agree that 'reset' is the better name. 'clear' would in
fact be actively confusing.

Thanks,

Ingo

Ingo Molnar

unread,
Mar 27, 2018, 4:02:29 PM3/27/18
to Andrey Konovalov, Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
Sure, sounds good to me!

Thanks,

Ingo

Andrey Ryabinin

unread,
Mar 30, 2018, 12:06:46 PM3/30/18
to Andrey Konovalov, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
On 03/23/2018 09:05 PM, Andrey Konovalov wrote:
> 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 <andre...@google.com>
> ---
> arch/arm64/mm/kasan_init.c | 11 ++++++++++-
> include/linux/kasan.h | 8 ++++++++
> mm/kasan/common.c | 7 +++++++
> 3 files changed, 25 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm64/mm/kasan_init.c b/arch/arm64/mm/kasan_init.c
> index dabfc1ecda3d..d4bceba60010 100644
> --- a/arch/arm64/mm/kasan_init.c
> +++ b/arch/arm64/mm/kasan_init.c
> @@ -90,6 +90,10 @@ static void __init kasan_pte_populate(pmd_t *pmdp, unsigned long addr,
> do {
> phys_addr_t page_phys = early ? __pa_symbol(kasan_zero_page)
> : kasan_alloc_zeroed_page(node);
> +#if KASAN_SHADOW_INIT != 0
> + if (!early)
> + memset(__va(page_phys), KASAN_SHADOW_INIT, PAGE_SIZE);
> +#endif

Less ugly way to do the same:
if (KASAN_SHADOW_INIT != 0 && !early)
memset(__va(page_phys), KASAN_SHADOW_INIT, PAGE_SIZE);


But the right approach here would be allocating uninitialized memory (see memblock_virt_alloc_try_nid_raw())
and do "if (!early) memset(.., KASAN_SHADOW_INIT, ..)" afterwards.


> next = addr + PAGE_SIZE;
> set_pte(ptep, pfn_pte(__phys_to_pfn(page_phys), PAGE_KERNEL));
> } while (ptep++, addr = next, addr != end && pte_none(READ_ONCE(*ptep)));
> @@ -139,6 +143,11 @@ asmlinkage void __init kasan_early_init(void)
> KASAN_SHADOW_END - (1UL << (64 - KASAN_SHADOW_SCALE_SHIFT)));
> BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_START, PGDIR_SIZE));
> BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_END, PGDIR_SIZE));
> +
> +#if KASAN_SHADOW_INIT != 0
> + memset(kasan_zero_page, KASAN_SHADOW_INIT, PAGE_SIZE);
> +#endif
> +

if (KASAN_SHADOW_INIT)
memset(...)

Note that, if poisoning of stack variables will work in the same fashion as classic
KASAN (compiler generated code writes to shadow in function prologue) than content
of this page will be ruined very fast. Which makes this initialization questionable.
Drop __GFP_ZERO from above and remove this #if/#endif.


> find_vm_area(addr)->flags |= VM_KASAN;
> kmemleak_ignore(ret);
> return 0;
> @@ -297,6 +300,10 @@ static int __meminit kasan_mem_notifier(struct notifier_block *nb,
> if (!ret)
> return NOTIFY_BAD;
>
> +#if KASAN_SHADOW_INIT != 0
> + __memset(ret, KASAN_SHADOW_INIT, shadow_end - shadow_start);
> +#endif
> +

No need to initialize anything here, kasan_free_pages() will do this later.


> kmemleak_ignore(ret);
> return NOTIFY_OK;
> }
>

Andrey Ryabinin

unread,
Mar 30, 2018, 12:12:31 PM3/30/18
to Andrey Konovalov, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand


On 03/23/2018 09:05 PM, Andrey Konovalov wrote:

> diff --git a/mm/kasan/khwasan.c b/mm/kasan/khwasan.c
> index 24d75245e9d0..da4b17997c71 100644
> --- a/mm/kasan/khwasan.c
> +++ b/mm/kasan/khwasan.c
> @@ -39,6 +39,57 @@
> #include "kasan.h"
> #include "../slab.h"
>
> +int khwasan_enabled;

This is not unused (set, but never used).

Andrey Ryabinin

unread,
Mar 30, 2018, 1:46:46 PM3/30/18
to Andrey Konovalov, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasa...@googlegroups.com, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-ar...@lists.infradead.org, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, linu...@kvack.org, linux-...@vger.kernel.org, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
On 03/23/2018 09:05 PM, Andrey Konovalov wrote:
This is way too much of copy-paste/code duplication. Ideally, you should have only
check_memory_region() stuff separated, the rest (poisoning/unpoisoning, slabs management) should be
in common.c code.

So it should be something like this:

in kasan.h
...
#ifdef CONFIG_KASAN_CLASSIC
#define KASAN_FREE_PAGE 0xFF /* page was freed */
#define KASAN_PAGE_REDZONE 0xFE /* redzone for kmalloc_large allocations */
#define KASAN_KMALLOC_REDZONE 0xFC /* redzone inside slub object */
#define KASAN_KMALLOC_FREE 0xFB /* object was freed (kmem_cache_free/kfree) */
#else
#define KASAN_FREE_PAGE 0xFE
#define KASAN_PAGE_REDZONE 0xFE
#define KASAN_KMALLOC_REDZONE 0xFE
#define KASAN_KMALLOC_FREE 0xFE
#endif

...

#ifdef CONFIG_KASAN_CLASSIC
static inline void *reset_tag(const void *addr)
{
return (void *)addr;
}
static inline u8 get_tag(const void *addr)
{
return 0;
}
#else
static inline u8 get_tag(const void *addr)
{
return (u8)((u64)addr >> KHWASAN_TAG_SHIFT);
}

static inline void *reset_tag(const void *addr)
{
return set_tag(addr, KHWASAN_TAG_KERNEL);
}
#endif


in kasan/common.c:


void kasan_poison_shadow(const void *address, size_t size, u8 value)
{
void *shadow_start, *shadow_end;

address = reset_tag(address);

shadow_start = kasan_mem_to_shadow(address);
shadow_end = kasan_mem_to_shadow(address + size);

memset(shadow_start, value, shadow_end - shadow_start);
}

void kasan_unpoison_shadow(const void *address, size_t size)
{

kasan_poison_shadow(address, size, get_tag(address));

if (size & KASAN_SHADOW_MASK) {
u8 *shadow = (u8 *)kasan_mem_to_shadow(address + size);

if (IS_ENABLED(CONFIG_KASAN_TAGS)
*shadow = get_tag(address);
else
*shadow = size & KASAN_SHADOW_MASK;
}
}

void kasan_free_pages(struct page *page, unsigned int order)
{
if (likely(!PageHighMem(page)))
kasan_poison_shadow(page_address(page),
PAGE_SIZE << order,
KASAN_FREE_PAGE);
}

etc.
You can save tag somewhere in page struct and make page_address() return tagged address.

I'm not sure it might be even possible to squeeze the tag into page->flags on some configurations,
see include/linux/page-flags-layout.h


> void *kasan_slab_alloc(struct kmem_cache *cache, void *object, gfp_t flags)
> {
> + if (!READ_ONCE(khwasan_enabled))
> + return object;

...

> void *kasan_kmalloc(struct kmem_cache *cache, const void *object,
> size_t size, gfp_t flags)
> {

> + if (!READ_ONCE(khwasan_enabled))
> + return (void *)object;
> +

...

> void *kasan_kmalloc_large(const void *ptr, size_t size, gfp_t flags)
> {

...

> +
> + if (!READ_ONCE(khwasan_enabled))
> + return (void *)ptr;
> +

I don't see any possible way of khwasan_enabled being 0 here.

Andrey Konovalov

unread,
Apr 3, 2018, 10:43:13 AM4/3/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Christoffer Dall, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Stephen Boyd, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
Will do!

>
>
>> next = addr + PAGE_SIZE;
>> set_pte(ptep, pfn_pte(__phys_to_pfn(page_phys), PAGE_KERNEL));
>> } while (ptep++, addr = next, addr != end && pte_none(READ_ONCE(*ptep)));
>> @@ -139,6 +143,11 @@ asmlinkage void __init kasan_early_init(void)
>> KASAN_SHADOW_END - (1UL << (64 - KASAN_SHADOW_SCALE_SHIFT)));
>> BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_START, PGDIR_SIZE));
>> BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_END, PGDIR_SIZE));
>> +
>> +#if KASAN_SHADOW_INIT != 0
>> + memset(kasan_zero_page, KASAN_SHADOW_INIT, PAGE_SIZE);
>> +#endif
>> +
>
> if (KASAN_SHADOW_INIT)
> memset(...)
>
> Note that, if poisoning of stack variables will work in the same fashion as classic
> KASAN (compiler generated code writes to shadow in function prologue) than content
> of this page will be ruined very fast. Which makes this initialization questionable.

I think I agree with you on this. Since this page immediately gets
dirty and we ignore all reports until proper shadow is set up anyway,
there's no need to initialize it.
Will do!

>
>
>> find_vm_area(addr)->flags |= VM_KASAN;
>> kmemleak_ignore(ret);
>> return 0;
>> @@ -297,6 +300,10 @@ static int __meminit kasan_mem_notifier(struct notifier_block *nb,
>> if (!ret)
>> return NOTIFY_BAD;
>>
>> +#if KASAN_SHADOW_INIT != 0
>> + __memset(ret, KASAN_SHADOW_INIT, shadow_end - shadow_start);
>> +#endif
>> +
>
> No need to initialize anything here, kasan_free_pages() will do this later.

OK, will fix this!

>
>
>> kmemleak_ignore(ret);
>> return NOTIFY_OK;
>> }
>>

Thanks!

Andrey Konovalov

unread,
Apr 3, 2018, 10:45:30 AM4/3/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
On Fri, Mar 30, 2018 at 6:13 PM, Andrey Ryabinin
<arya...@virtuozzo.com> wrote:
>
>
> On 03/23/2018 09:05 PM, Andrey Konovalov wrote:
>
>> diff --git a/mm/kasan/khwasan.c b/mm/kasan/khwasan.c
>> index 24d75245e9d0..da4b17997c71 100644
>> --- a/mm/kasan/khwasan.c
>> +++ b/mm/kasan/khwasan.c
>> @@ -39,6 +39,57 @@
>> #include "kasan.h"
>> #include "../slab.h"
>>
>> +int khwasan_enabled;
>
> This is not unused (set, but never used).

It's used in the "khwasan: add hooks implementation" patch. I'll move
it's declaration there as well.

Thanks!

Andrey Konovalov

unread,
Apr 3, 2018, 11:00:00 AM4/3/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
OK, I'll rework this part.
One page can contain multiple objects with different tags, so we would
need to save the tag for each of them.

>
>
>> void *kasan_slab_alloc(struct kmem_cache *cache, void *object, gfp_t flags)
>> {
>> + if (!READ_ONCE(khwasan_enabled))
>> + return object;
>
> ...
>
>> void *kasan_kmalloc(struct kmem_cache *cache, const void *object,
>> size_t size, gfp_t flags)
>> {
>
>> + if (!READ_ONCE(khwasan_enabled))
>> + return (void *)object;
>> +
>
> ...
>
>> void *kasan_kmalloc_large(const void *ptr, size_t size, gfp_t flags)
>> {
>
> ...
>
>> +
>> + if (!READ_ONCE(khwasan_enabled))
>> + return (void *)ptr;
>> +
>
> I don't see any possible way of khwasan_enabled being 0 here.

Can't kmem_cache_alloc be called for the temporary caches that are
used before the slab allocator and kasan are initialized?

>
> --
> You received this message because you are subscribed to the Google Groups "kasan-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to kasan-dev+...@googlegroups.com.
> To post to this group, send email to kasa...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/kasan-dev/805d1e85-2d3c-2327-6e6c-f14a56dc0b67%40virtuozzo.com.
> For more options, visit https://groups.google.com/d/optout.

Andrey Ryabinin

unread,
Apr 4, 2018, 8:39:10 AM4/4/18
to Andrey Konovalov, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
What do you mean? Slab page? The per-page tag is needed only for !PageSlab pages.
For slab pages we have kmalloc/kmem_cache_alloc() which already return properly tagged address.

But the page allocator returns a pointer to struct page. One has to call page_address(page)
to use that page. Returning 'ignore-me'-tagged address from page_address() makes the whole
class of bugs invisible to KHWASAN. This is a serious downside comparing to classic KASAN which can
detect missuses of page allocator API.



>>
>>
>>> void *kasan_slab_alloc(struct kmem_cache *cache, void *object, gfp_t flags)
>>> {
>>> + if (!READ_ONCE(khwasan_enabled))
>>> + return object;
>>
>> ...
>>
>>> void *kasan_kmalloc(struct kmem_cache *cache, const void *object,
>>> size_t size, gfp_t flags)
>>> {
>>
>>> + if (!READ_ONCE(khwasan_enabled))
>>> + return (void *)object;
>>> +
>>
>> ...
>>
>>> void *kasan_kmalloc_large(const void *ptr, size_t size, gfp_t flags)
>>> {
>>
>> ...
>>
>>> +
>>> + if (!READ_ONCE(khwasan_enabled))
>>> + return (void *)ptr;
>>> +
>>
>> I don't see any possible way of khwasan_enabled being 0 here.
>
> Can't kmem_cache_alloc be called for the temporary caches that are
> used before the slab allocator and kasan are initialized?

kasan_init() runs before allocators are initialized.
slab allocator obviously has to be initialized before it can be used.

Andrey Konovalov

unread,
Apr 4, 2018, 1:00:38 PM4/4/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
On Wed, Apr 4, 2018 at 2:39 PM, Andrey Ryabinin <arya...@virtuozzo.com> wrote:
>>>
>>> You can save tag somewhere in page struct and make page_address() return tagged address.
>>>
>>> I'm not sure it might be even possible to squeeze the tag into page->flags on some configurations,
>>> see include/linux/page-flags-layout.h
>>
>> One page can contain multiple objects with different tags, so we would
>> need to save the tag for each of them.
>
> What do you mean? Slab page? The per-page tag is needed only for !PageSlab pages.
> For slab pages we have kmalloc/kmem_cache_alloc() which already return properly tagged address.
>
> But the page allocator returns a pointer to struct page. One has to call page_address(page)
> to use that page. Returning 'ignore-me'-tagged address from page_address() makes the whole
> class of bugs invisible to KHWASAN. This is a serious downside comparing to classic KASAN which can
> detect missuses of page allocator API.

Yes, slab page. Here's an example:

1. do_get_write_access() allocates frozen_buffer with jbd2_alloc,
which calls kmem_cache_alloc, and then saves the result to
jh->b_frozen_data.

2. jbd2_journal_write_metadata_buffer() takes the value of
jh_in->b_frozen_data and calls virt_to_page() (and offset_in_page())
on it.

3. jbd2_journal_write_metadata_buffer() then calls kmap_atomic(),
which calls page_address(), on the resulting page address.

The tag gets erased. The page belongs to slab and can contain multiple
objects with different tags.

>>> I don't see any possible way of khwasan_enabled being 0 here.
>>
>> Can't kmem_cache_alloc be called for the temporary caches that are
>> used before the slab allocator and kasan are initialized?
>
> kasan_init() runs before allocators are initialized.
> slab allocator obviously has to be initialized before it can be used.

Checked the code, it seems you are right. Boot caches are created
after kasan_init() is called. I will remove khwasan_enabled.

Thanks!

Andrey Ryabinin

unread,
Apr 5, 2018, 9:01:39 AM4/5/18
to Andrey Konovalov, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
On 04/04/2018 08:00 PM, Andrey Konovalov wrote:
> On Wed, Apr 4, 2018 at 2:39 PM, Andrey Ryabinin <arya...@virtuozzo.com> wrote:
>>>>
>>>> You can save tag somewhere in page struct and make page_address() return tagged address.
>>>>
>>>> I'm not sure it might be even possible to squeeze the tag into page->flags on some configurations,
>>>> see include/linux/page-flags-layout.h
>>>
>>> One page can contain multiple objects with different tags, so we would
>>> need to save the tag for each of them.
>>
>> What do you mean? Slab page? The per-page tag is needed only for !PageSlab pages.
>> For slab pages we have kmalloc/kmem_cache_alloc() which already return properly tagged address.
>>
>> But the page allocator returns a pointer to struct page. One has to call page_address(page)
>> to use that page. Returning 'ignore-me'-tagged address from page_address() makes the whole
>> class of bugs invisible to KHWASAN. This is a serious downside comparing to classic KASAN which can
>> detect missuses of page allocator API.
>
> Yes, slab page. Here's an example:
>
> 1. do_get_write_access() allocates frozen_buffer with jbd2_alloc,
> which calls kmem_cache_alloc, and then saves the result to
> jh->b_frozen_data.
>
> 2. jbd2_journal_write_metadata_buffer() takes the value of
> jh_in->b_frozen_data and calls virt_to_page() (and offset_in_page())
> on it.
>
> 3. jbd2_journal_write_metadata_buffer() then calls kmap_atomic(),
> which calls page_address(), on the resulting page address.
>
> The tag gets erased. The page belongs to slab and can contain multiple
> objects with different tags.
>

I see. Ideally that kind of problem should be fixed by reworking/redesigning such code,
however jbd2_journal_write_metadata_buffer() is far from the only place which
does that trick. Fixing all of them would be a huge task probably, so ignoring such
accesses seems to be the only choice we have.

Nevertheless, this doesn't mean that we should ignore *all* accesses to !slab memory.

Andrey Konovalov

unread,
Apr 6, 2018, 8:14:04 AM4/6/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
So you mean we need to find a way to ignore accesses via pointers
returned by page_address(), but still check accesses through all other
pointers tagged with 0xFF? I don't see an obvious way to do this. I'm
open to suggestions though.

Andrey Ryabinin

unread,
Apr 6, 2018, 8:27:15 AM4/6/18
to Andrey Konovalov, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
I'm saying that we need to ignore accesses to slab objects if pointer
to slab object obtained via page_address() + offset_in_page() trick, but don't ignore
anything else.

So, save tag somewhere in page struct and poison shadow with that tag. Make page_address() to
return tagged address for all !PageSlab() pages. For PageSlab() pages page_address() should return
0xff tagged address, so we could ignore such accesses.


Andrey Konovalov

unread,
Apr 10, 2018, 12:07:41 PM4/10/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
Which pages do you mean by !PageSlab()? The ones that are allocated
and freed by pagealloc, but mot managed by the slab allocator? Perhaps
we should then add tagging to the pagealloc hook instead?

Andrey Ryabinin

unread,
Apr 10, 2018, 12:30:53 PM4/10/18
to Andrey Konovalov, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand


On 04/10/2018 07:07 PM, Andrey Konovalov wrote:
> On Fri, Apr 6, 2018 at 2:27 PM, Andrey Ryabinin <arya...@virtuozzo.com> wrote:
>> On 04/06/2018 03:14 PM, Andrey Konovalov wrote:
>>> On Thu, Apr 5, 2018 at 3:02 PM, Andrey Ryabinin <arya...@virtuozzo.com> wrote:
>>>> Nevertheless, this doesn't mean that we should ignore *all* accesses to !slab memory.
>>>
>>> So you mean we need to find a way to ignore accesses via pointers
>>> returned by page_address(), but still check accesses through all other
>>> pointers tagged with 0xFF? I don't see an obvious way to do this. I'm
>>> open to suggestions though.
>>>
>>
>> I'm saying that we need to ignore accesses to slab objects if pointer
>> to slab object obtained via page_address() + offset_in_page() trick, but don't ignore
>> anything else.
>>
>> So, save tag somewhere in page struct and poison shadow with that tag. Make page_address() to
>> return tagged address for all !PageSlab() pages. For PageSlab() pages page_address() should return
>> 0xff tagged address, so we could ignore such accesses.
>
> Which pages do you mean by !PageSlab()?

Literally the "PageSlab(page) == false" pages.

> The ones that are allocated and freed by pagealloc, but mot managed by the slab allocator?

Yes.

> Perhaps we should then add tagging to the pagealloc hook instead?
>

Of course the tagging would be in kasan_alloc_pages(), where else that could be? And instead of what?

Andrey Konovalov

unread,
Apr 12, 2018, 12:45:38 PM4/12/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
On Tue, Apr 10, 2018 at 6:31 PM, Andrey Ryabinin
I think I misunderstood your suggestion twice already :)

To make it clear, you're suggesting:

1. Tag memory with a random tag in kasan_alloc_pages() and returned a
tagged pointer from pagealloc.

2. Restore the tag for the pointers returned from page_address for
!PageSlab() pages.

3. Set the tag to 0xff for the pointers returned from page_address for
PageSlab() pages.

Is this correct?

In 2 instead of storing the tag in page_struct, we can just recover it
from the shadow memory that corresponds to that page. What do you
think about this?

Andrey Ryabinin

unread,
Apr 12, 2018, 1:20:03 PM4/12/18
to Andrey Konovalov, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
Tag memory with a random tag in kasan_alloc_pages() and store that tag in page struct (that part is also in kasan_alloc_pages()).
page_address(page) will retrieve that tag from struct page to return tagged address.

I've no idea what do you mean by "returning a tagged pointer from pagealloc".
Once again, the page allocator (__alloc_pages_nodemask()) returns pointer to *struct page*,
not the address in the linear mapping where is that page mapped (or not mapped at all if this is highmem).
One have to call page_address()/kmap() to use that page.


> 2. Restore the tag for the pointers returned from page_address for
> !PageSlab() pages.
>

Right.

> 3. Set the tag to 0xff for the pointers returned from page_address for
> PageSlab() pages.
>

Right.

> Is this correct?
>
> In 2 instead of storing the tag in page_struct, we can just recover it
> from the shadow memory that corresponds to that page. What do you
> think about this?

Sounds ok. Don't see any problem with that.


Andrey Konovalov

unread,
Apr 12, 2018, 1:37:30 PM4/12/18
to Andrey Ryabinin, Alexander Potapenko, Dmitry Vyukov, Jonathan Corbet, Catalin Marinas, Will Deacon, Marc Zyngier, Christopher Li, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, Masahiro Yamada, Michal Marek, Mark Rutland, Ard Biesheuvel, Yury Norov, Nick Desaulniers, Suzuki K Poulose, Kristina Martsenko, Punit Agrawal, Dave Martin, Michael Weiser, James Morse, Julien Thierry, Steve Capper, Tyler Baicar, Eric W . Biederman, Thomas Gleixner, Ingo Molnar, Paul Lawrence, Greg Kroah-Hartman, David Woodhouse, Sandipan Das, Kees Cook, Herbert Xu, Geert Uytterhoeven, Josh Poimboeuf, Arnd Bergmann, kasan-dev, linu...@vger.kernel.org, LKML, Linux ARM, kvm...@lists.cs.columbia.edu, linux-...@vger.kernel.org, Linux Memory Management List, Linux Kbuild mailing list, Kostya Serebryany, Evgeniy Stepanov, Lee Smith, Ramana Radhakrishnan, Jacob Bramley, Ruben Ayrapetyan, Kees Cook, Jann Horn, Mark Brand
On Thu, Apr 12, 2018 at 7:20 PM, Andrey Ryabinin
<arya...@virtuozzo.com> wrote:
>> 1. Tag memory with a random tag in kasan_alloc_pages() and returned a
>> tagged pointer from pagealloc.
>
> Tag memory with a random tag in kasan_alloc_pages() and store that tag in page struct (that part is also in kasan_alloc_pages()).
> page_address(page) will retrieve that tag from struct page to return tagged address.
>
> I've no idea what do you mean by "returning a tagged pointer from pagealloc".
> Once again, the page allocator (__alloc_pages_nodemask()) returns pointer to *struct page*,
> not the address in the linear mapping where is that page mapped (or not mapped at all if this is highmem).
> One have to call page_address()/kmap() to use that page.

Ah, that's what I've been missing.

OK, I'll do that.

Thanks!
Reply all
Reply to author
Forward
0 new messages