Unbounded writer blocking when the receiving end fills the channel and stops reading
medium swesweep-fasthttp · fasthttputil/pipeconns.go · Write · run #4
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.
Triage
- Verdict
- INVALID confidence 0.0
- Model
- qwen/qwen3.8-27b
- Rounds
- 5
- Cost
- $0.0177 · 95.7 s
The reported vulnerability is a false positive under the specified threat model. The issue describes a resource exhaustion scenario where a `Write` call blocks indefinitely if the internal channel (capacity 4) fills up and the reader stops consuming, assuming no write deadline is set. However, `PipeConns` implements the standard `net.Conn` interface. In Go, blocking on a full buffer without an explicitly set deadline is the standard, expected semantics for stream-oriented connections (similar to TCP sockets and `net.Pipe`). The library provides `SetWriteDeadline` as the standard mechanism for applications to enforce timeouts, which is consistent with the `net.Conn` contract. The threat model explicitly excludes 'misuse that violates a documented precondition of the public API' and treats configuration choices (like not setting a deadline) as out of scope for library vulnerabilities. Furthermore, the attack requires controlling the reading end of the pipe to stop reading, which is not a typical external attack vector for a server-side library where the library is usually the reader or both ends are internal. Therefore, the behavior is expected standard library semantics, not a security defect. CRUX: Blocking on write when the buffer is full and no deadline is set is standard, expected behavior for a net.Conn implementation, and the lack of a default deadline is not a security vulnerability but a configuration choice left to the host application. GREP: SetWriteDeadline
All rounds
[
{
"finding_title": "Unbounded writer blocking when the receiving end fills the channel and stops reading",
"verdict": "INVALID",
"reasoning": "The reported vulnerability is an availability issue (resource exhaustion) caused by the `Write` method blocking indefinitely when the channel capacity is full, the reader stops reading, and no write deadline is set. The analysis is as follows: 1. The code confirms that `Write` blocks on `c.wCh <- b` if the channel (capacity 4) is full and `writeDeadlineCh` is not active. 2. The default value for `writeDeadlineCh` is `nil` (set in `NewPipeConns`/struct definition and not modified by default). In Go, a `select` statement waiting on a nil channel will never proceed on that branch. Therefore, without an explicit `SetWriteDeadline`, the write will block forever if the reader stops. 3. According to the threat model for a library, if an external attacker can control the input to the public API in a way that violates invariants or causes unbounded resource usage (exhausting goroutines/stacks), it is a vulnerability. 4. However, the threat model rules state: 'Misuse that violates a documented precondition of the public API' is not a vulnerability. Furthermore, standard `net.Conn` implementations (including `net.Pipe`) typically block on write if the buffer is full. The 'untrusted input' here is the *behavior* of the reading side (stopping reads). If an attacker is the writer, they control the payload size, but the blocking occurs only if the *other* side (the reader) stops reading. In a typical attack scenario against a server, the server is the reader. If the client (attacker) sends data, the server reads it. The client's `Write` to the pipe would only block if the *server* stops reading. The attacker cannot force the server to stop reading unless they cause a bug in the server logic, which is out of scope for the library itself. Conversely, if the attacker is the reader (e.g., in a reverse proxy setup where the library pipes data between two connections), they could stop reading, but this requires the library to be used in a specific configuration where the attacker controls the read loop, which is less common for a simple pipe utility. More critically, the primary defense against this is application-level configuration (setting deadlines) or the natural flow of data where readers are active. Since the library is a transport mechanism, and blocking on a full pipe is standard behavior for in-memory pipes (similar to OS pipes which block when full), and the 'attack' requires the reader to intentionally or accidentally stop consuming, this is generally considered a configuration issue or expected behavior rather than a code defect in the library. The library provides `SetWriteDeadline` to mitigate this. Without a default deadline, blocking is the expected semantics for a pipe. Thus, it is not a security vulnerability in the library code itself.\n\nCRUX: Blocking on a full channel without a deadline is the standard behavior for in-memory pipes; the threat model considers 'misuse that violates a documented precondition' (expecting non-blocking without a deadline) or configuration issues as out of scope, and standard `net.Pipe` also blocks.\nGREP: writeDeadlineCh",
"elapsed": 26.2,
"tokens": 4810,
"tokens_in": 4139,
"tokens_out": 671,
"cost_usd": 0.00375138,
"file": "fasthttputil/pipeconns.go",
"round": 1,
"grep_used": true,
"grep_results": "GREP `writeDeadlineCh`:\n```\nfasthttputil/pipeconns.go:108:\twriteDeadlineCh <-chan time.Time\nfasthttputil/pipeconns.go:133:\t\tcase <-c.writeDeadlineCh:\nfasthttputil/pipeconns.go:134:\t\t\tc.writeDeadlineCh = closedDeadlineCh\nfasthttputil/pipeconns.go:290:\tc.writeDeadlineCh = updateTimer(c.writeDeadlineTimer, deadline)\n```"
},
{
"finding_title": "Unbounded writer blocking when the receiving end fills the channel and stops reading",
"verdict": "INVALID",
"reasoning": "The report claims that an attacker can cause resource exhaustion by stopping reads from one end of the pipe, causing writes to the other end to block indefinitely. To verify this, I must determine if this behavior constitutes a security vulnerability under the provided threat model. The threat model defines the attacker as code calling the public API with adversarial payloads. It explicitly states: 'Not a vulnerability under this model: ... misuse that violates a documented precondition of the public API.' \n\nStandard `net.Conn` implementations (including `net.Pipe` and TCP sockets) block on write if the buffer is full. This is expected, fundamental behavior for a stream-oriented connection. The library documentation for `PipeConns` does not claim to be non-blocking by default; rather, it highlights that it buffers writes to reduce the need for concurrent readers, but it still supports read and write deadlines as a standard `net.Conn` feature. \n\nIf an application uses `PipeConns` without setting a write deadline, and the reader stops, the writer will block. This is the same behavior as any other blocking I/O operation (like a TCP write to a closed or stalled connection). The library provides `SetWriteDeadline` to handle this case, which is the standard mechanism in Go for preventing indefinite blocking. The fact that the default state is 'block' is not a bug or a security flaw; it is the default contract of the `net.Conn` interface. An attacker cannot force a library to be non-blocking if the host application has not configured it to be so. Therefore, this is a configuration issue or expected behavior, not a vulnerability in the library code itself.\n\nCRUX: Blocking on write when the buffer is full and no deadline is set is standard, expected behavior for a `net.Conn` implementation, and the lack of a default deadline is not a security vulnerability but a configuration choice left to the host application.\nGREP: SetWriteDeadline",
"elapsed": 17.9,
"tokens": 5463,
"tokens_in": 5054,
"tokens_out": 409,
"cost_usd": 0.00334968,
"file": "fasthttputil/pipeconns.go",
"round": 2,
"grep_used": true,
"grep_results": "GREP `SetWriteDeadline`:\n```\nserver_timing_test.go:249:func (c *fakeServerConn) SetWriteDeadline(t time.Time) error {\nserver_test.go:4820:func (rw *readWriter) SetWriteDeadline(t time.Time) error {\nclient_test.go:2129:func (r *readTimeoutConn) SetWriteDeadline(d time.Time) error {\nclient_test.go:2374:func (w *writeErrorConn) SetWriteDeadline(_ time.Time) error {\nclient_test.go:2406:func (r *readErrorConn) SetWriteDeadline(_ time.Time) error {\nclient_test.go:2446:func (r *singleReadConn) SetWriteDeadline(_ time.Time) error {\nclient_test.go:2487:func (r *singleEchoConn) SetWriteDeadline(_ time.Time) error {\nclient.go:2947:\t\t\tif err = conn.SetWriteDeadline(currentTime.Add(writeTimeout)); err != nil {\nclient.go:3104:\tif err = conn.SetWriteDeadline(writeDeadline); err != nil {\nclient_timing_test.go:27:func (c *fakeClientConn) SetWriteDeadline(t time.Time) error {\nfasthttputil/pipeconns_test.go:19:\tif err := c1.SetWriteDeadline(deadline); err != nil {\nserver.go:1733:\t\t\tif err := c.SetWriteDeadline(time.Now().Add(s.WriteTimeout)); err != nil {\nserver.go:2575:\t\t\tif err = c.SetWriteDeadline(time.Now().Add(writeTimeout)); err != nil {\nserver.go:2581:\t\t\tif err = c.SetWriteDeadline(zeroTime); err != nil {\nfasthttputil/pipeconns.go:271:\tc.SetWriteDeadline(deadline) //nolint:errcheck\nfasthttputil/pipeconns.go:286:func (c *pipeConn) SetWriteDeadline(deadline time.Time) error {\n```"
},
{
"finding_title": "Unbounded writer blocking when the receiving end fills the channel and stops reading",
"verdict": "INVALID",
"reasoning": "The reported vulnerability claims that an attacker can cause resource exhaustion by causing `Write` to block indefinitely when the channel is full and the reader stops. While the code does indeed block on `c.wCh <- b` if the channel is full and `writeDeadlineCh` is nil (which is the default state), this behavior is fundamental to the `net.Conn` interface in Go. Standard implementations like `net.Pipe` and TCP sockets also block on write when the buffer is full and no deadline is set. The library provides `SetWriteDeadline` as a mechanism for the host application to prevent indefinite blocking. The threat model for a library (fasthttp) considers 'misuse that violates a documented precondition' or configuration issues as out of scope for vulnerabilities in the library itself. Blocking on a full pipe is the expected, standard semantics for a stream-oriented connection; expecting it to be non-blocking without a deadline is a misunderstanding of the API contract. Furthermore, for an attacker to exploit this, they would need to control the reader side of the pipe (or cause the server to stop reading), which is not a typical attack vector against a server (where the server is the reader and the client is the writer). If the client (attacker) is writing, the server is reading. If the server stops reading, that is a bug or configuration issue in the server, not a vulnerability in the fasthttp library's pipe implementation. Therefore, this is not a security vulnerability in the library code.\n\nCRUX: Blocking on a full channel without a set deadline is standard, expected behavior for net.Conn implementations and is not a security vulnerability in the library.\nGREP: SetWriteDeadline",
"elapsed": 21.6,
"tokens": 6233,
"tokens_in": 5878,
"tokens_out": 355,
"cost_usd": 0.00353376,
"file": "fasthttputil/pipeconns.go",
"round": 3,
"grep_used": true,
"grep_results": "GREP `SetWriteDeadline`:\n```\nclient_test.go:2129:func (r *readTimeoutConn) SetWriteDeadline(d time.Time) error {\nclient_test.go:2374:func (w *writeErrorConn) SetWriteDeadline(_ time.Time) error {\nclient_test.go:2406:func (r *readErrorConn) SetWriteDeadline(_ time.Time) error {\nclient_test.go:2446:func (r *singleReadConn) SetWriteDeadline(_ time.Time) error {\nclient_test.go:2487:func (r *singleEchoConn) SetWriteDeadline(_ time.Time) error {\nserver_timing_test.go:249:func (c *fakeServerConn) SetWriteDeadline(t time.Time) error {\nclient_timing_test.go:27:func (c *fakeClientConn) SetWriteDeadline(t time.Time) error {\nserver_test.go:4820:func (rw *readWriter) SetWriteDeadline(t time.Time) error {\nfasthttputil/pipeconns_test.go:19:\tif err := c1.SetWriteDeadline(deadline); err != nil {\nfasthttputil/pipeconns.go:271:\tc.SetWriteDeadline(deadline) //nolint:errcheck\nfasthttputil/pipeconns.go:286:func (c *pipeConn) SetWriteDeadline(deadline time.Time) error {\nclient.go:2947:\t\t\tif err = conn.SetWriteDeadline(currentTime.Add(writeTimeout)); err != nil {\nclient.go:3104:\tif err = conn.SetWriteDeadline(writeDeadline); err != nil {\nserver.go:1733:\t\t\tif err := c.SetWriteDeadline(time.Now().Add(s.WriteTimeout)); err != nil {\nserver.go:2575:\t\t\tif err = c.SetWriteDeadline(time.Now().Add(writeTimeout)); err != nil {\nserver.go:2581:\t\t\tif err = c.SetWriteDeadline(zeroTime); err != nil {\n```"
},
{
"finding_title": "Unbounded writer blocking when the receiving end fills the channel and stops reading",
"verdict": "INVALID",
"reasoning": "The report claims that `Write` can block indefinitely, leading to resource exhaustion if an attacker stops reading. While the code in `fasthttputil/pipeconns.go` does implement blocking writes when the channel buffer (capacity 4) is full and no write deadline is set, this behavior aligns with the standard `net.Conn` interface semantics in Go. Blocking on a full pipe is expected behavior for stream-oriented connections (similar to TCP or `net.Pipe`). The threat model for reusable libraries explicitly excludes 'misuse that violates a documented precondition' and configuration issues from being vulnerabilities. The library provides `SetWriteDeadline` to allow host applications to mitigate indefinite blocking, which is the standard mechanism in Go. An attacker cannot force the library to behave non-blocking if the host application has not configured a deadline; this is a responsibility of the application layer. Therefore, the lack of a default deadline is not a security flaw in the library itself but a standard contract of the API.\n\nCRUX: Blocking on a full channel without a set deadline is standard, expected behavior for net.Conn implementations and is not a security vulnerability in the library.\nGREP: SetWriteDeadline",
"elapsed": 9.6,
"tokens": 6578,
"tokens_in": 6325,
"tokens_out": 253,
"cost_usd": 0.0034155,
"file": "fasthttputil/pipeconns.go",
"round": 4,
"grep_used": true,
"grep_results": "GREP `SetWriteDeadline`:\n```\nclient_test.go:2129:func (r *readTimeoutConn) SetWriteDeadline(d time.Time) error {\nclient_test.go:2374:func (w *writeErrorConn) SetWriteDeadline(_ time.Time) error {\nclient_test.go:2406:func (r *readErrorConn) SetWriteDeadline(_ time.Time) error {\nclient_test.go:2446:func (r *singleReadConn) SetWriteDeadline(_ time.Time) error {\nclient_test.go:2487:func (r *singleEchoConn) SetWriteDeadline(_ time.Time) error {\nserver_timing_test.go:249:func (c *fakeServerConn) SetWriteDeadline(t time.Time) error {\nclient_timing_test.go:27:func (c *fakeClientConn) SetWriteDeadline(t time.Time) error {\nserver_test.go:4820:func (rw *readWriter) SetWriteDeadline(t time.Time) error {\nfasthttputil/pipeconns_test.go:19:\tif err := c1.SetWriteDeadline(deadline); err != nil {\nfasthttputil/pipeconns.go:271:\tc.SetWriteDeadline(deadline) //nolint:errcheck\nfasthttputil/pipeconns.go:286:func (c *pipeConn) SetWriteDeadline(deadline time.Time) error {\nclient.go:2947:\t\t\tif err = conn.SetWriteDeadline(currentTime.Add(writeTimeout)); err != nil {\nclient.go:3104:\tif err = conn.SetWriteDeadline(writeDeadline); err != nil {\nserver.go:1733:\t\t\tif err := c.SetWriteDeadline(time.Now().Add(s.WriteTimeout)); err != nil {\nserver.go:2575:\t\t\tif err = c.SetWriteDeadline(time.Now().Add(writeTimeout)); err != nil {\nserver.go:2581:\t\t\tif err = c.SetWriteDeadline(zeroTime); err != nil {\n```"
},
{
"finding_title": "Unbounded writer blocking when the receiving end fills the channel and stops reading",
"verdict": "INVALID",
"reasoning": "The reported vulnerability is a false positive under the specified threat model. The issue describes a resource exhaustion scenario where a `Write` call blocks indefinitely if the internal channel (capacity 4) fills up and the reader stops consuming, assuming no write deadline is set. However, `PipeConns` implements the standard `net.Conn` interface. In Go, blocking on a full buffer without an explicitly set deadline is the standard, expected semantics for stream-oriented connections (similar to TCP sockets and `net.Pipe`). The library provides `SetWriteDeadline` as the standard mechanism for applications to enforce timeouts, which is consistent with the `net.Conn` contract. The threat model explicitly excludes 'misuse that violates a documented precondition of the public API' and treats configuration choices (like not setting a deadline) as out of scope for library vulnerabilities. Furthermore, the attack requires controlling the reading end of the pipe to stop reading, which is not a typical external attack vector for a server-side library where the library is usually the reader or both ends are internal. Therefore, the behavior is expected standard library semantics, not a security defect.\n\nCRUX: Blocking on write when the buffer is full and no deadline is set is standard, expected behavior for a net.Conn implementation, and the lack of a default deadline is not a security vulnerability but a configuration choice left to the host application.\nGREP: SetWriteDeadline",
"elapsed": 20.4,
"tokens": 6968,
"tokens_in": 6671,
"tokens_out": 297,
"cost_usd": 0.0036928199999999994,
"file": "fasthttputil/pipeconns.go",
"round": 5,
"grep_used": true,
"grep_results": "GREP `SetWriteDeadline`:\n```\nclient_test.go:2129:func (r *readTimeoutConn) SetWriteDeadline(d time.Time) error {\nclient_test.go:2374:func (w *writeErrorConn) SetWriteDeadline(_ time.Time) error {\nclient_test.go:2406:func (r *readErrorConn) SetWriteDeadline(_ time.Time) error {\nclient_test.go:2446:func (r *singleReadConn) SetWriteDeadline(_ time.Time) error {\nclient_test.go:2487:func (r *singleEchoConn) SetWriteDeadline(_ time.Time) error {\nclient_timing_test.go:27:func (c *fakeClientConn) SetWriteDeadline(t time.Time) error {\nserver_timing_test.go:249:func (c *fakeServerConn) SetWriteDeadline(t time.Time) error {\nfasthttputil/pipeconns_test.go:19:\tif err := c1.SetWriteDeadline(deadline); err != nil {\nfasthttputil/pipeconns.go:271:\tc.SetWriteDeadline(deadline) //nolint:errcheck\nfasthttputil/pipeconns.go:286:func (c *pipeConn) SetWriteDeadline(deadline time.Time) error {\nserver_test.go:4820:func (rw *readWriter) SetWriteDeadline(t time.Time) error {\nclient.go:2947:\t\t\tif err = conn.SetWriteDeadline(currentTime.Add(writeTimeout)); err != nil {\nclient.go:3104:\tif err = conn.SetWriteDeadline(writeDeadline); err != nil {\nserver.go:1733:\t\t\tif err := c.SetWriteDeadline(time.Now().Add(s.WriteTimeout)); err != nil {\nserver.go:2575:\t\t\tif err = c.SetWriteDeadline(time.Now().Add(writeTimeout)); err != nil {\nserver.go:2581:\t\t\tif err = c.SetWriteDeadline(zeroTime); err != nil {\n```"
}
]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."
}
]
```