Skip to content

net_poll trusts the firmware's claimed read length — port c64-wireguard's drop-not-trim validation #140

Description

@JC-000

Offered in #70 (item 3b, reverse direction). Filing so it is trackable independently.

The gap

src/net/uci/net.s's net_poll takes the SOCKET_READ response header straight into uci_poll_rem and starts copying, with no check that the returned actual_len is a value the adapter actually asked for.

It survives a bogus length only incidentally: the inner copy loop re-checks ring-full and DATA_AV on every byte, so a wild length exits early rather than running away. That is a property of the copy loop, not a validation, and it is one refactor away from not holding.

Why this is worth a real check

This firmware is documented to return nonsense lengths. Measured on U64E fw 3.14d (c64-wireguard tools/test_uci_udp_size_probe.py, 2026-08-24):

requested returned
600 / 768 / 1280 512 (remainder silently discarded)
1024 / 1500 nothing delivered at all

and a 1500-byte request has been observed returning 0xFFFF. A firmware that returns 0xFFFF for a 1500-byte request can return a bogus length for a 512-byte one; nothing about the 512 cap makes the response trustworthy.

c64-wireguard learned this expensively. Its PR #62 fixed a case where net_poll trusted the returned length and copied ~18 KB from udp_recv_buf through $D000 — WireGuard packet bytes were found sitting in the VIC registers, presenting as a red screen and garbled charset that looked like a video bug rather than a memory-safety bug.

The check

c64-wireguard src/net/uci/net.s, the @hdr_done block — validate before trusting, and drop rather than trim:

        lda uci_read_hdr+1
        cmp #>UCI_READ_CHUNK_MAX
        bcc @len_ok                 ; hi < 2 -> certainly under the cap
        bne @len_bad                ; hi > 2 -> certainly over it
        lda uci_read_hdr+0          ; hi == 2 -> only exactly 512 is legal
        beq @len_ok
@len_bad:
        lda #UCI_ERR_LONG_READ
        sta net_last_error
        ; ...drain, do not copy

Dropping is deliberate: a trimmed datagram is a corrupt datagram, and for c64-wireguard it fails AEAD anyway. For a TCP stream the equivalent argument is that a truncated fill silently desynchronises the stream — which is the same class of defect d9cd021 chased through a ring wrap, arriving from the other end.

UCI_ERR_LONG_READ is $8A in c64-wireguard, proposed for reservation in #70 (3a) and filed against the contract as JC-000/c64-lib-contract#137.

Note on the reverse direction

d9cd021's ring-free-space clamp does not port to c64-wireguard — that adapter reads one datagram into a flat buffer behind a udp_recv_ready interlock, with no wrapping ring and no free-space computation. Detail in #70. These two fixes are complements, not the same fix seen twice.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions