]>
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; |
184b4f78 ILG |
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, "Failed to open BPF object: %m"); | |
66 | ||
67 | /* TODO Maybe choose a number based on runtime information? */ | |
68 | r = sym_bpf_map__resize(obj->maps.cgroup_hash, CGROUP_HASH_SIZE_MAX); | |
b7cba815 ZJS |
69 | assert(r <= 0); |
70 | if (r < 0) | |
71 | return log_error_errno(r, "Failed to resize BPF map '%s': %m", | |
72 | sym_bpf_map__name(obj->maps.cgroup_hash)); | |
184b4f78 ILG |
73 | |
74 | /* Dummy map to satisfy the verifier */ | |
75 | inner_map_fd = sym_bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint32_t), 128, 0); | |
76 | if (inner_map_fd < 0) | |
77 | return log_error_errno(errno, "Failed to create BPF map: %m"); | |
78 | ||
79 | r = sym_bpf_map__set_inner_map_fd(obj->maps.cgroup_hash, inner_map_fd); | |
b7cba815 | 80 | assert(r <= 0); |
184b4f78 ILG |
81 | if (r < 0) |
82 | return log_error_errno(r, "Failed to set inner map fd: %m"); | |
83 | ||
84 | r = restrict_fs_bpf__load(obj); | |
b7cba815 ZJS |
85 | assert(r <= 0); |
86 | if (r < 0) | |
2e09b223 | 87 | return log_error_errno(r, "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 ZJS |
106 | if (r != -ENOENT) |
107 | log_notice_errno(r, "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) { | |
b7cba815 | 120 | log_notice_errno(r, "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) { | |
144 | log_warning_errno(r, "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), | |
150 | "BPF LSM hook not enabled in the kernel, LSM BPF 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), | |
160 | "Failed to link BPF program. Assuming BPF 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) | |
181 | return log_error_errno(r, "Failed to link '%s' LSM BPF program: %m", | |
299d9417 | 182 | sym_bpf_program__name(obj->progs.restrict_filesystems)); |
184b4f78 ILG |
183 | |
184 | log_info("LSM BPF program attached"); | |
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), | |
203 | "Restrict filesystems BPF object is not set, BPF LSM setup has failed?"); | |
204 | ||
92698b0f | 205 | int inner_map_fd = sym_bpf_create_map( |
184b4f78 ILG |
206 | BPF_MAP_TYPE_HASH, |
207 | sizeof(uint32_t), | |
208 | sizeof(uint32_t), | |
209 | 128, /* Should be enough for all filesystem types */ | |
210 | 0); | |
211 | if (inner_map_fd < 0) | |
212 | return log_unit_error_errno(u, errno, "Failed to create inner LSM map: %m"); | |
213 | ||
92698b0f | 214 | int outer_map_fd = sym_bpf_map__fd(u->manager->restrict_fs->maps.cgroup_hash); |
184b4f78 ILG |
215 | if (outer_map_fd < 0) |
216 | return log_unit_error_errno(u, errno, "Failed to get BPF map fd: %m"); | |
217 | ||
218 | if (sym_bpf_map_update_elem(outer_map_fd, &u->cgroup_id, &inner_map_fd, BPF_ANY) != 0) | |
219 | return log_unit_error_errno(u, errno, "Error populating LSM BPF map: %m"); | |
220 | ||
221 | uint32_t allow = allow_list; | |
222 | ||
223 | /* Use key 0 to store whether this is an allow list or a deny list */ | |
224 | if (sym_bpf_map_update_elem(inner_map_fd, &zero, &allow, BPF_ANY) != 0) | |
225 | return log_unit_error_errno(u, errno, "Error initializing BPF map: %m"); | |
226 | ||
227 | SET_FOREACH(fs, filesystems) { | |
228 | r = fs_type_from_string(fs, &magic); | |
229 | if (r < 0) { | |
230 | log_unit_warning(u, "Invalid filesystem name '%s', ignoring.", fs); | |
231 | continue; | |
232 | } | |
233 | ||
234 | log_unit_debug(u, "Restricting filesystem access to '%s'", fs); | |
235 | ||
236 | for (int i = 0; i < FILESYSTEM_MAGIC_MAX; i++) { | |
237 | if (magic[i] == 0) | |
238 | break; | |
239 | ||
240 | if (sym_bpf_map_update_elem(inner_map_fd, &magic[i], &dummy_value, BPF_ANY) != 0) { | |
241 | r = log_unit_error_errno(u, errno, "Failed to update BPF map: %m"); | |
242 | ||
243 | if (sym_bpf_map_delete_elem(outer_map_fd, &u->cgroup_id) != 0) | |
244 | log_unit_debug_errno(u, errno, "Failed to delete cgroup entry from LSM BPF map: %m"); | |
245 | ||
246 | return r; | |
247 | } | |
248 | } | |
249 | } | |
250 | ||
251 | return 0; | |
252 | } | |
253 | ||
254 | int lsm_bpf_cleanup(const Unit *u) { | |
184b4f78 ILG |
255 | assert(u); |
256 | assert(u->manager); | |
257 | ||
ba187c9c ZJS |
258 | /* If we never successfully detected support, there is nothing to clean up. */ |
259 | if (!lsm_bpf_supported(/* initialize = */ false)) | |
184b4f78 ILG |
260 | return 0; |
261 | ||
262 | if (!u->manager->restrict_fs) | |
263 | return 0; | |
264 | ||
92698b0f | 265 | int fd = sym_bpf_map__fd(u->manager->restrict_fs->maps.cgroup_hash); |
184b4f78 ILG |
266 | if (fd < 0) |
267 | return log_unit_error_errno(u, errno, "Failed to get BPF map fd: %m"); | |
268 | ||
269 | if (sym_bpf_map_delete_elem(fd, &u->cgroup_id) != 0) | |
270 | return log_unit_debug_errno(u, errno, "Failed to delete cgroup entry from LSM BPF map: %m"); | |
271 | ||
272 | return 0; | |
273 | } | |
274 | ||
275 | int lsm_bpf_map_restrict_fs_fd(Unit *unit) { | |
276 | assert(unit); | |
277 | assert(unit->manager); | |
278 | ||
279 | if (!unit->manager->restrict_fs) | |
280 | return -ENOMEDIUM; | |
281 | ||
282 | return sym_bpf_map__fd(unit->manager->restrict_fs->maps.cgroup_hash); | |
283 | } | |
284 | ||
285 | void lsm_bpf_destroy(struct restrict_fs_bpf *prog) { | |
286 | restrict_fs_bpf__destroy(prog); | |
287 | } | |
288 | #else /* ! BPF_FRAMEWORK */ | |
ba187c9c | 289 | bool lsm_bpf_supported(bool initialize) { |
389db516 | 290 | return false; |
184b4f78 ILG |
291 | } |
292 | ||
293 | int lsm_bpf_setup(Manager *m) { | |
294 | return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set up LSM BPF: %m"); | |
295 | } | |
296 | ||
297 | int lsm_bpf_unit_restrict_filesystems(Unit *u, const Set *filesystems, const bool allow_list) { | |
298 | return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to restrict filesystems using LSM BPF: %m"); | |
299 | } | |
300 | ||
301 | int lsm_bpf_cleanup(const Unit *u) { | |
302 | return 0; | |
303 | } | |
304 | ||
305 | int lsm_bpf_map_restrict_fs_fd(Unit *unit) { | |
306 | return -ENOMEDIUM; | |
307 | } | |
308 | ||
309 | void lsm_bpf_destroy(struct restrict_fs_bpf *prog) { | |
310 | return; | |
311 | } | |
312 | #endif | |
e59ccd03 ILG |
313 | |
314 | int lsm_bpf_parse_filesystem( | |
315 | const char *name, | |
316 | Set **filesystems, | |
317 | FilesystemParseFlags flags, | |
318 | const char *unit, | |
319 | const char *filename, | |
320 | unsigned line) { | |
321 | int r; | |
322 | ||
323 | assert(name); | |
324 | assert(filesystems); | |
325 | ||
326 | if (name[0] == '@') { | |
327 | const FilesystemSet *set; | |
328 | const char *i; | |
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 | "Unknown filesystem group, ignoring: %s", name); | |
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 | } |