internal/proxy/request.go, requestTimedOut. When the app has answered before the client has sent its whole body, two reads of the body can wait for the client without going through requestBody.Read: Go's server, before it sends the answer's headers, reads what is left of the body when that is under 256 KiB, and the transport, once its write to the app fails, closes the body, which reads it the same way. waiting then stays false, so when SWWAF_CLIENT_REQUEST_TIMEOUT runs out requestTimedOut takes the 504 branch, which sets no read deadline on the connection. finish, which would set one, runs only after the answer has been sent.
Repro, with the change in #135 and SWWAF_CLIENT_REQUEST_TIMEOUT=5s: an app that answers 404 with Content-Length: 0 and Connection: close as soon as it has read the headers, and a client that sends POST / HTTP/1.1, Content-Length: 200000 and three bytes of the body, then 8 KiB 300 ms later, then nothing, keeping the connection open. After 10 seconds the client had no answer and there was no request log line. The test waited no longer.
SPEC.md: SWWAF_CLIENT_REQUEST_TIMEOUT is "how long a client may take to send its request line and headers, and then, from the end of the headers, its body", and under "Risks the design has to handle" it "bounds how long a request may take to arrive".
The cost is that of #130: each such request holds a connection to the client, goroutines and their buffers for as long as the client likes, and those already held outlive a ban. A client needs only to send more once the app has answered, then stop with less than 256 KiB of its body left.
Severity: major.
Model: opus-5-5
`internal/proxy/request.go`, `requestTimedOut`. When the app has answered before the client has sent its whole body, two reads of the body can wait for the client without going through `requestBody.Read`: Go's server, before it sends the answer's headers, reads what is left of the body when that is under 256 KiB, and the transport, once its write to the app fails, closes the body, which reads it the same way. `waiting` then stays false, so when `SWWAF_CLIENT_REQUEST_TIMEOUT` runs out `requestTimedOut` takes the 504 branch, which sets no read deadline on the connection. `finish`, which would set one, runs only after the answer has been sent.
Repro, with the change in https://git.eeqj.de/sneak/smallwebwaf/pulls/135 and `SWWAF_CLIENT_REQUEST_TIMEOUT=5s`: an app that answers `404` with `Content-Length: 0` and `Connection: close` as soon as it has read the headers, and a client that sends `POST / HTTP/1.1`, `Content-Length: 200000` and three bytes of the body, then 8 KiB 300 ms later, then nothing, keeping the connection open. After 10 seconds the client had no answer and there was no request log line. The test waited no longer.
SPEC.md: `SWWAF_CLIENT_REQUEST_TIMEOUT` is "how long a client may take to send its request line and headers, and then, from the end of the headers, its body", and under "Risks the design has to handle" it "bounds how long a request may take to arrive".
The cost is that of https://git.eeqj.de/sneak/smallwebwaf/issues/130: each such request holds a connection to the client, goroutines and their buffers for as long as the client likes, and those already held outlive a ban. A client needs only to send more once the app has answered, then stop with less than 256 KiB of its body left.
Severity: major.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
internal/proxy/request.go,requestTimedOut. When the app has answered before the client has sent its whole body, two reads of the body can wait for the client without going throughrequestBody.Read: Go's server, before it sends the answer's headers, reads what is left of the body when that is under 256 KiB, and the transport, once its write to the app fails, closes the body, which reads it the same way.waitingthen stays false, so whenSWWAF_CLIENT_REQUEST_TIMEOUTruns outrequestTimedOuttakes the 504 branch, which sets no read deadline on the connection.finish, which would set one, runs only after the answer has been sent.Repro, with the change in #135 and
SWWAF_CLIENT_REQUEST_TIMEOUT=5s: an app that answers404withContent-Length: 0andConnection: closeas soon as it has read the headers, and a client that sendsPOST / HTTP/1.1,Content-Length: 200000and three bytes of the body, then 8 KiB 300 ms later, then nothing, keeping the connection open. After 10 seconds the client had no answer and there was no request log line. The test waited no longer.SPEC.md:
SWWAF_CLIENT_REQUEST_TIMEOUTis "how long a client may take to send its request line and headers, and then, from the end of the headers, its body", and under "Risks the design has to handle" it "bounds how long a request may take to arrive".The cost is that of #130: each such request holds a connection to the client, goroutines and their buffers for as long as the client likes, and those already held outlive a ban. A client needs only to send more once the app has answered, then stop with less than 256 KiB of its body left.
Severity: major.
Model: opus-5-5