mm/kasan/kasan_test_c.c | 4 ++++ mm/maccess.c | 1 - 2 files changed, 4 insertions(+), 1 deletion(-)
`copy_to_kernel_nofault()` is an internal helper which should not be
visible to loadable modules – exporting it would give exploit code a
cheap oracle to probe kernel addresses. Instead, keep the helper
un-exported and compile the kunit case that exercises it only when
`mm/kasan/kasan_test.o` is linked into vmlinux.
Fixes: ca79a00bb9a8 ("kasan: migrate copy_user_test to kunit")
Suggested-by: Christoph Hellwig <hch@infradead.org>
Suggested-by: Marco Elver <elver@google.com>
Signed-off-by: Sabyrzhan Tasbolatov <snovitoll@gmail.com>
---
mm/kasan/kasan_test_c.c | 4 ++++
mm/maccess.c | 1 -
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/mm/kasan/kasan_test_c.c b/mm/kasan/kasan_test_c.c
index 5f922dd38ffa..094ecd27b707 100644
--- a/mm/kasan/kasan_test_c.c
+++ b/mm/kasan/kasan_test_c.c
@@ -1977,6 +1977,7 @@ static void rust_uaf(struct kunit *test)
KUNIT_EXPECT_KASAN_FAIL(test, kasan_test_rust_uaf());
}
+#ifndef MODULE
static void copy_to_kernel_nofault_oob(struct kunit *test)
{
char *ptr;
@@ -2011,6 +2012,7 @@ static void copy_to_kernel_nofault_oob(struct kunit *test)
kfree(ptr);
}
+#endif /* !MODULE */
static void copy_user_test_oob(struct kunit *test)
{
@@ -2131,7 +2133,9 @@ 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),
+#ifndef MODULE
KUNIT_CASE(copy_to_kernel_nofault_oob),
+#endif
KUNIT_CASE(rust_uaf),
KUNIT_CASE(copy_user_test_oob),
{}
diff --git a/mm/maccess.c b/mm/maccess.c
index 831b4dd7296c..486559d68858 100644
--- a/mm/maccess.c
+++ b/mm/maccess.c
@@ -82,7 +82,6 @@ long copy_to_kernel_nofault(void *dst, const void *src, size_t size)
pagefault_enable();
return -EFAULT;
}
-EXPORT_SYMBOL_GPL(copy_to_kernel_nofault);
long strncpy_from_kernel_nofault(char *dst, const void *unsafe_addr, long count)
{
--
2.34.1
On Sun, Jun 22, 2025 at 7:19 AM Sabyrzhan Tasbolatov
<snovitoll@gmail.com> wrote:
>
> `copy_to_kernel_nofault()` is an internal helper which should not be
> visible to loadable modules – exporting it would give exploit code a
> cheap oracle to probe kernel addresses. Instead, keep the helper
> un-exported and compile the kunit case that exercises it only when
> `mm/kasan/kasan_test.o` is linked into vmlinux.
>
> Fixes: ca79a00bb9a8 ("kasan: migrate copy_user_test to kunit")
> Suggested-by: Christoph Hellwig <hch@infradead.org>
> Suggested-by: Marco Elver <elver@google.com>
> Signed-off-by: Sabyrzhan Tasbolatov <snovitoll@gmail.com>
> ---
> mm/kasan/kasan_test_c.c | 4 ++++
> mm/maccess.c | 1 -
> 2 files changed, 4 insertions(+), 1 deletion(-)
>
> diff --git a/mm/kasan/kasan_test_c.c b/mm/kasan/kasan_test_c.c
> index 5f922dd38ffa..094ecd27b707 100644
> --- a/mm/kasan/kasan_test_c.c
> +++ b/mm/kasan/kasan_test_c.c
> @@ -1977,6 +1977,7 @@ static void rust_uaf(struct kunit *test)
> KUNIT_EXPECT_KASAN_FAIL(test, kasan_test_rust_uaf());
> }
>
> +#ifndef MODULE
Would be great to have a comment here explaining the ifndef.
> static void copy_to_kernel_nofault_oob(struct kunit *test)
> {
> char *ptr;
> @@ -2011,6 +2012,7 @@ static void copy_to_kernel_nofault_oob(struct kunit *test)
>
> kfree(ptr);
> }
> +#endif /* !MODULE */
>
> static void copy_user_test_oob(struct kunit *test)
> {
> @@ -2131,7 +2133,9 @@ 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),
> +#ifndef MODULE
> KUNIT_CASE(copy_to_kernel_nofault_oob),
> +#endif
> KUNIT_CASE(rust_uaf),
> KUNIT_CASE(copy_user_test_oob),
> {}
> diff --git a/mm/maccess.c b/mm/maccess.c
> index 831b4dd7296c..486559d68858 100644
> --- a/mm/maccess.c
> +++ b/mm/maccess.c
> @@ -82,7 +82,6 @@ long copy_to_kernel_nofault(void *dst, const void *src, size_t size)
> pagefault_enable();
> return -EFAULT;
> }
> -EXPORT_SYMBOL_GPL(copy_to_kernel_nofault);
>
> long strncpy_from_kernel_nofault(char *dst, const void *unsafe_addr, long count)
> {
> --
> 2.34.1
>
Other than that:
Reviewed-by: Andrey Konovalov <andreyknvl@gmail.com>
Thank you!
`copy_to_kernel_nofault()` is an internal helper which should not be
visible to loadable modules – exporting it would give exploit code a
cheap oracle to probe kernel addresses. Instead, keep the helper
un-exported and compile the kunit case that exercises it only when
`mm/kasan/kasan_test.o` is linked into vmlinux.
Fixes: ca79a00bb9a8 ("kasan: migrate copy_user_test to kunit")
Suggested-by: Christoph Hellwig <hch@infradead.org>
Suggested-by: Marco Elver <elver@google.com>
Reviewed-by: Andrey Konovalov <andreyknvl@gmail.com>
Signed-off-by: Sabyrzhan Tasbolatov <snovitoll@gmail.com>
---
Changes v2:
- add a brief comment to `#ifndef MODULE`
---
mm/kasan/kasan_test_c.c | 8 ++++++++
mm/maccess.c | 1 -
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/mm/kasan/kasan_test_c.c b/mm/kasan/kasan_test_c.c
index 5f922dd38ffa..2aa12dfa427a 100644
--- a/mm/kasan/kasan_test_c.c
+++ b/mm/kasan/kasan_test_c.c
@@ -1977,6 +1977,11 @@ static void rust_uaf(struct kunit *test)
KUNIT_EXPECT_KASAN_FAIL(test, kasan_test_rust_uaf());
}
+/*
+ * copy_to_kernel_nofault() is an internal helper available when
+ * kasan_test is built-in, so it must not be visible to loadable modules.
+ */
+#ifndef MODULE
static void copy_to_kernel_nofault_oob(struct kunit *test)
{
char *ptr;
@@ -2011,6 +2016,7 @@ static void copy_to_kernel_nofault_oob(struct kunit *test)
kfree(ptr);
}
+#endif /* !MODULE */
static void copy_user_test_oob(struct kunit *test)
{
@@ -2131,7 +2137,9 @@ 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),
+#ifndef MODULE
KUNIT_CASE(copy_to_kernel_nofault_oob),
+#endif
KUNIT_CASE(rust_uaf),
KUNIT_CASE(copy_user_test_oob),
{}
diff --git a/mm/maccess.c b/mm/maccess.c
index 831b4dd7296c..486559d68858 100644
--- a/mm/maccess.c
+++ b/mm/maccess.c
@@ -82,7 +82,6 @@ long copy_to_kernel_nofault(void *dst, const void *src, size_t size)
pagefault_enable();
return -EFAULT;
}
-EXPORT_SYMBOL_GPL(copy_to_kernel_nofault);
long strncpy_from_kernel_nofault(char *dst, const void *unsafe_addr, long count)
{
--
2.34.1
On 22.06.25 16:11, Sabyrzhan Tasbolatov wrote:
> `copy_to_kernel_nofault()` is an internal helper which should not be
> visible to loadable modules – exporting it would give exploit code a
> cheap oracle to probe kernel addresses. Instead, keep the helper
> un-exported and compile the kunit case that exercises it only when
> `mm/kasan/kasan_test.o` is linked into vmlinux.
>
> Fixes: ca79a00bb9a8 ("kasan: migrate copy_user_test to kunit")
> Suggested-by: Christoph Hellwig <hch@infradead.org>
> Suggested-by: Marco Elver <elver@google.com>
> Reviewed-by: Andrey Konovalov <andreyknvl@gmail.com>
> Signed-off-by: Sabyrzhan Tasbolatov <snovitoll@gmail.com>
> ---
Acked-by: David Hildenbrand <david@redhat.com>
--
Cheers,
David / dhildenb
On Sun, 22 Jun 2025 19:11:42 +0500 Sabyrzhan Tasbolatov <snovitoll@gmail.com> wrote:
> `copy_to_kernel_nofault()` is an internal helper which should not be
> visible to loadable modules – exporting it would give exploit code a
> cheap oracle to probe kernel addresses. Instead, keep the helper
> un-exported and compile the kunit case that exercises it only when
> `mm/kasan/kasan_test.o` is linked into vmlinux.
The recent 707f853d7fa3 ("module: Provide
EXPORT_SYMBOL_GPL_FOR_MODULES() helper") quietly added a thing which
might be useful here. As far as I understand it, this will permit us
to export copy_to_kernel_nofault to kasan_test_c.o and to nothing else.
"might". It depends on how "exploit code" might get hold of the
symbol. Perhaps you/we can discuss this further. Is the problem that
copy_to_kernel_nofault() is non-static? Or it the problem that
"exploit code" is itself a kernel module?
In other words, a fuller investigation of how this export presently benefits
exploiters would help us understand how much
EXPORT_SYMBOL_GPL_FOR_MODULES() will improve the situation.
On Sun, Jun 22, 2025 at 11:20 PM Andrew Morton
<akpm@linux-foundation.org> wrote:
>
> On Sun, 22 Jun 2025 19:11:42 +0500 Sabyrzhan Tasbolatov <snovitoll@gmail.com> wrote:
>
> > `copy_to_kernel_nofault()` is an internal helper which should not be
> > visible to loadable modules – exporting it would give exploit code a
> > cheap oracle to probe kernel addresses. Instead, keep the helper
> > un-exported and compile the kunit case that exercises it only when
> > `mm/kasan/kasan_test.o` is linked into vmlinux.
>
> The recent 707f853d7fa3 ("module: Provide
> EXPORT_SYMBOL_GPL_FOR_MODULES() helper") quietly added a thing which
> might be useful here. As far as I understand it, this will permit us
> to export copy_to_kernel_nofault to kasan_test_c.o and to nothing else.
Thanks for letting me know about this new method.
I believe, the usage for our case is:
EXPORT_SYMBOL_GPL_FOR_MODULES(copy_to_kernel_nofault, "kasan_test");
>
> "might". It depends on how "exploit code" might get hold of the
> symbol. Perhaps you/we can discuss this further. Is the problem that
> copy_to_kernel_nofault() is non-static? Or it the problem that
> "exploit code" is itself a kernel module?
I haven't verified this, but theoretically, it's a handy
“write-anywhere-safely” ROP gadget.
Assume the attacker has already gained an arbitrary RW primitive
via a UAF/OOB bug. Instead of stitching together
prepare_kernel_cred() + commit_creds(), which is a common path
of using exported symbols to achieve privilege escalation.
This path needs two symbols and register juggling.
With exported copy_to_kernel_nofault() they can do this:
/* Pseudocode of exploit for a ROP stage running in kernel context */
struct cred *cred = leaked_pointer;
rop_call(copy_to_kernel_nofault, &cred->uid, &zero, 4)
copy_to_kernel_nofault() disables page-faults around the write,
so even if cred corupts a guard-page, the write will not crash.
>
> In other words, a fuller investigation of how this export presently benefits
> exploiters would help us understand how much
> EXPORT_SYMBOL_GPL_FOR_MODULES() will improve the situation.
>
Please let me know if I should send v3 with using
EXPORT_SYMBOL_GPL_FOR_MODULES(copy_to_kernel_nofault, "kasan_test");
On Sun, Jun 22, 2025 at 9:09 PM Sabyrzhan Tasbolatov <snovitoll@gmail.com> wrote: > > I haven't verified this, but theoretically, it's a handy > “write-anywhere-safely” ROP gadget. > Assume the attacker has already gained an arbitrary RW primitive > via a UAF/OOB bug. Instead of stitching together > prepare_kernel_cred() + commit_creds(), which is a common path > of using exported symbols to achieve privilege escalation. > This path needs two symbols and register juggling. > With exported copy_to_kernel_nofault() they can do this: > > /* Pseudocode of exploit for a ROP stage running in kernel context */ > struct cred *cred = leaked_pointer; > rop_call(copy_to_kernel_nofault, &cred->uid, &zero, 4) > > copy_to_kernel_nofault() disables page-faults around the write, > so even if cred corupts a guard-page, the write will not crash. Attacker can use copy_to_kernel_nofault without it being exported as well. So I'd say this patch is more of a clean-up of exports.
On Sun, Jun 22, 2025, at 16:11, Sabyrzhan Tasbolatov wrote:
> `copy_to_kernel_nofault()` is an internal helper which should not be
> visible to loadable modules – exporting it would give exploit code a
> cheap oracle to probe kernel addresses. Instead, keep the helper
> un-exported and compile the kunit case that exercises it only when
> `mm/kasan/kasan_test.o` is linked into vmlinux.
>
> Fixes: ca79a00bb9a8 ("kasan: migrate copy_user_test to kunit")
> Suggested-by: Christoph Hellwig <hch@infradead.org>
> Suggested-by: Marco Elver <elver@google.com>
> Reviewed-by: Andrey Konovalov <andreyknvl@gmail.com>
> Signed-off-by: Sabyrzhan Tasbolatov <snovitoll@gmail.com>
Acked-by: Arnd Bergmann <arnd@arndb.de>
© 2016 - 2026 Red Hat, Inc.