1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <vmlinux.h> |
3 | #include <bpf/bpf_helpers.h> |
4 | #include <bpf/bpf_tracing.h> |
5 | |
6 | const volatile struct { |
7 | /* thread to activate trace programs for */ |
8 | pid_t tgid; |
9 | /* return error from __init function */ |
10 | int inject_error; |
11 | /* uffd monitored range start address */ |
12 | void *fault_addr; |
13 | } bpf_mod_race_config = { -1 }; |
14 | |
15 | int bpf_blocking = 0; |
16 | int res_try_get_module = -1; |
17 | |
18 | static __always_inline bool check_thread_id(void) |
19 | { |
20 | struct task_struct *task = bpf_get_current_task_btf(); |
21 | |
22 | return task->tgid == bpf_mod_race_config.tgid; |
23 | } |
24 | |
25 | /* The trace of execution is something like this: |
26 | * |
27 | * finit_module() |
28 | * load_module() |
29 | * prepare_coming_module() |
30 | * notifier_call(MODULE_STATE_COMING) |
31 | * btf_parse_module() |
32 | * btf_alloc_id() // Visible to userspace at this point |
33 | * list_add(btf_mod->list, &btf_modules) |
34 | * do_init_module() |
35 | * freeinit = kmalloc() |
36 | * ret = mod->init() |
37 | * bpf_prog_widen_race() |
38 | * bpf_copy_from_user() |
39 | * ...<sleep>... |
40 | * if (ret < 0) |
41 | * ... |
42 | * free_module() |
43 | * return ret |
44 | * |
45 | * At this point, module loading thread is blocked, we now load the program: |
46 | * |
47 | * bpf_check |
48 | * add_kfunc_call/check_pseudo_btf_id |
49 | * btf_try_get_module |
50 | * try_get_module_live == false |
51 | * return -ENXIO |
52 | * |
53 | * Without the fix (try_get_module_live in btf_try_get_module): |
54 | * |
55 | * bpf_check |
56 | * add_kfunc_call/check_pseudo_btf_id |
57 | * btf_try_get_module |
58 | * try_get_module == true |
59 | * <store module reference in btf_kfunc_tab or used_btf array> |
60 | * ... |
61 | * return fd |
62 | * |
63 | * Now, if we inject an error in the blocked program, our module will be freed |
64 | * (going straight from MODULE_STATE_COMING to MODULE_STATE_GOING). |
65 | * Later, when bpf program is freed, it will try to module_put already freed |
66 | * module. This is why try_get_module_live returns false if mod->state is not |
67 | * MODULE_STATE_LIVE. |
68 | */ |
69 | |
70 | SEC("fmod_ret.s/bpf_fentry_test1" ) |
71 | int BPF_PROG(widen_race, int a, int ret) |
72 | { |
73 | char dst; |
74 | |
75 | if (!check_thread_id()) |
76 | return 0; |
77 | /* Indicate that we will attempt to block */ |
78 | bpf_blocking = 1; |
79 | bpf_copy_from_user(&dst, 1, bpf_mod_race_config.fault_addr); |
80 | return bpf_mod_race_config.inject_error; |
81 | } |
82 | |
83 | SEC("fexit/do_init_module" ) |
84 | int BPF_PROG(fexit_init_module, struct module *mod, int ret) |
85 | { |
86 | if (!check_thread_id()) |
87 | return 0; |
88 | /* Indicate that we finished blocking */ |
89 | bpf_blocking = 2; |
90 | return 0; |
91 | } |
92 | |
93 | SEC("fexit/btf_try_get_module" ) |
94 | int BPF_PROG(fexit_module_get, const struct btf *btf, struct module *mod) |
95 | { |
96 | res_try_get_module = !!mod; |
97 | return 0; |
98 | } |
99 | |
100 | char _license[] SEC("license" ) = "GPL" ; |
101 | |