Describe the bug
In src/print.c:1333, the port-formatting block decrements the remaining-length counter nl by len - 1 instead of len + 1:
if (port) {
if (((len = strlen(port)) + 1) >= nl)
goto addr_too_long;
(void)snpf(np, nl, ":%s", port);
np += len + 1;
nl -= len - 1; // BUG: should be len + 1
}
snpf writes a colon plus the port string (len + 1 bytes), and np correctly advances by len + 1, but nl only shrinks by len - 1 — leaving it 2 too large after each port field. For an ESTABLISHED connection (two host:port pairs), the inflated nl lets the final snpf write 2 bytes past the end of the heap-allocated Namech buffer.
Fix: change nl -= len - 1 to nl -= len + 1.
To Reproduce
Self-contained Dockerfile that builds lsof from master with AddressSanitizer, injects a long /etc/services entry, and triggers the overflow:
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y \
git gcc make autoconf automake libtool pkg-config \
netcat-openbsd iproute2 groff-base \
&& rm -rf /var/lib/apt/lists/*
RUN git clone --depth 1 https://github.com/lsof-org/lsof.git /lsof
WORKDIR /lsof
RUN autoreconf -fiv && \
./configure CFLAGS="-fsanitize=address -g -O0 -fno-omit-frame-pointer" \
LDFLAGS="-fsanitize=address" \
--disable-manpage && \
make -j$(nproc)
RUN printf '%s\t\t44444/tcp\n' \
"$(head -c 4070 < /dev/zero | tr '\0' 'A')" >> /etc/services
CMD sh -c '\
nc -l -p 44444 &\
sleep 0.5 && \
nc 127.0.0.1 44444 < /dev/null &\
sleep 0.5 && \
./lsof -i :44444 ; \
echo "exit code: $?"'
- Save the above as
Dockerfile.poc
- Run
docker build -f Dockerfile.poc -t lsof-poc .
- Run
docker run --rm lsof-poc
- Observe AddressSanitizer heap-buffer-overflow report
The service-name length of 4070 is calibrated for Linux where MAXPATHLEN = 4096 (buffer = 4097 bytes). On macOS (MAXPATHLEN = 1024, buffer = 1025 bytes), a ~1000-character name triggers the same bug.
Expected behavior
nl should track the actual remaining buffer space. After writing ":" + port (len + 1 bytes), the counter should decrease by len + 1, not len - 1. With the fix, the addr_too_long guard fires correctly and no out-of-bounds write occurs.
Program output
ASAN output from docker run --rm lsof-poc:
==11==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x521000001101
at pc 0xffffb071a180 bp 0xffffc034d0f0 sp 0xffffc034c8b0
WRITE of size 7 at 0x521000001101 thread T0
#0 vsnprintf sanitizer_common_interceptors.inc:1652
#1 snprintf sanitizer_common_interceptors.inc:1723
#2 printinaddr src/print.c:1331
#3 printname src/print.c:1444
#4 print_file src/print.c:1188
#5 print_proc src/print.c:2243
#6 main src/main.c:1518
0x521000001101 is located 0 bytes after 4097-byte region
[0x521000000100,0x521000001101)
allocated by thread T0 here:
#0 malloc asan_malloc_linux.cpp:69
#1 lsof_new lib/lsof.c:69
#2 main src/main.c:152
SUMMARY: AddressSanitizer: heap-buffer-overflow in vsnprintf
==11==ABORTING
Environment (please complete the following information):
- Kernel: Linux (aarch64, Docker on macOS host)
- OS: Ubuntu 24.04
- lsof Version: 4.99.7 / master @
96abbd4
- Origin: Built from git (
lsof-org/lsof master, HEAD)
Additional context
Practical impact is low. The 2-byte overwrite lands in malloc alignment padding on every major allocator (glibc: 7 bytes of padding on the 4097-byte buffer; macOS libmalloc: 15 bytes on the 1025-byte buffer; FreeBSD jemalloc: 255 bytes), so it does not corrupt adjacent heap state or cause a crash under normal operation. The trigger also requires an unusually long service name in /etc/services. Still worth fixing since the accounting is plainly wrong and snpf trusts the value.
Also confirmed on macOS (Darwin, MAXPATHLEN = 1024) with an ASAN-instrumented build and a 1000-character /etc/services entry. Same stack trace, same 2-byte overwrite at the boundary of the 1025-byte Namech buffer.
Describe the bug
In
src/print.c:1333, the port-formatting block decrements the remaining-length counternlbylen - 1instead oflen + 1:snpfwrites a colon plus the port string (len + 1bytes), andnpcorrectly advances bylen + 1, butnlonly shrinks bylen - 1— leaving it 2 too large after each port field. For an ESTABLISHED connection (two host:port pairs), the inflatednllets the finalsnpfwrite 2 bytes past the end of the heap-allocatedNamechbuffer.Fix: change
nl -= len - 1tonl -= len + 1.To Reproduce
Self-contained Dockerfile that builds lsof from master with AddressSanitizer, injects a long
/etc/servicesentry, and triggers the overflow:Dockerfile.pocdocker build -f Dockerfile.poc -t lsof-poc .docker run --rm lsof-pocThe service-name length of 4070 is calibrated for Linux where
MAXPATHLEN = 4096(buffer = 4097 bytes). On macOS (MAXPATHLEN = 1024, buffer = 1025 bytes), a ~1000-character name triggers the same bug.Expected behavior
nlshould track the actual remaining buffer space. After writing":"+ port (len + 1bytes), the counter should decrease bylen + 1, notlen - 1. With the fix, theaddr_too_longguard fires correctly and no out-of-bounds write occurs.Program output
ASAN output from
docker run --rm lsof-poc:Environment (please complete the following information):
96abbd4lsof-org/lsofmaster, HEAD)Additional context
Practical impact is low. The 2-byte overwrite lands in malloc alignment padding on every major allocator (glibc: 7 bytes of padding on the 4097-byte buffer; macOS libmalloc: 15 bytes on the 1025-byte buffer; FreeBSD jemalloc: 255 bytes), so it does not corrupt adjacent heap state or cause a crash under normal operation. The trigger also requires an unusually long service name in
/etc/services. Still worth fixing since the accounting is plainly wrong andsnpftrusts the value.Also confirmed on macOS (Darwin,
MAXPATHLEN = 1024) with an ASAN-instrumented build and a 1000-character/etc/servicesentry. Same stack trace, same 2-byte overwrite at the boundary of the 1025-byteNamechbuffer.