aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-22 16:02:45 +0200
committeruser <user@clank>2026-09-22 16:02:45 +0200
commitf084772cf3c021fa2315fb17a0697c4be8c59a77 (patch)
tree7eca149269dafb78c40964cf185369e206763eb8 /docs
parentsh: background dhcp at boot so the prompt is never blocked (diff)
downloadgbos-f084772cf3c021fa2315fb17a0697c4be8c59a77.tar.gz
gbos-f084772cf3c021fa2315fb17a0697c4be8c59a77.tar.xz
gbos-f084772cf3c021fa2315fb17a0697c4be8c59a77.zip
net: three TCP receive bugs - caller corruption, data loss, early EOF
Found by writing nc(1): a 96-byte receive buffer against a host that answers in 200-byte segments hung the process every time. - recv/recv_nb copied RXLEN bytes into the caller's buffer and never looked at the caller's maxlen, so any short buffer got overrun and its stack smashed. Deliver min(RXLEN, maxlen) and keep the tail in RXBUF for the next call. wget only survived because its buffer (220) happens to exceed our MSS. - tcp_buffer_data overwrote RXBUF from offset 0 on every segment, so a segment arriving before the app drained the previous one destroyed unread bytes - silent, undetectable data loss. Append at RXBUF+RXLEN instead, and drop (without advancing rcv_nxt) when it doesn't fit, so the peer retransmits once there's room. RXLEN is one byte, so 255 is the capacity that matters, not RXBUF's 288. - A draining recv now clears RXLEN as well as HASRX: RXLEN is the append offset, and a stale one made every later segment look too big to fit, stalling the connection until the peer gave up. - A FIN past a gap is not end-of-stream. tcp_in accepted any FIN whose segment carried no data, without a sequence check, marking the socket DONE and making recv report EOF at the hole - truncating a transfer right before its last segments. Decide in-order-ness once, before rcv_nxt moves, and re-ACK an out-of-order FIN instead. Verified against a 6300-byte reply received while sending 831 bytes: byte-exact (6339 = payload + banner, 303 lines, first/mid/last intact), plus no regression in ping / nslookup / wget / irc.
Diffstat (limited to 'docs')
-rw-r--r--docs/internals.md24
1 files changed, 22 insertions, 2 deletions
diff --git a/docs/internals.md b/docs/internals.md
index c1007d1..8ee7816 100644
--- a/docs/internals.md
+++ b/docs/internals.md
@@ -145,11 +145,31 @@ recv_nb / close` driven by a request block — no program touches SLIP or IP.
Userland networking is plain C on top of that: `dhcp` (DISCOVER→ACK, then
`net_setip()`), `resolve.h` (dotted-quads + DNS A queries to 1.1.1.1),
`netd` (background RX pump so pings are answered), `ping`, `nslookup`, `wget`
-(HTTP GET over kernel TCP), `irc` (a full client: channels, /join /msg /me,
-nick colors, flood-safe rendering) and `chat`.
+(HTTP GET over kernel TCP), `nc` (raw TCP both ways: interactive, or `-s` to
+pump stdin and print the reply), `irc` (a full client: channels, /join /msg
+/me, nick colors, flood-safe rendering) and `chat`.
+
+**Receive-path invariants** (all three were bugs once, and each one is
+invisible until a peer sends more than a toy reply):
+
+- `recv`/`recv_nb` deliver `min(RXLEN, caller maxlen)` bytes. Copying the
+ whole segment regardless of the caller's buffer size corrupts the calling
+ process — a 200-byte segment into a 96-byte buffer smashes its stack.
+ A short read keeps the tail in `RXBUF` for the next call.
+- `tcp_buffer_data` **appends** at `RXBUF + RXLEN`, and a draining `recv`
+ resets `RXLEN` to 0 (it is the append offset). A segment that does not fit
+ is dropped *without* advancing `rcv_nxt`, and re-ACKed, so the peer
+ retransmits when there is room. Overwriting unread bytes instead is silent
+ data loss the application cannot detect.
+- A **FIN is only end-of-stream when it arrives in order**. Accepting a FIN
+ that sits past a gap marks the socket `TCP_DONE`, and `recv` then reports
+ EOF at the hole — truncating the transfer just before the last segments.
Bytes that arrive **outside** SLIP frames land in a console ring instead —
that's how the host can type into the shell remotely (`tools/gbtype`).
+`sys_pollcon` pumps the link itself before draining that ring, so a program
+that never touches the network (`more`) still sees injected keys, and the
+Game Boy keeps answering pings while it sits at a `--More--` prompt.
On the host side, `tools/gbhub` is a virtual switch/router: it spawns (or
accepts, `tools/gbjoin`) several emulator instances, leases each an IP via