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
