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