TristanInSec [Tue, 12 May 2026 10:01:57 +0000 (06:01 -0400)]
Fix heap OOB read in VLAN decapsulation memmove
In lldpd_decode(), the VLAN decapsulation memmove shifts frame data
4 bytes left starting at offset 2*ETHER_ADDR_LEN. The source pointer
is correctly offset by +4, but the length argument uses the full
remaining frame length (s - 2*ETHER_ADDR_LEN) instead of accounting
for the 4-byte shift (s - 2*ETHER_ADDR_LEN - 4).
When the received frame fills the hardware MTU allocation exactly,
the memmove reads 4 bytes past the end of the heap buffer.
Vincent Bernat [Sat, 9 May 2026 13:03:07 +0000 (15:03 +0200)]
daemon/lldpd: return NULL from lldpd_get_os_release on empty result
If `/etc/os-release` exists but has no PRETTY_NAME entry, the static
release buffer is left empty and the function returned a pointer to an
empty string. The caller then reads `lsb_release[strlen(...) - 1]`, i.e.
`lsb_release[-1]`. Match `lldpd_get_lsb_release()`'s contract by
returning NULL when there is no result, so the fallback path runs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Sat, 9 May 2026 13:02:38 +0000 (15:02 +0200)]
daemon/edp: dedup management addresses against the destination list
When merging VLAN-only EDP frames into an existing port, the dedup
loop searched chassis->c_mgmt — the source we were draining — instead
of oport->p_chassis->c_mgmt where the entries were about to be
inserted. The check could therefore never fire and duplicates
accumulated on the destination chassis. Free duplicates that the
destination already has.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Sat, 9 May 2026 13:01:50 +0000 (15:01 +0200)]
daemon/interfaces-bsd: free req in ifbsd_check_bridge
The function allocated req via `realloc()` and never freed it, leaking
on every interface refresh. Add a goto end pattern so all exit paths
release it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Sat, 9 May 2026 13:00:02 +0000 (15:00 +0200)]
daemon/priv-bsd: fix typos in asroot_iface_description_os
`socket()` returns -1 on error, not 1. The previous check treated fd 1
(stdout): as the failure case and any actual socket creation failure as
success, leading to ioctl(-1, ...) afterwards.
Also fix the FreeBSD-only line that referenced an undeclared 'ift'
instead of the local 'ifr', which would have failed to compile on
FreeBSD when IFDESCRSIZE is defined (but does it happen since we never
caught this before?).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Sat, 9 May 2026 12:04:13 +0000 (14:04 +0200)]
marshal: use ssize_t for size accumulators in unserialize
total_len and size were int. Comparisons against len (size_t) relied on
implicit signed/unsigned promotion to reject negative values, which
worked only because the resulting size_t was huge. Use ssize_t so the
sign is explicit, cast to size_t when comparing against len, and reject
a negative osize for fixed strings outright instead of letting calloc
fail later.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Sat, 9 May 2026 12:58:57 +0000 (14:58 +0200)]
daemon/seccomp: use only async-signal-safe calls in SIGSYS handler
`log_warnx()` and `fatalx()` use stdio/syslog and are not async-signal-
safe; if the handler interrupts the main thread mid-log, the resulting
deadlock would mask the real seccomp violation. Replace with direct
`write(2)` of a fixed prefix and the looked-up syscall name.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Sat, 9 May 2026 12:58:44 +0000 (14:58 +0200)]
log: do not re-feed caller fmt as a format string to stderr
vlog() built a new format string with the original fmt embedded as a
%s argument and then passed the composite to vfprintf(). Any %
specifier in fmt would be interpreted twice, which is fine for the
literal format strings used today but a footgun for any future caller
that lets fmt come from untrusted input.
Format the caller's message once via vasprintf() and substitute the
resulting plain string into the prefix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Sat, 9 May 2026 12:58:39 +0000 (14:58 +0200)]
daemon/event: avoid double-free of client on send failure
levent_ctl_send() used to free the client on write failure. When
called via levent_ctl_send_cb() from client_handle_client() inside
levent_ctl_recv(), the recv side then jumped to recv_error and freed
the same client a second time.
Stop freeing inside levent_ctl_send() and make the caller responsible
(levent_ctl_notify() now frees on -1; the recv path already does).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Sat, 9 May 2026 12:58:32 +0000 (14:58 +0200)]
ctl: tighten umask around bind() for the control socket
Between bind() and the chown()/chmod() done by the caller, the unix
socket inode existed with whatever 0777 & ~current_umask produced,
typically 0755 — non-root users could connect during that window.
Set umask(S_IRWXO) for the duration of bind() so 'other' bits are
masked. Only apply when ENABLE_PRIVSEP is set since the chown/chmod
follow-up is itself privsep-only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Sat, 9 May 2026 12:57:20 +0000 (14:57 +0200)]
daemon/priv-linux: reject embedded NUL in authorized path
The path comes from the unprivileged process and must_read() doesn't
care about NUL bytes inside the buffer. An embedded NUL would let the
regex check see a truncated path while open() saw the full one.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Sat, 9 May 2026 12:56:41 +0000 (14:56 +0200)]
daemon/priv: mark monitored as volatile sig_atomic_t
It is read by `sig_pass_to_chld()` and `sig_chld()` from signal context,
and written from the main thread (after fork). Plain int is not
guaranteed to be safe across this boundary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Fri, 8 May 2026 21:11:41 +0000 (23:11 +0200)]
client/tokenizer: heap-allocate the work buffer
Replace the variable-length array sized by 2 * strlen(line) + 3 with a
calloc()/free() pair. Long input lines (e.g. read from a config file)
could otherwise grow the stack without bound.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`CFStringGetCStringPtr` can return NULL if Core Foundation does not have
an internal C-string representation available; `strdup(NULL)` is
undefined. Fall back to CFStringGetCString into a heap buffer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Sat, 9 May 2026 12:42:46 +0000 (14:42 +0200)]
daemon/agent: guard against NULL/empty net-snmp log message
`strdup(NULL)` is undefined and `msg[strlen(msg) - 1]` reads `msg[-1]`
when the message is empty. Bail out on NULL and skip the
trailing-newline strip when the message is empty.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Sat, 9 May 2026 12:41:40 +0000 (14:41 +0200)]
daemon/client: bound MED location data length
A client could submit `set->med_location` with `data_len` negative or
absurdly large; the subsequent `malloc()`/`memcpy` in the daemon would
either request huge sizes or rely on malloc failure to recover. Reject
obviously invalid lengths at the boundary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Sat, 9 May 2026 12:40:54 +0000 (14:40 +0200)]
client/json: escape object keys
Object keys were emitted via `fprintf("\"%s\": ", ...)` without
escaping. `json_element_cleanup()` can promote a child element's name
string into the parent's key, so a neighbor-controlled string containing
a quote or backslash could inject into a key.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Sat, 9 May 2026 12:39:09 +0000 (14:39 +0200)]
daemon/priv: fix ctlname parameter shadowing file-scope global
The local parameter shadowed the static ctlname, so `strdup()` was
assigned to the parameter and the global stayed NULL. As a result,
`asroot_ctl_cleanup()` never cleaned up the registered control socket.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Sat, 9 May 2026 12:38:33 +0000 (14:38 +0200)]
daemon/seccomp: fix out-of-bounds index of syscall_names
The bound used `sizeof(syscall_names)` (byte size of the pointer array)
instead of the entry count, allowing the SIGSYS handler to read up to
`sizeof(char*)-1` entries past the end of the table when an unexpected
syscall number was trapped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A neighbor sending a Management Address TLV with addr_str_length == 0
caused `addr_length` to underflow and `addr_str_buffer[0]` (the address
family byte) to be read uninitialized.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vincent Bernat [Tue, 10 Mar 2026 07:29:47 +0000 (08:29 +0100)]
daemon: fix arbitrary file deletion in the privileged process
The `asroot_ctl_cleanup()` handler reads an arbitrary path from the
unprivileged process and deletes it. Instead, introduce
`asroot_ctl_cleanup_lock()` to only clean the lock and
`asroot_ctl_cleanup()` cleans the socket.
Ciro Iriarte [Mon, 9 Mar 2026 20:52:54 +0000 (17:52 -0300)]
interfaces: use ethtool link mode bits for accurate MAU type selection (#771)
* interfaces: use ethtool link mode bits for accurate MAU type selection
For speeds >= 10G, consult the ethtool supported link mode bitmask
to determine the correct MAU type instead of relying solely on
speed + port type. This fixes incorrect MAU type announcements
(e.g., 100G-SR4 transceivers being reported as 100G-LR4).
Fall back to the existing port-type approximation when no link mode
bit matches (e.g., legacy kernels without GLINKSETTINGS).
Add missing ethtool link mode bit definitions (bits 52-89) and speed
defines for 200G/400G to the local ethtool header.
Fixes: #477
* interfaces: move mau variable to function scope
Address review feedback: declare mau at the top of iflinux_macphy()
instead of inside each case block.
* interfaces: simplify mau assignment with combined if-assign pattern
Vincent Bernat [Mon, 9 Mar 2026 19:46:31 +0000 (20:46 +0100)]
daemon: fix path traversal vulnerability in asroot_iface_description_os()
`asroot_iface_description_os()` a sysfs path from an interface name
received from the unprivileged process. The validation only rejects `\0`
or `.` in first position. Add `/` to the list of rejected characters to
avoid path traversal.
Vincent Bernat [Mon, 9 Mar 2026 19:19:11 +0000 (20:19 +0100)]
daemon: fix path traversal vulnerability in asroot_open()
asroot_open() in src/daemon/priv-linux.c validates file paths against a
list of regex patterns using regexec(). The patterns are not anchored
with ^ or $, so regexec() performs substring matching. A path containing
an authorized pattern as a substring — with additional ../ traversal
components — passes validation but resolves to a different file when
passed to open().
For example, the pattern `/proc/net/bonding/[^.][^/]*` matches both:
- `/proc/net/bonding/bond0` (intended)
- `/proc/net/bonding/bond0/../../self/environ` (traversal — if bond0 is a directory)
Use anchors in the regex. Also, refuse any path containing `/..`, as it
is not possible to express with POSIX basic regular expression.
tcollet [Wed, 21 Jan 2026 09:29:07 +0000 (10:29 +0100)]
interface: add alias on neighbors display
With previous commit it is possible to configure (PortID / Port Descr) to
provide the macaddress and the interface alias.
To help to retrieve the information the command 'lldpcli show neighbors'
provides the alias of the interface if present.
xndr [Wed, 15 Oct 2025 18:41:57 +0000 (11:41 -0700)]
Add missing compat.h header under src/client/
Under certain conditions, build would fail due to a missing reference to
\#include "../compat/compat.h" in text_writer.c, kv_writer.c and
xml_writer.c.
example:
---------------
lldpd-1.0.20/src/client/text_writer.c:157:9: fatal error: call to undeclared function 'rpl_malloc'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
priv = malloc(sizeof(*priv));
^
../../config.h:316:16: note: expanded from macro 'malloc'
\#define malloc rpl_malloc
^
1 error generated.
make[2]: *** [lldpcli-text_writer.o] Error 1
make[1]: *** [install-recursive] Error 1
make: *** [stamp-x86_64] Error 2
---------------
lldpd-1.0.20/src/client/kv_writer.c:41:19: fatal error: call to undeclared function 'rpl_malloc'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
if ((newprefix = malloc(s + 1)) == NULL) fatal(NULL, NULL);
^
../../config.h:316:16: note: expanded from macro 'malloc'
\#define malloc rpl_malloc
^
1 error generated.
make[2]: *** [lldpcli-kv_writer.o] Error 1
make[1]: *** [install-recursive] Error 1
make: *** [stamp-x86_64] Error 2
---------------
lldpd-1.0.20/src/client/xml_writer.c:139:9: fatal error: call to undeclared function 'rpl_malloc'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
priv = malloc(sizeof(*priv));
^
../../config.h:316:16: note: expanded from macro 'malloc'
\#define malloc rpl_malloc
^
1 error generated.
make[2]: *** [lldpcli-xml_writer.o] Error 1
make[1]: *** [install-recursive] Error 1
make: *** [stamp-x86_64] Error 2
Vincent Bernat [Sat, 30 Aug 2025 07:56:30 +0000 (09:56 +0200)]
daemon/interfaces: fix management address selection when negative
When we had a negative IP address, and nothing positive, the address was
still selected as it didn't match an interface. When all negative, we
should only select an address if both IP and interface are allowed.
ugoldfeld [Tue, 19 Aug 2025 17:56:44 +0000 (20:56 +0300)]
add support for vlan-advertisements configuration (#740)
* add support for vlan-advertisements configuration
adding new configurations configure [ports ethx [,..]] lldp vlan-advertisements pattern and
unconfigure [ports ethx,[,...]] lldp vlan-advertisements pattern.
The commands enable control of which vlans are advertised
* Code review changes
Updated manual page.
Added add an example of invocation in tests/lldpcli.conf.
Added tests to tests/integration/test_dot1.py.
Vlan formatting fixes.
Vincent Bernat [Sat, 9 Aug 2025 16:45:30 +0000 (18:45 +0200)]
daemon/interfaces: fix double-free when unable to initialize interface
When an interface is converted from one type to another and cannot be
initialized, we free it twice: once on the error and again when removing
unused interfaces.
Remove the first occurrence and ensure we get in a state where the
interface can be both cleaned up or reinstantiated in a later round.
A std::error_code stores the error category only by reference, hence, we must use an object in static storage.
Add lldpcli::make_error_code for convenience similar to std::make_error_code.