Coverage Report

Created: 2026-09-14 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/systemd/src/core/bpf-devices.c
Line
Count
Source
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3
#include <fnmatch.h>
4
#include <linux/bpf.h>
5
#include <linux/bpf_insn.h>
6
#include <sys/stat.h>
7
8
#include "alloc-util.h"
9
#include "bpf-devices.h"
10
#include "bpf-program.h"
11
#include "cgroup.h"
12
#include "devnum-util.h"
13
#include "fd-util.h"
14
#include "fileio.h"
15
#include "log.h"
16
#include "nulstr-util.h"
17
#include "parse-util.h"
18
#include "path-util.h"
19
#include "stat-util.h"
20
#include "string-util.h"
21
22
0
#define PASS_JUMP_OFF 4096
23
24
/* Ensure the high level flags we use and the low-level BPF flags exposed on the kernel are defined the same way */
25
assert_cc((unsigned) BPF_DEVCG_ACC_MKNOD == (unsigned) CGROUP_DEVICE_MKNOD);
26
assert_cc((unsigned) BPF_DEVCG_ACC_READ  == (unsigned) CGROUP_DEVICE_READ);
27
assert_cc((unsigned) BPF_DEVCG_ACC_WRITE == (unsigned) CGROUP_DEVICE_WRITE);
28
29
static int bpf_prog_allow_list_device(
30
                BPFProgram *prog,
31
                char type,
32
                unsigned major,
33
                unsigned minor,
34
0
                CGroupDevicePermissions p) {
35
36
0
        int r;
37
38
0
        assert(prog);
39
40
0
        log_trace("%s: %c %u:%u %s", __func__, type, major, minor, cgroup_device_permissions_to_string(p));
41
42
0
        if (p <= 0 || p >= _CGROUP_DEVICE_PERMISSIONS_MAX)
43
0
                return -EINVAL;
44
45
0
        assert(IN_SET(type, 'b', 'c'));
46
0
        const int bpf_type = type == 'c' ? BPF_DEVCG_DEV_CHAR : BPF_DEVCG_DEV_BLOCK;
47
48
0
        const struct bpf_insn insn[] = {
49
0
                BPF_MOV32_REG(BPF_REG_1, BPF_REG_3),
50
0
                BPF_ALU32_IMM(BPF_AND, BPF_REG_1, p),
51
0
                BPF_JMP_REG(BPF_JNE, BPF_REG_1, BPF_REG_3, 4), /* compare access type */
52
53
0
                BPF_JMP_IMM(BPF_JNE, BPF_REG_2, bpf_type, 3),  /* compare device type */
54
0
                BPF_JMP_IMM(BPF_JNE, BPF_REG_4, major, 2),     /* compare major */
55
0
                BPF_JMP_IMM(BPF_JNE, BPF_REG_5, minor, 1),     /* compare minor */
56
0
                BPF_JMP_A(PASS_JUMP_OFF),                      /* jump to PASS */
57
0
        };
58
59
0
        if (p == _CGROUP_DEVICE_PERMISSIONS_ALL)
60
0
                r = bpf_program_add_instructions(prog, insn + 3, ELEMENTSOF(insn) - 3);
61
0
        else
62
0
                r = bpf_program_add_instructions(prog, insn, ELEMENTSOF(insn));
63
0
        if (r < 0)
64
0
                return log_error_errno(r, "Extending device control BPF program failed: %m");
65
66
0
        return 1; /* return 1 → we did something */
67
0
}
68
69
static int bpf_prog_allow_list_major(
70
                BPFProgram *prog,
71
                char type,
72
                unsigned major,
73
0
                CGroupDevicePermissions p) {
74
75
0
        int r;
76
77
0
        assert(prog);
78
79
0
        log_trace("%s: %c %u:* %s", __func__, type, major, cgroup_device_permissions_to_string(p));
80
81
0
        if (p <= 0 || p >= _CGROUP_DEVICE_PERMISSIONS_MAX)
82
0
                return -EINVAL;
83
84
0
        assert(IN_SET(type, 'b', 'c'));
85
0
        const int bpf_type = type == 'c' ? BPF_DEVCG_DEV_CHAR : BPF_DEVCG_DEV_BLOCK;
86
87
0
        const struct bpf_insn insn[] = {
88
0
                BPF_MOV32_REG(BPF_REG_1, BPF_REG_3),
89
0
                BPF_ALU32_IMM(BPF_AND, BPF_REG_1, p),
90
0
                BPF_JMP_REG(BPF_JNE, BPF_REG_1, BPF_REG_3, 3), /* compare access type */
91
92
0
                BPF_JMP_IMM(BPF_JNE, BPF_REG_2, bpf_type, 2),  /* compare device type */
93
0
                BPF_JMP_IMM(BPF_JNE, BPF_REG_4, major, 1),     /* compare major */
94
0
                BPF_JMP_A(PASS_JUMP_OFF),                      /* jump to PASS */
95
0
        };
96
97
0
        if (p == _CGROUP_DEVICE_PERMISSIONS_ALL)
98
0
                r = bpf_program_add_instructions(prog, insn + 3, ELEMENTSOF(insn) - 3);
99
0
        else
100
0
                r = bpf_program_add_instructions(prog, insn, ELEMENTSOF(insn));
101
0
        if (r < 0)
102
0
                return log_error_errno(r, "Extending device control BPF program failed: %m");
103
104
0
        return 1; /* return 1 → we did something */
105
0
}
106
107
static int bpf_prog_allow_list_class(
108
                BPFProgram *prog,
109
                char type,
110
0
                CGroupDevicePermissions p) {
111
112
0
        int r;
113
114
0
        assert(prog);
115
116
0
        log_trace("%s: %c *:* %s", __func__, type, cgroup_device_permissions_to_string(p));
117
118
0
        if (p <= 0 || p >= _CGROUP_DEVICE_PERMISSIONS_MAX)
119
0
                return -EINVAL;
120
121
0
        assert(IN_SET(type, 'b', 'c'));
122
0
        const int bpf_type = type == 'c' ? BPF_DEVCG_DEV_CHAR : BPF_DEVCG_DEV_BLOCK;
123
124
0
        const struct bpf_insn insn[] = {
125
0
                BPF_MOV32_REG(BPF_REG_1, BPF_REG_3),
126
0
                BPF_ALU32_IMM(BPF_AND, BPF_REG_1, p),
127
0
                BPF_JMP_REG(BPF_JNE, BPF_REG_1, BPF_REG_3, 2), /* compare access type */
128
129
0
                BPF_JMP_IMM(BPF_JNE, BPF_REG_2, bpf_type, 1), /* compare device type */
130
0
                BPF_JMP_A(PASS_JUMP_OFF),                     /* jump to PASS */
131
0
        };
132
133
0
        if (p == _CGROUP_DEVICE_PERMISSIONS_ALL)
134
0
                r = bpf_program_add_instructions(prog, insn + 3, ELEMENTSOF(insn) - 3);
135
0
        else
136
0
                r = bpf_program_add_instructions(prog, insn, ELEMENTSOF(insn));
137
0
        if (r < 0)
138
0
                return log_error_errno(r, "Extending device control BPF program failed: %m");
139
140
0
        return 1; /* return 1 → we did something */
141
0
}
142
143
int bpf_devices_cgroup_init(
144
                BPFProgram **ret,
145
                CGroupDevicePolicy policy,
146
0
                bool allow_list) {
147
148
0
        const struct bpf_insn pre_insn[] = {
149
                /* load device type to r2 */
150
0
                BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1,
151
0
                            offsetof(struct bpf_cgroup_dev_ctx, access_type)),
152
0
                BPF_ALU32_IMM(BPF_AND, BPF_REG_2, 0xFFFF),
153
154
                /* load access type to r3 */
155
0
                BPF_LDX_MEM(BPF_W, BPF_REG_3, BPF_REG_1,
156
0
                            offsetof(struct bpf_cgroup_dev_ctx, access_type)),
157
0
                BPF_ALU32_IMM(BPF_RSH, BPF_REG_3, 16),
158
159
                /* load major number to r4 */
160
0
                BPF_LDX_MEM(BPF_W, BPF_REG_4, BPF_REG_1,
161
0
                            offsetof(struct bpf_cgroup_dev_ctx, major)),
162
163
                /* load minor number to r5 */
164
0
                BPF_LDX_MEM(BPF_W, BPF_REG_5, BPF_REG_1,
165
0
                            offsetof(struct bpf_cgroup_dev_ctx, minor)),
166
0
        };
167
168
0
        _cleanup_(bpf_program_freep) BPFProgram *prog = NULL;
169
0
        int r;
170
171
0
        assert(ret);
172
173
0
        if (policy == CGROUP_DEVICE_POLICY_AUTO && !allow_list) {
174
0
                *ret = NULL;
175
0
                return 0;
176
0
        }
177
178
0
        r = bpf_program_new(BPF_PROG_TYPE_CGROUP_DEVICE, "sd_devices", &prog);
179
0
        if (r < 0)
180
0
                return log_error_errno(r, "Loading device control BPF program failed: %m");
181
182
0
        if (policy == CGROUP_DEVICE_POLICY_CLOSED || allow_list) {
183
0
                r = bpf_program_add_instructions(prog, pre_insn, ELEMENTSOF(pre_insn));
184
0
                if (r < 0)
185
0
                        return log_error_errno(r, "Extending device control BPF program failed: %m");
186
0
        }
187
188
0
        *ret = TAKE_PTR(prog);
189
0
        return 1;
190
0
}
191
192
int bpf_devices_apply_policy(
193
                BPFProgram **prog,
194
                CGroupDevicePolicy policy,
195
                bool allow_list,
196
                const char *cgroup_path,
197
0
                BPFProgram **prog_installed) {
198
199
0
        _cleanup_free_ char *controller_path = NULL;
200
0
        int r;
201
202
        /* This will assign *prog_installed if everything goes well. */
203
204
0
        assert(prog);
205
0
        if (!*prog)
206
0
                goto finish;
207
208
0
        const bool deny_everything = policy == CGROUP_DEVICE_POLICY_STRICT && !allow_list;
209
210
0
        const struct bpf_insn post_insn[] = {
211
                /* return DENY */
212
0
                BPF_MOV64_IMM(BPF_REG_0, 0),
213
0
                BPF_JMP_A(1),
214
0
        };
215
216
0
        const struct bpf_insn exit_insn[] = {
217
                /* finally return DENY if deny_everything else ALLOW */
218
0
                BPF_MOV64_IMM(BPF_REG_0, deny_everything ? 0 : 1),
219
0
                BPF_EXIT_INSN()
220
0
        };
221
222
0
        if (!deny_everything) {
223
0
                r = bpf_program_add_instructions(*prog, post_insn, ELEMENTSOF(post_insn));
224
0
                if (r < 0)
225
0
                        return log_error_errno(r, "Extending device control BPF program failed: %m");
226
227
                /* Fixup PASS_JUMP_OFF jump offsets. */
228
0
                for (size_t off = 0; off < (*prog)->n_instructions; off++) {
229
0
                        struct bpf_insn *ins = &((*prog)->instructions[off]);
230
231
0
                        if (ins->code == (BPF_JMP | BPF_JA) && ins->off == PASS_JUMP_OFF)
232
0
                                ins->off = (*prog)->n_instructions - off - 1;
233
0
                }
234
0
        }
235
236
0
        r = bpf_program_add_instructions(*prog, exit_insn, ELEMENTSOF(exit_insn));
237
0
        if (r < 0)
238
0
                return log_error_errno(r, "Extending device control BPF program failed: %m");
239
240
0
        r = cg_get_path(cgroup_path, /* suffix= */ NULL, &controller_path);
241
0
        if (r < 0)
242
0
                return log_error_errno(r, "Failed to determine cgroup path: %m");
243
244
0
        r = bpf_program_cgroup_attach(*prog, BPF_CGROUP_DEVICE, controller_path, BPF_F_ALLOW_MULTI);
245
0
        if (r < 0)
246
0
                return log_error_errno(r, "Attaching device control BPF program to cgroup %s failed: %m",
247
0
                                       empty_to_root(cgroup_path));
248
249
0
 finish:
250
        /* Unref the old BPF program (which will implicitly detach it) right before attaching the new program. */
251
0
        if (prog_installed) {
252
0
                bpf_program_free(*prog_installed);
253
0
                *prog_installed = TAKE_PTR(*prog);
254
0
        }
255
0
        return 0;
256
0
}
257
258
static int allow_list_device_pattern(
259
                BPFProgram *prog,
260
                const char *path,
261
                char type,
262
                unsigned major,
263
                unsigned minor,
264
0
                CGroupDevicePermissions p) {
265
266
0
        assert(IN_SET(type, 'b', 'c'));
267
268
0
        if (!prog)
269
0
                return 0;
270
271
0
        if (major != UINT_MAX && minor != UINT_MAX)
272
0
                return bpf_prog_allow_list_device(prog, type, major, minor, p);
273
0
        if (major != UINT_MAX)
274
0
                return bpf_prog_allow_list_major(prog, type, major, p);
275
276
0
        return bpf_prog_allow_list_class(prog, type, p);
277
0
}
278
279
int bpf_devices_allow_list_device(
280
                BPFProgram *prog,
281
                const char *path,
282
                const char *node,
283
0
                CGroupDevicePermissions p) {
284
285
0
        mode_t mode;
286
0
        dev_t rdev;
287
0
        int r;
288
289
0
        assert(path);
290
0
        assert(p >= 0 && p < _CGROUP_DEVICE_PERMISSIONS_MAX);
291
292
0
        log_trace("%s: %s %s", __func__, node, cgroup_device_permissions_to_string(p));
293
294
        /* Some special handling for /dev/block/%u:%u, /dev/char/%u:%u, /run/systemd/inaccessible/chr and
295
         * /run/systemd/inaccessible/blk paths. Instead of stat()ing these we parse out the major/minor directly. This
296
         * means clients can use these path without the device node actually around */
297
0
        r = device_path_parse_major_minor(node, &mode, &rdev);
298
0
        if (r < 0) {
299
0
                if (r != -ENODEV)
300
0
                        return log_warning_errno(r, "Couldn't parse major/minor from device path '%s': %m", node);
301
302
0
                struct stat st;
303
0
                if (stat(node, &st) < 0) {
304
0
                        if (errno == ENOENT) {
305
0
                                log_debug_errno(errno, "Device '%s' does not exist, skipping.", node);
306
0
                                return 0; /* returning 0 means → skipped */
307
0
                        }
308
309
0
                        return log_warning_errno(errno, "Couldn't stat device %s: %m", node);
310
0
                }
311
312
0
                r = stat_verify_device_node(&st);
313
0
                if (r < 0)
314
0
                        return log_warning_errno(r, "'%s' is not a device node.", node);
315
316
0
                mode = st.st_mode;
317
0
                rdev = (dev_t) st.st_rdev;
318
0
        }
319
320
0
        return allow_list_device_pattern(prog, path, S_ISCHR(mode) ? 'c' : 'b', major(rdev), minor(rdev), p);
321
0
}
322
323
int bpf_devices_allow_list_major(
324
                BPFProgram *prog,
325
                const char *path,
326
                const char *name,
327
                char type,
328
0
                CGroupDevicePermissions permissions) {
329
330
0
        unsigned major;
331
0
        int r;
332
333
0
        assert(path);
334
0
        assert(IN_SET(type, 'b', 'c'));
335
0
        assert(permissions >= 0 && permissions < _CGROUP_DEVICE_PERMISSIONS_MAX);
336
337
0
        if (streq(name, "*"))
338
                /* If the name is a wildcard, then apply this list to all devices of this type */
339
0
                return allow_list_device_pattern(prog, path, type, /* major= */ UINT_MAX, /* minor= */ UINT_MAX, permissions);
340
341
0
        if (safe_atou(name, &major) >= 0 && DEVICE_MAJOR_VALID(major))
342
                /* The name is numeric and suitable as major. In that case, let's take its major, and create
343
                 * the entry directly. */
344
0
                return allow_list_device_pattern(prog, path, type, major, /* minor= */ UINT_MAX, permissions);
345
346
0
        _cleanup_fclose_ FILE *f = NULL;
347
0
        bool good = false, any = false;
348
349
0
        f = fopen("/proc/devices", "re");
350
0
        if (!f)
351
0
                return log_warning_errno(errno, "Cannot open /proc/devices to resolve %s: %m", name);
352
353
0
        for (;;) {
354
0
                _cleanup_free_ char *line = NULL;
355
0
                char *w, *p;
356
357
0
                r = read_line(f, LONG_LINE_MAX, &line);
358
0
                if (r < 0)
359
0
                        return log_warning_errno(r, "Failed to read /proc/devices: %m");
360
0
                if (r == 0)
361
0
                        break;
362
363
0
                if (type == 'c' && streq(line, "Character devices:")) {
364
0
                        good = true;
365
0
                        continue;
366
0
                }
367
368
0
                if (type == 'b' && streq(line, "Block devices:")) {
369
0
                        good = true;
370
0
                        continue;
371
0
                }
372
373
0
                if (isempty(line)) {
374
0
                        good = false;
375
0
                        continue;
376
0
                }
377
378
0
                if (!good)
379
0
                        continue;
380
381
0
                p = strstrip(line);
382
383
0
                w = strpbrk(p, WHITESPACE);
384
0
                if (!w)
385
0
                        continue;
386
0
                *w = 0;
387
388
0
                r = safe_atou(p, &major);
389
0
                if (r < 0)
390
0
                        continue;
391
0
                if (major <= 0)
392
0
                        continue;
393
394
0
                w++;
395
0
                w += strspn(w, WHITESPACE);
396
397
0
                if (fnmatch(name, w, 0) != 0)
398
0
                        continue;
399
400
0
                if (allow_list_device_pattern(prog, path, type, major, /* minor= */ UINT_MAX, permissions) > 0)
401
0
                        any = true;
402
0
        }
403
404
0
        if (!any)
405
0
                return log_debug_errno(SYNTHETIC_ERRNO(ENOENT),
406
0
                                       "Device allow list pattern \"%s\" did not match anything.", name);
407
408
0
        return any;
409
0
}
410
411
int bpf_devices_allow_list_static(
412
                BPFProgram *prog,
413
0
                const char *path) {
414
415
0
        static const char auto_devices[] =
416
0
                "/dev/null\0" "rwm\0"
417
0
                "/dev/zero\0" "rwm\0"
418
0
                "/dev/full\0" "rwm\0"
419
0
                "/dev/random\0" "rwm\0"
420
0
                "/dev/urandom\0" "rwm\0"
421
0
                "/dev/tty\0" "rwm\0"
422
0
                "/dev/ptmx\0" "rwm\0"
423
                /* Allow /run/systemd/inaccessible/{chr,blk} devices for mapping InaccessiblePaths */
424
0
                "/run/systemd/inaccessible/chr\0" "rwm\0"
425
0
                "/run/systemd/inaccessible/blk\0" "rwm\0";
426
0
        int r = 0, k;
427
428
0
        NULSTR_FOREACH_PAIR(node, acc, auto_devices) {
429
0
                k = bpf_devices_allow_list_device(prog, path, node, cgroup_device_permissions_from_string(acc));
430
0
                if ((r >= 0 && k < 0) || (r >= 0 && k > 0))
431
0
                        r = k;
432
0
        }
433
434
        /* PTS (/dev/pts) devices may not be duplicated, but accessed */
435
0
        k = bpf_devices_allow_list_major(prog, path, "pts", 'c', CGROUP_DEVICE_READ|CGROUP_DEVICE_WRITE);
436
0
        if ((r >= 0 && k < 0) || (r >= 0 && k > 0))
437
0
                r = k;
438
439
0
        return r;
440
0
}