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