]>
Commit | Line | Data |
---|---|---|
084c7007 RG |
1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
2 | #include <linux/libbpf.h> | |
3 | ||
4 | #include "bpf-devices.h" | |
5 | #include "bpf-program.h" | |
6 | ||
7 | #define PASS_JUMP_OFF 4096 | |
8 | ||
9 | static int bpf_access_type(const char *acc) { | |
10 | int r = 0; | |
11 | ||
12 | assert(acc); | |
13 | ||
14 | for (; *acc; acc++) | |
15 | switch(*acc) { | |
16 | case 'r': | |
17 | r |= BPF_DEVCG_ACC_READ; | |
18 | break; | |
19 | case 'w': | |
20 | r |= BPF_DEVCG_ACC_WRITE; | |
21 | break; | |
22 | case 'm': | |
23 | r |= BPF_DEVCG_ACC_MKNOD; | |
24 | break; | |
25 | default: | |
26 | return -EINVAL; | |
27 | } | |
28 | ||
29 | return r; | |
30 | } | |
31 | ||
32 | int cgroup_bpf_whitelist_device(BPFProgram *prog, int type, int major, int minor, const char *acc) { | |
33 | struct bpf_insn insn[] = { | |
34 | BPF_JMP_IMM(BPF_JNE, BPF_REG_2, type, 6), /* compare device type */ | |
35 | BPF_MOV32_REG(BPF_REG_1, BPF_REG_3), /* calculate access type */ | |
36 | BPF_ALU32_IMM(BPF_AND, BPF_REG_1, 0), | |
37 | BPF_JMP_REG(BPF_JNE, BPF_REG_1, BPF_REG_3, 3), /* compare access type */ | |
38 | BPF_JMP_IMM(BPF_JNE, BPF_REG_4, major, 2), /* compare major */ | |
39 | BPF_JMP_IMM(BPF_JNE, BPF_REG_5, minor, 1), /* compare minor */ | |
40 | BPF_JMP_A(PASS_JUMP_OFF), /* jump to PASS */ | |
41 | }; | |
42 | int r, access; | |
43 | ||
44 | assert(prog); | |
45 | assert(acc); | |
46 | ||
47 | access = bpf_access_type(acc); | |
48 | if (access <= 0) | |
49 | return -EINVAL; | |
50 | ||
51 | insn[2].imm = access; | |
52 | ||
53 | r = bpf_program_add_instructions(prog, insn, ELEMENTSOF(insn)); | |
54 | if (r < 0) | |
55 | log_error_errno(r, "Extending device control BPF program failed: %m"); | |
56 | ||
57 | return r; | |
58 | } | |
59 | ||
60 | int cgroup_bpf_whitelist_major(BPFProgram *prog, int type, int major, const char *acc) { | |
61 | struct bpf_insn insn[] = { | |
62 | BPF_JMP_IMM(BPF_JNE, BPF_REG_2, type, 5), /* compare device type */ | |
63 | BPF_MOV32_REG(BPF_REG_1, BPF_REG_3), /* calculate access type */ | |
64 | BPF_ALU32_IMM(BPF_AND, BPF_REG_1, 0), | |
65 | BPF_JMP_REG(BPF_JNE, BPF_REG_1, BPF_REG_3, 2), /* compare access type */ | |
66 | BPF_JMP_IMM(BPF_JNE, BPF_REG_4, major, 1), /* compare major */ | |
67 | BPF_JMP_A(PASS_JUMP_OFF), /* jump to PASS */ | |
68 | }; | |
69 | int r, access; | |
70 | ||
71 | assert(prog); | |
72 | assert(acc); | |
73 | ||
74 | access = bpf_access_type(acc); | |
75 | if (access <= 0) | |
76 | return -EINVAL; | |
77 | ||
78 | insn[2].imm = access; | |
79 | ||
80 | r = bpf_program_add_instructions(prog, insn, ELEMENTSOF(insn)); | |
81 | if (r < 0) | |
82 | log_error_errno(r, "Extending device control BPF program failed: %m"); | |
8e8b5d2e LP |
83 | |
84 | return r; | |
85 | } | |
86 | ||
87 | int cgroup_bpf_whitelist_class(BPFProgram *prog, int type, const char *acc) { | |
88 | struct bpf_insn insn[] = { | |
89 | BPF_JMP_IMM(BPF_JNE, BPF_REG_2, type, 5), /* compare device type */ | |
90 | BPF_MOV32_REG(BPF_REG_1, BPF_REG_3), /* calculate access type */ | |
91 | BPF_ALU32_IMM(BPF_AND, BPF_REG_1, 0), | |
92 | BPF_JMP_REG(BPF_JNE, BPF_REG_1, BPF_REG_3, 1), /* compare access type */ | |
93 | BPF_JMP_A(PASS_JUMP_OFF), /* jump to PASS */ | |
94 | }; | |
95 | int r, access; | |
96 | ||
97 | assert(prog); | |
98 | assert(acc); | |
99 | ||
100 | access = bpf_access_type(acc); | |
101 | if (access <= 0) | |
102 | return -EINVAL; | |
103 | ||
104 | insn[2].imm = access; | |
105 | ||
106 | r = bpf_program_add_instructions(prog, insn, ELEMENTSOF(insn)); | |
107 | if (r < 0) | |
108 | log_error_errno(r, "Extending device control BPF program failed: %m"); | |
084c7007 RG |
109 | |
110 | return r; | |
111 | } | |
112 | ||
113 | int cgroup_init_device_bpf(BPFProgram **ret, CGroupDevicePolicy policy, bool whitelist) { | |
114 | struct bpf_insn pre_insn[] = { | |
115 | /* load device type to r2 */ | |
116 | BPF_LDX_MEM(BPF_H, BPF_REG_2, BPF_REG_1, | |
117 | offsetof(struct bpf_cgroup_dev_ctx, access_type)), | |
118 | ||
119 | /* load access type to r3 */ | |
120 | BPF_LDX_MEM(BPF_W, BPF_REG_3, BPF_REG_1, | |
121 | offsetof(struct bpf_cgroup_dev_ctx, access_type)), | |
122 | BPF_ALU32_IMM(BPF_RSH, BPF_REG_3, 16), | |
123 | ||
124 | /* load major number to r4 */ | |
125 | BPF_LDX_MEM(BPF_W, BPF_REG_4, BPF_REG_1, | |
126 | offsetof(struct bpf_cgroup_dev_ctx, major)), | |
127 | ||
128 | /* load minor number to r5 */ | |
129 | BPF_LDX_MEM(BPF_W, BPF_REG_5, BPF_REG_1, | |
130 | offsetof(struct bpf_cgroup_dev_ctx, minor)), | |
131 | }; | |
132 | ||
133 | _cleanup_(bpf_program_unrefp) BPFProgram *prog = NULL; | |
134 | int r; | |
135 | ||
136 | assert(ret); | |
137 | ||
138 | if (policy == CGROUP_AUTO && !whitelist) | |
139 | return 0; | |
140 | ||
141 | r = bpf_program_new(BPF_PROG_TYPE_CGROUP_DEVICE, &prog); | |
142 | if (r < 0) | |
143 | return log_error_errno(r, "Loading device control BPF program failed: %m"); | |
144 | ||
145 | if (policy == CGROUP_CLOSED || whitelist) { | |
146 | r = bpf_program_add_instructions(prog, pre_insn, ELEMENTSOF(pre_insn)); | |
147 | if (r < 0) | |
148 | return log_error_errno(r, "Extending device control BPF program failed: %m"); | |
149 | } | |
150 | ||
151 | *ret = TAKE_PTR(prog); | |
152 | ||
153 | return 0; | |
154 | } | |
155 | ||
156 | int cgroup_apply_device_bpf(Unit *u, BPFProgram *prog, CGroupDevicePolicy policy, bool whitelist) { | |
157 | struct bpf_insn post_insn[] = { | |
158 | /* return DENY */ | |
159 | BPF_MOV64_IMM(BPF_REG_0, 0), | |
160 | BPF_JMP_A(1), | |
161 | ||
162 | }; | |
163 | ||
164 | struct bpf_insn exit_insn[] = { | |
165 | /* else return ALLOW */ | |
166 | BPF_MOV64_IMM(BPF_REG_0, 1), | |
167 | BPF_EXIT_INSN() | |
168 | }; | |
169 | ||
170 | _cleanup_free_ char *path = NULL; | |
084c7007 RG |
171 | int r; |
172 | ||
173 | if (!prog) { | |
174 | /* Remove existing program. */ | |
175 | u->bpf_device_control_installed = bpf_program_unref(u->bpf_device_control_installed); | |
176 | return 0; | |
177 | } | |
178 | ||
179 | if (policy != CGROUP_STRICT || whitelist) { | |
180 | size_t off; | |
181 | ||
182 | r = bpf_program_add_instructions(prog, post_insn, ELEMENTSOF(post_insn)); | |
183 | if (r < 0) | |
184 | return log_error_errno(r, "Extending device control BPF program failed: %m"); | |
185 | ||
186 | /* Fixup PASS_JUMP_OFF jump offsets. */ | |
187 | for (off = 0; off < prog->n_instructions; off++) { | |
188 | struct bpf_insn *ins = &prog->instructions[off]; | |
189 | ||
190 | if (ins->code == (BPF_JMP | BPF_JA) && ins->off == PASS_JUMP_OFF) | |
191 | ins->off = prog->n_instructions - off - 1; | |
192 | } | |
193 | } else | |
194 | /* Explicitly forbid everything. */ | |
195 | exit_insn[0].imm = 0; | |
196 | ||
197 | r = bpf_program_add_instructions(prog, exit_insn, ELEMENTSOF(exit_insn)); | |
198 | if (r < 0) | |
199 | return log_error_errno(r, "Extending device control BPF program failed: %m"); | |
200 | ||
201 | r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, NULL, &path); | |
202 | if (r < 0) | |
203 | return log_error_errno(r, "Failed to determine cgroup path: %m"); | |
204 | ||
2af3eed1 | 205 | r = bpf_program_cgroup_attach(prog, BPF_CGROUP_DEVICE, path, BPF_F_ALLOW_MULTI); |
084c7007 RG |
206 | if (r < 0) |
207 | return log_error_errno(r, "Attaching device control BPF program to cgroup %s failed: %m", path); | |
208 | ||
2af3eed1 PH |
209 | /* Unref the old BPF program (which will implicitly detach it) right before attaching the new program. */ |
210 | u->bpf_device_control_installed = bpf_program_unref(u->bpf_device_control_installed); | |
211 | ||
084c7007 RG |
212 | /* Remember that this BPF program is installed now. */ |
213 | u->bpf_device_control_installed = bpf_program_ref(prog); | |
214 | ||
215 | return 0; | |
216 | } | |
217 | ||
218 | int bpf_devices_supported(void) { | |
219 | struct bpf_insn trivial[] = { | |
220 | BPF_MOV64_IMM(BPF_REG_0, 1), | |
221 | BPF_EXIT_INSN() | |
222 | }; | |
223 | ||
224 | _cleanup_(bpf_program_unrefp) BPFProgram *program = NULL; | |
225 | static int supported = -1; | |
226 | int r; | |
227 | ||
228 | /* Checks whether BPF device controller is supported. For this, we check five things: | |
229 | * | |
230 | * a) whether we are privileged | |
231 | * b) whether the unified hierarchy is being used | |
232 | * c) the BPF implementation in the kernel supports BPF_PROG_TYPE_CGROUP_DEVICE programs, which we require | |
233 | */ | |
234 | ||
235 | if (supported >= 0) | |
236 | return supported; | |
237 | ||
238 | if (geteuid() != 0) { | |
239 | log_debug("Not enough privileges, BPF device control is not supported."); | |
240 | return supported = 0; | |
241 | } | |
242 | ||
243 | r = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER); | |
244 | if (r < 0) | |
245 | return log_error_errno(r, "Can't determine whether the unified hierarchy is used: %m"); | |
246 | if (r == 0) { | |
247 | log_debug("Not running with unified cgroups, BPF device control is not supported."); | |
248 | return supported = 0; | |
249 | } | |
250 | ||
251 | r = bpf_program_new(BPF_PROG_TYPE_CGROUP_DEVICE, &program); | |
252 | if (r < 0) { | |
253 | log_debug_errno(r, "Can't allocate CGROUP DEVICE BPF program, BPF device control is not supported: %m"); | |
254 | return supported = 0; | |
255 | } | |
256 | ||
257 | r = bpf_program_add_instructions(program, trivial, ELEMENTSOF(trivial)); | |
258 | if (r < 0) { | |
259 | log_debug_errno(r, "Can't add trivial instructions to CGROUP DEVICE BPF program, BPF device control is not supported: %m"); | |
260 | return supported = 0; | |
261 | } | |
262 | ||
263 | r = bpf_program_load_kernel(program, NULL, 0); | |
264 | if (r < 0) { | |
265 | log_debug_errno(r, "Can't load kernel CGROUP DEVICE BPF program, BPF device control is not supported: %m"); | |
266 | return supported = 0; | |
267 | } | |
268 | ||
0b82cd25 | 269 | return supported = 1; |
084c7007 | 270 | } |