Queued attacker data can be read after a read deadline has expired
low swesweep-fasthttp · fasthttputil/pipeconns.go · readNextByteBuffer · run #4
Description
On read deadline expiry, readNextByteBuffer sets readDeadlineCh to closedDeadlineCh and then non-blockingly drains one rCh buffer. If an attacker has queued data at that moment, Read returns the data instead of ErrTimeout. Repeated one-buffer dribbles can keep a timed-out connection delivering data, weakening deadline-based protection against slow clients.
Triage
Not triaged.
Raw scan report for this file
The public surface is `NewPipeConns`, the returned `net.Conn` ends, `SetAddresses`, and `Close`. Attacker-relevant input is primarily byte data passed through `Write`/`Read` and the behavior of the peer end (for example, whether it keeps reading). Classic out-of-bounds memory corruption is unlikely because `append` and `copy` are bounds-checked. The main risk areas are channel backpressure, deadline state handling, buffer pooling, and goroutine/resource exhaustion.
Key observations:
- `Write` copies data into a pooled buffer and sends it over a capacity-4 channel.
- If the channel is full and no write deadline is set, `Write` can block indefinitely.
- On write timeout, the buffer is not returned to the pool and the write deadline channel is latched to a closed channel.
- Read deadline handling can deliver attacker-queued data after the deadline has expired.
- Zero-length writes inject empty buffers, which can cause `Read` to return `0, nil`.
- `c.writeDeadlineCh` is written by `SetWriteDeadline` and read by `Write` without synchronization.
```json
[
{
"severity": "medium",
"title": "Unbounded writer blocking when the receiving end fills the channel and stops reading",
"function": "Write",
"description": "The write channel has capacity 4. If an attacker controls the reading end, it can stop calling Read after four buffers have been queued. Write then blocks in the inner select on c.wCh <- b. Without a write deadline, only Close can wake it, so many stalled connections can exhaust goroutines, stacks, and heap in the host process."
},
{
"severity": "medium",
"title": "Write timeout discards pooled buffer and latches write deadline to closed",
"function": "Write",
"description": "When c.wCh is full and c.writeDeadlineCh fires, Write returns ErrTimeout without returning b to byteBufferPool. The buffer becomes garbage instead of being reused, so attacker-induced repeated write timeouts cause repeated allocation of potentially large byte slices and GC/memory pressure. c.writeDeadlineCh is also set to closedDeadlineCh permanently, making later full-channel writes discard buffers immediately."
},
{
"severity": "low",
"title": "Queued attacker data can be read after a read deadline has expired",
"function": "readNextByteBuffer",
"description": "On read deadline expiry, readNextByteBuffer sets readDeadlineCh to closedDeadlineCh and then non-blockingly drains one rCh buffer. If an attacker has queued data at that moment, Read returns the data instead of ErrTimeout. Repeated one-buffer dribbles can keep a timed-out connection delivering data, weakening deadline-based protection against slow clients."
},
{
"severity": "low",
"title": "Empty writes can produce Read returns of 0, nil",
"function": "Write, Read",
"description": "Write accepts len(p)==0 and sends an empty byteBuffer. The peer consumes the empty buffer; read() then returns 0, nil and the top-level Read may convert the following errWouldBlock to nil, returning 0, nil. Protocol handlers that expect either bytes or an error may misbehave, and an attacker can trigger this with zero-length writes."
},
{
"severity": "low",
"title": "Data race on writeDeadlineCh between Write and SetWriteDeadline",
"function": "Write, SetWriteDeadline",
"description": "Write reads c.writeDeadlineCh in a select without synchronization, while SetWriteDeadline writes it without synchronization. A slow attacker can keep Write blocked, widening the window for host code to update the deadline concurrently. The race can cause missed deadlines, use of a stale channel, or undefined behavior under the Go memory model."
}
]
```