Skip to content

Commit

Permalink
kasan: add memory corruption identification for software tag-based mode
Browse files Browse the repository at this point in the history
Add memory corruption identification at bug report for software tag-based
mode.  The report shows whether it is "use-after-free" or "out-of-bound"
error instead of "invalid-access" error.  This will make it easier for
programmers to see the memory corruption problem.

We extend the slab to store five old free pointer tag and free backtrace,
we can check if the tagged address is in the slab record and make a good
guess if the object is more like "use-after-free" or "out-of-bound".
therefore every slab memory corruption can be identified whether it's
"use-after-free" or "out-of-bound".

[[email protected]: simplify & clenup code]
  Link: https://lkml.kernel.org/r/[email protected]]
Link: http://lkml.kernel.org/r/[email protected]
Signed-off-by: Walter Wu <[email protected]>
Signed-off-by: Andrey Ryabinin <[email protected]>
Acked-by: Andrey Konovalov <[email protected]>
Cc: Dmitry Vyukov <[email protected]>
Cc: Alexander Potapenko <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
truhuan authored and torvalds committed Sep 24, 2019
1 parent c59180a commit ae8f06b
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 13 deletions.
8 changes: 8 additions & 0 deletions lib/Kconfig.kasan
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ config KASAN_S390_4_LEVEL_PAGING
to 3TB of RAM with KASan enabled). This options allows to force
4-level paging instead.

config KASAN_SW_TAGS_IDENTIFY
bool "Enable memory corruption identification"
depends on KASAN_SW_TAGS
help
This option enables best-effort identification of bug type
(use-after-free or out-of-bounds) at the cost of increased
memory consumption.

config TEST_KASAN
tristate "Module for testing KASAN for bug detection"
depends on m && KASAN
Expand Down
22 changes: 20 additions & 2 deletions mm/kasan/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@ size_t kasan_metadata_size(struct kmem_cache *cache)
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;
}

Expand All @@ -315,6 +314,24 @@ struct kasan_free_meta *get_free_info(struct kmem_cache *cache,
return (void *)object + cache->kasan_info.free_meta_offset;
}


static void kasan_set_free_info(struct kmem_cache *cache,
void *object, u8 tag)
{
struct kasan_alloc_meta *alloc_meta;
u8 idx = 0;

alloc_meta = get_alloc_info(cache, object);

#ifdef CONFIG_KASAN_SW_TAGS_IDENTIFY
idx = alloc_meta->free_track_idx;
alloc_meta->free_pointer_tag[idx] = tag;
alloc_meta->free_track_idx = (idx + 1) % KASAN_NR_FREE_STACKS;
#endif

set_track(&alloc_meta->free_track[idx], GFP_NOWAIT);
}

void kasan_poison_slab(struct page *page)
{
unsigned long i;
Expand Down Expand Up @@ -452,7 +469,8 @@ static bool __kasan_slab_free(struct kmem_cache *cache, void *object,
unlikely(!(cache->flags & SLAB_KASAN)))
return false;

set_track(&get_alloc_info(cache, object)->free_track, GFP_NOWAIT);
kasan_set_free_info(cache, object, tag);

quarantine_put(get_free_info(cache, object), cache);

return IS_ENABLED(CONFIG_KASAN_GENERIC);
Expand Down
14 changes: 13 additions & 1 deletion mm/kasan/kasan.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,19 @@ struct kasan_track {
depot_stack_handle_t stack;
};

#ifdef CONFIG_KASAN_SW_TAGS_IDENTIFY
#define KASAN_NR_FREE_STACKS 5
#else
#define KASAN_NR_FREE_STACKS 1
#endif

struct kasan_alloc_meta {
struct kasan_track alloc_track;
struct kasan_track free_track;
struct kasan_track free_track[KASAN_NR_FREE_STACKS];
#ifdef CONFIG_KASAN_SW_TAGS_IDENTIFY
u8 free_pointer_tag[KASAN_NR_FREE_STACKS];
u8 free_track_idx;
#endif
};

struct qlist_node {
Expand Down Expand Up @@ -146,6 +156,8 @@ 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);

struct page *kasan_addr_to_page(const void *addr);

#if defined(CONFIG_KASAN_GENERIC) && \
(defined(CONFIG_SLAB) || defined(CONFIG_SLUB))
void quarantine_put(struct kasan_free_meta *info, struct kmem_cache *cache);
Expand Down
44 changes: 34 additions & 10 deletions mm/kasan/report.c
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ static void print_track(struct kasan_track *track, const char *prefix)
}
}

static struct page *addr_to_page(const void *addr)
struct page *kasan_addr_to_page(const void *addr)
{
if ((addr >= (void *)PAGE_OFFSET) &&
(addr < high_memory))
Expand Down Expand Up @@ -151,15 +151,38 @@ static void describe_object_addr(struct kmem_cache *cache, void *object,
(void *)(object_addr + cache->object_size));
}

static struct kasan_track *kasan_get_free_track(struct kmem_cache *cache,
void *object, u8 tag)
{
struct kasan_alloc_meta *alloc_meta;
int i = 0;

alloc_meta = get_alloc_info(cache, object);

#ifdef CONFIG_KASAN_SW_TAGS_IDENTIFY
for (i = 0; i < KASAN_NR_FREE_STACKS; i++) {
if (alloc_meta->free_pointer_tag[i] == tag)
break;
}
if (i == KASAN_NR_FREE_STACKS)
i = alloc_meta->free_track_idx;
#endif

return &alloc_meta->free_track[i];
}

static void describe_object(struct kmem_cache *cache, void *object,
const void *addr)
const void *addr, u8 tag)
{
struct kasan_alloc_meta *alloc_info = get_alloc_info(cache, object);

if (cache->flags & SLAB_KASAN) {
struct kasan_track *free_track;

print_track(&alloc_info->alloc_track, "Allocated");
pr_err("\n");
print_track(&alloc_info->free_track, "Freed");
free_track = kasan_get_free_track(cache, object, tag);
print_track(free_track, "Freed");
pr_err("\n");
}

Expand Down Expand Up @@ -344,9 +367,9 @@ static void print_address_stack_frame(const void *addr)
print_decoded_frame_descr(frame_descr);
}

static void print_address_description(void *addr)
static void print_address_description(void *addr, u8 tag)
{
struct page *page = addr_to_page(addr);
struct page *page = kasan_addr_to_page(addr);

dump_stack();
pr_err("\n");
Expand All @@ -355,7 +378,7 @@ static void print_address_description(void *addr)
struct kmem_cache *cache = page->slab_cache;
void *object = nearest_obj(cache, page, addr);

describe_object(cache, object, addr);
describe_object(cache, object, addr, tag);
}

if (kernel_or_module_addr(addr) && !init_task_stack_addr(addr)) {
Expand Down Expand Up @@ -435,13 +458,14 @@ static bool report_enabled(void)
void kasan_report_invalid_free(void *object, unsigned long ip)
{
unsigned long flags;
u8 tag = get_tag(object);

object = reset_tag(object);
start_report(&flags);
pr_err("BUG: KASAN: double-free or invalid-free in %pS\n", (void *)ip);
print_tags(get_tag(object), reset_tag(object));
object = reset_tag(object);
print_tags(tag, object);
pr_err("\n");
print_address_description(object);
print_address_description(object, tag);
pr_err("\n");
print_shadow_for_address(object);
end_report(&flags);
Expand Down Expand Up @@ -479,7 +503,7 @@ void __kasan_report(unsigned long addr, size_t size, bool is_write, unsigned lon
pr_err("\n");

if (addr_has_shadow(untagged_addr)) {
print_address_description(untagged_addr);
print_address_description(untagged_addr, get_tag(tagged_addr));
pr_err("\n");
print_shadow_for_address(info.first_bad_addr);
} else {
Expand Down
24 changes: 24 additions & 0 deletions mm/kasan/tags_report.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,30 @@

const char *get_bug_type(struct kasan_access_info *info)
{
#ifdef CONFIG_KASAN_SW_TAGS_IDENTIFY
struct kasan_alloc_meta *alloc_meta;
struct kmem_cache *cache;
struct page *page;
const void *addr;
void *object;
u8 tag;
int i;

tag = get_tag(info->access_addr);
addr = reset_tag(info->access_addr);
page = kasan_addr_to_page(addr);
if (page && PageSlab(page)) {
cache = page->slab_cache;
object = nearest_obj(cache, page, (void *)addr);
alloc_meta = get_alloc_info(cache, object);

for (i = 0; i < KASAN_NR_FREE_STACKS; i++)
if (alloc_meta->free_pointer_tag[i] == tag)
return "use-after-free";
return "out-of-bounds";
}

#endif
return "invalid-access";
}

Expand Down

0 comments on commit ae8f06b

Please sign in to comment.