diff mbox series

[8/8] bpf: rework the compat kernel probe handling

Message ID c2869537-9090-8555-1506-37f6ac4684c9@amazon.com
State New
Headers show
Series Fix bpf: fix userspace access for bpf_probe_read{,str}() | expand

Commit Message

Zidenberg, Tsahi April 21, 2021, 1:14 p.m. UTC
commit 8d92db5c04d10381f4db70ed99b1b576f5db18a7 upstream

Instead of using the dangerous probe_kernel_read and strncpy_from_unsafe
helpers, rework the compat probes to check if an address is a kernel or
userspace one, and then use the low-level kernel or user probe helper
shared by the proper kernel and user probe helpers.  This slightly
changes behavior as the compat probe on a user address doesn't check
the lockdown flags, just as the pure user probes do.

conflict resolution: in kernel 5.4, these bpf_func_proto structs are
static.

Signed-off-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: Daniel Borkmann <daniel@iogearbox.net>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Link: http://lkml.kernel.org/r/20200521152301.2587579-14-hch@lst.de
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Cc: <stable@vger.kernel.org> # 5.4
Signed-off-by: Tsahi Zidenberg <tsahee@amazon.com>
---
 kernel/trace/bpf_trace.c | 109 ++++++++++++++++++++++++---------------
 1 file changed, 67 insertions(+), 42 deletions(-)
diff mbox series

Patch

diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 7b905aa800b2..ac07eeb4fa06 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -138,17 +138,23 @@  static const struct bpf_func_proto bpf_override_return_proto = {
 };
 #endif
 
-BPF_CALL_3(bpf_probe_read_user, void *, dst, u32, size,
-       const void __user *, unsafe_ptr)
+static __always_inline int
+bpf_probe_read_user_common(void *dst, u32 size, const void __user *unsafe_ptr)
 {
-    int ret = probe_user_read(dst, unsafe_ptr, size);
+    int ret;
 
+    ret = probe_user_read(dst, unsafe_ptr, size);
     if (unlikely(ret < 0))
         memset(dst, 0, size);
-
     return ret;
 }
 
+BPF_CALL_3(bpf_probe_read_user, void *, dst, u32, size,
+       const void __user *, unsafe_ptr)
+{
+    return bpf_probe_read_user_common(dst, size, unsafe_ptr);
+}
+
 static const struct bpf_func_proto bpf_probe_read_user_proto = {
     .func        = bpf_probe_read_user,
     .gpl_only    = true,
@@ -158,17 +164,24 @@  static const struct bpf_func_proto bpf_probe_read_user_proto = {
     .arg3_type    = ARG_ANYTHING,
 };
 
-BPF_CALL_3(bpf_probe_read_user_str, void *, dst, u32, size,
-       const void __user *, unsafe_ptr)
+static __always_inline int
+bpf_probe_read_user_str_common(void *dst, u32 size,
+                   const void __user *unsafe_ptr)
 {
-    int ret = strncpy_from_user_nofault(dst, unsafe_ptr, size);
+    int ret;
 
+    ret = strncpy_from_user_nofault(dst, unsafe_ptr, size);
     if (unlikely(ret < 0))
         memset(dst, 0, size);
-
     return ret;
 }
 
+BPF_CALL_3(bpf_probe_read_user_str, void *, dst, u32, size,
+       const void __user *, unsafe_ptr)
+{
+    return bpf_probe_read_user_str_common(dst, size, unsafe_ptr);
+}
+
 static const struct bpf_func_proto bpf_probe_read_user_str_proto = {
     .func        = bpf_probe_read_user_str,
     .gpl_only    = true,
@@ -179,25 +192,25 @@  static const struct bpf_func_proto bpf_probe_read_user_str_proto = {
 };
 
 static __always_inline int
-bpf_probe_read_kernel_common(void *dst, u32 size, const void *unsafe_ptr,
-                 const bool compat)
+bpf_probe_read_kernel_common(void *dst, u32 size, const void *unsafe_ptr)
 {
     int ret = security_locked_down(LOCKDOWN_BPF_READ);
 
     if (unlikely(ret < 0))
-        goto out;
-    ret = compat ? probe_kernel_read(dst, unsafe_ptr, size) :
-          probe_kernel_read_strict(dst, unsafe_ptr, size);
+        goto fail;
+    ret = probe_kernel_read_strict(dst, unsafe_ptr, size);
     if (unlikely(ret < 0))
-out:
-        memset(dst, 0, size);
+        goto fail;
+    return ret;
+fail:
+    memset(dst, 0, size);
     return ret;
 }
 
 BPF_CALL_3(bpf_probe_read_kernel, void *, dst, u32, size,
        const void *, unsafe_ptr)
 {
-    return bpf_probe_read_kernel_common(dst, size, unsafe_ptr, false);
+    return bpf_probe_read_kernel_common(dst, size, unsafe_ptr);
 }
 
 static const struct bpf_func_proto bpf_probe_read_kernel_proto = {
@@ -209,50 +222,37 @@  static const struct bpf_func_proto bpf_probe_read_kernel_proto = {
     .arg3_type    = ARG_ANYTHING,
 };
 
-BPF_CALL_3(bpf_probe_read_compat, void *, dst, u32, size,
-       const void *, unsafe_ptr)
-{
-    return bpf_probe_read_kernel_common(dst, size, unsafe_ptr, true);
-}
-
-static const struct bpf_func_proto bpf_probe_read_compat_proto = {
-    .func        = bpf_probe_read_compat,
-    .gpl_only    = true,
-    .ret_type    = RET_INTEGER,
-    .arg1_type    = ARG_PTR_TO_UNINIT_MEM,
-    .arg2_type    = ARG_CONST_SIZE_OR_ZERO,
-    .arg3_type    = ARG_ANYTHING,
-};
-
 static __always_inline int
-bpf_probe_read_kernel_str_common(void *dst, u32 size, const void *unsafe_ptr,
-                 const bool compat)
+bpf_probe_read_kernel_str_common(void *dst, u32 size, const void *unsafe_ptr)
 {
     int ret = security_locked_down(LOCKDOWN_BPF_READ);
 
     if (unlikely(ret < 0))
-        goto out;
+        goto fail;
+
     /*
-     * The strncpy_from_unsafe_*() call will likely not fill the entire
-     * buffer, but that's okay in this circumstance as we're probing
+     * The strncpy_from_kernel_nofault() call will likely not fill the
+     * entire buffer, but that's okay in this circumstance as we're probing
      * arbitrary memory anyway similar to bpf_probe_read_*() and might
      * as well probe the stack. Thus, memory is explicitly cleared
      * only in error case, so that improper users ignoring return
      * code altogether don't copy garbage; otherwise length of string
      * is returned that can be used for bpf_perf_event_output() et al.
      */
-    ret = compat ? strncpy_from_unsafe(dst, unsafe_ptr, size) :
-          strncpy_from_kernel_nofault(dst, unsafe_ptr, size);
+    ret = strncpy_from_kernel_nofault(dst, unsafe_ptr, size);
     if (unlikely(ret < 0))
-out:
-        memset(dst, 0, size);
+        goto fail;
+
+    return 0;
+fail:
+    memset(dst, 0, size);
     return ret;
 }
 
 BPF_CALL_3(bpf_probe_read_kernel_str, void *, dst, u32, size,
        const void *, unsafe_ptr)
 {
-    return bpf_probe_read_kernel_str_common(dst, size, unsafe_ptr, false);
+    return bpf_probe_read_kernel_str_common(dst, size, unsafe_ptr);
 }
 
 static const struct bpf_func_proto bpf_probe_read_kernel_str_proto = {
@@ -264,10 +264,34 @@  static const struct bpf_func_proto bpf_probe_read_kernel_str_proto = {
     .arg3_type    = ARG_ANYTHING,
 };
 
+#ifdef CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE
+BPF_CALL_3(bpf_probe_read_compat, void *, dst, u32, size,
+       const void *, unsafe_ptr)
+{
+    if ((unsigned long)unsafe_ptr < TASK_SIZE) {
+        return bpf_probe_read_user_common(dst, size,
+                (__force void __user *)unsafe_ptr);
+    }
+    return bpf_probe_read_kernel_common(dst, size, unsafe_ptr);
+}
+
+static const struct bpf_func_proto bpf_probe_read_compat_proto = {
+    .func        = bpf_probe_read_compat,
+    .gpl_only    = true,
+    .ret_type    = RET_INTEGER,
+    .arg1_type    = ARG_PTR_TO_UNINIT_MEM,
+    .arg2_type    = ARG_CONST_SIZE_OR_ZERO,
+    .arg3_type    = ARG_ANYTHING,
+};
+
 BPF_CALL_3(bpf_probe_read_compat_str, void *, dst, u32, size,
        const void *, unsafe_ptr)
 {
-    return bpf_probe_read_kernel_str_common(dst, size, unsafe_ptr, true);
+    if ((unsigned long)unsafe_ptr < TASK_SIZE) {
+        return bpf_probe_read_user_str_common(dst, size,
+                (__force void __user *)unsafe_ptr);
+    }
+    return bpf_probe_read_kernel_str_common(dst, size, unsafe_ptr);
 }
 
 static const struct bpf_func_proto bpf_probe_read_compat_str_proto = {
@@ -278,6 +302,7 @@  static const struct bpf_func_proto bpf_probe_read_compat_str_proto = {
     .arg2_type    = ARG_CONST_SIZE_OR_ZERO,
     .arg3_type    = ARG_ANYTHING,
 };
+#endif /* CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE */
 
 BPF_CALL_3(bpf_probe_write_user, void __user *, unsafe_ptr, const void *, src,
        u32, size)