BudgetScan

Reset silently changes destination writer when stackless reset cannot execute

high swesweep-fasthttp · stackless/writer.go · (*writer).Reset · run #4

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.

Triage

Verdict
INVALID confidence 0.0
Model
qwen/qwen3.8-27b
Rounds
6
Cost
$0.0162 · 123.4 s
[ARBITER] The reported bug is triggered by 'errHighLoad', which occurs when the stackless execution mechanism fails to schedule the function due to high system load or resource exhaustion. This condition depends on the host environment's resource state and concurrency, not on attacker-controlled input data (such as malformed compression streams). Under the specified threat model, the attacker controls the data passed to the public API, not the system resource limits or runtime scheduling constraints. Therefore, the attacker cannot reliably trigger this state to cause the specific state confusion described. Furthermore, the resulting state inconsistency (stale compression writer) leads to data corruption or errors on subsequent writes, which is a reliability/quality issue, not a memory safety violation (out-of-bounds, use-after-free, etc.) or unbounded resource consumption reachable via crafted input.
All rounds
[
  {
    "finding_title": "Reset silently changes destination writer when stackless reset cannot execute",
    "verdict": "INVALID",
    "reasoning": "The report claims that `writer.Reset` assigns `w.dstW = dstW` even if `w.do(opReset)` fails with `errHighLoad`, leaving the compression writer (`w.zw`) in an inconsistent state. \n\n1. **Bug Existence**: The code confirms `Reset` calls `w.do(opReset)` but ignores the error (`//nolint:errcheck`) and unconditionally sets `w.dstW = dstW`. If `do` returns `errHighLoad`, `writerFunc` is not executed, so `w.zw.Reset(&w.xw)` is skipped. The internal state of `zw` remains dirty, while `dstW` is updated. This is a logic bug.\n\n2. **Attacker Reachability**: The trigger condition is `stacklessWriterFunc` returning `false`, which happens when `errHighLoad` is generated. `errHighLoad` is typically triggered by resource exhaustion or specific concurrency limits in the stackless runtime. In a standard library threat model, the attacker controls the *data* passed to the API, but not the system's resource availability or the host application's concurrency load. Inducing `errHighLoad` requires exhausting system resources or forcing a specific runtime state that is outside the control of the input payload alone. \n\n3. **Security Impact**: Even if triggered, the result is a state inconsistency in the compression stream. The library is a compression helper. If `errHighLoad` occurs, the `Write` calls themselves return an error to the caller, who is expected to handle the failure (e.g., by aborting the stream or handling the error). The `Reset` call is typically used for reuse in connection pooling. If a `Write` fails due to high load, the caller should not be reusing the writer without proper error handling. Furthermore, `errHighLoad` is a transient resource issue. The threat model for a library focuses on memory safety, correctness on malformed *input*, and resource bounds. This issue is a consequence of a resource constraint (high load) rather than a malicious input payload. It is not a memory safety violation (no OOB access, no UAF), and it does not allow an attacker to crash the host process via a crafted byte sequence. It is a robustness issue in the error handling path of the stackless runtime, which is generally considered a code quality or reliability issue rather than a security vulnerability in this context, especially since the primary `Write` operations report the error, allowing the host to react.\n\n4. **Verdict**: The bug is real but not security-relevant under the provided threat model. It requires a specific system state (high load) not directly controllable by the attacker's input data, and the impact is state confusion that is mitigated by the error reporting on the primary write path.\n\nCRUX: The bug requires triggering 'errHighLoad' which depends on system resource/concurrency state, not just input data, and the resulting state confusion is not a memory safety violation nor is it hidden from the caller (who receives errors on Write).\nGREP: errHighLoad",
    "elapsed": 95.7,
    "tokens": 3538,
    "tokens_in": 2876,
    "tokens_out": 662,
    "cost_usd": 0.00319392,
    "file": "stackless/writer.go",
    "round": 1,
    "grep_used": true,
    "grep_results": "GREP `errHighLoad`:\n```\nstackless/writer.go:94:\t\treturn errHighLoad\nstackless/writer.go:108:var errHighLoad = errors.New(\"cannot compress data due to high load\")\n```"
  },
  {
    "finding_title": "Reset silently changes destination writer when stackless reset cannot execute",
    "verdict": "UNCERTAIN",
    "reasoning": "The report claims that `Reset` silently changes the destination writer (`w.dstW`) even when the underlying compression writer reset fails due to `errHighLoad`, leading to stream corruption or state confusion. \n\n1. **Bug Verification**: The code in `writer.go` confirms that `Reset` calls `w.do(opReset)` and ignores the return value. `do` returns `errHighLoad` if `stacklessWriterFunc` fails to execute in a stackless manner (due to high load/resource constraints). In this case, `w.zw.Reset(&w.xw)` is skipped, but `w.dstW = dstW` is executed. The bug pattern exists.\n\n2. **Threat Model & Reachability**: The threat model defines the attacker as an entity sending adversarial payloads to public API parsers/decoders. The trigger for this bug is `errHighLoad`, which depends on the system's concurrency or resource state (specifically, the failure of `stacklessWriterFunc` to run the operation without a stack switch). An attacker controlling the *input data* (bytes passed to `Write`) cannot directly force `errHighLoad` unless the stackless runtime's failure mode is triggered by the specific pattern or volume of data in a way that exhausts a fixed resource tied to the input. However, `errHighLoad` is typically a signal for ",
    "elapsed": 5.4,
    "tokens": 4012,
    "tokens_in": 3719,
    "tokens_out": 293,
    "cost_usd": 0.0024409799999999997,
    "file": "stackless/writer.go",
    "round": 2
  },
  {
    "finding_title": "Reset silently changes destination writer when stackless reset cannot execute",
    "verdict": "INVALID",
    "reasoning": "The reported bug is a logic error where `writer.Reset` updates `w.dstW` even if `w.do(opReset)` fails with `errHighLoad`, leaving the underlying compression writer (`w.zw`) in an inconsistent state. While this is a code quality and robustness issue, it does not constitute a security vulnerability under the provided threat model. The threat model defines the attacker as one who controls the data passed to the public API (parsers, decoders, etc.). However, triggering this specific bug requires the `stacklessWriterFunc` to fail, which returns `errHighLoad`. This failure mode is driven by system resource constraints (e.g., stack limits, goroutine pool exhaustion, or high concurrency) rather than the content of the input payload itself. An attacker sending malformed or large data through `Write` will either succeed or result in a standard error returned to the caller via the `Write` path (which does check and return the error from `do`). The `Reset` path is an internal state management operation typically called by trusted host code during connection pooling/reuse. Even if the state becomes inconsistent, it does not lead to memory safety violations (like OOB or UAF) or arbitrary code execution; it merely results in corrupted stream output, which is a functional integrity issue dependent on the host's resource management, not a direct exploit vector controlled by untrusted input. Therefore, it is INVALID.\n\nCRUX: The bug requires triggering 'errHighLoad' which depends on system resource/concurrency state, not just input data, and the resulting state confusion is not a memory safety violation nor is it hidden from the caller (who receives errors on Write).\nGREP: errHighLoad",
    "elapsed": 5.7,
    "tokens": 4361,
    "tokens_in": 3998,
    "tokens_out": 363,
    "cost_usd": 0.0027681599999999995,
    "file": "stackless/writer.go",
    "round": 3,
    "grep_used": true,
    "grep_results": "GREP `errHighLoad`:\n```\nstackless/writer.go:94:\t\treturn errHighLoad\nstackless/writer.go:108:var errHighLoad = errors.New(\"cannot compress data due to high load\")\n```"
  },
  {
    "finding_title": "Reset silently changes destination writer when stackless reset cannot execute",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability relies on the assumption that an attacker can trigger the `errHighLoad` state by manipulating input data or concurrency to cause the `Reset` function to silently update `w.dstW` while the underlying compression writer (`w.zw`) remains in a stale state. To evaluate this, we must determine how `errHighLoad` is generated. The code shows `errHighLoad` is returned by `w.do(op)` when `stacklessWriterFunc(w)` returns `false`. `stacklessWriterFunc` wraps `writerFunc` using `NewFunc`. In the context of the `stackless` package (a runtime optimization for high-concurrency, low-stack environments), `NewFunc` typically allocates a dedicated stack or goroutine context. If this allocation fails or a specific resource limit is hit, it returns `false`. This failure is driven by system-level resource constraints (e.g., memory pressure, goroutine limits) or internal state of the stackless runtime, not by the content of the data payload (`p`) passed to `Write` or `Reset`. An attacker controlling the input bytes cannot force the runtime to fail to allocate a stack context. Furthermore, even if `errHighLoad` were triggered, the consequence is a state inconsistency in a compression stream (data corruption), not a memory safety violation (OOB, UAF) or arbitrary code execution. The threat model explicitly focuses on memory safety, correct behavior on malformed input, and resource bounds. A logic bug in error handling that leads to data corruption under specific system resource exhaustion conditions, which is not directly controllable by the attacker's input payload, is considered a code quality or reliability issue, not a security vulnerability. The primary write path (`Write`) correctly propagates errors, allowing the host application to detect the high-load state and handle it appropriately. Therefore, the bug is not attacker-reachable in a way that causes meaningful security harm defined by the model.\n\nCRUX: The 'errHighLoad' error is triggered by resource exhaustion or runtime state, not by the attacker-controlled input data, and the resulting state inconsistency is a data integrity issue rather than a memory safety vulnerability.\nGREP: errHighLoad",
    "elapsed": 7.3,
    "tokens": 4873,
    "tokens_in": 4412,
    "tokens_out": 461,
    "cost_usd": 0.00323604,
    "file": "stackless/writer.go",
    "round": 4,
    "grep_used": true,
    "grep_results": "GREP `errHighLoad`:\n```\nstackless/writer.go:94:\t\treturn errHighLoad\nstackless/writer.go:108:var errHighLoad = errors.New(\"cannot compress data due to high load\")\n```"
  },
  {
    "finding_title": "Reset silently changes destination writer when stackless reset cannot execute",
    "verdict": "INVALID",
    "reasoning": "The report identifies a logic flaw in `writer.Reset` where `w.dstW` is updated even if `w.do(opReset)` fails. However, under the provided threat model for a reusable library, the attacker is defined as controlling the *data* passed to public APIs (parsers, decoders), not the host application's system resource state or concurrency limits. The trigger for the reported bug is `errHighLoad`, which is returned when `stacklessWriterFunc` fails to execute, typically due to resource exhaustion or high system load (external to the input payload). An attacker sending malicious bytes to `Write` cannot force the stackless runtime to fail in a way that triggers this specific state inconsistency on a subsequent `Reset` called by trusted host code. Furthermore, `Reset` is an internal state management operation used by trusted callers (e.g., for connection pooling), not a direct entry point for untrusted data processing. While the logic bug exists, it is not reachable by an external attacker through untrusted input, nor does it lead to memory safety violations or arbitrary code execution via the public API surface defined in the threat model. It is a robustness/code quality issue dependent on system resource constraints.\n\nCRUX: The trigger condition (errHighLoad) depends on system resource/concurrency state, which is not controllable by the attacker via untrusted input data.\nGREP: func stacklessWriterFunc",
    "elapsed": 5.3,
    "tokens": 5218,
    "tokens_in": 4917,
    "tokens_out": 301,
    "cost_usd": 0.00296814,
    "file": "stackless/writer.go",
    "round": 5,
    "grep_used": true,
    "grep_results": "GREP `func stacklessWriterFunc`:\n```\nstackless/writer.go:115:func stacklessWriterFunc(ctx any) bool {\n```"
  },
  {
    "verdict": "INVALID",
    "reasoning": "[ARBITER] The reported bug is triggered by 'errHighLoad', which occurs when the stackless execution mechanism fails to schedule the function due to high system load or resource exhaustion. This condition depends on the host environment's resource state and concurrency, not on attacker-controlled input data (such as malformed compression streams). Under the specified threat model, the attacker controls the data passed to the public API, not the system resource limits or runtime scheduling constraints. Therefore, the attacker cannot reliably trigger this state to cause the specific state confusion described. Furthermore, the resulting state inconsistency (stale compression writer) leads to data corruption or errors on subsequent writes, which is a reliability/quality issue, not a memory safety violation (out-of-bounds, use-after-free, etc.) or unbounded resource consumption reachable via crafted input.",
    "round": 6,
    "file": "stackless/writer.go",
    "finding_title": "Reset silently changes destination writer when stackless reset cannot execute",
    "elapsed": 4.0,
    "tokens_in": 2554,
    "tokens_out": 178,
    "cost_usd": 0.00160668
  }
]
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."
  }
]
```