]>
Commit | Line | Data |
---|---|---|
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-util.h" | |
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); | |
49 | ||
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; | |
54 | } | |
55 | ||
56 | static int prepare_restrict_fs_bpf(struct restrict_fs_bpf **ret_obj) { | |
57 | _cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL; | |
58 | _cleanup_close_ int inner_map_fd = -1; | |
59 | int r; | |
60 | ||
61 | assert(ret_obj); | |
62 | ||
63 | obj = restrict_fs_bpf__open(); | |
64 | if (!obj) | |
65 | return log_error_errno(errno, "bpf-lsm: Failed to open BPF object: %m"); | |
66 | ||
67 | /* TODO Maybe choose a number based on runtime information? */ | |
68 | r = sym_bpf_map__set_max_entries(obj->maps.cgroup_hash, CGROUP_HASH_SIZE_MAX); | |
69 | assert(r <= 0); | |
70 | if (r < 0) | |
71 | return log_error_errno(r, "bpf-lsm: Failed to resize BPF map '%s': %m", | |
72 | sym_bpf_map__name(obj->maps.cgroup_hash)); | |
73 | ||
74 | /* Dummy map to satisfy the verifier */ | |
75 | inner_map_fd = compat_bpf_map_create(BPF_MAP_TYPE_HASH, NULL, sizeof(uint32_t), sizeof(uint32_t), 128U, NULL); | |
76 | if (inner_map_fd < 0) | |
77 | return log_error_errno(errno, "bpf-lsm: Failed to create BPF map: %m"); | |
78 | ||
79 | r = sym_bpf_map__set_inner_map_fd(obj->maps.cgroup_hash, inner_map_fd); | |
80 | assert(r <= 0); | |
81 | if (r < 0) | |
82 | return log_error_errno(r, "bpf-lsm: Failed to set inner map fd: %m"); | |
83 | ||
84 | r = restrict_fs_bpf__load(obj); | |
85 | assert(r <= 0); | |
86 | if (r < 0) | |
87 | return log_error_errno(r, "bpf-lsm: Failed to load BPF object: %m"); | |
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) { | |
106 | if (r != -ENOENT) | |
107 | log_notice_errno(r, "bpf-lsm: Failed to read /sys/kernel/security/lsm, assuming bpf is unavailable: %m"); | |
108 | return 0; | |
109 | } | |
110 | ||
111 | for (const char *p = lsm_list;;) { | |
112 | _cleanup_free_ char *word = NULL; | |
113 | ||
114 | r = extract_first_word(&p, &word, ",", 0); | |
115 | if (r == 0) | |
116 | return 0; | |
117 | if (r == -ENOMEM) | |
118 | return log_oom(); | |
119 | if (r < 0) { | |
120 | log_notice_errno(r, "bpf-lsm: Failed to parse /sys/kernel/security/lsm, assuming bpf is unavailable: %m"); | |
121 | return 0; | |
122 | } | |
123 | ||
124 | if (streq(word, "bpf")) | |
125 | return cached_use = 1; | |
126 | } | |
127 | } | |
128 | ||
129 | bool lsm_bpf_supported(bool initialize) { | |
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; | |
136 | if (!initialize) | |
137 | return false; | |
138 | ||
139 | if (!cgroup_bpf_supported()) | |
140 | return (supported = false); | |
141 | ||
142 | r = mac_bpf_use(); | |
143 | if (r < 0) { | |
144 | log_warning_errno(r, "bpf-lsm: Can't determine whether the BPF LSM module is used: %m"); | |
145 | return (supported = false); | |
146 | } | |
147 | ||
148 | if (r == 0) { | |
149 | log_info_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), | |
150 | "bpf-lsm: BPF LSM hook not enabled in the kernel, BPF LSM not supported"); | |
151 | return (supported = false); | |
152 | } | |
153 | ||
154 | r = prepare_restrict_fs_bpf(&obj); | |
155 | if (r < 0) | |
156 | return (supported = false); | |
157 | ||
158 | if (!bpf_can_link_lsm_program(obj->progs.restrict_filesystems)) { | |
159 | log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), | |
160 | "bpf-lsm: Failed to link program; assuming BPF LSM is not available"); | |
161 | return (supported = false); | |
162 | } | |
163 | ||
164 | return (supported = true); | |
165 | } | |
166 | ||
167 | int lsm_bpf_setup(Manager *m) { | |
168 | _cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL; | |
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 | ||
178 | link = sym_bpf_program__attach_lsm(obj->progs.restrict_filesystems); | |
179 | r = sym_libbpf_get_error(link); | |
180 | if (r != 0) | |
181 | return log_error_errno(r, "bpf-lsm: Failed to link '%s' LSM BPF program: %m", | |
182 | sym_bpf_program__name(obj->progs.restrict_filesystems)); | |
183 | ||
184 | log_info("bpf-lsm: LSM BPF program attached"); | |
185 | ||
186 | obj->links.restrict_filesystems = TAKE_PTR(link); | |
187 | m->restrict_fs = TAKE_PTR(obj); | |
188 | ||
189 | return 0; | |
190 | } | |
191 | ||
192 | int lsm_bpf_unit_restrict_filesystems(Unit *u, const Set *filesystems, bool allow_list) { | |
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 | ||
201 | if (!u->manager->restrict_fs) | |
202 | return log_unit_error_errno(u, SYNTHETIC_ERRNO(EINVAL), | |
203 | "bpf-lsm: BPF LSM object is not installed, has setup failed?"); | |
204 | ||
205 | int inner_map_fd = compat_bpf_map_create( | |
206 | BPF_MAP_TYPE_HASH, | |
207 | NULL, | |
208 | sizeof(uint32_t), | |
209 | sizeof(uint32_t), | |
210 | 128U, /* Should be enough for all filesystem types */ | |
211 | NULL); | |
212 | if (inner_map_fd < 0) | |
213 | return log_unit_error_errno(u, errno, "bpf-lsm: Failed to create inner BPF map: %m"); | |
214 | ||
215 | int outer_map_fd = sym_bpf_map__fd(u->manager->restrict_fs->maps.cgroup_hash); | |
216 | if (outer_map_fd < 0) | |
217 | return log_unit_error_errno(u, errno, "bpf-lsm: Failed to get BPF map fd: %m"); | |
218 | ||
219 | if (sym_bpf_map_update_elem(outer_map_fd, &u->cgroup_id, &inner_map_fd, BPF_ANY) != 0) | |
220 | return log_unit_error_errno(u, errno, "bpf-lsm: Error populating BPF map: %m"); | |
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) | |
226 | return log_unit_error_errno(u, errno, "bpf-lsm: Error initializing map: %m"); | |
227 | ||
228 | SET_FOREACH(fs, filesystems) { | |
229 | r = fs_type_from_string(fs, &magic); | |
230 | if (r < 0) { | |
231 | log_unit_warning(u, "bpf-lsm: Invalid filesystem name '%s', ignoring.", fs); | |
232 | continue; | |
233 | } | |
234 | ||
235 | log_unit_debug(u, "bpf-lsm: Restricting filesystem access to '%s'", fs); | |
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) { | |
242 | r = log_unit_error_errno(u, errno, "bpf-lsm: Failed to update BPF map: %m"); | |
243 | ||
244 | if (sym_bpf_map_delete_elem(outer_map_fd, &u->cgroup_id) != 0) | |
245 | log_unit_debug_errno(u, errno, "bpf-lsm: Failed to delete cgroup entry from BPF map: %m"); | |
246 | ||
247 | return r; | |
248 | } | |
249 | } | |
250 | } | |
251 | ||
252 | return 0; | |
253 | } | |
254 | ||
255 | int lsm_bpf_cleanup(const Unit *u) { | |
256 | assert(u); | |
257 | assert(u->manager); | |
258 | ||
259 | /* If we never successfully detected support, there is nothing to clean up. */ | |
260 | if (!lsm_bpf_supported(/* initialize = */ false)) | |
261 | return 0; | |
262 | ||
263 | if (!u->manager->restrict_fs) | |
264 | return 0; | |
265 | ||
266 | int fd = sym_bpf_map__fd(u->manager->restrict_fs->maps.cgroup_hash); | |
267 | if (fd < 0) | |
268 | return log_unit_error_errno(u, errno, "bpf-lsm: Failed to get BPF map fd: %m"); | |
269 | ||
270 | if (sym_bpf_map_delete_elem(fd, &u->cgroup_id) != 0) | |
271 | return log_unit_debug_errno(u, errno, "bpf-lsm: Failed to delete cgroup entry from LSM BPF map: %m"); | |
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 */ | |
290 | bool lsm_bpf_supported(bool initialize) { | |
291 | return false; | |
292 | } | |
293 | ||
294 | int lsm_bpf_setup(Manager *m) { | |
295 | return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-lsm: Failed to set up LSM BPF: %m"); | |
296 | } | |
297 | ||
298 | int lsm_bpf_unit_restrict_filesystems(Unit *u, const Set *filesystems, const bool allow_list) { | |
299 | return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-lsm: Failed to restrict filesystems using LSM BPF: %m"); | |
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 | |
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; | |
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, | |
333 | "bpf-lsm: Unknown filesystem group, ignoring: %s", name); | |
334 | return 0; | |
335 | } | |
336 | ||
337 | NULSTR_FOREACH(i, set->value) { | |
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. */ | |
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); | |
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; | |
355 | } else | |
356 | free(set_remove(*filesystems, name)); | |
357 | } | |
358 | ||
359 | return 0; | |
360 | } |