The user nofault helpers (copy_{from,to}_user_nofault(),
strncpy_from_user_nofault() and strnlen_user_nofault()) are meant to be
callable from any context, e.g. from BPF programs, kprobes or perf, which
may run on behalf of a kernel thread or of a task that has already gone
through exit_mm(). In such a context, there is no user memory to access,
and the right answer is simply to fail.

Since the previous commit, the uaccess helpers they use warn when
current->mm is NULL, e.g. bpf_probe_read_user_str() from a BPF program
attached to a function called by a kernel thread would now trip the
WARN_ON_ONCE() in strncpy_from_user().

Check for a NULL current->mm up front in these helpers, and fail without
warning and without attempting the access.

On x86, copy_from_user_nofault() was already refusing such accesses, as
nmi_uaccess_okay() returns false when current->mm is not the loaded mm.

For the same reason, make copy_to_user_iter_nofault() use
should_fail_usercopy() directly, leaving the NULL mm check to
copy_to_user_nofault().

Signed-off-by: James Houghton <[email protected]>
---
 lib/iov_iter.c |  2 +-
 mm/maccess.c   | 17 +++++++++++++++++
 2 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/lib/iov_iter.c b/lib/iov_iter.c
index 3d6ecb50d778..21e2ccf1e8f2 100644
--- a/lib/iov_iter.c
+++ b/lib/iov_iter.c
@@ -33,7 +33,7 @@ size_t copy_to_user_iter_nofault(void __user *iter_to, size_t 
progress,
 {
        ssize_t res;
 
-       if (uaccess_should_fail())
+       if (should_fail_usercopy())
                return len;
 
        from += progress;
diff --git a/mm/maccess.c b/mm/maccess.c
index 486559d68858..678bb6165b39 100644
--- a/mm/maccess.c
+++ b/mm/maccess.c
@@ -121,6 +121,14 @@ long copy_from_user_nofault(void *dst, const void __user 
*src, size_t size)
 {
        long ret = -EFAULT;
 
+       /*
+        * The user nofault helpers may be called from any context, including
+        * from tasks that have no user memory to access (e.g. kernel threads).
+        * Fail quietly instead of tripping the warning in the uaccess helpers.
+        */
+       if (!current->mm)
+               return ret;
+
        if (!__access_ok(src, size))
                return ret;
 
@@ -150,6 +158,9 @@ long copy_to_user_nofault(void __user *dst, const void 
*src, size_t size)
 {
        long ret = -EFAULT;
 
+       if (!current->mm)
+               return ret;
+
        if (access_ok(dst, size)) {
                pagefault_disable();
                ret = __copy_to_user_inatomic(dst, src, size);
@@ -188,6 +199,9 @@ long strncpy_from_user_nofault(char *dst, const void __user 
*unsafe_addr,
        if (unlikely(count <= 0))
                return 0;
 
+       if (!current->mm)
+               return -EFAULT;
+
        pagefault_disable();
        ret = strncpy_from_user(dst, unsafe_addr, count);
        pagefault_enable();
@@ -222,6 +236,9 @@ long strnlen_user_nofault(const void __user *unsafe_addr, 
long count)
 {
        int ret;
 
+       if (!current->mm)
+               return 0;
+
        pagefault_disable();
        ret = strnlen_user(unsafe_addr, count);
        pagefault_enable();
-- 
2.56.0.307.g5fe7bd680c-goog


Reply via email to