Coverage Report

Created: 2026-04-12 07:02

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
  for (f = authorized; *f != NULL; f++) {
83
0
    if (regcomp(&preg, *f, REG_NOSUB) != 0)
84
0
      fatal("privsep", "unable to compile a regex");
85
0
    if (regexec(&preg, file, 0, NULL, 0) == 0) {
86
0
      regfree(&preg);
87
0
      break;
88
0
    }
89
0
    regfree(&preg);
90
0
  }
91
0
  if (*f == NULL || strstr(file, "/..")) {
92
0
    log_warnx("privsep", "not authorized to access %s", file);
93
0
    free(file);
94
0
    return NULL;
95
0
  }
96
0
  return file;
97
0
}
98
99
void
100
asroot_open()
101
0
{
102
0
  const char *authorized[] = {
103
0
    "^" PROCFS_SYS_NET "ipv4/ip_forward" "$",
104
0
    "^" PROCFS_SYS_NET "ipv6/conf/all/forwarding" "$",
105
0
    "^" "/proc/net/bonding/[^/]*" "$",
106
0
    "^" "/proc/self/net/bonding/[^/]*" "$",
107
#ifdef ENABLE_OLDIES
108
    "^" SYSFS_CLASS_NET "[^/]*/brforward" "$",
109
    "^" SYSFS_CLASS_NET "[^/]*/brport" "$",
110
    "^" SYSFS_CLASS_NET "[^/]*/brif/[^/]*/port_no" "$",
111
#endif
112
0
    "^" SYSFS_CLASS_DMI "product_version" "$",
113
0
    "^" SYSFS_CLASS_DMI "product_serial" "$",
114
0
    "^" SYSFS_CLASS_DMI "product_name" "$",
115
0
    "^" SYSFS_CLASS_DMI "bios_version" "$",
116
0
    "^" SYSFS_CLASS_DMI "sys_vendor" "$",
117
0
    "^" SYSFS_CLASS_DMI "chassis_asset_tag" "$",
118
0
    NULL,
119
0
  };
120
0
  char *file;
121
0
  int fd, rc;
122
123
0
  if ((file = asroot_read_authorized_path(authorized)) == NULL ||
124
0
    (fd = open(file, O_RDONLY)) == -1) {
125
0
    rc = -1;
126
0
    must_write(PRIV_PRIVILEGED, &rc, sizeof(int));
127
0
    free(file);
128
0
    return;
129
0
  }
130
0
  free(file);
131
0
  must_write(PRIV_PRIVILEGED, &fd, sizeof(int));
132
0
  send_fd(PRIV_PRIVILEGED, fd);
133
0
  close(fd);
134
0
}
135
136
/* Proxy for checking file existence */
137
int
138
priv_exist(const char *file)
139
0
{
140
0
  int len, rc;
141
0
  enum priv_cmd cmd = PRIV_EXIST;
142
0
  must_write(PRIV_UNPRIVILEGED, &cmd, sizeof(enum priv_cmd));
143
0
  len = strlen(file);
144
0
  must_write(PRIV_UNPRIVILEGED, &len, sizeof(int));
145
0
  must_write(PRIV_UNPRIVILEGED, file, len);
146
0
  priv_wait();
147
0
  must_read(PRIV_UNPRIVILEGED, &rc, sizeof(int));
148
0
  return rc;
149
0
}
150
151
void
152
asroot_exist()
153
0
{
154
0
  const char *authorized[] = {
155
0
    "^" SYSFS_CLASS_NET "[^/]*/wireless" "$",
156
0
    NULL,
157
0
  };
158
0
  char *file;
159
0
  int rc;
160
0
  struct stat st;
161
162
0
  if ((file = asroot_read_authorized_path(authorized)) == NULL) {
163
0
    rc = -1;
164
0
    must_write(PRIV_PRIVILEGED, &rc, sizeof(int));
165
0
    return;
166
0
  }
167
0
  rc = stat(file, &st) == 0 ? 0 : -1;
168
0
  must_write(PRIV_PRIVILEGED, &rc, sizeof(int));
169
0
  free(file);
170
0
}
171
172
/* Quirks needed by some additional interfaces. Currently, this is limited to
173
 * disabling LLDP firmware for i40e. */
174
static void
175
asroot_iface_init_quirks(int ifindex, char *name)
176
0
{
177
0
  int s = -1;
178
0
  int fd = -1;
179
180
  /* Check driver. */
181
0
  struct ethtool_drvinfo ethc = { .cmd = ETHTOOL_GDRVINFO };
182
0
  struct ifreq ifr = { .ifr_data = (caddr_t)&ethc };
183
0
  if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
184
0
    log_warn("privsep", "unable to open a socket");
185
0
    goto end;
186
0
  }
187
0
  strlcpy(ifr.ifr_name, name, IFNAMSIZ);
188
0
  if (ioctl(s, SIOCETHTOOL, &ifr) != 0 ||
189
0
      strncmp("i40e", ethc.driver, sizeof(ethc.driver))) {
190
    /* Not i40e */
191
0
    goto end;
192
0
  }
193
0
  log_info("interfaces",
194
0
      "i40e driver detected for %s, disabling LLDP in firmware", name);
195
196
  /* We assume debugfs is mounted. Otherwise, we would need to check if it
197
   * is mounted, then unshare a new mount namespace, mount it, issues the
198
   * command, leave the namespace. Let's see if there is such a need. */
199
200
  /* Alternative is to use ethtool (ethtool --set-priv-flags ens5f0
201
   * disable-fw-lldp on). However, this requires a recent firmware (from
202
   * i40e_ethtool.c):
203
   *
204
   * If the driver detected FW LLDP was disabled on init, this flag could
205
   * be set, however we do not support _changing_ the flag:
206
   * - on XL710 if NPAR is enabled or FW API version < 1.7
207
   * - on X722 with FW API version < 1.6
208
   */
209
210
0
  char command[] = "lldp stop";
211
0
  char sysfs_path[SYSFS_PATH_MAX + 1];
212
0
  if (snprintf(sysfs_path, SYSFS_PATH_MAX, "/sys/kernel/debug/i40e/%.*s/command",
213
0
    (int)sizeof(ethc.bus_info), ethc.bus_info) >= SYSFS_PATH_MAX) {
214
0
    log_warnx("interfaces", "path truncated");
215
0
    goto end;
216
0
  }
217
0
  if ((fd = open(sysfs_path, O_WRONLY)) == -1) {
218
0
    if (errno == ENOENT) {
219
0
      log_info("interfaces",
220
0
          "%s does not exist, "
221
0
          "cannot disable LLDP in firmware for %s",
222
0
          sysfs_path, name);
223
0
      goto end;
224
0
    }
225
0
    log_warn("interfaces",
226
0
        "cannot open %s to disable LLDP in firmware for %s", sysfs_path,
227
0
        name);
228
0
    goto end;
229
0
  }
230
0
  if (write(fd, command, sizeof(command) - 1) == -1) {
231
0
    log_warn("interfaces", "cannot disable LLDP in firmware for %s", name);
232
0
    goto end;
233
0
  }
234
0
end:
235
0
  if (s != -1) close(s);
236
0
  if (fd != -1) close(fd);
237
0
}
238
239
int
240
asroot_iface_init_os(int ifindex, char *name, int *fd)
241
0
{
242
0
  int rc;
243
  /* Open listening socket to receive/send frames */
244
0
  if ((*fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) {
245
0
    rc = errno;
246
0
    return rc;
247
0
  }
248
249
0
  struct sockaddr_ll sa = { .sll_family = AF_PACKET, .sll_ifindex = ifindex };
250
0
  if (bind(*fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
251
0
    rc = errno;
252
0
    log_warn("privsep", "unable to bind to raw socket for interface %s",
253
0
        name);
254
0
    return rc;
255
0
  }
256
257
  /* Set filter */
258
0
  log_debug("privsep", "set BPF filter for %s", name);
259
0
  static struct sock_filter lldpd_filter_f[] = { LLDPD_FILTER_F };
260
0
  struct sock_fprog prog = { .filter = lldpd_filter_f,
261
0
    .len = sizeof(lldpd_filter_f) / sizeof(struct sock_filter) };
262
0
  if (setsockopt(*fd, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)) < 0) {
263
0
    rc = errno;
264
0
    log_warn("privsep", "unable to change filter for %s", name);
265
0
    return rc;
266
0
  }
267
268
  /* Set priority to TC_PRIO_CONTROL for ice Intel cards. See #444. */
269
0
  int prio = TC_PRIO_CONTROL;
270
0
  if (setsockopt(*fd, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio)) < 0) {
271
0
    rc = errno;
272
0
    log_warn("privsep",
273
0
        "unable to set priority \"control\" to socket for interface %s",
274
0
        name);
275
0
    return rc;
276
0
  }
277
278
0
#ifdef SO_LOCK_FILTER
279
0
  int lock = 1;
280
0
  if (setsockopt(*fd, SOL_SOCKET, SO_LOCK_FILTER, &lock, sizeof(lock)) < 0) {
281
0
    if (errno != ENOPROTOOPT) {
282
0
      rc = errno;
283
0
      log_warn("privsep", "unable to lock filter for %s", name);
284
0
      return rc;
285
0
    }
286
0
  }
287
0
#endif
288
0
#ifdef PACKET_IGNORE_OUTGOING
289
0
  int ignore = 1;
290
0
  if (setsockopt(*fd, SOL_PACKET, PACKET_IGNORE_OUTGOING, &ignore,
291
0
    sizeof(ignore)) < 0) {
292
0
    if (errno != ENOPROTOOPT) {
293
0
      rc = errno;
294
0
      log_warn("privsep",
295
0
          "unable to set packet direction for BPF filter on %s",
296
0
          name);
297
0
      return rc;
298
0
    }
299
0
  }
300
0
#endif
301
302
0
  asroot_iface_init_quirks(ifindex, name);
303
0
  return 0;
304
0
}
305
306
int
307
asroot_iface_description_os(const char *name, const char *description)
308
0
{
309
  /* We could use netlink but this is a lot to do in a privileged
310
   * process. Just write to /sys/class/net/XXXX/ifalias. */
311
0
  char *file;
312
0
  char descr[IFALIASZ];
313
0
  FILE *fp;
314
0
  int rc;
315
0
  if (name[0] == '\0' || name[0] == '.' || strchr(name, '/') != NULL) {
316
0
    log_warnx("privsep", "odd interface name %s", name);
317
0
    return -1;
318
0
  }
319
0
  if (asprintf(&file, SYSFS_CLASS_NET "%s/ifalias", name) == -1) {
320
0
    log_warn("privsep",
321
0
        "unable to allocate memory for setting description");
322
0
    return -1;
323
0
  }
324
0
  if ((fp = fopen(file, "r+")) == NULL) {
325
0
    rc = errno;
326
0
    log_debug("privsep", "cannot open interface description for %s: %s",
327
0
        name, strerror(errno));
328
0
    free(file);
329
0
    return rc;
330
0
  }
331
0
  free(file);
332
0
  if (strlen(description) == 0 && fgets(descr, sizeof(descr), fp) != NULL) {
333
0
    if (strncmp(descr, "lldpd: ", 7) == 0) {
334
0
      if (strncmp(descr + 7, "was ", 4) == 0) {
335
        /* Already has an old neighbor */
336
0
        fclose(fp);
337
0
        return 0;
338
0
      } else {
339
        /* Append was */
340
0
        memmove(descr + 11, descr + 7, sizeof(descr) - 11);
341
0
        memcpy(descr, "lldpd: was ", 11);
342
0
      }
343
0
    } else {
344
      /* No description, no neighbor */
345
0
      strlcpy(descr, "lldpd: no neighbor", sizeof(descr));
346
0
    }
347
0
  } else
348
0
    snprintf(descr, sizeof(descr), "lldpd: connected to %s", description);
349
0
  if (fputs(descr, fp) == EOF) {
350
0
    log_debug("privsep", "cannot set interface description for %s", name);
351
0
    fclose(fp);
352
0
    return -1;
353
0
  }
354
0
  fclose(fp);
355
0
  return 0;
356
0
}
357
358
int
359
asroot_iface_promisc_os(const char *name)
360
0
{
361
0
  int s, rc;
362
0
  if ((s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) {
363
0
    rc = errno;
364
0
    log_warn("privsep", "unable to open raw socket");
365
0
    return rc;
366
0
  }
367
368
0
  struct ifreq ifr = {};
369
0
  strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
370
371
0
  if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) {
372
0
    rc = errno;
373
0
    log_warn("privsep", "unable to get interface flags for %s", name);
374
0
    close(s);
375
0
    return rc;
376
0
  }
377
378
0
  if (ifr.ifr_flags & IFF_PROMISC) {
379
0
    close(s);
380
0
    return 0;
381
0
  }
382
0
  ifr.ifr_flags |= IFF_PROMISC;
383
0
  if (ioctl(s, SIOCSIFFLAGS, &ifr) == -1) {
384
0
    rc = errno;
385
0
    log_warn("privsep", "unable to set promisc mode for %s", name);
386
0
    close(s);
387
0
    return rc;
388
0
  }
389
0
  log_info("privsep", "promiscuous mode enabled for %s", name);
390
0
  close(s);
391
0
  return 0;
392
0
}