Skip to content

Commit 8cceeff

Browse files
truhuantorvalds
authored andcommitted
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/[email protected]/ 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 [[email protected]: fix -Wdeclaration-after-statement warn] Link: http://lkml.kernel.org/r/[email protected] [[email protected]: fix objtool warning] Link: http://lkml.kernel.org/r/[email protected] Reported-by: kernel test robot <[email protected]> Reported-by: Dmitry Vyukov <[email protected]> Suggested-by: Dmitry Vyukov <[email protected]> Signed-off-by: Walter Wu <[email protected]> Signed-off-by: Qian Cai <[email protected]> Signed-off-by: Andrew Morton <[email protected]> Reviewed-by: Dmitry Vyukov <[email protected]> Reviewed-by: Andrey Ryabinin <[email protected]> Cc: Alexander Potapenko <[email protected]> Link: http://lkml.kernel.org/r/[email protected] Signed-off-by: Linus Torvalds <[email protected]>
1 parent 4027149 commit 8cceeff

File tree

8 files changed

+54
-21
lines changed

8 files changed

+54
-21
lines changed

include/linux/kasan.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ void kasan_init_tags(void);
190190

191191
void *kasan_reset_tag(const void *addr);
192192

193-
void kasan_report(unsigned long addr, size_t size,
193+
bool kasan_report(unsigned long addr, size_t size,
194194
bool is_write, unsigned long ip);
195195

196196
#else /* CONFIG_KASAN_SW_TAGS */

mm/kasan/common.c

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ EXPORT_SYMBOL(__kasan_check_write);
105105
#undef memset
106106
void *memset(void *addr, int c, size_t len)
107107
{
108-
check_memory_region((unsigned long)addr, len, true, _RET_IP_);
108+
if (!check_memory_region((unsigned long)addr, len, true, _RET_IP_))
109+
return NULL;
109110

110111
return __memset(addr, c, len);
111112
}
@@ -114,8 +115,9 @@ void *memset(void *addr, int c, size_t len)
114115
#undef memmove
115116
void *memmove(void *dest, const void *src, size_t len)
116117
{
117-
check_memory_region((unsigned long)src, len, false, _RET_IP_);
118-
check_memory_region((unsigned long)dest, len, true, _RET_IP_);
118+
if (!check_memory_region((unsigned long)src, len, false, _RET_IP_) ||
119+
!check_memory_region((unsigned long)dest, len, true, _RET_IP_))
120+
return NULL;
119121

120122
return __memmove(dest, src, len);
121123
}
@@ -124,8 +126,9 @@ void *memmove(void *dest, const void *src, size_t len)
124126
#undef memcpy
125127
void *memcpy(void *dest, const void *src, size_t len)
126128
{
127-
check_memory_region((unsigned long)src, len, false, _RET_IP_);
128-
check_memory_region((unsigned long)dest, len, true, _RET_IP_);
129+
if (!check_memory_region((unsigned long)src, len, false, _RET_IP_) ||
130+
!check_memory_region((unsigned long)dest, len, true, _RET_IP_))
131+
return NULL;
129132

130133
return __memcpy(dest, src, len);
131134
}
@@ -634,12 +637,21 @@ void kasan_free_shadow(const struct vm_struct *vm)
634637
#endif
635638

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

638-
void kasan_report(unsigned long addr, size_t size, bool is_write, unsigned long ip)
642+
bool kasan_report(unsigned long addr, size_t size, bool is_write, unsigned long ip)
639643
{
640644
unsigned long flags = user_access_save();
641-
__kasan_report(addr, size, is_write, ip);
645+
bool ret = false;
646+
647+
if (likely(report_enabled())) {
648+
__kasan_report(addr, size, is_write, ip);
649+
ret = true;
650+
}
651+
642652
user_access_restore(flags);
653+
654+
return ret;
643655
}
644656

645657
#ifdef CONFIG_MEMORY_HOTPLUG

mm/kasan/generic.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,17 +173,18 @@ static __always_inline bool check_memory_region_inline(unsigned long addr,
173173
if (unlikely(size == 0))
174174
return true;
175175

176+
if (unlikely(addr + size < addr))
177+
return !kasan_report(addr, size, write, ret_ip);
178+
176179
if (unlikely((void *)addr <
177180
kasan_shadow_to_mem((void *)KASAN_SHADOW_START))) {
178-
kasan_report(addr, size, write, ret_ip);
179-
return false;
181+
return !kasan_report(addr, size, write, ret_ip);
180182
}
181183

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

185-
kasan_report(addr, size, write, ret_ip);
186-
return false;
187+
return !kasan_report(addr, size, write, ret_ip);
187188
}
188189

189190
bool check_memory_region(unsigned long addr, size_t size, bool write,

mm/kasan/generic_report.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,17 @@ static const char *get_wild_bug_type(struct kasan_access_info *info)
110110

111111
const char *get_bug_type(struct kasan_access_info *info)
112112
{
113+
/*
114+
* If access_size is a negative number, then it has reason to be
115+
* defined as out-of-bounds bug type.
116+
*
117+
* Casting negative numbers to size_t would indeed turn up as
118+
* a large size_t and its value will be larger than ULONG_MAX/2,
119+
* so that this can qualify as out-of-bounds.
120+
*/
121+
if (info->access_addr + info->access_size < info->access_addr)
122+
return "out-of-bounds";
123+
113124
if (addr_has_shadow(info->access_addr))
114125
return get_shadow_bug_type(info);
115126
return get_wild_bug_type(info);

mm/kasan/kasan.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ bool check_memory_region(unsigned long addr, size_t size, bool write,
153153
void *find_first_bad_addr(void *addr, size_t size);
154154
const char *get_bug_type(struct kasan_access_info *info);
155155

156-
void kasan_report(unsigned long addr, size_t size,
156+
bool kasan_report(unsigned long addr, size_t size,
157157
bool is_write, unsigned long ip);
158158
void kasan_report_invalid_free(void *object, unsigned long ip);
159159

mm/kasan/report.c

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ static void print_shadow_for_address(const void *addr)
446446
}
447447
}
448448

449-
static bool report_enabled(void)
449+
bool report_enabled(void)
450450
{
451451
if (current->kasan_depth)
452452
return false;
@@ -478,9 +478,6 @@ void __kasan_report(unsigned long addr, size_t size, bool is_write, unsigned lon
478478
void *untagged_addr;
479479
unsigned long flags;
480480

481-
if (likely(!report_enabled()))
482-
return;
483-
484481
disable_trace_on_warning();
485482

486483
tagged_addr = (void *)addr;

mm/kasan/tags.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ bool check_memory_region(unsigned long addr, size_t size, bool write,
8686
if (unlikely(size == 0))
8787
return true;
8888

89+
if (unlikely(addr + size < addr))
90+
return !kasan_report(addr, size, write, ret_ip);
91+
8992
tag = get_tag((const void *)addr);
9093

9194
/*
@@ -111,15 +114,13 @@ bool check_memory_region(unsigned long addr, size_t size, bool write,
111114
untagged_addr = reset_tag((const void *)addr);
112115
if (unlikely(untagged_addr <
113116
kasan_shadow_to_mem((void *)KASAN_SHADOW_START))) {
114-
kasan_report(addr, size, write, ret_ip);
115-
return false;
117+
return !kasan_report(addr, size, write, ret_ip);
116118
}
117119
shadow_first = kasan_mem_to_shadow(untagged_addr);
118120
shadow_last = kasan_mem_to_shadow(untagged_addr + size - 1);
119121
for (shadow = shadow_first; shadow <= shadow_last; shadow++) {
120122
if (*shadow != tag) {
121-
kasan_report(addr, size, write, ret_ip);
122-
return false;
123+
return !kasan_report(addr, size, write, ret_ip);
123124
}
124125
}
125126

mm/kasan/tags_report.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,17 @@ const char *get_bug_type(struct kasan_access_info *info)
6060
}
6161

6262
#endif
63+
/*
64+
* If access_size is a negative number, then it has reason to be
65+
* defined as out-of-bounds bug type.
66+
*
67+
* Casting negative numbers to size_t would indeed turn up as
68+
* a large size_t and its value will be larger than ULONG_MAX/2,
69+
* so that this can qualify as out-of-bounds.
70+
*/
71+
if (info->access_addr + info->access_size < info->access_addr)
72+
return "out-of-bounds";
73+
6374
return "invalid-access";
6475
}
6576

0 commit comments

Comments
 (0)