2023-03-06 11:14:12

by Alexander Potapenko

[permalink] [raw]
Subject: [PATCH 1/2] lib/stackdepot: kmsan: mark API outputs as initialized

KMSAN does not instrument stackdepot and may treat memory allocated by
it as uninitialized. This is not a problem for KMSAN itself, because its
functions calling stackdepot API are also not instrumented.
But other kernel features (e.g. netdev tracker) may access stack depot
from instrumented code, which will lead to false positives, unless we
explicitly mark stackdepot outputs as initialized.

Cc: Andrey Konovalov <[email protected]>
Cc: Marco Elver <[email protected]>
Suggested-by: Dmitry Vyukov <[email protected]>
Signed-off-by: Alexander Potapenko <[email protected]>
---
lib/stackdepot.c | 12 ++++++++++++
1 file changed, 12 insertions(+)

diff --git a/lib/stackdepot.c b/lib/stackdepot.c
index 036da8e295d19..2f5aa851834eb 100644
--- a/lib/stackdepot.c
+++ b/lib/stackdepot.c
@@ -17,6 +17,7 @@
#include <linux/gfp.h>
#include <linux/jhash.h>
#include <linux/kernel.h>
+#include <linux/kmsan.h>
#include <linux/mm.h>
#include <linux/mutex.h>
#include <linux/percpu.h>
@@ -306,6 +307,11 @@ depot_alloc_stack(unsigned long *entries, int size, u32 hash, void **prealloc)
stack->handle.extra = 0;
memcpy(stack->entries, entries, flex_array_size(stack, entries, size));
pool_offset += required_size;
+ /*
+ * Let KMSAN know the stored stack record is initialized. This shall
+ * prevent false positive reports if instrumented code accesses it.
+ */
+ kmsan_unpoison_memory(stack, required_size);

return stack;
}
@@ -465,6 +471,12 @@ unsigned int stack_depot_fetch(depot_stack_handle_t handle,
struct stack_record *stack;

*entries = NULL;
+ /*
+ * Let KMSAN know *entries is initialized. This shall prevent false
+ * positive reports if instrumented code accesses it.
+ */
+ kmsan_unpoison_memory(entries, sizeof(*entries));
+
if (!handle)
return 0;

--
2.40.0.rc0.216.gc4246ad0f0-goog



2023-03-06 11:14:16

by Alexander Potapenko

[permalink] [raw]
Subject: [PATCH 2/2] kmsan: add test_stackdepot_roundtrip

Ensure that KMSAN does not report false positives in instrumented callers
of stack_depot_save(), stack_depot_print(), and stack_depot_fetch().

Signed-off-by: Alexander Potapenko <[email protected]>
---
mm/kmsan/kmsan_test.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)

diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
index 7095d3fbb23ac..d9eb141c27aa4 100644
--- a/mm/kmsan/kmsan_test.c
+++ b/mm/kmsan/kmsan_test.c
@@ -551,6 +551,36 @@ static void test_long_origin_chain(struct kunit *test)
KUNIT_EXPECT_TRUE(test, report_matches(&expect));
}

+/*
+ * Test case: ensure that saving/restoring/printing stacks to/from stackdepot
+ * does not trigger errors.
+ *
+ * KMSAN uses stackdepot to store origin stack traces, that's why we do not
+ * instrument lib/stackdepot.c. Yet it must properly mark its outputs as
+ * initialized because other kernel features (e.g. netdev tracker) may also
+ * access stackdepot from instrumented code.
+ */
+static void test_stackdepot_roundtrip(struct kunit *test)
+{
+ unsigned long src_entries[16], *dst_entries;
+ unsigned int src_nentries, dst_nentries;
+ EXPECTATION_NO_REPORT(expect);
+ depot_stack_handle_t handle;
+
+ kunit_info(test, "testing stackdepot roundtrip (no reports)\n");
+
+ src_nentries =
+ stack_trace_save(src_entries, ARRAY_SIZE(src_entries), 1);
+ handle = stack_depot_save(src_entries, src_nentries, GFP_KERNEL);
+ stack_depot_print(handle);
+ dst_nentries = stack_depot_fetch(handle, &dst_entries);
+ KUNIT_EXPECT_TRUE(test, src_nentries == dst_nentries);
+
+ kmsan_check_memory((void *)dst_entries,
+ sizeof(*dst_entries) * dst_nentries);
+ KUNIT_EXPECT_TRUE(test, report_matches(&expect));
+}
+
static struct kunit_case kmsan_test_cases[] = {
KUNIT_CASE(test_uninit_kmalloc),
KUNIT_CASE(test_init_kmalloc),
@@ -573,6 +603,7 @@ static struct kunit_case kmsan_test_cases[] = {
KUNIT_CASE(test_memset32),
KUNIT_CASE(test_memset64),
KUNIT_CASE(test_long_origin_chain),
+ KUNIT_CASE(test_stackdepot_roundtrip),
{},
};

--
2.40.0.rc0.216.gc4246ad0f0-goog


2023-03-06 11:37:42

by Dmitry Vyukov

[permalink] [raw]
Subject: Re: [PATCH 1/2] lib/stackdepot: kmsan: mark API outputs as initialized

On Mon, 6 Mar 2023 at 12:13, Alexander Potapenko <[email protected]> wrote:
>
> KMSAN does not instrument stackdepot and may treat memory allocated by
> it as uninitialized. This is not a problem for KMSAN itself, because its
> functions calling stackdepot API are also not instrumented.
> But other kernel features (e.g. netdev tracker) may access stack depot
> from instrumented code, which will lead to false positives, unless we
> explicitly mark stackdepot outputs as initialized.
>
> Cc: Andrey Konovalov <[email protected]>
> Cc: Marco Elver <[email protected]>
> Suggested-by: Dmitry Vyukov <[email protected]>
> Signed-off-by: Alexander Potapenko <[email protected]>

Add:
Reported-by: syzbot <[email protected]>

Otherwise:
Reviewed-by: Dmitry Vyukov <[email protected]>


> ---
> lib/stackdepot.c | 12 ++++++++++++
> 1 file changed, 12 insertions(+)
>
> diff --git a/lib/stackdepot.c b/lib/stackdepot.c
> index 036da8e295d19..2f5aa851834eb 100644
> --- a/lib/stackdepot.c
> +++ b/lib/stackdepot.c
> @@ -17,6 +17,7 @@
> #include <linux/gfp.h>
> #include <linux/jhash.h>
> #include <linux/kernel.h>
> +#include <linux/kmsan.h>
> #include <linux/mm.h>
> #include <linux/mutex.h>
> #include <linux/percpu.h>
> @@ -306,6 +307,11 @@ depot_alloc_stack(unsigned long *entries, int size, u32 hash, void **prealloc)
> stack->handle.extra = 0;
> memcpy(stack->entries, entries, flex_array_size(stack, entries, size));
> pool_offset += required_size;
> + /*
> + * Let KMSAN know the stored stack record is initialized. This shall
> + * prevent false positive reports if instrumented code accesses it.
> + */
> + kmsan_unpoison_memory(stack, required_size);
>
> return stack;
> }
> @@ -465,6 +471,12 @@ unsigned int stack_depot_fetch(depot_stack_handle_t handle,
> struct stack_record *stack;
>
> *entries = NULL;
> + /*
> + * Let KMSAN know *entries is initialized. This shall prevent false
> + * positive reports if instrumented code accesses it.
> + */
> + kmsan_unpoison_memory(entries, sizeof(*entries));
> +
> if (!handle)
> return 0;
>
> --
> 2.40.0.rc0.216.gc4246ad0f0-goog
>

2023-03-10 23:50:29

by Andrey Konovalov

[permalink] [raw]
Subject: Re: [PATCH 1/2] lib/stackdepot: kmsan: mark API outputs as initialized

On Mon, Mar 6, 2023 at 12:13 PM Alexander Potapenko <[email protected]> wrote:
>
> KMSAN does not instrument stackdepot and may treat memory allocated by
> it as uninitialized. This is not a problem for KMSAN itself, because its
> functions calling stackdepot API are also not instrumented.
> But other kernel features (e.g. netdev tracker) may access stack depot
> from instrumented code, which will lead to false positives, unless we
> explicitly mark stackdepot outputs as initialized.
>
> Cc: Andrey Konovalov <[email protected]>
> Cc: Marco Elver <[email protected]>
> Suggested-by: Dmitry Vyukov <[email protected]>
> Signed-off-by: Alexander Potapenko <[email protected]>
> ---
> lib/stackdepot.c | 12 ++++++++++++
> 1 file changed, 12 insertions(+)
>
> diff --git a/lib/stackdepot.c b/lib/stackdepot.c
> index 036da8e295d19..2f5aa851834eb 100644
> --- a/lib/stackdepot.c
> +++ b/lib/stackdepot.c
> @@ -17,6 +17,7 @@
> #include <linux/gfp.h>
> #include <linux/jhash.h>
> #include <linux/kernel.h>
> +#include <linux/kmsan.h>
> #include <linux/mm.h>
> #include <linux/mutex.h>
> #include <linux/percpu.h>
> @@ -306,6 +307,11 @@ depot_alloc_stack(unsigned long *entries, int size, u32 hash, void **prealloc)
> stack->handle.extra = 0;
> memcpy(stack->entries, entries, flex_array_size(stack, entries, size));
> pool_offset += required_size;
> + /*
> + * Let KMSAN know the stored stack record is initialized. This shall
> + * prevent false positive reports if instrumented code accesses it.
> + */
> + kmsan_unpoison_memory(stack, required_size);
>
> return stack;
> }
> @@ -465,6 +471,12 @@ unsigned int stack_depot_fetch(depot_stack_handle_t handle,
> struct stack_record *stack;
>
> *entries = NULL;
> + /*
> + * Let KMSAN know *entries is initialized. This shall prevent false
> + * positive reports if instrumented code accesses it.
> + */
> + kmsan_unpoison_memory(entries, sizeof(*entries));
> +
> if (!handle)
> return 0;
>
> --
> 2.40.0.rc0.216.gc4246ad0f0-goog
>

Reviewed-by: Andrey Konovalov <[email protected]>