[PATCH 5.15.y] mm: Fix copy_from_user_nofault().
Jakov Novak <[email protected]> Tue, 16 Jun 2026 19:40:10 +0200
| Newsgroups | dev.linux.lists.linux-kernel-mentees,org.kernel.vger.stable |
|---|---|
| Message-ID | <[email protected]> |
From: Alexei Starovoitov <[email protected]> [ Upstream commit d319f344561de23e810515d109c7278919bff7b0 ] There are several issues with copy_from_user_nofault(): - access_ok() is designed for user context only and for that reason it has WARN_ON_IN_IRQ() which triggers when bpf, kprobe, eprobe and perf on ppc are calling it from irq. - it's missing nmi_uaccess_okay() which is a nop on all architectures except x86 where it's required. The comment in arch/x86/mm/tlb.c explains the details why it's necessary. Calling copy_from_user_nofault() from bpf, [ke]probe without this check is not safe. - __copy_from_user_inatomic() under CONFIG_HARDENED_USERCOPY is calling check_object_size()->__check_object_size()->check_heap_object()->find_vmap_area()->spin_lock() which is not safe to do from bpf, [ke]probe and perf due to potential deadlock. Fix all three issues. At the end the copy_from_user_nofault() becomes equivalent to copy_from_user_nmi() from safety point of view with a difference in the return value. Reported-by: Hsin-Wei Hung <[email protected]> Signed-off-by: Alexei Starovoitov <[email protected]> Signed-off-by: Florian Lehner <[email protected]> Tested-by: Hsin-Wei Hung <[email protected]> Tested-by: Florian Lehner <[email protected]> Link: https://lore.kernel.org/r/[email protected] [ merge conflicts copy_from_user_nofault: caused by force_uaccess_begin and force_uaccess_end functions. moved the code around to call those functions in the same way after the additional checks. check_heap_object: completely different implementation in 5.15.y. removed any changes from this commit as the current implementation doesn't call the same lock which caused problems. ] Signed-off-by: Jakov Novak <[email protected]> --- Note: I have checked all syzbot issues linked with this backport and verified that they work locally (the ones with repros) and on the syzbot servers and I ran KUnit tests to make sure this doesn't break anything. This is my first time sending a patch to the stable repo, so I am a bit unsure if there is more to be done for testing, apart from inspecting the code and running those mentioned tests. mm/maccess.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/mm/maccess.c b/mm/maccess.c index ded4bfaba7f3..2b186ce822c9 100644 --- a/mm/maccess.c +++ b/mm/maccess.c @@ -5,6 +5,7 @@ #include <linux/export.h> #include <linux/mm.h> #include <linux/uaccess.h> +#include <asm/tlb.h> bool __weak copy_from_kernel_nofault_allowed(const void *unsafe_src, size_t size) @@ -221,13 +222,18 @@ long strncpy_from_kernel_nofault(char *dst, const void *unsafe_addr, long count) long copy_from_user_nofault(void *dst, const void __user *src, size_t size) { long ret = -EFAULT; - mm_segment_t old_fs = force_uaccess_begin(); + mm_segment_t old_fs; - if (access_ok(src, size)) { - pagefault_disable(); - ret = __copy_from_user_inatomic(dst, src, size); - pagefault_enable(); - } + if (!__access_ok(src, size)) + return ret; + + if (!nmi_uaccess_okay()) + return ret; + + old_fs = force_uaccess_begin(); + pagefault_disable(); + ret = __copy_from_user_inatomic(dst, src, size); + pagefault_enable(); force_uaccess_end(old_fs); if (ret) -- 2.54.0