2019-05-20 18:18:17

by Marco Elver

[permalink] [raw]
Subject: [PATCH v2] mm/kasan: Print frame description for stack bugs

This adds support for printing stack frame description on invalid stack
accesses. The frame description is embedded by the compiler, which is
parsed and then pretty-printed.

Currently, we can only print the stack frame info for accesses to the
task's own stack, but not accesses to other tasks' stacks.

Example of what it looks like:

[ 17.924050] page dumped because: kasan: bad access detected
[ 17.924908]
[ 17.925153] addr ffff8880673ef98a is located in stack of task insmod/2008 at offset 106 in frame:
[ 17.926542] kasan_stack_oob+0x0/0xf5 [test_kasan]
[ 17.927932]
[ 17.928206] this frame has 2 objects:
[ 17.928783] [32, 36) 'i'
[ 17.928784] [96, 106) 'stack_array'
[ 17.929216]
[ 17.930031] Memory state around the buggy address:

Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=198435
Signed-off-by: Marco Elver <[email protected]>
---

Changes since V1:
- Fix types in printf (%zu -> %lu).
- Prefer 'unsigned long', to ensure offsets/addrs are pointer sized, as
emitted by ASAN instrumentation.

Change-Id: I4836cde103052991ac8871796a45b4c977c9e2e7
---
mm/kasan/kasan.h | 5 ++
mm/kasan/report.c | 163 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 168 insertions(+)

diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h
index 3ce956efa0cb..1979db4763e2 100644
--- a/mm/kasan/kasan.h
+++ b/mm/kasan/kasan.h
@@ -43,6 +43,11 @@

#define KASAN_ALLOCA_REDZONE_SIZE 32

+/*
+ * Stack frame marker (compiler ABI).
+ */
+#define KASAN_CURRENT_STACK_FRAME_MAGIC 0x41B58AB3
+
/* Don't break randconfig/all*config builds */
#ifndef KASAN_ABI_VERSION
#define KASAN_ABI_VERSION 1
diff --git a/mm/kasan/report.c b/mm/kasan/report.c
index 03a443579386..36e55956acaf 100644
--- a/mm/kasan/report.c
+++ b/mm/kasan/report.c
@@ -28,6 +28,7 @@
#include <linux/types.h>
#include <linux/kasan.h>
#include <linux/module.h>
+#include <linux/sched/task_stack.h>

#include <asm/sections.h>

@@ -181,6 +182,166 @@ static inline bool init_task_stack_addr(const void *addr)
sizeof(init_thread_union.stack));
}

+static bool __must_check tokenize_frame_descr(const char **frame_descr,
+ char *token, size_t max_tok_len,
+ unsigned long *value)
+{
+ const char *sep = strchr(*frame_descr, ' ');
+
+ if (sep == NULL)
+ sep = *frame_descr + strlen(*frame_descr);
+
+ if (token != NULL) {
+ const size_t tok_len = sep - *frame_descr;
+
+ if (tok_len + 1 > max_tok_len) {
+ pr_err("KASAN internal error: frame description too long: %s\n",
+ *frame_descr);
+ return false;
+ }
+
+ /* Copy token (+ 1 byte for '\0'). */
+ strlcpy(token, *frame_descr, tok_len + 1);
+ }
+
+ /* Advance frame_descr past separator. */
+ *frame_descr = sep + 1;
+
+ if (value != NULL && kstrtoul(token, 10, value)) {
+ pr_err("KASAN internal error: not a valid number: %s\n", token);
+ return false;
+ }
+
+ return true;
+}
+
+static void print_decoded_frame_descr(const char *frame_descr)
+{
+ /*
+ * We need to parse the following string:
+ * "n alloc_1 alloc_2 ... alloc_n"
+ * where alloc_i looks like
+ * "offset size len name"
+ * or "offset size len name:line".
+ */
+
+ char token[64];
+ unsigned long num_objects;
+
+ if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
+ &num_objects))
+ return;
+
+ pr_err("\n");
+ pr_err("this frame has %lu %s:\n", num_objects,
+ num_objects == 1 ? "object" : "objects");
+
+ while (num_objects--) {
+ unsigned long offset;
+ unsigned long size;
+
+ /* access offset */
+ if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
+ &offset))
+ return;
+ /* access size */
+ if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
+ &size))
+ return;
+ /* name length (unused) */
+ if (!tokenize_frame_descr(&frame_descr, NULL, 0, NULL))
+ return;
+ /* object name */
+ if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
+ NULL))
+ return;
+
+ /* Strip line number, if it exists. */
+ strreplace(token, ':', '\0');
+
+ /* Finally, print object information. */
+ pr_err(" [%lu, %lu) '%s'", offset, offset + size, token);
+ }
+}
+
+static bool __must_check get_address_stack_frame_info(const void *addr,
+ unsigned long *offset,
+ const char **frame_descr,
+ const void **frame_pc)
+{
+ unsigned long aligned_addr;
+ unsigned long mem_ptr;
+ const u8 *shadow_bottom;
+ const u8 *shadow_ptr;
+ const unsigned long *frame;
+
+ /*
+ * NOTE: We currently only support printing frame information for
+ * accesses to the task's own stack.
+ */
+ if (!object_is_on_stack(addr))
+ return false;
+
+ aligned_addr = round_down((unsigned long)addr, sizeof(long));
+ mem_ptr = round_down(aligned_addr, KASAN_SHADOW_SCALE_SIZE);
+ shadow_ptr = kasan_mem_to_shadow((void *)aligned_addr);
+ shadow_bottom = kasan_mem_to_shadow(end_of_stack(current));
+
+ while (shadow_ptr >= shadow_bottom && *shadow_ptr != KASAN_STACK_LEFT) {
+ shadow_ptr--;
+ mem_ptr -= KASAN_SHADOW_SCALE_SIZE;
+ }
+
+ while (shadow_ptr >= shadow_bottom && *shadow_ptr == KASAN_STACK_LEFT) {
+ shadow_ptr--;
+ mem_ptr -= KASAN_SHADOW_SCALE_SIZE;
+ }
+
+ if (shadow_ptr < shadow_bottom)
+ return false;
+
+ frame = (const unsigned long *)(mem_ptr + KASAN_SHADOW_SCALE_SIZE);
+ if (frame[0] != KASAN_CURRENT_STACK_FRAME_MAGIC) {
+ pr_err("KASAN internal error: frame info validation failed; invalid marker: %lu\n",
+ frame[0]);
+ return false;
+ }
+
+ *offset = (unsigned long)addr - (unsigned long)frame;
+ *frame_descr = (const char *)frame[1];
+ *frame_pc = (void *)frame[2];
+
+ return true;
+}
+
+static void print_address_stack_frame(const void *addr)
+{
+ unsigned long offset;
+ const char *frame_descr;
+ const void *frame_pc;
+
+ if (IS_ENABLED(CONFIG_KASAN_SW_TAGS))
+ return;
+
+ if (!get_address_stack_frame_info(addr, &offset, &frame_descr,
+ &frame_pc))
+ return;
+
+ /*
+ * get_address_stack_frame_info only returns true if the given addr is
+ * on the current task's stack.
+ */
+ pr_err("\n");
+ pr_err("addr %px is located in stack of task %s/%d at offset %lu in frame:\n",
+ addr, current->comm, task_pid_nr(current), offset);
+ pr_err(" %pS\n", frame_pc);
+
+ if (!frame_descr)
+ return;
+
+ print_decoded_frame_descr(frame_descr);
+}
+
static void print_address_description(void *addr)
{
struct page *page = addr_to_page(addr);
@@ -204,6 +365,8 @@ static void print_address_description(void *addr)
pr_err("The buggy address belongs to the page:\n");
dump_page(page, "kasan: bad access detected");
}
+
+ print_address_stack_frame(addr);
}

static bool row_is_guilty(const void *row, const void *guilty)
--
2.21.0.1020.gf2820cf01a-goog



2019-05-21 15:45:18

by Andrey Ryabinin

[permalink] [raw]
Subject: Re: [PATCH v2] mm/kasan: Print frame description for stack bugs



On 5/20/19 6:47 PM, Marco Elver wrote:

> +static void print_decoded_frame_descr(const char *frame_descr)
> +{
> + /*
> + * We need to parse the following string:
> + * "n alloc_1 alloc_2 ... alloc_n"
> + * where alloc_i looks like
> + * "offset size len name"
> + * or "offset size len name:line".
> + */
> +
> + char token[64];
> + unsigned long num_objects;
> +
> + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
> + &num_objects))
> + return;
> +
> + pr_err("\n");
> + pr_err("this frame has %lu %s:\n", num_objects,
> + num_objects == 1 ? "object" : "objects");
> +
> + while (num_objects--) {
> + unsigned long offset;
> + unsigned long size;
> +
> + /* access offset */
> + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
> + &offset))
> + return;
> + /* access size */
> + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
> + &size))
> + return;
> + /* name length (unused) */
> + if (!tokenize_frame_descr(&frame_descr, NULL, 0, NULL))
> + return;
> + /* object name */
> + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
> + NULL))
> + return;
> +
> + /* Strip line number, if it exists. */

Why?

> + strreplace(token, ':', '\0');
> +

...

> +
> + aligned_addr = round_down((unsigned long)addr, sizeof(long));
> + mem_ptr = round_down(aligned_addr, KASAN_SHADOW_SCALE_SIZE);
> + shadow_ptr = kasan_mem_to_shadow((void *)aligned_addr);
> + shadow_bottom = kasan_mem_to_shadow(end_of_stack(current));
> +
> + while (shadow_ptr >= shadow_bottom && *shadow_ptr != KASAN_STACK_LEFT) {
> + shadow_ptr--;
> + mem_ptr -= KASAN_SHADOW_SCALE_SIZE;
> + }
> +
> + while (shadow_ptr >= shadow_bottom && *shadow_ptr == KASAN_STACK_LEFT) {
> + shadow_ptr--;
> + mem_ptr -= KASAN_SHADOW_SCALE_SIZE;
> + }
> +

I suppose this won't work if stack grows up, which is fine because it grows up only on parisc arch.
But "BUILD_BUG_ON(IS_ENABLED(CONFIG_STACK_GROUWSUP))" somewhere wouldn't hurt.



2019-05-21 15:54:46

by Alexander Potapenko

[permalink] [raw]
Subject: Re: [PATCH v2] mm/kasan: Print frame description for stack bugs

On Tue, May 21, 2019 at 5:43 PM Andrey Ryabinin <[email protected]> wrote:
>
>
>
> On 5/20/19 6:47 PM, Marco Elver wrote:
>
> > +static void print_decoded_frame_descr(const char *frame_descr)
> > +{
> > + /*
> > + * We need to parse the following string:
> > + * "n alloc_1 alloc_2 ... alloc_n"
> > + * where alloc_i looks like
> > + * "offset size len name"
> > + * or "offset size len name:line".
> > + */
> > +
> > + char token[64];
> > + unsigned long num_objects;
> > +
> > + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
> > + &num_objects))
> > + return;
> > +
> > + pr_err("\n");
> > + pr_err("this frame has %lu %s:\n", num_objects,
> > + num_objects == 1 ? "object" : "objects");
> > +
> > + while (num_objects--) {
> > + unsigned long offset;
> > + unsigned long size;
> > +
> > + /* access offset */
> > + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
> > + &offset))
> > + return;
> > + /* access size */
> > + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
> > + &size))
> > + return;
> > + /* name length (unused) */
> > + if (!tokenize_frame_descr(&frame_descr, NULL, 0, NULL))
> > + return;
> > + /* object name */
> > + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
> > + NULL))
> > + return;
> > +
> > + /* Strip line number, if it exists. */
>
> Why?
>
> > + strreplace(token, ':', '\0');
> > +
>
> ...
>
> > +
> > + aligned_addr = round_down((unsigned long)addr, sizeof(long));
> > + mem_ptr = round_down(aligned_addr, KASAN_SHADOW_SCALE_SIZE);
> > + shadow_ptr = kasan_mem_to_shadow((void *)aligned_addr);
> > + shadow_bottom = kasan_mem_to_shadow(end_of_stack(current));
> > +
> > + while (shadow_ptr >= shadow_bottom && *shadow_ptr != KASAN_STACK_LEFT) {
> > + shadow_ptr--;
> > + mem_ptr -= KASAN_SHADOW_SCALE_SIZE;
> > + }
> > +
> > + while (shadow_ptr >= shadow_bottom && *shadow_ptr == KASAN_STACK_LEFT) {
> > + shadow_ptr--;
> > + mem_ptr -= KASAN_SHADOW_SCALE_SIZE;
> > + }
> > +
>
> I suppose this won't work if stack grows up, which is fine because it grows up only on parisc arch.
> But "BUILD_BUG_ON(IS_ENABLED(CONFIG_STACK_GROUWSUP))" somewhere wouldn't hurt.
Note that KASAN was broken on parisc from day 1 because of other
assumptions on the stack growth direction hardcoded into KASAN
(e.g. __kasan_unpoison_stack() and __asan_allocas_unpoison()).
So maybe this BUILD_BUG_ON can be added in a separate patch as it's
not specific to what Marco is doing here?
>
> --
> 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 [email protected].
> To post to this group, send email to [email protected].
> To view this discussion on the web visit https://groups.google.com/d/msgid/kasan-dev/ebec4325-f91b-b392-55ed-95dbd36bbb8e%40virtuozzo.com.
> For more options, visit https://groups.google.com/d/optout.



--
Alexander Potapenko
Software Engineer

Google Germany GmbH
Erika-Mann-Straße, 33
80636 München

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
Registergericht und -nummer: Hamburg, HRB 86891
Sitz der Gesellschaft: Hamburg

2019-05-21 16:11:00

by Marco Elver

[permalink] [raw]
Subject: Re: [PATCH v2] mm/kasan: Print frame description for stack bugs

On Tue, 21 May 2019 at 17:53, Alexander Potapenko <[email protected]> wrote:
>
> On Tue, May 21, 2019 at 5:43 PM Andrey Ryabinin <[email protected]> wrote:
> >
> > On 5/20/19 6:47 PM, Marco Elver wrote:
> >
> > > +static void print_decoded_frame_descr(const char *frame_descr)
> > > +{
> > > + /*
> > > + * We need to parse the following string:
> > > + * "n alloc_1 alloc_2 ... alloc_n"
> > > + * where alloc_i looks like
> > > + * "offset size len name"
> > > + * or "offset size len name:line".
> > > + */
> > > +
> > > + char token[64];
> > > + unsigned long num_objects;
> > > +
> > > + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
> > > + &num_objects))
> > > + return;
> > > +
> > > + pr_err("\n");
> > > + pr_err("this frame has %lu %s:\n", num_objects,
> > > + num_objects == 1 ? "object" : "objects");
> > > +
> > > + while (num_objects--) {
> > > + unsigned long offset;
> > > + unsigned long size;
> > > +
> > > + /* access offset */
> > > + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
> > > + &offset))
> > > + return;
> > > + /* access size */
> > > + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
> > > + &size))
> > > + return;
> > > + /* name length (unused) */
> > > + if (!tokenize_frame_descr(&frame_descr, NULL, 0, NULL))
> > > + return;
> > > + /* object name */
> > > + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
> > > + NULL))
> > > + return;
> > > +
> > > + /* Strip line number, if it exists. */
> >
> > Why?

The filename is not included, and I don't think it adds much in terms
of ability to debug; nor is the line number included with all
descriptions. I think, the added complexity of separating the line
number and parsing is not worthwhile here. Alternatively, I could not
pay attention to the line number at all, and leave it as is -- in that
case, some variable names will display as "foo:123".

> >
> > > + strreplace(token, ':', '\0');
> > > +
> >
> > ...
> >
> > > +
> > > + aligned_addr = round_down((unsigned long)addr, sizeof(long));
> > > + mem_ptr = round_down(aligned_addr, KASAN_SHADOW_SCALE_SIZE);
> > > + shadow_ptr = kasan_mem_to_shadow((void *)aligned_addr);
> > > + shadow_bottom = kasan_mem_to_shadow(end_of_stack(current));
> > > +
> > > + while (shadow_ptr >= shadow_bottom && *shadow_ptr != KASAN_STACK_LEFT) {
> > > + shadow_ptr--;
> > > + mem_ptr -= KASAN_SHADOW_SCALE_SIZE;
> > > + }
> > > +
> > > + while (shadow_ptr >= shadow_bottom && *shadow_ptr == KASAN_STACK_LEFT) {
> > > + shadow_ptr--;
> > > + mem_ptr -= KASAN_SHADOW_SCALE_SIZE;
> > > + }
> > > +
> >
> > I suppose this won't work if stack grows up, which is fine because it grows up only on parisc arch.
> > But "BUILD_BUG_ON(IS_ENABLED(CONFIG_STACK_GROUWSUP))" somewhere wouldn't hurt.
> Note that KASAN was broken on parisc from day 1 because of other
> assumptions on the stack growth direction hardcoded into KASAN
> (e.g. __kasan_unpoison_stack() and __asan_allocas_unpoison()).
> So maybe this BUILD_BUG_ON can be added in a separate patch as it's
> not specific to what Marco is doing here?

Happy to send a follow-up patch, or add here. Let me know what you prefer.

Thanks,
-- Marco

2019-05-21 18:10:27

by Andrey Ryabinin

[permalink] [raw]
Subject: Re: [PATCH v2] mm/kasan: Print frame description for stack bugs



On 5/21/19 7:07 PM, Marco Elver wrote:
> On Tue, 21 May 2019 at 17:53, Alexander Potapenko <[email protected]> wrote:
>>
>> On Tue, May 21, 2019 at 5:43 PM Andrey Ryabinin <[email protected]> wrote:
>>>
>>> On 5/20/19 6:47 PM, Marco Elver wrote:
>>>
>>>> +static void print_decoded_frame_descr(const char *frame_descr)
>>>> +{
>>>> + /*
>>>> + * We need to parse the following string:
>>>> + * "n alloc_1 alloc_2 ... alloc_n"
>>>> + * where alloc_i looks like
>>>> + * "offset size len name"
>>>> + * or "offset size len name:line".
>>>> + */
>>>> +
>>>> + char token[64];
>>>> + unsigned long num_objects;
>>>> +
>>>> + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
>>>> + &num_objects))
>>>> + return;
>>>> +
>>>> + pr_err("\n");
>>>> + pr_err("this frame has %lu %s:\n", num_objects,
>>>> + num_objects == 1 ? "object" : "objects");
>>>> +
>>>> + while (num_objects--) {
>>>> + unsigned long offset;
>>>> + unsigned long size;
>>>> +
>>>> + /* access offset */
>>>> + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
>>>> + &offset))
>>>> + return;
>>>> + /* access size */
>>>> + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
>>>> + &size))
>>>> + return;
>>>> + /* name length (unused) */
>>>> + if (!tokenize_frame_descr(&frame_descr, NULL, 0, NULL))
>>>> + return;
>>>> + /* object name */
>>>> + if (!tokenize_frame_descr(&frame_descr, token, sizeof(token),
>>>> + NULL))
>>>> + return;
>>>> +
>>>> + /* Strip line number, if it exists. */
>>>
>>> Why?
>
> The filename is not included, and I don't think it adds much in terms
> of ability to debug; nor is the line number included with all
> descriptions. I think, the added complexity of separating the line
> number and parsing is not worthwhile here. Alternatively, I could not
> pay attention to the line number at all, and leave it as is -- in that
> case, some variable names will display as "foo:123".
>

Either way is fine by me. But explain why in comment if you decide
to keep current code. Something like
/* Strip line number cause it's not very helpful. */


>>>
>>>> + strreplace(token, ':', '\0');
>>>> +
>>>
>>> ...
>>>
>>>> +
>>>> + aligned_addr = round_down((unsigned long)addr, sizeof(long));
>>>> + mem_ptr = round_down(aligned_addr, KASAN_SHADOW_SCALE_SIZE);
>>>> + shadow_ptr = kasan_mem_to_shadow((void *)aligned_addr);
>>>> + shadow_bottom = kasan_mem_to_shadow(end_of_stack(current));
>>>> +
>>>> + while (shadow_ptr >= shadow_bottom && *shadow_ptr != KASAN_STACK_LEFT) {
>>>> + shadow_ptr--;
>>>> + mem_ptr -= KASAN_SHADOW_SCALE_SIZE;
>>>> + }
>>>> +
>>>> + while (shadow_ptr >= shadow_bottom && *shadow_ptr == KASAN_STACK_LEFT) {
>>>> + shadow_ptr--;
>>>> + mem_ptr -= KASAN_SHADOW_SCALE_SIZE;
>>>> + }
>>>> +
>>>
>>> I suppose this won't work if stack grows up, which is fine because it grows up only on parisc arch.
>>> But "BUILD_BUG_ON(IS_ENABLED(CONFIG_STACK_GROUWSUP))" somewhere wouldn't hurt.
>> Note that KASAN was broken on parisc from day 1 because of other
>> assumptions on the stack growth direction hardcoded into KASAN
>> (e.g. __kasan_unpoison_stack() and __asan_allocas_unpoison()).

It's not broken, it doesn't exist.

>> So maybe this BUILD_BUG_ON can be added in a separate patch as it's
>> not specific to what Marco is doing here?
>

I think it's fine to add it in this patch because BUILD_BUG_ON() is just a hint for developers
that this particular function depends on growing down stack. So it's more a property of the function
rather than KASAN in general.

Other functions you mentioned can be marked with BUILD_BUG_ON()s as well, but not in this patch indeed.

> Happy to send a follow-up patch, or add here. Let me know what you prefer.
>

Send v3 please.