]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/bpf-lsm.c
216fc341c18ec65f1d430a53fd5ed064f869b57b
[thirdparty/systemd.git] / src / core / bpf-lsm.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <linux/types.h>
6 #include <sys/resource.h>
7 #include <sys/stat.h>
8 #include <sys/time.h>
9 #include <sys/types.h>
10 #include <unistd.h>
11
12 #include "alloc-util.h"
13 #include "bpf-lsm.h"
14 #include "cgroup-util.h"
15 #include "fd-util.h"
16 #include "fileio.h"
17 #include "filesystems.h"
18 #include "log.h"
19 #include "lsm-util.h"
20 #include "manager.h"
21 #include "mkdir.h"
22 #include "nulstr-util.h"
23 #include "stat-util.h"
24 #include "strv.h"
25
26 #if BPF_FRAMEWORK
27 /* libbpf, clang and llc compile time dependencies are satisfied */
28 #include "bpf-dlopen.h"
29 #include "bpf-link.h"
30 #include "bpf-util.h"
31 #include "bpf/restrict_fs/restrict-fs-skel.h"
32
33 #define CGROUP_HASH_SIZE_MAX 2048
34
35 static struct restrict_fs_bpf *restrict_fs_bpf_free(struct restrict_fs_bpf *obj) {
36 /* restrict_fs_bpf__destroy handles object == NULL case */
37 (void) restrict_fs_bpf__destroy(obj);
38
39 return NULL;
40 }
41
42 DEFINE_TRIVIAL_CLEANUP_FUNC(struct restrict_fs_bpf *, restrict_fs_bpf_free);
43
44 static bool bpf_can_link_lsm_program(struct bpf_program *prog) {
45 _cleanup_(bpf_link_freep) struct bpf_link *link = NULL;
46
47 assert(prog);
48
49 link = sym_bpf_program__attach_lsm(prog);
50
51 /* If bpf_program__attach_lsm fails the resulting value stores libbpf error code instead of memory
52 * pointer. That is the case when the helper is called on architectures where BPF trampoline (hence
53 * BPF_LSM_MAC attach type) is not supported. */
54 return sym_libbpf_get_error(link) == 0;
55 }
56
57 static int prepare_restrict_fs_bpf(struct restrict_fs_bpf **ret_obj) {
58 _cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL;
59 _cleanup_close_ int inner_map_fd = -EBADF;
60 int r;
61
62 assert(ret_obj);
63
64 obj = restrict_fs_bpf__open();
65 if (!obj)
66 return log_error_errno(errno, "bpf-lsm: Failed to open BPF object: %m");
67
68 /* TODO Maybe choose a number based on runtime information? */
69 r = sym_bpf_map__set_max_entries(obj->maps.cgroup_hash, CGROUP_HASH_SIZE_MAX);
70 assert(r <= 0);
71 if (r < 0)
72 return log_error_errno(r, "bpf-lsm: Failed to resize BPF map '%s': %m",
73 sym_bpf_map__name(obj->maps.cgroup_hash));
74
75 /* Dummy map to satisfy the verifier */
76 inner_map_fd = compat_bpf_map_create(BPF_MAP_TYPE_HASH, NULL, sizeof(uint32_t), sizeof(uint32_t), 128U, NULL);
77 if (inner_map_fd < 0)
78 return log_error_errno(errno, "bpf-lsm: Failed to create BPF map: %m");
79
80 r = sym_bpf_map__set_inner_map_fd(obj->maps.cgroup_hash, inner_map_fd);
81 assert(r <= 0);
82 if (r < 0)
83 return log_error_errno(r, "bpf-lsm: Failed to set inner map fd: %m");
84
85 r = restrict_fs_bpf__load(obj);
86 assert(r <= 0);
87 if (r < 0)
88 return log_error_errno(r, "bpf-lsm: Failed to load BPF object: %m");
89
90 *ret_obj = TAKE_PTR(obj);
91
92 return 0;
93 }
94
95 bool lsm_bpf_supported(bool initialize) {
96 _cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL;
97 static int supported = -1;
98 int r;
99
100 if (supported >= 0)
101 return supported;
102 if (!initialize)
103 return false;
104
105 if (!cgroup_bpf_supported())
106 return (supported = false);
107
108 r = lsm_supported("bpf");
109 if (r < 0) {
110 log_warning_errno(r, "bpf-lsm: Can't determine whether the BPF LSM module is used: %m");
111 return (supported = false);
112 }
113 if (r == 0) {
114 log_info_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
115 "bpf-lsm: BPF LSM hook not enabled in the kernel, BPF LSM not supported");
116 return (supported = false);
117 }
118
119 r = prepare_restrict_fs_bpf(&obj);
120 if (r < 0)
121 return (supported = false);
122
123 if (!bpf_can_link_lsm_program(obj->progs.restrict_filesystems)) {
124 log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
125 "bpf-lsm: Failed to link program; assuming BPF LSM is not available");
126 return (supported = false);
127 }
128
129 return (supported = true);
130 }
131
132 int lsm_bpf_setup(Manager *m) {
133 _cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL;
134 _cleanup_(bpf_link_freep) struct bpf_link *link = NULL;
135 int r;
136
137 assert(m);
138
139 r = prepare_restrict_fs_bpf(&obj);
140 if (r < 0)
141 return r;
142
143 link = sym_bpf_program__attach_lsm(obj->progs.restrict_filesystems);
144 r = sym_libbpf_get_error(link);
145 if (r != 0)
146 return log_error_errno(r, "bpf-lsm: Failed to link '%s' LSM BPF program: %m",
147 sym_bpf_program__name(obj->progs.restrict_filesystems));
148
149 log_info("bpf-lsm: LSM BPF program attached");
150
151 obj->links.restrict_filesystems = TAKE_PTR(link);
152 m->restrict_fs = TAKE_PTR(obj);
153
154 return 0;
155 }
156
157 int lsm_bpf_restrict_filesystems(const Set *filesystems, uint64_t cgroup_id, int outer_map_fd, bool allow_list) {
158 uint32_t dummy_value = 1, zero = 0;
159 const char *fs;
160 const statfs_f_type_t *magic;
161 int r;
162
163 assert(filesystems);
164 assert(outer_map_fd >= 0);
165
166 int inner_map_fd = compat_bpf_map_create(
167 BPF_MAP_TYPE_HASH,
168 NULL,
169 sizeof(uint32_t),
170 sizeof(uint32_t),
171 128U, /* Should be enough for all filesystem types */
172 NULL);
173 if (inner_map_fd < 0)
174 return log_error_errno(errno, "bpf-lsm: Failed to create inner BPF map: %m");
175
176 if (sym_bpf_map_update_elem(outer_map_fd, &cgroup_id, &inner_map_fd, BPF_ANY) != 0)
177 return log_error_errno(errno, "bpf-lsm: Error populating BPF map: %m");
178
179 uint32_t allow = allow_list;
180
181 /* Use key 0 to store whether this is an allow list or a deny list */
182 if (sym_bpf_map_update_elem(inner_map_fd, &zero, &allow, BPF_ANY) != 0)
183 return log_error_errno(errno, "bpf-lsm: Error initializing map: %m");
184
185 SET_FOREACH(fs, filesystems) {
186 r = fs_type_from_string(fs, &magic);
187 if (r < 0) {
188 log_warning("bpf-lsm: Invalid filesystem name '%s', ignoring.", fs);
189 continue;
190 }
191
192 log_debug("bpf-lsm: Restricting filesystem access to '%s'", fs);
193
194 for (int i = 0; i < FILESYSTEM_MAGIC_MAX; i++) {
195 if (magic[i] == 0)
196 break;
197
198 if (sym_bpf_map_update_elem(inner_map_fd, &magic[i], &dummy_value, BPF_ANY) != 0) {
199 r = log_error_errno(errno, "bpf-lsm: Failed to update BPF map: %m");
200
201 if (sym_bpf_map_delete_elem(outer_map_fd, &cgroup_id) != 0)
202 log_debug_errno(errno, "bpf-lsm: Failed to delete cgroup entry from BPF map: %m");
203
204 return r;
205 }
206 }
207 }
208
209 return 0;
210 }
211
212 int lsm_bpf_cleanup(const Unit *u) {
213 assert(u);
214 assert(u->manager);
215
216 /* If we never successfully detected support, there is nothing to clean up. */
217 if (!lsm_bpf_supported(/* initialize = */ false))
218 return 0;
219
220 if (!u->manager->restrict_fs)
221 return 0;
222
223 if (u->cgroup_id == 0)
224 return 0;
225
226 int fd = sym_bpf_map__fd(u->manager->restrict_fs->maps.cgroup_hash);
227 if (fd < 0)
228 return log_unit_error_errno(u, errno, "bpf-lsm: Failed to get BPF map fd: %m");
229
230 if (sym_bpf_map_delete_elem(fd, &u->cgroup_id) != 0 && errno != ENOENT)
231 return log_unit_debug_errno(u, errno, "bpf-lsm: Failed to delete cgroup entry from LSM BPF map: %m");
232
233 return 0;
234 }
235
236 int lsm_bpf_map_restrict_fs_fd(Unit *unit) {
237 assert(unit);
238 assert(unit->manager);
239
240 if (!unit->manager->restrict_fs)
241 return -ENOMEDIUM;
242
243 return sym_bpf_map__fd(unit->manager->restrict_fs->maps.cgroup_hash);
244 }
245
246 void lsm_bpf_destroy(struct restrict_fs_bpf *prog) {
247 restrict_fs_bpf__destroy(prog);
248 }
249 #else /* ! BPF_FRAMEWORK */
250 bool lsm_bpf_supported(bool initialize) {
251 return false;
252 }
253
254 int lsm_bpf_setup(Manager *m) {
255 return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-lsm: Failed to set up LSM BPF: %m");
256 }
257
258 int lsm_bpf_restrict_filesystems(const Set *filesystems, uint64_t cgroup_id, int outer_map_fd, const bool allow_list) {
259 return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-lsm: Failed to restrict filesystems using LSM BPF: %m");
260 }
261
262 int lsm_bpf_cleanup(const Unit *u) {
263 return 0;
264 }
265
266 int lsm_bpf_map_restrict_fs_fd(Unit *unit) {
267 return -ENOMEDIUM;
268 }
269
270 void lsm_bpf_destroy(struct restrict_fs_bpf *prog) {
271 return;
272 }
273 #endif
274
275 int lsm_bpf_parse_filesystem(
276 const char *name,
277 Set **filesystems,
278 FilesystemParseFlags flags,
279 const char *unit,
280 const char *filename,
281 unsigned line) {
282 int r;
283
284 assert(name);
285 assert(filesystems);
286
287 if (name[0] == '@') {
288 const FilesystemSet *set;
289
290 set = filesystem_set_find(name);
291 if (!set) {
292 log_syntax(unit, flags & FILESYSTEM_PARSE_LOG ? LOG_WARNING : LOG_DEBUG, filename, line, 0,
293 "bpf-lsm: Unknown filesystem group, ignoring: %s", name);
294 return 0;
295 }
296
297 NULSTR_FOREACH(i, set->value) {
298 /* Call ourselves again, for the group to parse. Note that we downgrade logging here
299 * (i.e. take away the FILESYSTEM_PARSE_LOG flag) since any issues in the group table
300 * are our own problem, not a problem in user configuration data and we shouldn't
301 * pretend otherwise by complaining about them. */
302 r = lsm_bpf_parse_filesystem(i, filesystems, flags &~ FILESYSTEM_PARSE_LOG, unit, filename, line);
303 if (r < 0)
304 return r;
305 }
306 } else {
307 /* If we previously wanted to forbid access to a filesystem and now
308 * we want to allow it, then remove it from the list. */
309 if (!(flags & FILESYSTEM_PARSE_INVERT) == !!(flags & FILESYSTEM_PARSE_ALLOW_LIST)) {
310 r = set_put_strdup(filesystems, name);
311 if (r == -ENOMEM)
312 return flags & FILESYSTEM_PARSE_LOG ? log_oom() : -ENOMEM;
313 if (r < 0 && r != -EEXIST) /* When already in set, ignore */
314 return r;
315 } else
316 free(set_remove(*filesystems, name));
317 }
318
319 return 0;
320 }