Skip to content

Commit 54d46c9

Browse files
author
Alexei Starovoitov
committed
Merge branch 'fix-the-read-of-vsyscall-page-through-bpf'
Hou Tao says: ==================== Fix the read of vsyscall page through bpf From: Hou Tao <[email protected]> Hi, As reported by syzboot [1] and [2], when trying to read vsyscall page by using bpf_probe_read_kernel() or bpf_probe_read(), oops may happen. Thomas Gleixner had proposed a test patch [3], but it seems that no formal patch is posted after about one month [4], so I post it instead and add an Originally-by tag in patch #2. Patch #1 makes is_vsyscall_vaddr() being a common helper. Patch #2 fixes the problem by disallowing vsyscall page read for copy_from_kernel_nofault(). Patch #3 adds one test case to ensure the read of vsyscall page through bpf is rejected. Please see individual patches for more details. Comments are always welcome. [1]: https://lore.kernel.org/bpf/CAG48ez06TZft=ATH1qh2c5mpS5BT8UakwNkzi6nvK5_djC-4Nw@mail.gmail.com/ [2]: https://lore.kernel.org/bpf/CABOYnLynjBoFZOf3Z4BhaZkc5hx_kHfsjiW+UWLoB=w33LvScw@mail.gmail.com/ [3]: https://lore.kernel.org/bpf/87r0jwquhv.ffs@tglx/ [4]: https://lore.kernel.org/bpf/[email protected]/ Change Log: v3: * rephrase commit message for patch #1 & #2 (Sohil) * reword comments in copy_from_kernel_nofault_allowed() (Sohil) * add Rvb tag for patch #1 and Acked-by tag for patch #3 (Sohil, Yonghong) v2: https://lore.kernel.org/bpf/[email protected]/ * move is_vsyscall_vaddr to asm/vsyscall.h instead (Sohil) * elaborate on the reason for disallowing of vsyscall page read in copy_from_kernel_nofault_allowed() (Sohil) * update the commit message of patch #2 to more clearly explain how the oops occurs. (Sohil) * update the commit message of patch #3 to explain the expected return values of various bpf helpers (Yonghong) v1: https://lore.kernel.org/bpf/[email protected]/ ==================== Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
2 parents e37243b + be66d79 commit 54d46c9

File tree

5 files changed

+122
-9
lines changed

5 files changed

+122
-9
lines changed

arch/x86/include/asm/vsyscall.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <linux/seqlock.h>
66
#include <uapi/asm/vsyscall.h>
7+
#include <asm/page_types.h>
78

89
#ifdef CONFIG_X86_VSYSCALL_EMULATION
910
extern void map_vsyscall(void);
@@ -24,4 +25,13 @@ static inline bool emulate_vsyscall(unsigned long error_code,
2425
}
2526
#endif
2627

28+
/*
29+
* The (legacy) vsyscall page is the long page in the kernel portion
30+
* of the address space that has user-accessible permissions.
31+
*/
32+
static inline bool is_vsyscall_vaddr(unsigned long vaddr)
33+
{
34+
return unlikely((vaddr & PAGE_MASK) == VSYSCALL_ADDR);
35+
}
36+
2737
#endif /* _ASM_X86_VSYSCALL_H */

arch/x86/mm/fault.c

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -798,15 +798,6 @@ show_signal_msg(struct pt_regs *regs, unsigned long error_code,
798798
show_opcodes(regs, loglvl);
799799
}
800800

801-
/*
802-
* The (legacy) vsyscall page is the long page in the kernel portion
803-
* of the address space that has user-accessible permissions.
804-
*/
805-
static bool is_vsyscall_vaddr(unsigned long vaddr)
806-
{
807-
return unlikely((vaddr & PAGE_MASK) == VSYSCALL_ADDR);
808-
}
809-
810801
static void
811802
__bad_area_nosemaphore(struct pt_regs *regs, unsigned long error_code,
812803
unsigned long address, u32 pkey, int si_code)

arch/x86/mm/maccess.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#include <linux/uaccess.h>
44
#include <linux/kernel.h>
55

6+
#include <asm/vsyscall.h>
7+
68
#ifdef CONFIG_X86_64
79
bool copy_from_kernel_nofault_allowed(const void *unsafe_src, size_t size)
810
{
@@ -15,6 +17,14 @@ bool copy_from_kernel_nofault_allowed(const void *unsafe_src, size_t size)
1517
if (vaddr < TASK_SIZE_MAX + PAGE_SIZE)
1618
return false;
1719

20+
/*
21+
* Reading from the vsyscall page may cause an unhandled fault in
22+
* certain cases. Though it is at an address above TASK_SIZE_MAX, it is
23+
* usually considered as a user space address.
24+
*/
25+
if (is_vsyscall_vaddr(vaddr))
26+
return false;
27+
1828
/*
1929
* Allow everything during early boot before 'x86_virt_bits'
2030
* is initialized. Needed for instruction decoding in early
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (C) 2024. Huawei Technologies Co., Ltd */
3+
#include "test_progs.h"
4+
#include "read_vsyscall.skel.h"
5+
6+
#if defined(__x86_64__)
7+
/* For VSYSCALL_ADDR */
8+
#include <asm/vsyscall.h>
9+
#else
10+
/* To prevent build failure on non-x86 arch */
11+
#define VSYSCALL_ADDR 0UL
12+
#endif
13+
14+
struct read_ret_desc {
15+
const char *name;
16+
int ret;
17+
} all_read[] = {
18+
{ .name = "probe_read_kernel", .ret = -ERANGE },
19+
{ .name = "probe_read_kernel_str", .ret = -ERANGE },
20+
{ .name = "probe_read", .ret = -ERANGE },
21+
{ .name = "probe_read_str", .ret = -ERANGE },
22+
{ .name = "probe_read_user", .ret = -EFAULT },
23+
{ .name = "probe_read_user_str", .ret = -EFAULT },
24+
{ .name = "copy_from_user", .ret = -EFAULT },
25+
{ .name = "copy_from_user_task", .ret = -EFAULT },
26+
};
27+
28+
void test_read_vsyscall(void)
29+
{
30+
struct read_vsyscall *skel;
31+
unsigned int i;
32+
int err;
33+
34+
#if !defined(__x86_64__)
35+
test__skip();
36+
return;
37+
#endif
38+
skel = read_vsyscall__open_and_load();
39+
if (!ASSERT_OK_PTR(skel, "read_vsyscall open_load"))
40+
return;
41+
42+
skel->bss->target_pid = getpid();
43+
err = read_vsyscall__attach(skel);
44+
if (!ASSERT_EQ(err, 0, "read_vsyscall attach"))
45+
goto out;
46+
47+
/* userspace may don't have vsyscall page due to LEGACY_VSYSCALL_NONE,
48+
* but it doesn't affect the returned error codes.
49+
*/
50+
skel->bss->user_ptr = (void *)VSYSCALL_ADDR;
51+
usleep(1);
52+
53+
for (i = 0; i < ARRAY_SIZE(all_read); i++)
54+
ASSERT_EQ(skel->bss->read_ret[i], all_read[i].ret, all_read[i].name);
55+
out:
56+
read_vsyscall__destroy(skel);
57+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (C) 2024. Huawei Technologies Co., Ltd */
3+
#include <linux/types.h>
4+
#include <bpf/bpf_helpers.h>
5+
6+
#include "bpf_misc.h"
7+
8+
int target_pid = 0;
9+
void *user_ptr = 0;
10+
int read_ret[8];
11+
12+
char _license[] SEC("license") = "GPL";
13+
14+
SEC("fentry/" SYS_PREFIX "sys_nanosleep")
15+
int do_probe_read(void *ctx)
16+
{
17+
char buf[8];
18+
19+
if ((bpf_get_current_pid_tgid() >> 32) != target_pid)
20+
return 0;
21+
22+
read_ret[0] = bpf_probe_read_kernel(buf, sizeof(buf), user_ptr);
23+
read_ret[1] = bpf_probe_read_kernel_str(buf, sizeof(buf), user_ptr);
24+
read_ret[2] = bpf_probe_read(buf, sizeof(buf), user_ptr);
25+
read_ret[3] = bpf_probe_read_str(buf, sizeof(buf), user_ptr);
26+
read_ret[4] = bpf_probe_read_user(buf, sizeof(buf), user_ptr);
27+
read_ret[5] = bpf_probe_read_user_str(buf, sizeof(buf), user_ptr);
28+
29+
return 0;
30+
}
31+
32+
SEC("fentry.s/" SYS_PREFIX "sys_nanosleep")
33+
int do_copy_from_user(void *ctx)
34+
{
35+
char buf[8];
36+
37+
if ((bpf_get_current_pid_tgid() >> 32) != target_pid)
38+
return 0;
39+
40+
read_ret[6] = bpf_copy_from_user(buf, sizeof(buf), user_ptr);
41+
read_ret[7] = bpf_copy_from_user_task(buf, sizeof(buf), user_ptr,
42+
bpf_get_current_task_btf(), 0);
43+
44+
return 0;
45+
}

0 commit comments

Comments
 (0)