/src/lldpd/src/daemon/priv-linux.c
Line | Count | Source |
1 | | /* -*- mode: c; c-file-style: "openbsd" -*- */ |
2 | | /* |
3 | | * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx> |
4 | | * |
5 | | * Permission to use, copy, modify, and/or distribute this software for any |
6 | | * purpose with or without fee is hereby granted, provided that the above |
7 | | * copyright notice and this permission notice appear in all copies. |
8 | | * |
9 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
10 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
11 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
12 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
13 | | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
14 | | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
15 | | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | | */ |
17 | | |
18 | | #include "lldpd.h" |
19 | | |
20 | | #include <unistd.h> |
21 | | #include <inttypes.h> |
22 | | #include <sys/types.h> |
23 | | #include <sys/stat.h> |
24 | | #include <limits.h> |
25 | | #include <fcntl.h> |
26 | | #include <errno.h> |
27 | | #include <regex.h> |
28 | | #include <sys/ioctl.h> |
29 | | #if defined(__clang__) |
30 | | # pragma clang diagnostic push |
31 | | # pragma clang diagnostic ignored "-Wdocumentation" |
32 | | #endif |
33 | | #include <linux/filter.h> /* For BPF filtering */ |
34 | | #include <linux/sockios.h> |
35 | | #include <linux/if_ether.h> |
36 | | #include <linux/if_packet.h> |
37 | | #include <linux/ethtool.h> |
38 | | #if defined(__clang__) |
39 | | # pragma clang diagnostic pop |
40 | | #endif |
41 | | |
42 | | /* Defined in linux/pkt_sched.h */ |
43 | 0 | #define TC_PRIO_CONTROL 7 |
44 | | /* Defined in sysfs/libsysfs.h */ |
45 | 0 | #define SYSFS_PATH_MAX 256 |
46 | | |
47 | | /* Proxy for open */ |
48 | | int |
49 | | priv_open(const char *file) |
50 | 0 | { |
51 | 0 | int len, rc; |
52 | 0 | enum priv_cmd cmd = PRIV_OPEN; |
53 | 0 | must_write(PRIV_UNPRIVILEGED, &cmd, sizeof(enum priv_cmd)); |
54 | 0 | len = strlen(file); |
55 | 0 | must_write(PRIV_UNPRIVILEGED, &len, sizeof(int)); |
56 | 0 | must_write(PRIV_UNPRIVILEGED, file, len); |
57 | 0 | priv_wait(); |
58 | 0 | must_read(PRIV_UNPRIVILEGED, &rc, sizeof(int)); |
59 | 0 | if (rc == -1) return rc; |
60 | 0 | return receive_fd(PRIV_UNPRIVILEGED); |
61 | 0 | } |
62 | | |
63 | | /** |
64 | | * Read a file path from the privileged channel and validate it against a list |
65 | | * of authorized regex patterns. Returns the allocated path on success or NULL |
66 | | * on failure. |
67 | | */ |
68 | | static char * |
69 | | asroot_read_authorized_path(const char **authorized) |
70 | 0 | { |
71 | 0 | const char **f; |
72 | 0 | char *file; |
73 | 0 | int len; |
74 | 0 | regex_t preg; |
75 | |
|
76 | 0 | must_read(PRIV_PRIVILEGED, &len, sizeof(len)); |
77 | 0 | if (len < 0 || len > PATH_MAX) fatalx("privsep", "too large value requested"); |
78 | 0 | if ((file = (char *)malloc(len + 1)) == NULL) fatal("privsep", NULL); |
79 | 0 | must_read(PRIV_PRIVILEGED, file, len); |
80 | 0 | file[len] = '\0'; |
81 | |
|
82 | 0 | if (memchr(file, '\0', len) != NULL) { |
83 | 0 | log_warnx("privsep", "embedded NUL byte in path"); |
84 | 0 | free(file); |
85 | 0 | return NULL; |
86 | 0 | } |
87 | | |
88 | 0 | for (f = authorized; *f != NULL; f++) { |
89 | 0 | if (regcomp(&preg, *f, REG_NOSUB) != 0) |
90 | 0 | fatal("privsep", "unable to compile a regex"); |
91 | 0 | if (regexec(&preg, file, 0, NULL, 0) == 0) { |
92 | 0 | regfree(&preg); |
93 | 0 | break; |
94 | 0 | } |
95 | 0 | regfree(&preg); |
96 | 0 | } |
97 | 0 | if (*f == NULL || strstr(file, "/..")) { |
98 | 0 | log_warnx("privsep", "not authorized to access %s", file); |
99 | 0 | free(file); |
100 | 0 | return NULL; |
101 | 0 | } |
102 | 0 | return file; |
103 | 0 | } |
104 | | |
105 | | void |
106 | | asroot_open() |
107 | 0 | { |
108 | 0 | const char *authorized[] = { |
109 | 0 | "^" PROCFS_SYS_NET "ipv4/ip_forward" "$", |
110 | 0 | "^" PROCFS_SYS_NET "ipv6/conf/all/forwarding" "$", |
111 | 0 | "^" "/proc/net/bonding/[^/]*" "$", |
112 | 0 | "^" "/proc/self/net/bonding/[^/]*" "$", |
113 | | #ifdef ENABLE_OLDIES |
114 | | "^" SYSFS_CLASS_NET "[^/]*/brforward" "$", |
115 | | "^" SYSFS_CLASS_NET "[^/]*/brport" "$", |
116 | | "^" SYSFS_CLASS_NET "[^/]*/brif/[^/]*/port_no" "$", |
117 | | #endif |
118 | 0 | "^" SYSFS_CLASS_DMI "product_version" "$", |
119 | 0 | "^" SYSFS_CLASS_DMI "product_serial" "$", |
120 | 0 | "^" SYSFS_CLASS_DMI "product_name" "$", |
121 | 0 | "^" SYSFS_CLASS_DMI "bios_version" "$", |
122 | 0 | "^" SYSFS_CLASS_DMI "sys_vendor" "$", |
123 | 0 | "^" SYSFS_CLASS_DMI "chassis_asset_tag" "$", |
124 | 0 | NULL, |
125 | 0 | }; |
126 | 0 | char *file; |
127 | 0 | int fd, rc; |
128 | |
|
129 | 0 | if ((file = asroot_read_authorized_path(authorized)) == NULL || |
130 | 0 | (fd = open(file, O_RDONLY)) == -1) { |
131 | 0 | rc = -1; |
132 | 0 | must_write(PRIV_PRIVILEGED, &rc, sizeof(int)); |
133 | 0 | free(file); |
134 | 0 | return; |
135 | 0 | } |
136 | 0 | free(file); |
137 | 0 | must_write(PRIV_PRIVILEGED, &fd, sizeof(int)); |
138 | 0 | send_fd(PRIV_PRIVILEGED, fd); |
139 | 0 | close(fd); |
140 | 0 | } |
141 | | |
142 | | /* Proxy for checking file existence */ |
143 | | int |
144 | | priv_exist(const char *file) |
145 | 0 | { |
146 | 0 | int len, rc; |
147 | 0 | enum priv_cmd cmd = PRIV_EXIST; |
148 | 0 | must_write(PRIV_UNPRIVILEGED, &cmd, sizeof(enum priv_cmd)); |
149 | 0 | len = strlen(file); |
150 | 0 | must_write(PRIV_UNPRIVILEGED, &len, sizeof(int)); |
151 | 0 | must_write(PRIV_UNPRIVILEGED, file, len); |
152 | 0 | priv_wait(); |
153 | 0 | must_read(PRIV_UNPRIVILEGED, &rc, sizeof(int)); |
154 | 0 | return rc; |
155 | 0 | } |
156 | | |
157 | | void |
158 | | asroot_exist() |
159 | 0 | { |
160 | 0 | const char *authorized[] = { |
161 | 0 | "^" SYSFS_CLASS_NET "[^/]*/wireless" "$", |
162 | 0 | NULL, |
163 | 0 | }; |
164 | 0 | char *file; |
165 | 0 | int rc; |
166 | 0 | struct stat st; |
167 | |
|
168 | 0 | if ((file = asroot_read_authorized_path(authorized)) == NULL) { |
169 | 0 | rc = -1; |
170 | 0 | must_write(PRIV_PRIVILEGED, &rc, sizeof(int)); |
171 | 0 | return; |
172 | 0 | } |
173 | 0 | rc = stat(file, &st) == 0 ? 0 : -1; |
174 | 0 | must_write(PRIV_PRIVILEGED, &rc, sizeof(int)); |
175 | 0 | free(file); |
176 | 0 | } |
177 | | |
178 | | /* Quirks needed by some additional interfaces. Currently, this is limited to |
179 | | * disabling LLDP firmware for i40e. */ |
180 | | static void |
181 | | asroot_iface_init_quirks(int ifindex, char *name) |
182 | 0 | { |
183 | 0 | int s = -1; |
184 | 0 | int fd = -1; |
185 | | |
186 | | /* Check driver. */ |
187 | 0 | struct ethtool_drvinfo ethc = { .cmd = ETHTOOL_GDRVINFO }; |
188 | 0 | struct ifreq ifr = { .ifr_data = (caddr_t)ðc }; |
189 | 0 | if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { |
190 | 0 | log_warn("privsep", "unable to open a socket"); |
191 | 0 | goto end; |
192 | 0 | } |
193 | 0 | strlcpy(ifr.ifr_name, name, IFNAMSIZ); |
194 | 0 | if (ioctl(s, SIOCETHTOOL, &ifr) != 0 || |
195 | 0 | strncmp("i40e", ethc.driver, sizeof(ethc.driver))) { |
196 | | /* Not i40e */ |
197 | 0 | goto end; |
198 | 0 | } |
199 | 0 | log_info("interfaces", |
200 | 0 | "i40e driver detected for %s, disabling LLDP in firmware", name); |
201 | | |
202 | | /* We assume debugfs is mounted. Otherwise, we would need to check if it |
203 | | * is mounted, then unshare a new mount namespace, mount it, issues the |
204 | | * command, leave the namespace. Let's see if there is such a need. */ |
205 | | |
206 | | /* Alternative is to use ethtool (ethtool --set-priv-flags ens5f0 |
207 | | * disable-fw-lldp on). However, this requires a recent firmware (from |
208 | | * i40e_ethtool.c): |
209 | | * |
210 | | * If the driver detected FW LLDP was disabled on init, this flag could |
211 | | * be set, however we do not support _changing_ the flag: |
212 | | * - on XL710 if NPAR is enabled or FW API version < 1.7 |
213 | | * - on X722 with FW API version < 1.6 |
214 | | */ |
215 | |
|
216 | 0 | char command[] = "lldp stop"; |
217 | 0 | char sysfs_path[SYSFS_PATH_MAX + 1]; |
218 | 0 | if (snprintf(sysfs_path, SYSFS_PATH_MAX, "/sys/kernel/debug/i40e/%.*s/command", |
219 | 0 | (int)sizeof(ethc.bus_info), ethc.bus_info) >= SYSFS_PATH_MAX) { |
220 | 0 | log_warnx("interfaces", "path truncated"); |
221 | 0 | goto end; |
222 | 0 | } |
223 | 0 | if ((fd = open(sysfs_path, O_WRONLY)) == -1) { |
224 | 0 | if (errno == ENOENT) { |
225 | 0 | log_info("interfaces", |
226 | 0 | "%s does not exist, " |
227 | 0 | "cannot disable LLDP in firmware for %s", |
228 | 0 | sysfs_path, name); |
229 | 0 | goto end; |
230 | 0 | } |
231 | 0 | log_warn("interfaces", |
232 | 0 | "cannot open %s to disable LLDP in firmware for %s", sysfs_path, |
233 | 0 | name); |
234 | 0 | goto end; |
235 | 0 | } |
236 | 0 | if (write(fd, command, sizeof(command) - 1) == -1) { |
237 | 0 | log_warn("interfaces", "cannot disable LLDP in firmware for %s", name); |
238 | 0 | goto end; |
239 | 0 | } |
240 | 0 | end: |
241 | 0 | if (s != -1) close(s); |
242 | 0 | if (fd != -1) close(fd); |
243 | 0 | } |
244 | | |
245 | | int |
246 | | asroot_iface_init_os(int ifindex, char *name, int *fd) |
247 | 0 | { |
248 | 0 | int rc; |
249 | | /* Open listening socket to receive/send frames */ |
250 | 0 | if ((*fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) { |
251 | 0 | rc = errno; |
252 | 0 | return rc; |
253 | 0 | } |
254 | | |
255 | 0 | struct sockaddr_ll sa = { .sll_family = AF_PACKET, .sll_ifindex = ifindex }; |
256 | 0 | if (bind(*fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { |
257 | 0 | rc = errno; |
258 | 0 | log_warn("privsep", "unable to bind to raw socket for interface %s", |
259 | 0 | name); |
260 | 0 | return rc; |
261 | 0 | } |
262 | | |
263 | | /* Set filter */ |
264 | 0 | log_debug("privsep", "set BPF filter for %s", name); |
265 | 0 | static struct sock_filter lldpd_filter_f[] = { LLDPD_FILTER_F }; |
266 | 0 | struct sock_fprog prog = { .filter = lldpd_filter_f, |
267 | 0 | .len = sizeof(lldpd_filter_f) / sizeof(struct sock_filter) }; |
268 | 0 | if (setsockopt(*fd, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)) < 0) { |
269 | 0 | rc = errno; |
270 | 0 | log_warn("privsep", "unable to change filter for %s", name); |
271 | 0 | return rc; |
272 | 0 | } |
273 | | |
274 | | /* Set priority to TC_PRIO_CONTROL for ice Intel cards. See #444. */ |
275 | 0 | int prio = TC_PRIO_CONTROL; |
276 | 0 | if (setsockopt(*fd, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio)) < 0) { |
277 | 0 | rc = errno; |
278 | 0 | log_warn("privsep", |
279 | 0 | "unable to set priority \"control\" to socket for interface %s", |
280 | 0 | name); |
281 | 0 | return rc; |
282 | 0 | } |
283 | | |
284 | 0 | #ifdef SO_LOCK_FILTER |
285 | 0 | int lock = 1; |
286 | 0 | if (setsockopt(*fd, SOL_SOCKET, SO_LOCK_FILTER, &lock, sizeof(lock)) < 0) { |
287 | 0 | if (errno != ENOPROTOOPT) { |
288 | 0 | rc = errno; |
289 | 0 | log_warn("privsep", "unable to lock filter for %s", name); |
290 | 0 | return rc; |
291 | 0 | } |
292 | 0 | } |
293 | 0 | #endif |
294 | 0 | #ifdef PACKET_IGNORE_OUTGOING |
295 | 0 | int ignore = 1; |
296 | 0 | if (setsockopt(*fd, SOL_PACKET, PACKET_IGNORE_OUTGOING, &ignore, |
297 | 0 | sizeof(ignore)) < 0) { |
298 | 0 | if (errno != ENOPROTOOPT) { |
299 | 0 | rc = errno; |
300 | 0 | log_warn("privsep", |
301 | 0 | "unable to set packet direction for BPF filter on %s", |
302 | 0 | name); |
303 | 0 | return rc; |
304 | 0 | } |
305 | 0 | } |
306 | 0 | #endif |
307 | | |
308 | 0 | asroot_iface_init_quirks(ifindex, name); |
309 | 0 | return 0; |
310 | 0 | } |
311 | | |
312 | | int |
313 | | asroot_iface_description_os(const char *name, const char *description) |
314 | 0 | { |
315 | | /* We could use netlink but this is a lot to do in a privileged |
316 | | * process. Just write to /sys/class/net/XXXX/ifalias. */ |
317 | 0 | char *file; |
318 | 0 | char descr[IFALIASZ]; |
319 | 0 | FILE *fp; |
320 | 0 | int rc; |
321 | 0 | if (name[0] == '\0' || name[0] == '.' || strchr(name, '/') != NULL) { |
322 | 0 | log_warnx("privsep", "odd interface name %s", name); |
323 | 0 | return -1; |
324 | 0 | } |
325 | 0 | if (asprintf(&file, SYSFS_CLASS_NET "%s/ifalias", name) == -1) { |
326 | 0 | log_warn("privsep", |
327 | 0 | "unable to allocate memory for setting description"); |
328 | 0 | return -1; |
329 | 0 | } |
330 | 0 | if ((fp = fopen(file, "r+")) == NULL) { |
331 | 0 | rc = errno; |
332 | 0 | log_debug("privsep", "cannot open interface description for %s: %s", |
333 | 0 | name, strerror(errno)); |
334 | 0 | free(file); |
335 | 0 | return rc; |
336 | 0 | } |
337 | 0 | free(file); |
338 | 0 | if (strlen(description) == 0 && fgets(descr, sizeof(descr), fp) != NULL) { |
339 | 0 | if (strncmp(descr, "lldpd: ", 7) == 0) { |
340 | 0 | if (strncmp(descr + 7, "was ", 4) == 0) { |
341 | | /* Already has an old neighbor */ |
342 | 0 | fclose(fp); |
343 | 0 | return 0; |
344 | 0 | } else { |
345 | | /* Append was */ |
346 | 0 | memmove(descr + 11, descr + 7, sizeof(descr) - 11); |
347 | 0 | memcpy(descr, "lldpd: was ", 11); |
348 | 0 | } |
349 | 0 | } else { |
350 | | /* No description, no neighbor */ |
351 | 0 | strlcpy(descr, "lldpd: no neighbor", sizeof(descr)); |
352 | 0 | } |
353 | 0 | } else |
354 | 0 | snprintf(descr, sizeof(descr), "lldpd: connected to %s", description); |
355 | 0 | if (fputs(descr, fp) == EOF) { |
356 | 0 | log_debug("privsep", "cannot set interface description for %s", name); |
357 | 0 | fclose(fp); |
358 | 0 | return -1; |
359 | 0 | } |
360 | 0 | fclose(fp); |
361 | 0 | return 0; |
362 | 0 | } |
363 | | |
364 | | int |
365 | | asroot_iface_promisc_os(const char *name) |
366 | 0 | { |
367 | 0 | int s, rc; |
368 | 0 | if ((s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) { |
369 | 0 | rc = errno; |
370 | 0 | log_warn("privsep", "unable to open raw socket"); |
371 | 0 | return rc; |
372 | 0 | } |
373 | | |
374 | 0 | struct ifreq ifr = {}; |
375 | 0 | strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); |
376 | |
|
377 | 0 | if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) { |
378 | 0 | rc = errno; |
379 | 0 | log_warn("privsep", "unable to get interface flags for %s", name); |
380 | 0 | close(s); |
381 | 0 | return rc; |
382 | 0 | } |
383 | | |
384 | 0 | if (ifr.ifr_flags & IFF_PROMISC) { |
385 | 0 | close(s); |
386 | 0 | return 0; |
387 | 0 | } |
388 | 0 | ifr.ifr_flags |= IFF_PROMISC; |
389 | 0 | if (ioctl(s, SIOCSIFFLAGS, &ifr) == -1) { |
390 | 0 | rc = errno; |
391 | 0 | log_warn("privsep", "unable to set promisc mode for %s", name); |
392 | 0 | close(s); |
393 | 0 | return rc; |
394 | 0 | } |
395 | 0 | log_info("privsep", "promiscuous mode enabled for %s", name); |
396 | 0 | close(s); |
397 | 0 | return 0; |
398 | 0 | } |