2024-01-26 11:55:06

by Hou Tao

[permalink] [raw]
Subject: [PATCH bpf v2 3/3] selftest/bpf: Test the read of vsyscall page under x86-64

From: Hou Tao <[email protected]>

Under x86-64, when using bpf_probe_read_kernel{_str}() or
bpf_probe_read{_str}() to read vsyscall page, the read may trigger oops,
so add one test case to ensure that the problem is fixed. Beside those
four bpf helpers mentioned above, testing the read of vsyscall page by
using bpf_probe_read_user{_str} and bpf_copy_from_user{_task}() as well.

The test case passes the address of vsyscall page to these six helpers
and checks whether the returned values are expected:

1) For bpf_probe_read_kernel{_str}()/bpf_probe_read{_str}(), the
expected return value is -ERANGE as shown below:

bpf_probe_read_kernel_common
copy_from_kernel_nofault
// false, return -ERANGE
copy_from_kernel_nofault_allowed

2) For bpf_probe_read_user{_str}(), the expected return value is -EFAULT
as show below:

bpf_probe_read_user_common
copy_from_user_nofault
// false, return -EFAULT
__access_ok

3) For bpf_copy_from_user(), the expected return value is -EFAULT:

// return -EFAULT
bpf_copy_from_user
copy_from_user
_copy_from_user
// return false
access_ok

4) For bpf_copy_from_user_task(), the expected return value is -EFAULT:

// return -EFAULT
bpf_copy_from_user_task
access_process_vm
// return 0
vma_lookup()
// return 0
expand_stack()

The occurrence of oops depends on the availability of CPU SMAP [1]
feature and there are three possible configurations of vsyscall page in
boot cmd-line: vsyscall={xonly|none|emulate}, so there are totally six
possible combinations. Under all these combinations, the running of the
test case succeeds.

[1]: https://en.wikipedia.org/wiki/Supervisor_Mode_Access_Prevention

Signed-off-by: Hou Tao <[email protected]>
---
.../selftests/bpf/prog_tests/read_vsyscall.c | 57 +++++++++++++++++++
.../selftests/bpf/progs/read_vsyscall.c | 45 +++++++++++++++
2 files changed, 102 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/read_vsyscall.c
create mode 100644 tools/testing/selftests/bpf/progs/read_vsyscall.c

diff --git a/tools/testing/selftests/bpf/prog_tests/read_vsyscall.c b/tools/testing/selftests/bpf/prog_tests/read_vsyscall.c
new file mode 100644
index 0000000000000..3405923fe4e65
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/read_vsyscall.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2024. Huawei Technologies Co., Ltd */
+#include "test_progs.h"
+#include "read_vsyscall.skel.h"
+
+#if defined(__x86_64__)
+/* For VSYSCALL_ADDR */
+#include <asm/vsyscall.h>
+#else
+/* To prevent build failure on non-x86 arch */
+#define VSYSCALL_ADDR 0UL
+#endif
+
+struct read_ret_desc {
+ const char *name;
+ int ret;
+} all_read[] = {
+ { .name = "probe_read_kernel", .ret = -ERANGE },
+ { .name = "probe_read_kernel_str", .ret = -ERANGE },
+ { .name = "probe_read", .ret = -ERANGE },
+ { .name = "probe_read_str", .ret = -ERANGE },
+ { .name = "probe_read_user", .ret = -EFAULT },
+ { .name = "probe_read_user_str", .ret = -EFAULT },
+ { .name = "copy_from_user", .ret = -EFAULT },
+ { .name = "copy_from_user_task", .ret = -EFAULT },
+};
+
+void test_read_vsyscall(void)
+{
+ struct read_vsyscall *skel;
+ unsigned int i;
+ int err;
+
+#if !defined(__x86_64__)
+ test__skip();
+ return;
+#endif
+ skel = read_vsyscall__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "read_vsyscall open_load"))
+ return;
+
+ skel->bss->target_pid = getpid();
+ err = read_vsyscall__attach(skel);
+ if (!ASSERT_EQ(err, 0, "read_vsyscall attach"))
+ goto out;
+
+ /* userspace may don't have vsyscall page due to LEGACY_VSYSCALL_NONE,
+ * but it doesn't affect the returned error codes.
+ */
+ skel->bss->user_ptr = (void *)VSYSCALL_ADDR;
+ usleep(1);
+
+ for (i = 0; i < ARRAY_SIZE(all_read); i++)
+ ASSERT_EQ(skel->bss->read_ret[i], all_read[i].ret, all_read[i].name);
+out:
+ read_vsyscall__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/read_vsyscall.c b/tools/testing/selftests/bpf/progs/read_vsyscall.c
new file mode 100644
index 0000000000000..986f96687ae15
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/read_vsyscall.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2024. Huawei Technologies Co., Ltd */
+#include <linux/types.h>
+#include <bpf/bpf_helpers.h>
+
+#include "bpf_misc.h"
+
+int target_pid = 0;
+void *user_ptr = 0;
+int read_ret[8];
+
+char _license[] SEC("license") = "GPL";
+
+SEC("fentry/" SYS_PREFIX "sys_nanosleep")
+int do_probe_read(void *ctx)
+{
+ char buf[8];
+
+ if ((bpf_get_current_pid_tgid() >> 32) != target_pid)
+ return 0;
+
+ read_ret[0] = bpf_probe_read_kernel(buf, sizeof(buf), user_ptr);
+ read_ret[1] = bpf_probe_read_kernel_str(buf, sizeof(buf), user_ptr);
+ read_ret[2] = bpf_probe_read(buf, sizeof(buf), user_ptr);
+ read_ret[3] = bpf_probe_read_str(buf, sizeof(buf), user_ptr);
+ read_ret[4] = bpf_probe_read_user(buf, sizeof(buf), user_ptr);
+ read_ret[5] = bpf_probe_read_user_str(buf, sizeof(buf), user_ptr);
+
+ return 0;
+}
+
+SEC("fentry.s/" SYS_PREFIX "sys_nanosleep")
+int do_copy_from_user(void *ctx)
+{
+ char buf[8];
+
+ if ((bpf_get_current_pid_tgid() >> 32) != target_pid)
+ return 0;
+
+ read_ret[6] = bpf_copy_from_user(buf, sizeof(buf), user_ptr);
+ read_ret[7] = bpf_copy_from_user_task(buf, sizeof(buf), user_ptr,
+ bpf_get_current_task_btf(), 0);
+
+ return 0;
+}
--
2.29.2



2024-01-26 19:37:10

by Yonghong Song

[permalink] [raw]
Subject: Re: [PATCH bpf v2 3/3] selftest/bpf: Test the read of vsyscall page under x86-64


On 1/26/24 3:54 AM, Hou Tao wrote:
> From: Hou Tao <[email protected]>
>
> Under x86-64, when using bpf_probe_read_kernel{_str}() or
> bpf_probe_read{_str}() to read vsyscall page, the read may trigger oops,
> so add one test case to ensure that the problem is fixed. Beside those
> four bpf helpers mentioned above, testing the read of vsyscall page by
> using bpf_probe_read_user{_str} and bpf_copy_from_user{_task}() as well.
>
> The test case passes the address of vsyscall page to these six helpers
> and checks whether the returned values are expected:
>
> 1) For bpf_probe_read_kernel{_str}()/bpf_probe_read{_str}(), the
> expected return value is -ERANGE as shown below:
>
> bpf_probe_read_kernel_common
> copy_from_kernel_nofault
> // false, return -ERANGE
> copy_from_kernel_nofault_allowed
>
> 2) For bpf_probe_read_user{_str}(), the expected return value is -EFAULT
> as show below:
>
> bpf_probe_read_user_common
> copy_from_user_nofault
> // false, return -EFAULT
> __access_ok
>
> 3) For bpf_copy_from_user(), the expected return value is -EFAULT:
>
> // return -EFAULT
> bpf_copy_from_user
> copy_from_user
> _copy_from_user
> // return false
> access_ok
>
> 4) For bpf_copy_from_user_task(), the expected return value is -EFAULT:
>
> // return -EFAULT
> bpf_copy_from_user_task
> access_process_vm
> // return 0
> vma_lookup()
> // return 0
> expand_stack()
>
> The occurrence of oops depends on the availability of CPU SMAP [1]
> feature and there are three possible configurations of vsyscall page in
> boot cmd-line: vsyscall={xonly|none|emulate}, so there are totally six
> possible combinations. Under all these combinations, the running of the
> test case succeeds.
>
> [1]: https://en.wikipedia.org/wiki/Supervisor_Mode_Access_Prevention
>
> Signed-off-by: Hou Tao <[email protected]>

The first two patches look good to me but I think it would be better
if x86 folks can ack on them. The selftest patch LGTM.

Acked-by: Yonghong Song <[email protected]>