Commit 8cceeff4 authored by Walter Wu's avatar Walter Wu Committed by Linus Torvalds
Browse files

kasan: detect negative size in memory operation function

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: default avatarkernel test robot <lkp@intel.com>
Reported-by: default avatarDmitry Vyukov <dvyukov@google.com>
Suggested-by: default avatarDmitry Vyukov <dvyukov@google.com>
Signed-off-by: default avatarWalter Wu <walter-zh.wu@mediatek.com>
Signed-off-by: default avatarQian Cai <cai@lca.pw>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Reviewed-by: default avatarDmitry Vyukov <dvyukov@google.com>
Reviewed-by: default avatarAndrey 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: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 4027149a
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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 */
+19 −7
Original line number Diff line number Diff line
@@ -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);
}
@@ -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);
}
@@ -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);
}
@@ -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();
	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
+5 −4
Original line number Diff line number Diff line
@@ -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,
+11 −0
Original line number Diff line number Diff line
@@ -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);
+1 −1
Original line number Diff line number Diff line
@@ -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);

Loading