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