Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : : #include <linux/bpf_insn.h>
3 : :
4 : : #include "bpf-devices.h"
5 : : #include "bpf-program.h"
6 : :
7 : : #define PASS_JUMP_OFF 4096
8 : :
9 : 0 : static int bpf_access_type(const char *acc) {
10 : 0 : int r = 0;
11 : :
12 [ # # ]: 0 : assert(acc);
13 : :
14 [ # # ]: 0 : for (; *acc; acc++)
15 [ # # # # ]: 0 : switch(*acc) {
16 : 0 : case 'r':
17 : 0 : r |= BPF_DEVCG_ACC_READ;
18 : 0 : break;
19 : 0 : case 'w':
20 : 0 : r |= BPF_DEVCG_ACC_WRITE;
21 : 0 : break;
22 : 0 : case 'm':
23 : 0 : r |= BPF_DEVCG_ACC_MKNOD;
24 : 0 : break;
25 : 0 : default:
26 : 0 : return -EINVAL;
27 : : }
28 : :
29 : 0 : return r;
30 : : }
31 : :
32 : 0 : int cgroup_bpf_whitelist_device(BPFProgram *prog, int type, int major, int minor, const char *acc) {
33 : 0 : 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 [ # # ]: 0 : assert(prog);
45 [ # # ]: 0 : assert(acc);
46 : :
47 : 0 : access = bpf_access_type(acc);
48 [ # # ]: 0 : if (access <= 0)
49 : 0 : return -EINVAL;
50 : :
51 : 0 : insn[2].imm = access;
52 : :
53 : 0 : r = bpf_program_add_instructions(prog, insn, ELEMENTSOF(insn));
54 [ # # ]: 0 : if (r < 0)
55 [ # # ]: 0 : log_error_errno(r, "Extending device control BPF program failed: %m");
56 : :
57 : 0 : return r;
58 : : }
59 : :
60 : 0 : int cgroup_bpf_whitelist_major(BPFProgram *prog, int type, int major, const char *acc) {
61 : 0 : 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 [ # # ]: 0 : assert(prog);
72 [ # # ]: 0 : assert(acc);
73 : :
74 : 0 : access = bpf_access_type(acc);
75 [ # # ]: 0 : if (access <= 0)
76 : 0 : return -EINVAL;
77 : :
78 : 0 : insn[2].imm = access;
79 : :
80 : 0 : r = bpf_program_add_instructions(prog, insn, ELEMENTSOF(insn));
81 [ # # ]: 0 : if (r < 0)
82 [ # # ]: 0 : log_error_errno(r, "Extending device control BPF program failed: %m");
83 : :
84 : 0 : return r;
85 : : }
86 : :
87 : 0 : int cgroup_bpf_whitelist_class(BPFProgram *prog, int type, const char *acc) {
88 : 0 : 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 [ # # ]: 0 : assert(prog);
98 [ # # ]: 0 : assert(acc);
99 : :
100 : 0 : access = bpf_access_type(acc);
101 [ # # ]: 0 : if (access <= 0)
102 : 0 : return -EINVAL;
103 : :
104 : 0 : insn[2].imm = access;
105 : :
106 : 0 : r = bpf_program_add_instructions(prog, insn, ELEMENTSOF(insn));
107 [ # # ]: 0 : if (r < 0)
108 [ # # ]: 0 : log_error_errno(r, "Extending device control BPF program failed: %m");
109 : :
110 : 0 : return r;
111 : : }
112 : :
113 : 0 : int cgroup_init_device_bpf(BPFProgram **ret, CGroupDevicePolicy policy, bool whitelist) {
114 : 0 : 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 : 0 : _cleanup_(bpf_program_unrefp) BPFProgram *prog = NULL;
134 : : int r;
135 : :
136 [ # # ]: 0 : assert(ret);
137 : :
138 [ # # # # ]: 0 : if (policy == CGROUP_AUTO && !whitelist)
139 : 0 : return 0;
140 : :
141 : 0 : r = bpf_program_new(BPF_PROG_TYPE_CGROUP_DEVICE, &prog);
142 [ # # ]: 0 : if (r < 0)
143 [ # # ]: 0 : return log_error_errno(r, "Loading device control BPF program failed: %m");
144 : :
145 [ # # # # ]: 0 : if (policy == CGROUP_CLOSED || whitelist) {
146 : 0 : r = bpf_program_add_instructions(prog, pre_insn, ELEMENTSOF(pre_insn));
147 [ # # ]: 0 : if (r < 0)
148 [ # # ]: 0 : return log_error_errno(r, "Extending device control BPF program failed: %m");
149 : : }
150 : :
151 : 0 : *ret = TAKE_PTR(prog);
152 : :
153 : 0 : return 0;
154 : : }
155 : :
156 : 0 : int cgroup_apply_device_bpf(Unit *u, BPFProgram *prog, CGroupDevicePolicy policy, bool whitelist) {
157 : 0 : 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 : 0 : struct bpf_insn exit_insn[] = {
165 : : /* else return ALLOW */
166 : : BPF_MOV64_IMM(BPF_REG_0, 1),
167 : : BPF_EXIT_INSN()
168 : : };
169 : :
170 : 0 : _cleanup_free_ char *path = NULL;
171 : : int r;
172 : :
173 [ # # ]: 0 : if (!prog) {
174 : : /* Remove existing program. */
175 : 0 : u->bpf_device_control_installed = bpf_program_unref(u->bpf_device_control_installed);
176 : 0 : return 0;
177 : : }
178 : :
179 [ # # # # ]: 0 : if (policy != CGROUP_STRICT || whitelist) {
180 : : size_t off;
181 : :
182 : 0 : r = bpf_program_add_instructions(prog, post_insn, ELEMENTSOF(post_insn));
183 [ # # ]: 0 : if (r < 0)
184 [ # # ]: 0 : return log_error_errno(r, "Extending device control BPF program failed: %m");
185 : :
186 : : /* Fixup PASS_JUMP_OFF jump offsets. */
187 [ # # ]: 0 : for (off = 0; off < prog->n_instructions; off++) {
188 : 0 : struct bpf_insn *ins = &prog->instructions[off];
189 : :
190 [ # # # # ]: 0 : if (ins->code == (BPF_JMP | BPF_JA) && ins->off == PASS_JUMP_OFF)
191 : 0 : ins->off = prog->n_instructions - off - 1;
192 : : }
193 : : } else
194 : : /* Explicitly forbid everything. */
195 : 0 : exit_insn[0].imm = 0;
196 : :
197 : 0 : r = bpf_program_add_instructions(prog, exit_insn, ELEMENTSOF(exit_insn));
198 [ # # ]: 0 : if (r < 0)
199 [ # # ]: 0 : return log_error_errno(r, "Extending device control BPF program failed: %m");
200 : :
201 : 0 : r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, NULL, &path);
202 [ # # ]: 0 : if (r < 0)
203 [ # # ]: 0 : return log_error_errno(r, "Failed to determine cgroup path: %m");
204 : :
205 : 0 : r = bpf_program_cgroup_attach(prog, BPF_CGROUP_DEVICE, path, BPF_F_ALLOW_MULTI);
206 [ # # ]: 0 : if (r < 0)
207 [ # # ]: 0 : return log_error_errno(r, "Attaching device control BPF program to cgroup %s failed: %m", path);
208 : :
209 : : /* Unref the old BPF program (which will implicitly detach it) right before attaching the new program. */
210 : 0 : u->bpf_device_control_installed = bpf_program_unref(u->bpf_device_control_installed);
211 : :
212 : : /* Remember that this BPF program is installed now. */
213 : 0 : u->bpf_device_control_installed = bpf_program_ref(prog);
214 : :
215 : 0 : return 0;
216 : : }
217 : :
218 : 44 : int bpf_devices_supported(void) {
219 : 44 : struct bpf_insn trivial[] = {
220 : : BPF_MOV64_IMM(BPF_REG_0, 1),
221 : : BPF_EXIT_INSN()
222 : : };
223 : :
224 : 44 : _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 [ + + ]: 44 : if (supported >= 0)
236 : 24 : return supported;
237 : :
238 [ + - ]: 20 : if (geteuid() != 0) {
239 [ + + ]: 20 : log_debug("Not enough privileges, BPF device control is not supported.");
240 : 20 : return supported = 0;
241 : : }
242 : :
243 : 0 : r = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER);
244 [ # # ]: 0 : if (r < 0)
245 [ # # ]: 0 : return log_error_errno(r, "Can't determine whether the unified hierarchy is used: %m");
246 [ # # ]: 0 : if (r == 0) {
247 [ # # ]: 0 : log_debug("Not running with unified cgroups, BPF device control is not supported.");
248 : 0 : return supported = 0;
249 : : }
250 : :
251 : 0 : r = bpf_program_new(BPF_PROG_TYPE_CGROUP_DEVICE, &program);
252 [ # # ]: 0 : if (r < 0) {
253 [ # # ]: 0 : log_debug_errno(r, "Can't allocate CGROUP DEVICE BPF program, BPF device control is not supported: %m");
254 : 0 : return supported = 0;
255 : : }
256 : :
257 : 0 : r = bpf_program_add_instructions(program, trivial, ELEMENTSOF(trivial));
258 [ # # ]: 0 : if (r < 0) {
259 [ # # ]: 0 : log_debug_errno(r, "Can't add trivial instructions to CGROUP DEVICE BPF program, BPF device control is not supported: %m");
260 : 0 : return supported = 0;
261 : : }
262 : :
263 : 0 : r = bpf_program_load_kernel(program, NULL, 0);
264 [ # # ]: 0 : if (r < 0) {
265 [ # # ]: 0 : log_debug_errno(r, "Can't load kernel CGROUP DEVICE BPF program, BPF device control is not supported: %m");
266 : 0 : return supported = 0;
267 : : }
268 : :
269 : 0 : return supported = 1;
270 : : }
|