Add KUnit coverage that kfree() warns and returns for ERR_PTR values while remaining silent for NULL and ZERO_SIZE_PTR.
Add an LKDTM test that verifies hardened usercopy rejects an ERR_PTR before attempting the copy. Assisted-by: LLM Signed-off-by: Karl Mehltretter <[email protected]> --- drivers/misc/lkdtm/usercopy.c | 27 +++++++++++++++++++++++++ lib/tests/slub_kunit.c | 18 +++++++++++++++++ tools/testing/selftests/lkdtm/tests.txt | 1 + 3 files changed, 46 insertions(+) diff --git a/drivers/misc/lkdtm/usercopy.c b/drivers/misc/lkdtm/usercopy.c index 67db57249a34..10751e48211f 100644 --- a/drivers/misc/lkdtm/usercopy.c +++ b/drivers/misc/lkdtm/usercopy.c @@ -4,6 +4,7 @@ * hardening. */ #include "lkdtm.h" +#include <linux/err.h> #include <linux/slab.h> #include <linux/highmem.h> #include <linux/vmalloc.h> @@ -273,6 +274,31 @@ static void do_usercopy_slab_whitelist(bool to_user) } /* Callable tests. */ +static void lkdtm_USERCOPY_ERR_PTR(void) +{ + unsigned long user_addr; + size_t size = unconst + 1; + + user_addr = vm_mmap(NULL, 0, PAGE_SIZE, + PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, 0); + if (user_addr >= TASK_SIZE) { + pr_warn("Failed to allocate user memory\n"); + return; + } + + pr_info("attempting bad one-byte copy_to_user() from ERR_PTR\n"); + if (copy_to_user((void __user *)user_addr, ERR_PTR(-EINVAL), size)) { + pr_warn("copy_to_user failed, but lacked Oops\n"); + goto free_user; + } + pr_err("FAIL: ERR_PTR usercopy not detected!\n"); + pr_expected_config_param(CONFIG_HARDENED_USERCOPY, "hardened_usercopy"); + +free_user: + vm_munmap(user_addr, PAGE_SIZE); +} + static void lkdtm_USERCOPY_SLAB_SIZE_TO(void) { do_usercopy_slab_size(true); @@ -439,6 +465,7 @@ void __exit lkdtm_usercopy_exit(void) } static struct crashtype crashtypes[] = { + CRASHTYPE(USERCOPY_ERR_PTR), CRASHTYPE(USERCOPY_SLAB_SIZE_TO), CRASHTYPE(USERCOPY_SLAB_SIZE_FROM), CRASHTYPE(USERCOPY_SLAB_WHITELIST_TO), diff --git a/lib/tests/slub_kunit.c b/lib/tests/slub_kunit.c index 923d8646bca1..079563387007 100644 --- a/lib/tests/slub_kunit.c +++ b/lib/tests/slub_kunit.c @@ -507,6 +507,23 @@ static void test_zero_size_alloc(struct kunit *test) kvfree(ZERO_SIZE_PTR); } +static void test_kfree_err_ptr(struct kunit *test) +{ + if (!IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + + kunit_warning_suppress(test) { + kfree(NULL); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 0); + + kfree(ZERO_SIZE_PTR); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 0); + + kfree(ERR_PTR(-EINVAL)); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); + } +} + static int test_init(struct kunit *test) { slab_errors = 0; @@ -532,6 +549,7 @@ static struct kunit_case test_cases[] = { KUNIT_CASE(test_leak_destroy), KUNIT_CASE(test_krealloc_redzone_zeroing), KUNIT_CASE(test_zero_size_alloc), + KUNIT_CASE(test_kfree_err_ptr), #ifdef CONFIG_PERF_EVENTS KUNIT_CASE_SLOW(test_kmalloc_nolock_and_friends_perf), #endif diff --git a/tools/testing/selftests/lkdtm/tests.txt b/tools/testing/selftests/lkdtm/tests.txt index bec57a02913a..d3e38c1c75cd 100644 --- a/tools/testing/selftests/lkdtm/tests.txt +++ b/tools/testing/selftests/lkdtm/tests.txt @@ -70,6 +70,7 @@ REFCOUNT_DEC_AND_TEST_SATURATED Saturation detected: still saturated REFCOUNT_SUB_AND_TEST_SATURATED Saturation detected: still saturated #REFCOUNT_TIMING timing only #ATOMIC_TIMING timing only +USERCOPY_ERR_PTR Kernel memory exposure attempt detected from ERR_PTR USERCOPY_SLAB_SIZE_TO USERCOPY_SLAB_SIZE_FROM USERCOPY_SLAB_WHITELIST_TO -- 2.53.0

