id: 476e0fbb-25b5-4051-92ae-8e38a1fef02e title: 'serve: unbounded thread-per-connection + 500ms ack sleep = trivial availability DoS' state: closed labels:
- security assignees: [] milestone: null created: 2026-10-27T14:53:46.365269127Z updated: 2026-10-27T15:48:00.976093000Z
hu13 serve is thread-per-connection with no cap, and every inbox response sleeps 500 ms.
Verified:
before: 1 thread
$ python3 open 300 idle sockets sending "GET / HTTP/1.1\r\nHost: x\r\n" (no terminator)
during: 301 threads VmRSS 11.8 MB
Those sockets cost nothing per connection and can be kept alive indefinitely (a byte every < read timeout). ~10-30k cheap sockets exhaust the thread/pid limit or memory, and the server stops accepting.
Amplifiers:
- inbox handler always
sleep(ACK_DELAY_MS)(500 ms) before answering, on every path including 400/403/429 — a rejected POST holds a thread for half a second settle()will additionally read up to 256 KiB from a lyingContent-Lengthbefore answering- no accept-backlog tuning, no per-IP connection limit, no global connection cap
Related, same class but separate resource: #63 (git upload-pack with no timeout/cap).
Fix direction:
- cap concurrent connections (e.g. semaphore of 64-256) and answer 503 immediately when exhausted
- keep the fixed ack delay for semantic uniformity, but do not let it pin a thread: either keep it (with the cap above) or use a small timer wheel; the cap is the simpler fix
- optionally lower
POST_READ_TIMEOUT_SECSand require the head within a shorter window than the body
Acceptance:
- N idle sockets beyond the cap get refused/closed, not thread-per-connection
- process thread count has a documented upper bound
-
a test/bench opens >cap sockets and asserts the server still answers
/