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