Skip to content

[BUG]off-by-two in printinaddr() remaining-length accounting #365

Description

@f9j2n6nd8k-eng

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: $?"'
  1. Save the above as Dockerfile.poc
  2. Run docker build -f Dockerfile.poc -t lsof-poc .
  3. Run docker run --rm lsof-poc
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions