Skip to content

Commit

Permalink
kasan: detect negative size in memory operation function
Browse files Browse the repository at this point in the history
Patch series "fix the missing underflow in memory operation function", v4.

The patchset helps to produce a KASAN report when size is negative in
memory operation functions.  It is helpful for programmer to solve an
undefined behavior issue.  Patch 1 based on Dmitry's review and
suggestion, patch 2 is a test in order to verify the patch 1.

[1]https://bugzilla.kernel.org/show_bug.cgi?id=199341
[2]https://lore.kernel.org/linux-arm-kernel/20190927034338.15813-1-walter-zh.wu@mediatek.com/

This patch (of 2):

KASAN missed detecting size is a negative number in memset(), memcpy(),
and memmove(), it will cause out-of-bounds bug.  So needs to be detected
by KASAN.

If size is a negative number, then it has a reason to be defined as
out-of-bounds bug type.  Casting negative numbers to size_t would indeed
turn up as a large size_t and its value will be larger than ULONG_MAX/2,
so that this can qualify as out-of-bounds.

KASAN report is shown below:

 BUG: KASAN: out-of-bounds in kmalloc_memmove_invalid_size+0x70/0xa0
 Read of size 18446744073709551608 at addr ffffff8069660904 by task cat/72

 CPU: 2 PID: 72 Comm: cat Not tainted 5.4.0-rc1-next-20191004ajb-00001-gdb8af2f372b2-dirty #1
 Hardware name: linux,dummy-virt (DT)
 Call trace:
  dump_backtrace+0x0/0x288
  show_stack+0x14/0x20
  dump_stack+0x10c/0x164
  print_address_description.isra.9+0x68/0x378
  __kasan_report+0x164/0x1a0
  kasan_report+0xc/0x18
  check_memory_region+0x174/0x1d0
  memmove+0x34/0x88
  kmalloc_memmove_invalid_size+0x70/0xa0

[1] https://bugzilla.kernel.org/show_bug.cgi?id=199341

[cai@lca.pw: fix -Wdeclaration-after-statement warn]
  Link: http://lkml.kernel.org/r/1583509030-27939-1-git-send-email-cai@lca.pw
[peterz@infradead.org: fix objtool warning]
  Link: http://lkml.kernel.org/r/20200305095436.GV2596@hirez.programming.kicks-ass.net
Reported-by: kernel test robot <lkp@intel.com>
Reported-by: Dmitry Vyukov <dvyukov@google.com>
Suggested-by: Dmitry Vyukov <dvyukov@google.com>
Signed-off-by: Walter Wu <walter-zh.wu@mediatek.com>
Signed-off-by: Qian Cai <cai@lca.pw>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Andrey Ryabinin <aryabinin@virtuozzo.com>
Cc: Alexander Potapenko <glider@google.com>
Link: http://lkml.kernel.org/r/20191112065302.7015-1-walter-zh.wu@mediatek.com
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
  • Loading branch information
Walter Wu authored and Linus Torvalds committed Apr 2, 2020
1 parent 4027149 commit 8cceeff
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 21 deletions.
2 changes: 1 addition & 1 deletion include/linux/kasan.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ void kasan_init_tags(void);

void *kasan_reset_tag(const void *addr);

void kasan_report(unsigned long addr, size_t size,
bool kasan_report(unsigned long addr, size_t size,
bool is_write, unsigned long ip);

#else /* CONFIG_KASAN_SW_TAGS */
Expand Down
26 changes: 19 additions & 7 deletions mm/kasan/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ 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_);
if (!check_memory_region((unsigned long)addr, len, true, _RET_IP_))
return NULL;

return __memset(addr, c, len);
}
Expand All @@ -114,8 +115,9 @@ void *memset(void *addr, int c, size_t 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_);
if (!check_memory_region((unsigned long)src, len, false, _RET_IP_) ||
!check_memory_region((unsigned long)dest, len, true, _RET_IP_))
return NULL;

return __memmove(dest, src, len);
}
Expand All @@ -124,8 +126,9 @@ void *memmove(void *dest, const void *src, size_t 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_);
if (!check_memory_region((unsigned long)src, len, false, _RET_IP_) ||
!check_memory_region((unsigned long)dest, len, true, _RET_IP_))
return NULL;

return __memcpy(dest, src, len);
}
Expand Down Expand Up @@ -634,12 +637,21 @@ void kasan_free_shadow(const struct vm_struct *vm)
#endif

extern void __kasan_report(unsigned long addr, size_t size, bool is_write, unsigned long ip);
extern bool report_enabled(void);

void kasan_report(unsigned long addr, size_t size, bool is_write, unsigned long ip)
bool kasan_report(unsigned long addr, size_t size, bool is_write, unsigned long ip)
{
unsigned long flags = user_access_save();
__kasan_report(addr, size, is_write, ip);
bool ret = false;

if (likely(report_enabled())) {
__kasan_report(addr, size, is_write, ip);
ret = true;
}

user_access_restore(flags);

return ret;
}

#ifdef CONFIG_MEMORY_HOTPLUG
Expand Down
9 changes: 5 additions & 4 deletions mm/kasan/generic.c
Original file line number Diff line number Diff line change
Expand Up @@ -173,17 +173,18 @@ static __always_inline bool check_memory_region_inline(unsigned long addr,
if (unlikely(size == 0))
return true;

if (unlikely(addr + size < addr))
return !kasan_report(addr, size, write, ret_ip);

if (unlikely((void *)addr <
kasan_shadow_to_mem((void *)KASAN_SHADOW_START))) {
kasan_report(addr, size, write, ret_ip);
return false;
return !kasan_report(addr, size, write, ret_ip);
}

if (likely(!memory_is_poisoned(addr, size)))
return true;

kasan_report(addr, size, write, ret_ip);
return false;
return !kasan_report(addr, size, write, ret_ip);
}

bool check_memory_region(unsigned long addr, size_t size, bool write,
Expand Down
11 changes: 11 additions & 0 deletions mm/kasan/generic_report.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@ static const char *get_wild_bug_type(struct kasan_access_info *info)

const char *get_bug_type(struct kasan_access_info *info)
{
/*
* If access_size is a negative number, then it has reason to be
* defined as out-of-bounds bug type.
*
* Casting negative numbers to size_t would indeed turn up as
* a large size_t and its value will be larger than ULONG_MAX/2,
* so that this can qualify as out-of-bounds.
*/
if (info->access_addr + info->access_size < info->access_addr)
return "out-of-bounds";

if (addr_has_shadow(info->access_addr))
return get_shadow_bug_type(info);
return get_wild_bug_type(info);
Expand Down
2 changes: 1 addition & 1 deletion mm/kasan/kasan.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ bool check_memory_region(unsigned long addr, size_t size, bool write,
void *find_first_bad_addr(void *addr, size_t size);
const char *get_bug_type(struct kasan_access_info *info);

void kasan_report(unsigned long addr, size_t size,
bool kasan_report(unsigned long addr, size_t size,
bool is_write, unsigned long ip);
void kasan_report_invalid_free(void *object, unsigned long ip);

Expand Down
5 changes: 1 addition & 4 deletions mm/kasan/report.c
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ static void print_shadow_for_address(const void *addr)
}
}

static bool report_enabled(void)
bool report_enabled(void)
{
if (current->kasan_depth)
return false;
Expand Down Expand Up @@ -478,9 +478,6 @@ void __kasan_report(unsigned long addr, size_t size, bool is_write, unsigned lon
void *untagged_addr;
unsigned long flags;

if (likely(!report_enabled()))
return;

disable_trace_on_warning();

tagged_addr = (void *)addr;
Expand Down
9 changes: 5 additions & 4 deletions mm/kasan/tags.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ bool check_memory_region(unsigned long addr, size_t size, bool write,
if (unlikely(size == 0))
return true;

if (unlikely(addr + size < addr))
return !kasan_report(addr, size, write, ret_ip);

tag = get_tag((const void *)addr);

/*
Expand All @@ -111,15 +114,13 @@ bool check_memory_region(unsigned long addr, size_t size, bool write,
untagged_addr = reset_tag((const void *)addr);
if (unlikely(untagged_addr <
kasan_shadow_to_mem((void *)KASAN_SHADOW_START))) {
kasan_report(addr, size, write, ret_ip);
return false;
return !kasan_report(addr, size, write, ret_ip);
}
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) {
kasan_report(addr, size, write, ret_ip);
return false;
return !kasan_report(addr, size, write, ret_ip);
}
}

Expand Down
11 changes: 11 additions & 0 deletions mm/kasan/tags_report.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ const char *get_bug_type(struct kasan_access_info *info)
}

#endif
/*
* If access_size is a negative number, then it has reason to be
* defined as out-of-bounds bug type.
*
* Casting negative numbers to size_t would indeed turn up as
* a large size_t and its value will be larger than ULONG_MAX/2,
* so that this can qualify as out-of-bounds.
*/
if (info->access_addr + info->access_size < info->access_addr)
return "out-of-bounds";

return "invalid-access";
}

Expand Down

0 comments on commit 8cceeff

Please sign in to comment.