Coverage Report

Created: 2026-08-13 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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)&ethc };
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
}