mm, kasan, kmsan: instrument copy_from/to_kernel_nofault
authorSabyrzhan Tasbolatov <snovitoll@gmail.com>
Fri, 11 Oct 2024 03:53:10 +0000 (08:53 +0500)
committerAndrew Morton <akpm@linux-foundation.org>
Thu, 7 Nov 2024 04:11:14 +0000 (20:11 -0800)
Instrument copy_from_kernel_nofault() with KMSAN for uninitialized kernel
memory check and copy_to_kernel_nofault() with KASAN, KCSAN to detect the
memory corruption.

syzbot reported that bpf_probe_read_kernel() kernel helper triggered KASAN
report via kasan_check_range() which is not the expected behaviour as
copy_from_kernel_nofault() is meant to be a non-faulting helper.

Solution is, suggested by Marco Elver, to replace KASAN, KCSAN check in
copy_from_kernel_nofault() with KMSAN detection of copying uninitilaized
kernel memory.  In copy_to_kernel_nofault() we can retain
instrument_write() explicitly for the memory corruption instrumentation.

copy_to_kernel_nofault() is tested on x86_64 and arm64 with
CONFIG_KASAN_SW_TAGS.  On arm64 with CONFIG_KASAN_HW_TAGS, kunit test
currently fails.  Need more clarification on it.

[akpm@linux-foundation.org: fix comment layout, per checkpatch
Link: https://lore.kernel.org/linux-mm/CANpmjNMAVFzqnCZhEity9cjiqQ9CVN1X7qeeeAp_6yKjwKo8iw@mail.gmail.com/
Link: https://lkml.kernel.org/r/20241011035310.2982017-1-snovitoll@gmail.com
Signed-off-by: Sabyrzhan Tasbolatov <snovitoll@gmail.com>
Reviewed-by: Marco Elver <elver@google.com>
Reported-by: syzbot+61123a5daeb9f7454599@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=61123a5daeb9f7454599
Reported-by: Andrey Konovalov <andreyknvl@gmail.com>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=210505
Reviewed-by: Andrey Konovalov <andreyknvl@gmail.com> [KASAN]
Tested-by: Andrey Konovalov <andreyknvl@gmail.com> [KASAN]
Cc: Alexander Potapenko <glider@google.com>
Cc: Andrey Ryabinin <ryabinin.a.a@gmail.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Vincenzo Frascino <vincenzo.frascino@arm.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
mm/kasan/kasan_test_c.c
mm/kmsan/kmsan_test.c
mm/maccess.c

index d8fb281e439d56a58d4934d43f7166ecec69cb06..fe132ce3c2b34ea5137c1ba373a5d98e61a86de8 100644 (file)
@@ -1928,6 +1928,41 @@ static void rust_uaf(struct kunit *test)
        KUNIT_EXPECT_KASAN_FAIL(test, kasan_test_rust_uaf());
 }
 
+static void copy_to_kernel_nofault_oob(struct kunit *test)
+{
+       char *ptr;
+       char buf[128];
+       size_t size = sizeof(buf);
+
+       /*
+        * This test currently fails with the HW_TAGS mode. The reason is
+        * unknown and needs to be investigated.
+        */
+       KASAN_TEST_NEEDS_CONFIG_OFF(test, CONFIG_KASAN_HW_TAGS);
+
+       ptr = kmalloc(size - KASAN_GRANULE_SIZE, GFP_KERNEL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr);
+       OPTIMIZER_HIDE_VAR(ptr);
+
+       /*
+        * We test copy_to_kernel_nofault() to detect corrupted memory that is
+        * being written into the kernel. In contrast,
+        * copy_from_kernel_nofault() is primarily used in kernel helper
+        * functions where the source address might be random or uninitialized.
+        * Applying KASAN instrumentation to copy_from_kernel_nofault() could
+        * lead to false positives.  By focusing KASAN checks only on
+        * copy_to_kernel_nofault(), we ensure that only valid memory is
+        * written to the kernel, minimizing the risk of kernel corruption
+        * while avoiding false positives in the reverse case.
+        */
+       KUNIT_EXPECT_KASAN_FAIL(test,
+               copy_to_kernel_nofault(&buf[0], ptr, size));
+       KUNIT_EXPECT_KASAN_FAIL(test,
+               copy_to_kernel_nofault(ptr, &buf[0], size));
+
+       kfree(ptr);
+}
+
 static struct kunit_case kasan_kunit_test_cases[] = {
        KUNIT_CASE(kmalloc_oob_right),
        KUNIT_CASE(kmalloc_oob_left),
@@ -2000,6 +2035,7 @@ static struct kunit_case kasan_kunit_test_cases[] = {
        KUNIT_CASE(match_all_not_assigned),
        KUNIT_CASE(match_all_ptr_tag),
        KUNIT_CASE(match_all_mem_tag),
+       KUNIT_CASE(copy_to_kernel_nofault_oob),
        KUNIT_CASE(rust_uaf),
        {}
 };
index 13236d579ebaa22851a482c7e1cf3879a0ecf744..9733a22c46c1d1e0a1a2917902810524cbb65e38 100644 (file)
@@ -640,6 +640,22 @@ static void test_unpoison_memory(struct kunit *test)
        KUNIT_EXPECT_TRUE(test, report_matches(&expect));
 }
 
+static void test_copy_from_kernel_nofault(struct kunit *test)
+{
+       long ret;
+       char buf[4], src[4];
+       size_t size = sizeof(buf);
+
+       EXPECTATION_UNINIT_VALUE_FN(expect, "copy_from_kernel_nofault");
+       kunit_info(
+               test,
+               "testing copy_from_kernel_nofault with uninitialized memory\n");
+
+       ret = copy_from_kernel_nofault((char *)&buf[0], (char *)&src[0], size);
+       USE(ret);
+       KUNIT_EXPECT_TRUE(test, report_matches(&expect));
+}
+
 static struct kunit_case kmsan_test_cases[] = {
        KUNIT_CASE(test_uninit_kmalloc),
        KUNIT_CASE(test_init_kmalloc),
@@ -664,6 +680,7 @@ static struct kunit_case kmsan_test_cases[] = {
        KUNIT_CASE(test_long_origin_chain),
        KUNIT_CASE(test_stackdepot_roundtrip),
        KUNIT_CASE(test_unpoison_memory),
+       KUNIT_CASE(test_copy_from_kernel_nofault),
        {},
 };
 
index 518a25667323ee4edd1b54577c5f0598d05bd51c..3ca55ec63a6aa695cc06897c943957b95590f9cb 100644 (file)
@@ -13,9 +13,14 @@ bool __weak copy_from_kernel_nofault_allowed(const void *unsafe_src,
        return true;
 }
 
+/*
+ * The below only uses kmsan_check_memory() to ensure uninitialized kernel
+ * memory isn't leaked.
+ */
 #define copy_from_kernel_nofault_loop(dst, src, len, type, err_label)  \
        while (len >= sizeof(type)) {                                   \
-               __get_kernel_nofault(dst, src, type, err_label);                \
+               __get_kernel_nofault(dst, src, type, err_label);        \
+               kmsan_check_memory(src, sizeof(type));                  \
                dst += sizeof(type);                                    \
                src += sizeof(type);                                    \
                len -= sizeof(type);                                    \
@@ -49,7 +54,8 @@ EXPORT_SYMBOL_GPL(copy_from_kernel_nofault);
 
 #define copy_to_kernel_nofault_loop(dst, src, len, type, err_label)    \
        while (len >= sizeof(type)) {                                   \
-               __put_kernel_nofault(dst, src, type, err_label);                \
+               __put_kernel_nofault(dst, src, type, err_label);        \
+               instrument_write(dst, sizeof(type));                    \
                dst += sizeof(type);                                    \
                src += sizeof(type);                                    \
                len -= sizeof(type);                                    \