Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */
2 :
3 : #include <fcntl.h>
4 : #include <sys/stat.h>
5 : #include <sys/types.h>
6 : #include <unistd.h>
7 :
8 : #include "alloc-util.h"
9 : #include "bpf-program.h"
10 : #include "fd-util.h"
11 : #include "log.h"
12 : #include "memory-util.h"
13 : #include "missing.h"
14 : #include "path-util.h"
15 :
16 5 : int bpf_program_new(uint32_t prog_type, BPFProgram **ret) {
17 5 : _cleanup_(bpf_program_unrefp) BPFProgram *p = NULL;
18 :
19 5 : p = new0(BPFProgram, 1);
20 5 : if (!p)
21 0 : return log_oom();
22 :
23 5 : p->n_ref = 1;
24 5 : p->prog_type = prog_type;
25 5 : p->kernel_fd = -1;
26 :
27 5 : *ret = TAKE_PTR(p);
28 :
29 5 : return 0;
30 : }
31 :
32 5 : static BPFProgram *bpf_program_free(BPFProgram *p) {
33 5 : assert(p);
34 :
35 : /* Unfortunately, the kernel currently doesn't implicitly detach BPF programs from their cgroups when the last
36 : * fd to the BPF program is closed. This has nasty side-effects since this means that abnormally terminated
37 : * programs that attached one of their BPF programs to a cgroup will leave this programs pinned for good with
38 : * zero chance of recovery, until the cgroup is removed. This is particularly problematic if the cgroup in
39 : * question is the root cgroup (or any other cgroup belonging to a service that cannot be restarted during
40 : * operation, such as dbus), as the memory for the BPF program can only be reclaimed through a reboot. To
41 : * counter this, we track closely to which cgroup a program was attached to and will detach it on our own
42 : * whenever we close the BPF fd. */
43 5 : (void) bpf_program_cgroup_detach(p);
44 :
45 5 : safe_close(p->kernel_fd);
46 5 : free(p->instructions);
47 5 : free(p->attached_path);
48 :
49 5 : return mfree(p);
50 : }
51 :
52 10805 : DEFINE_TRIVIAL_REF_UNREF_FUNC(BPFProgram, bpf_program, bpf_program_free);
53 :
54 5 : int bpf_program_add_instructions(BPFProgram *p, const struct bpf_insn *instructions, size_t count) {
55 :
56 5 : assert(p);
57 :
58 5 : if (p->kernel_fd >= 0) /* don't allow modification after we uploaded things to the kernel */
59 0 : return -EBUSY;
60 :
61 5 : if (!GREEDY_REALLOC(p->instructions, p->allocated, p->n_instructions + count))
62 0 : return -ENOMEM;
63 :
64 5 : memcpy(p->instructions + p->n_instructions, instructions, sizeof(struct bpf_insn) * count);
65 5 : p->n_instructions += count;
66 :
67 5 : return 0;
68 : }
69 :
70 5 : int bpf_program_load_kernel(BPFProgram *p, char *log_buf, size_t log_size) {
71 : union bpf_attr attr;
72 :
73 5 : assert(p);
74 :
75 5 : if (p->kernel_fd >= 0) { /* make this idempotent */
76 0 : memzero(log_buf, log_size);
77 0 : return 0;
78 : }
79 :
80 20 : attr = (union bpf_attr) {
81 5 : .prog_type = p->prog_type,
82 5 : .insns = PTR_TO_UINT64(p->instructions),
83 5 : .insn_cnt = p->n_instructions,
84 5 : .license = PTR_TO_UINT64("GPL"),
85 5 : .log_buf = PTR_TO_UINT64(log_buf),
86 5 : .log_level = !!log_buf,
87 : .log_size = log_size,
88 : };
89 :
90 5 : p->kernel_fd = bpf(BPF_PROG_LOAD, &attr, sizeof(attr));
91 5 : if (p->kernel_fd < 0)
92 0 : return -errno;
93 :
94 5 : return 0;
95 : }
96 :
97 0 : int bpf_program_load_from_bpf_fs(BPFProgram *p, const char *path) {
98 : union bpf_attr attr;
99 :
100 0 : assert(p);
101 :
102 0 : if (p->kernel_fd >= 0) /* don't overwrite an assembled or loaded program */
103 0 : return -EBUSY;
104 :
105 0 : attr = (union bpf_attr) {
106 0 : .pathname = PTR_TO_UINT64(path),
107 : };
108 :
109 0 : p->kernel_fd = bpf(BPF_OBJ_GET, &attr, sizeof(attr));
110 0 : if (p->kernel_fd < 0)
111 0 : return -errno;
112 :
113 0 : return 0;
114 : }
115 :
116 0 : int bpf_program_cgroup_attach(BPFProgram *p, int type, const char *path, uint32_t flags) {
117 0 : _cleanup_free_ char *copy = NULL;
118 0 : _cleanup_close_ int fd = -1;
119 : union bpf_attr attr;
120 : int r;
121 :
122 0 : assert(p);
123 0 : assert(type >= 0);
124 0 : assert(path);
125 :
126 0 : if (!IN_SET(flags, 0, BPF_F_ALLOW_OVERRIDE, BPF_F_ALLOW_MULTI))
127 0 : return -EINVAL;
128 :
129 : /* We need to track which cgroup the program is attached to, and we can only track one attachment, hence let's
130 : * refuse this early. */
131 0 : if (p->attached_path) {
132 0 : if (!path_equal(p->attached_path, path))
133 0 : return -EBUSY;
134 0 : if (p->attached_type != type)
135 0 : return -EBUSY;
136 0 : if (p->attached_flags != flags)
137 0 : return -EBUSY;
138 :
139 : /* Here's a shortcut: if we previously attached this program already, then we don't have to do so
140 : * again. Well, with one exception: if we are in BPF_F_ALLOW_OVERRIDE mode then someone else might have
141 : * replaced our program since the last time, hence let's reattach it again, just to be safe. In flags
142 : * == 0 mode this is not an issue since nobody else can replace our program in that case, and in flags
143 : * == BPF_F_ALLOW_MULTI mode any other's program would be installed in addition to ours hence ours
144 : * would remain in effect. */
145 0 : if (flags != BPF_F_ALLOW_OVERRIDE)
146 0 : return 0;
147 : }
148 :
149 : /* Ensure we have a kernel object for this. */
150 0 : r = bpf_program_load_kernel(p, NULL, 0);
151 0 : if (r < 0)
152 0 : return r;
153 :
154 0 : copy = strdup(path);
155 0 : if (!copy)
156 0 : return -ENOMEM;
157 :
158 0 : fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
159 0 : if (fd < 0)
160 0 : return -errno;
161 :
162 0 : attr = (union bpf_attr) {
163 : .attach_type = type,
164 : .target_fd = fd,
165 0 : .attach_bpf_fd = p->kernel_fd,
166 : .attach_flags = flags,
167 : };
168 :
169 0 : if (bpf(BPF_PROG_ATTACH, &attr, sizeof(attr)) < 0)
170 0 : return -errno;
171 :
172 0 : free_and_replace(p->attached_path, copy);
173 0 : p->attached_type = type;
174 0 : p->attached_flags = flags;
175 :
176 0 : return 0;
177 : }
178 :
179 5 : int bpf_program_cgroup_detach(BPFProgram *p) {
180 5 : _cleanup_close_ int fd = -1;
181 :
182 5 : assert(p);
183 :
184 5 : if (!p->attached_path)
185 5 : return -EUNATCH;
186 :
187 0 : fd = open(p->attached_path, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
188 0 : if (fd < 0) {
189 0 : if (errno != ENOENT)
190 0 : return -errno;
191 :
192 : /* If the cgroup does not exist anymore, then we don't have to explicitly detach, it got detached
193 : * implicitly by the removal, hence don't complain */
194 :
195 : } else {
196 : union bpf_attr attr;
197 :
198 0 : attr = (union bpf_attr) {
199 0 : .attach_type = p->attached_type,
200 : .target_fd = fd,
201 0 : .attach_bpf_fd = p->kernel_fd,
202 : };
203 :
204 0 : if (bpf(BPF_PROG_DETACH, &attr, sizeof(attr)) < 0)
205 0 : return -errno;
206 : }
207 :
208 0 : p->attached_path = mfree(p->attached_path);
209 :
210 0 : return 0;
211 : }
212 :
213 0 : int bpf_map_new(enum bpf_map_type type, size_t key_size, size_t value_size, size_t max_entries, uint32_t flags) {
214 0 : union bpf_attr attr = {
215 : .map_type = type,
216 : .key_size = key_size,
217 : .value_size = value_size,
218 : .max_entries = max_entries,
219 : .map_flags = flags,
220 : };
221 : int fd;
222 :
223 0 : fd = bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
224 0 : if (fd < 0)
225 0 : return -errno;
226 :
227 0 : return fd;
228 : }
229 :
230 0 : int bpf_map_update_element(int fd, const void *key, void *value) {
231 :
232 0 : union bpf_attr attr = {
233 : .map_fd = fd,
234 0 : .key = PTR_TO_UINT64(key),
235 0 : .value = PTR_TO_UINT64(value),
236 : };
237 :
238 0 : if (bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)) < 0)
239 0 : return -errno;
240 :
241 0 : return 0;
242 : }
243 :
244 0 : int bpf_map_lookup_element(int fd, const void *key, void *value) {
245 :
246 0 : union bpf_attr attr = {
247 : .map_fd = fd,
248 0 : .key = PTR_TO_UINT64(key),
249 0 : .value = PTR_TO_UINT64(value),
250 : };
251 :
252 0 : if (bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)) < 0)
253 0 : return -errno;
254 :
255 0 : return 0;
256 : }
|