/src/systemd/src/core/bpf-socket-bind.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include "alloc-util.h" |
4 | | #include "bpf-socket-bind.h" |
5 | | #include "cgroup.h" |
6 | | #include "fd-util.h" |
7 | | #include "fdset.h" |
8 | | #include "unit.h" |
9 | | |
10 | | #if BPF_FRAMEWORK |
11 | | /* libbpf, clang, llvm and bpftool compile time dependencies are satisfied */ |
12 | | #include "bpf-dlopen.h" |
13 | | #include "bpf-link.h" |
14 | | #include "bpf/socket_bind/socket-bind-api.bpf.h" |
15 | | #include "bpf/socket_bind/socket-bind-skel.h" |
16 | | |
17 | | static struct socket_bind_bpf *socket_bind_bpf_free(struct socket_bind_bpf *obj) { |
18 | | /* socket_bind_bpf__destroy handles object == NULL case */ |
19 | | (void) socket_bind_bpf__destroy(obj); |
20 | | |
21 | | return NULL; |
22 | | } |
23 | | |
24 | | DEFINE_TRIVIAL_CLEANUP_FUNC(struct socket_bind_bpf *, socket_bind_bpf_free); |
25 | | |
26 | | static int update_rules_map( |
27 | | int map_fd, |
28 | | CGroupSocketBindItem *head) { |
29 | | |
30 | | uint32_t i = 0; |
31 | | |
32 | | assert(map_fd >= 0); |
33 | | |
34 | | if (!head) { |
35 | | static const struct socket_bind_rule val = { |
36 | | .address_family = SOCKET_BIND_RULE_AF_MATCH_NOTHING, |
37 | | }; |
38 | | |
39 | | if (sym_bpf_map_update_elem(map_fd, &i, &val, BPF_ANY) != 0) |
40 | | return -errno; |
41 | | } |
42 | | |
43 | | LIST_FOREACH(socket_bind_items, item, head) { |
44 | | struct socket_bind_rule val = { |
45 | | .address_family = (uint32_t) item->address_family, |
46 | | .protocol = item->ip_protocol, |
47 | | .nr_ports = item->nr_ports, |
48 | | .port_min = item->port_min, |
49 | | }; |
50 | | |
51 | | uint32_t key = i++; |
52 | | |
53 | | if (sym_bpf_map_update_elem(map_fd, &key, &val, BPF_ANY) != 0) |
54 | | return -errno; |
55 | | } |
56 | | |
57 | | return 0; |
58 | | } |
59 | | |
60 | | static int prepare_socket_bind_bpf( |
61 | | Unit *unit, |
62 | | CGroupSocketBindItem *allow, |
63 | | CGroupSocketBindItem *deny, |
64 | | struct socket_bind_bpf **ret_obj) { |
65 | | |
66 | | _cleanup_(socket_bind_bpf_freep) struct socket_bind_bpf *obj = NULL; |
67 | | size_t allow_count = 0, deny_count = 0; |
68 | | int allow_map_fd, deny_map_fd, r; |
69 | | |
70 | | assert(ret_obj); |
71 | | |
72 | | LIST_FOREACH(socket_bind_items, item, allow) |
73 | | allow_count++; |
74 | | |
75 | | LIST_FOREACH(socket_bind_items, item, deny) |
76 | | deny_count++; |
77 | | |
78 | | if (allow_count > SOCKET_BIND_MAX_RULES) |
79 | | return log_unit_full_errno(unit, unit ? LOG_ERR : LOG_WARNING, SYNTHETIC_ERRNO(EINVAL), |
80 | | "bpf-socket-bind: Maximum number of socket bind rules=%i is exceeded", SOCKET_BIND_MAX_RULES); |
81 | | |
82 | | if (deny_count > SOCKET_BIND_MAX_RULES) |
83 | | return log_unit_full_errno(unit, unit ? LOG_ERR : LOG_WARNING, SYNTHETIC_ERRNO(EINVAL), |
84 | | "bpf-socket-bind: Maximum number of socket bind rules=%i is exceeded", SOCKET_BIND_MAX_RULES); |
85 | | |
86 | | obj = socket_bind_bpf__open(); |
87 | | if (!obj) |
88 | | return log_unit_full_errno(unit, unit ? LOG_ERR : LOG_DEBUG, errno, "bpf-socket-bind: Failed to open BPF object: %m"); |
89 | | |
90 | | if (sym_bpf_map__set_max_entries(obj->maps.sd_bind_allow, MAX(allow_count, 1u)) != 0) |
91 | | return log_unit_full_errno(unit, unit ? LOG_ERR : LOG_WARNING, errno, |
92 | | "bpf-socket-bind: Failed to resize BPF map '%s': %m", sym_bpf_map__name(obj->maps.sd_bind_allow)); |
93 | | |
94 | | if (sym_bpf_map__set_max_entries(obj->maps.sd_bind_deny, MAX(deny_count, 1u)) != 0) |
95 | | return log_unit_full_errno(unit, unit ? LOG_ERR : LOG_WARNING, errno, |
96 | | "bpf-socket-bind: Failed to resize BPF map '%s': %m", sym_bpf_map__name(obj->maps.sd_bind_deny)); |
97 | | |
98 | | if (socket_bind_bpf__load(obj) != 0) |
99 | | return log_unit_full_errno(unit, unit ? LOG_ERR : LOG_DEBUG, errno, |
100 | | "bpf-socket-bind: Failed to load BPF object: %m"); |
101 | | |
102 | | allow_map_fd = sym_bpf_map__fd(obj->maps.sd_bind_allow); |
103 | | assert(allow_map_fd >= 0); |
104 | | |
105 | | r = update_rules_map(allow_map_fd, allow); |
106 | | if (r < 0) |
107 | | return log_unit_full_errno(unit, unit ? LOG_ERR : LOG_WARNING, r, |
108 | | "bpf-socket-bind: Failed to put socket bind allow rules into BPF map '%s'", |
109 | | sym_bpf_map__name(obj->maps.sd_bind_allow)); |
110 | | |
111 | | deny_map_fd = sym_bpf_map__fd(obj->maps.sd_bind_deny); |
112 | | assert(deny_map_fd >= 0); |
113 | | |
114 | | r = update_rules_map(deny_map_fd, deny); |
115 | | if (r < 0) |
116 | | return log_unit_full_errno(unit, unit ? LOG_ERR : LOG_WARNING, r, |
117 | | "bpf-socket-bind: Failed to put socket bind deny rules into BPF map '%s'", |
118 | | sym_bpf_map__name(obj->maps.sd_bind_deny)); |
119 | | |
120 | | *ret_obj = TAKE_PTR(obj); |
121 | | return 0; |
122 | | } |
123 | | |
124 | | int bpf_socket_bind_supported(void) { |
125 | | _cleanup_(socket_bind_bpf_freep) struct socket_bind_bpf *obj = NULL; |
126 | | int r; |
127 | | |
128 | | if (dlopen_bpf_full(LOG_WARNING) < 0) |
129 | | return false; |
130 | | |
131 | | r = prepare_socket_bind_bpf(/*unit=*/NULL, /*allow_rules=*/NULL, /*deny_rules=*/NULL, &obj); |
132 | | if (r < 0) { |
133 | | log_debug_errno(r, "bpf-socket-bind: socket bind filtering is not supported: %m"); |
134 | | return false; |
135 | | } |
136 | | |
137 | | return bpf_can_link_program(obj->progs.sd_bind4); |
138 | | } |
139 | | |
140 | | int bpf_socket_bind_add_initial_link_fd(Unit *u, int fd) { |
141 | | int r; |
142 | | |
143 | | assert(u); |
144 | | |
145 | | CGroupRuntime *crt = unit_get_cgroup_runtime(u); |
146 | | if (!crt) |
147 | | return log_unit_error_errno(u, SYNTHETIC_ERRNO(EINVAL), |
148 | | "Failed to get control group runtime object."); |
149 | | |
150 | | if (!crt->initial_socket_bind_link_fds) { |
151 | | crt->initial_socket_bind_link_fds = fdset_new(); |
152 | | if (!crt->initial_socket_bind_link_fds) |
153 | | return log_oom(); |
154 | | } |
155 | | |
156 | | r = fdset_put(crt->initial_socket_bind_link_fds, fd); |
157 | | if (r < 0) |
158 | | return log_unit_error_errno(u, r, "bpf-socket-bind: Failed to put BPF fd %d to initial fdset", fd); |
159 | | |
160 | | return 0; |
161 | | } |
162 | | |
163 | | static int socket_bind_install_impl(Unit *u) { |
164 | | _cleanup_(bpf_link_freep) struct bpf_link *ipv4 = NULL, *ipv6 = NULL; |
165 | | _cleanup_(socket_bind_bpf_freep) struct socket_bind_bpf *obj = NULL; |
166 | | _cleanup_free_ char *cgroup_path = NULL; |
167 | | _cleanup_close_ int cgroup_fd = -EBADF; |
168 | | CGroupContext *cc; |
169 | | CGroupRuntime *crt; |
170 | | int r; |
171 | | |
172 | | assert(u); |
173 | | |
174 | | cc = unit_get_cgroup_context(u); |
175 | | if (!cc) |
176 | | return 0; |
177 | | |
178 | | crt = unit_get_cgroup_runtime(u); |
179 | | if (!crt) |
180 | | return 0; |
181 | | |
182 | | r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, crt->cgroup_path, NULL, &cgroup_path); |
183 | | if (r < 0) |
184 | | return log_unit_error_errno(u, r, "bpf-socket-bind: Failed to get cgroup path: %m"); |
185 | | |
186 | | if (!cc->socket_bind_allow && !cc->socket_bind_deny) |
187 | | return 0; |
188 | | |
189 | | r = prepare_socket_bind_bpf(u, cc->socket_bind_allow, cc->socket_bind_deny, &obj); |
190 | | if (r < 0) |
191 | | return log_unit_error_errno(u, r, "bpf-socket-bind: Failed to load BPF object: %m"); |
192 | | |
193 | | cgroup_fd = open(cgroup_path, O_RDONLY | O_CLOEXEC, 0); |
194 | | if (cgroup_fd < 0) |
195 | | return log_unit_error_errno(u, errno, "bpf-socket-bind: Failed to open cgroup %s for reading: %m", cgroup_path); |
196 | | |
197 | | ipv4 = sym_bpf_program__attach_cgroup(obj->progs.sd_bind4, cgroup_fd); |
198 | | r = bpf_get_error_translated(ipv4); |
199 | | if (r != 0) |
200 | | return log_unit_error_errno(u, r, "bpf-socket-bind: Failed to link '%s' cgroup-bpf program: %m", |
201 | | sym_bpf_program__name(obj->progs.sd_bind4)); |
202 | | |
203 | | ipv6 = sym_bpf_program__attach_cgroup(obj->progs.sd_bind6, cgroup_fd); |
204 | | r = bpf_get_error_translated(ipv6); |
205 | | if (r != 0) |
206 | | return log_unit_error_errno(u, r, "bpf-socket-bind: Failed to link '%s' cgroup-bpf program: %m", |
207 | | sym_bpf_program__name(obj->progs.sd_bind6)); |
208 | | |
209 | | crt->ipv4_socket_bind_link = TAKE_PTR(ipv4); |
210 | | crt->ipv6_socket_bind_link = TAKE_PTR(ipv6); |
211 | | |
212 | | return 0; |
213 | | } |
214 | | |
215 | | int bpf_socket_bind_install(Unit *u) { |
216 | | CGroupRuntime *crt; |
217 | | int r; |
218 | | |
219 | | assert(u); |
220 | | |
221 | | crt = unit_get_cgroup_runtime(u); |
222 | | if (!crt) |
223 | | return 0; |
224 | | |
225 | | r = socket_bind_install_impl(u); |
226 | | fdset_close(crt->initial_socket_bind_link_fds, /* async= */ false); |
227 | | return r; |
228 | | } |
229 | | |
230 | | int bpf_socket_bind_serialize(Unit *u, FILE *f, FDSet *fds) { |
231 | | CGroupRuntime *crt; |
232 | | int r; |
233 | | |
234 | | assert(u); |
235 | | |
236 | | crt = unit_get_cgroup_runtime(u); |
237 | | if (!crt) |
238 | | return 0; |
239 | | |
240 | | r = bpf_serialize_link(f, fds, "ipv4-socket-bind-bpf-link", crt->ipv4_socket_bind_link); |
241 | | if (r < 0) |
242 | | return r; |
243 | | |
244 | | return bpf_serialize_link(f, fds, "ipv6-socket-bind-bpf-link", crt->ipv6_socket_bind_link); |
245 | | } |
246 | | |
247 | | #else /* ! BPF_FRAMEWORK */ |
248 | 0 | int bpf_socket_bind_supported(void) { |
249 | 0 | return false; |
250 | 0 | } |
251 | | |
252 | 0 | int bpf_socket_bind_add_initial_link_fd(Unit *u, int fd) { |
253 | 0 | return 0; |
254 | 0 | } |
255 | | |
256 | 0 | int bpf_socket_bind_install(Unit *u) { |
257 | 0 | return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EOPNOTSUPP), |
258 | 0 | "bpf-socket-bind: Failed to install; BPF framework is not supported"); |
259 | 0 | } |
260 | | |
261 | 6.16k | int bpf_socket_bind_serialize(Unit *u, FILE *f, FDSet *fds) { |
262 | 6.16k | return 0; |
263 | 6.16k | } |
264 | | #endif |