BudgetScan

Nil destination writer causes panic when flushing buffered output

medium swesweep-fasthttp · stackless/writer.go · (*writer).do · run #4

Description

do() calls w.dstW.Write(w.xw.bb.B) when the internal buffer is non-empty, but NewWriter does not validate dstW. If dstW is a nil io.Writer, the first operation that produces buffered output dereferences a nil interface and panics the host process.

Triage

Verdict
INVALID confidence 0.0
Model
qwen/qwen3.8-27b
Rounds
5
Cost
$0.0155 · 43.1 s
The reported vulnerability is a nil pointer dereference panic in `writer.do` when `w.dstW` is nil. While the code defect exists (no nil check in `NewWriter` or `do`), the threat model for this library defines the attacker as 'Code that calls the public API... and above all the data it passes through' (e.g., parsers, decoders, protocol handlers receiving adversarial payloads). The `dstW` parameter is a dependency (the destination sink) provided by the host application at construction time. It is not derived from untrusted external input such as network packets or file contents. An external attacker controlling the input data passed to `Write(p)` cannot influence the value of the `dstW` interface, which is set by the trusted host's configuration. Passing a nil `dstW` is a programming error or misuse by the trusted host application, which falls under the 'out of scope' category of 'misuse that violates a documented precondition' or simply a robustness issue not exploitable by an external threat actor via untrusted input. Therefore, this is a code quality/robustness issue, not a security vulnerability reachable by an external attacker under the specified threat model.

CRUX: The `dstW` parameter is a trusted dependency provided by the host application at construction, not a field controlled by untrusted external input; thus, a nil `dstW` cannot be triggered by an external attacker via malicious payload data.
GREP: func NewWriter
All rounds
[
  {
    "finding_title": "Nil destination writer causes panic when flushing buffered output",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability is a panic caused by a nil pointer dereference in `writer.do` when `w.dstW` is nil. The code `NewWriter(dstW io.Writer, newWriter NewWriterFunc)` is a public constructor that accepts `dstW` as an `io.Writer` interface. In Go, passing `nil` for an interface type results in a nil interface. If the caller passes a nil `dstW` (or a nil concrete pointer that implements `io.Writer`), `w.dstW` will be nil. Later, when `Write` is called and data is buffered in `w.xw.bb`, the method `do` attempts to execute `w.dstW.Write(w.xw.bb.B)`. Calling a method on a nil interface causes a runtime panic (`nil pointer dereference`), which crashes the host process. \n\nUnder the threat model for a library, the public API is the trust boundary. The attacker is defined as \"Code that calls the public API... and the data it passes through\". While the attacker does not control the `dstW` pointer itself (it is set by the host application), the host application is the one integrating the library. If the host application passes a nil writer (e.g., due to a logic error or a failed initialization), the library panics. More critically, in the context of a security scanner triage, \"Missing parameter validation in public APIs\" is generally considered a robustness issue that can lead to crashes (Availability impact). However, standard security triage for Go libraries often distinguishes between *attacker-controlled input causing a crash* (like unbounded recursion or overflow) and *misconfiguration/misuse by the host*. \n\nLet's re-read the threat model: \"Attacker: Code that calls the public API... data it passes through\". The entry points are public functions. The asset is \"Memory safety and integrity... correct behaviour on malformed input\". Is a nil `dstW` \"malformed input\"? It is an invalid argument. The library does not validate that `dstW` is not nil. If I am the host, I am responsible for passing a valid writer. Does an *external* attacker (e.g., an HTTP client) cause the host to pass a nil writer? Unlikely. The host code initializes the writer once. \n\nHowever, consider the `Reset` method: `func (w *writer) Reset(dstW io.Writer)`. This is also public. If an attacker can influence the code path that calls `Reset` (unlikely, `Reset` is usually internal logic), they might pass nil. But `Reset` is typically called by the library user to reuse the buffer. \n\nThe key distinction in library security is usually: Can an *untrusted external input* (like a network packet) cause the library to crash without the library user doing something obviously wrong (like passing nil)? No. Passing nil to a constructor is a programmer error in the host application. It is not an input from an untrusted source. The untrusted source is the data *written* to the writer (`p` in `Write(p)`), not the writer itself. The writer is a sink provided by the trusted host. \n\nTherefore, this is a bug in the library's lack of defensive programming, but it is not reachable by an external attacker via untrusted data input. The attacker controls the *bytes* written, not the *destination* of the bytes. The destination is set by the host. If the host sets a nil destination, the host is buggy, not the attacker. This falls under \"misuse that violates a documented precondition\" or simply a robustness issue not exploitable by an external threat actor. The threat model explicitly excludes \"misuse that violates a documented precondition of the public API\". While a nil check isn't explicitly documented, the Go convention for `io.Writer` parameters is that they are non-nil valid writers. \n\nThus, the bug is real (it panics), but it is not security-relevant under the provided threat model because the attacker cannot control the `dstW` parameter. The attacker only controls the data passed to `Write`, which is buffered safely in `xWriter` before being flushed. The flush fails safely (with a panic) only if the host provided a nil destination, which is a host-side error, not an external attack.\n\nCRUX: The nil dereference occurs on `w.dstW`, which is a parameter set by the host application during `NewWriter` or `Reset`, not by external untrusted input. External attackers control the data written (`p`), not the destination writer.\nGREP: func NewWriter",
    "elapsed": 20.0,
    "tokens": 3812,
    "tokens_in": 2826,
    "tokens_out": 986,
    "cost_usd": 0.00414492,
    "file": "stackless/writer.go",
    "round": 1,
    "grep_used": true,
    "grep_results": "GREP `func NewWriter`:\n```\nstackless/writer.go:33:func NewWriter(dstW io.Writer, newWriter NewWriterFunc) Writer {\n```"
  },
  {
    "finding_title": "Nil destination writer causes panic when flushing buffered output",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability is a panic due to a nil pointer dereference when `w.dstW` is nil. This occurs in the `do` method: `w.dstW.Write(...)`. The `dstW` field is initialized in the public constructor `NewWriter(dstW io.Writer, ...)`. \n\nAccording to the binding threat model for a library:\n1. **Attacker**: \"Code that calls the public API... and above all the data it passes through\".\n2. **Entry points**: Public functions and \"every buffer, string, file or stream handed to them\".\n3. **Out of scope**: \"misuse that violates a documented precondition of the public API\".\n\nIn Go, passing `nil` for an `io.Writer` interface parameter is a misuse by the caller (the host application). The host application is trusted to provide a valid writer. An external attacker (e.g., an HTTP client) controls the *payload* bytes passed to `Write(p)`, but they do not control the `dstW` argument passed to `NewWriter` or `Reset`. The `dstW` is a structural component of the host application's request handling pipeline, not data derived from untrusted input. \n\nTherefore, while the lack of nil-check is a robustness flaw that could crash the host if the host developer makes a mistake, it is not a security vulnerability reachable by an external attacker through untrusted input. The attacker cannot force the host to pass `nil` to `NewWriter` via malicious HTTP payloads. This falls under code quality/robustness, not security.\n\nCRUX: The `dstW` parameter is a trusted dependency provided by the host application, not a field controlled by untrusted external input; passing nil is a host-side programming error, not an external attack vector.\nGREP: func NewWriter",
    "elapsed": 8.9,
    "tokens": 4367,
    "tokens_in": 3967,
    "tokens_out": 400,
    "cost_usd": 0.0028661399999999997,
    "file": "stackless/writer.go",
    "round": 2,
    "grep_used": true,
    "grep_results": "GREP `func NewWriter`:\n```\nstackless/writer.go:33:func NewWriter(dstW io.Writer, newWriter NewWriterFunc) Writer {\n```"
  },
  {
    "finding_title": "Nil destination writer causes panic when flushing buffered output",
    "verdict": "INVALID",
    "reasoning": "The report claims that a nil `dstW` causes a panic in `writer.do`. The code analysis confirms that `NewWriter` does not validate `dstW` for nil, and `do` dereferences it when flushing the buffer. However, the threat model for a library defines the attacker as code calling the public API and the *data* they pass through (parsers, decoders, etc.). The `dstW` parameter is a dependency provided by the host application at construction time (`NewWriter`) or reset time (`Reset`). It is not derived from untrusted external input (like network packets or file contents). A malicious attacker controlling the input bytes to `Write(p)` cannot influence the value of `dstW`. Therefore, a nil `dstW` is a result of a programming error or misconfiguration by the host developer (passing a nil interface), which falls under the 'out of scope' category of misuse/misconfiguration. Since an external attacker cannot trigger this condition through untrusted data input, it is not a security vulnerability under the given threat model.\n\nCRUX: The `dstW` parameter is controlled by the trusted host application and cannot be influenced by an external attacker via untrusted data input.\nGREP: func NewWriter",
    "elapsed": 4.6,
    "tokens": 4656,
    "tokens_in": 4385,
    "tokens_out": 271,
    "cost_usd": 0.0026547000000000003,
    "file": "stackless/writer.go",
    "round": 3,
    "grep_used": true,
    "grep_results": "GREP `func NewWriter`:\n```\nstackless/writer.go:33:func NewWriter(dstW io.Writer, newWriter NewWriterFunc) Writer {\n```"
  },
  {
    "finding_title": "Nil destination writer causes panic when flushing buffered output",
    "verdict": "INVALID",
    "reasoning": "The reported issue is that `NewWriter` does not check if `dstW` is nil, leading to a panic in `writer.do` when flushing buffered data. Under the provided threat model for a reusable library, the attacker is defined as external code/data passing through the public API (e.g., malicious HTTP payloads). The `dstW` parameter is a dependency (the destination sink) provided by the trusted host application at construction time (`NewWriter`) or via `Reset`. An external attacker controlling the input data (the bytes passed to `Write`) cannot influence the value of `dstW`, which is set by the host's internal configuration or pipeline setup. A nil `dstW` results only if the host developer explicitly passes a nil interface, which constitutes a programming error or misuse of the API by the trusted host, not an attack vector reachable via untrusted input. Since the condition for the panic (nil `dstW`) cannot be triggered by untrusted external data, the vulnerability is not security-relevant under this threat model.\n\nCRUX: The `dstW` parameter is a trusted dependency provided by the host application and is not influenced by untrusted external input.\nGREP: func NewWriter",
    "elapsed": 4.5,
    "tokens": 4943,
    "tokens_in": 4682,
    "tokens_out": 261,
    "cost_usd": 0.0027494399999999997,
    "file": "stackless/writer.go",
    "round": 4,
    "grep_used": true,
    "grep_results": "GREP `func NewWriter`:\n```\nstackless/writer.go:33:func NewWriter(dstW io.Writer, newWriter NewWriterFunc) Writer {\n```"
  },
  {
    "finding_title": "Nil destination writer causes panic when flushing buffered output",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability is a nil pointer dereference panic in `writer.do` when `w.dstW` is nil. While the code defect exists (no nil check in `NewWriter` or `do`), the threat model for this library defines the attacker as 'Code that calls the public API... and above all the data it passes through' (e.g., parsers, decoders, protocol handlers receiving adversarial payloads). The `dstW` parameter is a dependency (the destination sink) provided by the host application at construction time. It is not derived from untrusted external input such as network packets or file contents. An external attacker controlling the input data passed to `Write(p)` cannot influence the value of the `dstW` interface, which is set by the trusted host's configuration. Passing a nil `dstW` is a programming error or misuse by the trusted host application, which falls under the 'out of scope' category of 'misuse that violates a documented precondition' or simply a robustness issue not exploitable by an external threat actor via untrusted input. Therefore, this is a code quality/robustness issue, not a security vulnerability reachable by an external attacker under the specified threat model.\n\nCRUX: The `dstW` parameter is a trusted dependency provided by the host application at construction, not a field controlled by untrusted external input; thus, a nil `dstW` cannot be triggered by an external attacker via malicious payload data.\nGREP: func NewWriter",
    "elapsed": 5.1,
    "tokens": 5290,
    "tokens_in": 4969,
    "tokens_out": 321,
    "cost_usd": 0.00304998,
    "file": "stackless/writer.go",
    "round": 5,
    "grep_used": true,
    "grep_results": "GREP `func NewWriter`:\n```\nstackless/writer.go:33:func NewWriter(dstW io.Writer, newWriter NewWriterFunc) Writer {\n```"
  }
]
Raw scan report for this file

`stackless/writer.go` exposes a small adapter whose security depends heavily on the correctness of the stackless runtime, the caller-supplied `dstW`, and the caller-supplied `newWriter`. There are no fixed-size buffers or direct integer-arithmetic bugs in this file, but there are robustness and state-integrity problems that can panic the host process or silently corrupt compressed output.

Key observations:

- `NewWriter` accepts `io.Writer` and `NewWriterFunc` without validation.
- `Write` / `WriteString` store caller input in `w.p` and run the underlying writer through `stacklessWriterFunc`.
- `do` executes the operation, then flushes the pooled `xWriter` buffer to `w.dstW`.
- `Reset` changes the destination writer even if the low-stack operation cannot be executed.
- `xWriter` uses `bytebufferpool`, so buffer lifecycle matters.

Most serious issue is `Reset()`:

```go
func (w *writer) Reset(dstW io.Writer) {
	w.xw.Reset()
	w.do(opReset) //nolint:errcheck
	w.dstW = dstW
}
```

`w.do(opReset)` can return `errHighLoad` when the stackless runtime is saturated. In that case `writerFunc` is not executed, so `w.zw.Reset(&w.xw)` is not called. However, `Reset` still updates `w.dstW`. The caller receives no error because `Reset` has a `void` signature. This can leave the underlying compression writer in a stale state while subsequent writes are sent to a new destination, causing stream corruption, cross-request state confusion, or availability impact under load.

Other issues:

- `do()` dereferences `w.dstW` when flushing `w.xw.bb.B`. If `NewWriter` was called with a nil `dstW`, the first write that produces buffered output panics.
- `NewWriter` does not check `newWriter` for nil, nor does it check the returned `Writer` for nil. A nil `newWriter` panics immediately; a nil returned `zw` panics later in `writerFunc`.
- If `w.err` is non-nil, `do()` returns before calling `w.xw.Reset()`. This can retain the pooled output buffer. If the underlying writer continues to append output while returning errors, the retained buffer can grow, causing memory retention or exhaustion.

Concurrent use of a single `writer` is also unsafe because `w.p`, `w.n`, `w.err`, `w.op`, and `w.xw.bb` are mutated without synchronization. This file does not document the writer as thread-safe, so this is treated more as an API contract issue than an exploitable zero-day.

```json
[
  {
    "severity": "high",
    "title": "Reset silently changes destination writer when stackless reset cannot execute",
    "function": "(*writer).Reset",
    "description": "Reset calls w.xw.Reset(), then w.do(opReset) and ignores the returned error. If do() returns errHighLoad, writerFunc is not executed and w.zw.Reset(&w.xw) is skipped, but Reset still assigns w.dstW = dstW. The underlying compression writer remains in its old state while future writes are flushed to the new destination. An attacker can increase concurrent load to make stackless execution unavailable, causing stream corruption, state confusion, or denial of service without the caller receiving an error."
  },
  {
    "severity": "medium",
    "title": "Nil destination writer causes panic when flushing buffered output",
    "function": "(*writer).do",
    "description": "do() calls w.dstW.Write(w.xw.bb.B) when the internal buffer is non-empty, but NewWriter does not validate dstW. If dstW is a nil io.Writer, the first operation that produces buffered output dereferences a nil interface and panics the host process."
  },
  {
    "severity": "low",
    "title": "Missing validation of newWriter and returned underlying writer can cause nil interface panics",
    "function": "NewWriter",
    "description": "NewWriter calls newWriter(&w.xw) without checking whether newWriter is nil, and it does not check whether the returned Writer is nil. A nil newWriter panics immediately, while a nil returned zw causes a nil interface method call later in writerFunc during Write, Flush, Close, or Reset."
  },
  {
    "severity": "low",
    "title": "Error path can retain pooled output buffer and allow unbounded growth",
    "function": "(*writer).do",
    "description": "When w.err is non-nil, do() returns before calling w.xw.Reset(), leaving the pooled ByteBuffer allocated. If the underlying writer from newWriter can both append output to xWriter and return errors, repeated operations can retain and grow the same buffer without returning it to the pool. This is memory-retention or resource-exhaustion risk, especially if the underlying writer is faulty or adversarial."
  }
]
```