Plan 9 from Bell Labs’s /usr/web/sources/contrib/mospak/hget-readline-overflow/README

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


hget-readline-overflow: fix input-buffer overflow in hget readline()

A memory-safety bug in 9legacy's sys/src/cmd/hget.c: readline()
never resets its rp/wp pointers when the 4096-byte input buffer
drains, so the write pointer walks past the end of buf[] and the
next read() deposits up to 2048 bytes of response data into
whatever static storage the linker placed after it.

The twin of this fault in sys/src/cmd/webfs/buf.c ships as
contrib/webfs-readline-overflow.  The two functions are copies of
one another and differ only in field order, which decides how the
damage presents.  webfs puts rp and wp after buf, so the overrun
corrupts its own pointers and the next read faults inside
readline, where a reader will find it.  hget puts rp and wp
before buf, so the overrun leaves the struct entirely and lands
in unrelated static data.  Nothing faults at the time and the
program continues on corrupted state, which is why this copy
outlived the one next door.

Independent of TLS.  It needs only a response whose headers and
body cycle the buffer past 4096 bytes, which any large page does.

CHANGES
    v1 -- 2026-08-12 (initial)
        hget-readline-overflow    readline() resets rp/wp to
                                  b.buf on drain.

Scope
    One-line change to sys/src/cmd/hget.c.  No kernel, no
    library, no ABI change.  No reboot.

Files
    hget-readline-overflow.diff   the fix

Apply
    cd /
    ape/patch -p0 < /path/to/hget-readline-overflow/hget-readline-overflow.diff

    Apply this before contrib/tls-1.2 if you are installing both.
    tls-1.2 adds thirteen lines to hget.c above readline, so applied
    afterwards this one lands thirteen lines lower than its header
    says: patch finds it by context, reports the offset and leaves a
    .orig behind.  The resulting source is identical either way; the
    order only decides whether you get the message and the backup
    file.

Rebuild
    cd /sys/src/cmd && mk hget.install

Prerequisites
    A 9legacy tree with sys/src/cmd/hget.c present.  No other
    patches required; this fix stands alone.

    Sanity-check that the drain path is present before patching:
        grep -n 'b.rp >= b.wp' /sys/src/cmd/hget.c

    grep cannot tell a patched tree from an unpatched one --
    initibuf() has contained the same rp/wp assignment since 4th
    edition, so searching for it matches either way.  A reverse
    dry-run is definitive:
        ape/patch -R --dry-run -p0 < hget-readline-overflow.diff

Verification
    The symptom depends on what the linker placed after the
    buffer, so it varies between trees and is not a dependable
    signal on its own.  8l orders bss by symbol hash, not by
    source order, so adding or renaming any static in hget moves
    the landing site.

    A direct check does not depend on the layout.  Fetch a page
    whose headers alone run to several hundred bytes:

        hget https://en.wikipedia.org/ >/dev/null

    then read the buffer's neighbours out of the running or
    broken process with acid(1).  On an unpatched tree they hold
    response text.  This is how the fault was found: on one tree
    the overrun reached libc's allocator bookkeeping and the next
    malloc aborted in plock, several call frames away from
    anything to do with hget's input.

    After the fix the same fetch leaves the neighbours untouched.

Rollback
    cd /
    ape/patch -R -p0 < /path/to/hget-readline-overflow/hget-readline-overflow.diff

    Then rebuild as above.

Known gap, deliberately not fixed here
    unreadline() in the same file copies a pushed-back line into
    the buffer with memmove and tests no bound.  With this fix
    applied it cannot overflow: the header buffer bounds the line
    at 2047 bytes, one refill bounds the remainder at 2048, and
    the write ends at index 4095 of 4096.  Without this fix the
    remainder is unbounded and that memmove overflows too, so the
    two are one fault with two symptoms.  A bounds check there
    would be unreachable code.  It becomes worth adding if any of
    the three numbers moves: the header buffer size, the refill
    quantum, or the number of unreadline callers.

    Plan 9 already has the shape both functions want.  libbio's
    Brdline compacts the remainder to the start of the buffer and
    rewinds the cursor before refilling, and reads only the room
    that is left; Bungetc pushes back by decrementing a count,
    because the bytes never left the buffer and there is nothing
    to copy.  Rewriting hget's reader onto bio(2) is a larger
    change than this package makes.

See also
    contrib/webfs-readline-overflow -- the same fault in webfs.
    Recommended alongside; neither depends on the other.
    contrib/tls-1.2 -- HTTPS client stack, which drives hget hard
    enough to make this fault easy to reach.

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.