BudgetScan

ReleaseTimer panics on nil timer

medium swesweep-fasthttp · timer.go · ReleaseTimer · run #4

Description

ReleaseTimer calls stopTimer without checking whether t is nil. stopTimer dereferences t.Stop() and t.C, so passing a nil timer causes a nil pointer dereference panic. A public API caller can crash the host process if nil reaches this function through buggy or malicious usage.

Triage

Verdict
INVALID confidence 0.0
Model
qwen/qwen3.8-27b
Rounds
5
Cost
$0.0109 · 243.4 s
The report claims `ReleaseTimer` panics if passed a nil pointer. While `stopTimer` indeed lacks a nil check, the threat model for this library defines the attacker as providing untrusted data to public APIs (e.g., network payloads, file contents). `ReleaseTimer` is a lifecycle management function intended to be paired with `AcquireTimer`. The argument `t` is a pointer to a `time.Timer` object, which is controlled by the host application's control flow (e.g., variables in `client.go` or `tcpdialer.go`). An external attacker cannot influence the Go control flow to inject a nil pointer into this specific function call; they can only influence the data processed by the application, which does not dictate the existence or nil-ness of timer objects created by the application developer. Passing nil to `ReleaseTimer` is a violation of the library's usage contract (programmer error), not a result of processing malicious input. Under the threat model, missing parameter validation in a public API that requires trusted input (like a valid object reference from internal state) is considered a robustness issue or code quality flaw, not a security vulnerability, unless the input itself is untrusted data (which a function argument pointer is not in this context). Therefore, it is not attacker-reachable.

CRUX: The nil pointer argument to ReleaseTimer is a result of host application programmer error (lifecycle misuse), not attacker-controlled untrusted data, making it a code quality issue rather than a security vulnerability under the library threat model.
GREP: ReleaseTimer
All rounds
[
  {
    "finding_title": "ReleaseTimer panics on nil timer",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability is a nil pointer dereference in `ReleaseTimer` when passed a nil `*time.Timer`. While the code path exists (`stopTimer` dereferences `t.Stop()` and `t.C` without a nil check), the threat model for this library defines the attacker as code calling the public API with data from untrusted sources. In `fasthttp`, timers are managed internally using timeouts derived from server/client configuration (e.g., `ReadTimeout`, `WriteTimeout`), not from raw request bytes. The `AcquireTimer` function guarantees a non-nil return. `ReleaseTimer` is an internal lifecycle management function intended to be paired with `AcquireTimer`. Passing `nil` to `ReleaseTimer` is a violation of the library's contract (precondition) by the host application (developer error), not a result of processing adversarial input. A remote attacker cannot control the flow of control to invoke `ReleaseTimer(nil)`; they can only influence the data payloads handled by the server, which do not dictate whether the internal timer cleanup function receives a nil pointer. Therefore, this is a robustness issue (lack of defensive programming against programmer error) rather than a security vulnerability exploitable by an external attacker.\n\nCRUX: The input to ReleaseTimer is controlled by the host application's developer (lifecycle management), not by untrusted external data, making a nil pointer argument a programmer error rather than a security flaw.\nGREP: ReleaseTimer",
    "elapsed": 60.0,
    "tokens": 2518,
    "tokens_in": 2205,
    "tokens_out": 313,
    "cost_usd": 0.0018650999999999997,
    "file": "timer.go",
    "round": 1,
    "grep_used": true,
    "grep_results": "GREP `ReleaseTimer`:\n```\ntcpdialer.go:350:\t\t\tReleaseTimer(tc)\nclient.go:1101:\tReleaseTimer(tc)\nclient.go:1711:\t\tdefer ReleaseTimer(tc)\ntimer.go:33:// The returned Timer may be returned to the pool with ReleaseTimer\ntimer.go:45:// ReleaseTimer returns the time.Timer acquired via AcquireTimer to the pool\ntimer.go:50:func ReleaseTimer(t *time.Timer) {\n```"
  },
  {
    "finding_title": "ReleaseTimer panics on nil timer",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability is a nil pointer dereference in `ReleaseTimer` when passed a nil `*time.Timer`. According to the threat model for this library, the attacker is code calling the public API with data from untrusted sources. However, `ReleaseTimer` is a lifecycle management function intended to be paired with `AcquireTimer`. The `AcquireTimer` function guarantees a non-nil return by either returning a newly created timer or reusing one from the pool. Passing `nil` to `ReleaseTimer` is a violation of the library's contract (precondition) by the host application, which is a developer error, not a result of processing adversarial input. A remote attacker cannot control the flow of control to invoke `ReleaseTimer(nil)`; they can only influence the data payloads handled by the server, which do not dictate whether the internal timer cleanup function receives a nil pointer. Therefore, this is a robustness issue (lack of defensive programming against programmer error) rather than a security vulnerability exploitable by an external attacker.\n\nCRUX: The input to ReleaseTimer is controlled by the host application's developer (lifecycle management), not by untrusted external data, making a nil pointer argument a programmer error rather than a security flaw.\nGREP: ReleaseTimer",
    "elapsed": 104.1,
    "tokens": 3038,
    "tokens_in": 2765,
    "tokens_out": 273,
    "cost_usd": 0.0019803,
    "file": "timer.go",
    "round": 2,
    "grep_used": true,
    "grep_results": "GREP `ReleaseTimer`:\n```\ntcpdialer.go:350:\t\t\tReleaseTimer(tc)\ntimer.go:33:// The returned Timer may be returned to the pool with ReleaseTimer\ntimer.go:45:// ReleaseTimer returns the time.Timer acquired via AcquireTimer to the pool\ntimer.go:50:func ReleaseTimer(t *time.Timer) {\nclient.go:1101:\tReleaseTimer(tc)\nclient.go:1711:\t\tdefer ReleaseTimer(tc)\n```"
  },
  {
    "finding_title": "ReleaseTimer panics on nil timer",
    "verdict": "INVALID",
    "reasoning": "The report claims `ReleaseTimer` panics on a nil pointer. While the code in `stopTimer` does dereference `t` without a nil check, the threat model for a reusable library defines the attacker as external code providing untrusted data to the public API. `ReleaseTimer` is a lifecycle management function intended to be called with a timer acquired by `AcquireTimer`. `AcquireTimer` guarantees a non-nil return. Passing `nil` to `ReleaseTimer` is a violation of the documented contract by the host application (a programmer error), not a consequence of processing adversarial network input. An external attacker cannot control the flow of execution to inject a nil pointer into this specific lifecycle call; they can only influence the data payloads (e.g., request bodies) which do not dictate the internal management of timer objects in the host application's code. Therefore, this is a robustness issue/bug in the library's defensive programming, but not a security vulnerability exploitable by an external attacker under the defined threat model.\n\nCRUX: The nil pointer argument to ReleaseTimer is a result of host application programmer error, not attacker-controlled untrusted data.\nGREP: func AcquireTimer",
    "elapsed": 29.7,
    "tokens": 3354,
    "tokens_in": 3095,
    "tokens_out": 259,
    "cost_usd": 0.0020769,
    "file": "timer.go",
    "round": 3,
    "grep_used": true,
    "grep_results": "GREP `func AcquireTimer`:\n```\ntimer.go:35:func AcquireTimer(timeout time.Duration) *time.Timer {\n```"
  },
  {
    "finding_title": "ReleaseTimer panics on nil timer",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability is a nil pointer dereference in `ReleaseTimer` if passed a nil `*time.Timer`. While the code does lack a nil check in `stopTimer`, the threat model for a library defines the attacker as providing untrusted data to public APIs (e.g., network payloads, file contents). `ReleaseTimer` is an internal lifecycle management function intended to be paired with `AcquireTimer`. The `t` argument is a pointer to a struct managed by the host application's control flow (e.g., variables in `client.go` or `tcpdialer.go`). An external attacker cannot influence the Go control flow to inject a nil pointer into this specific function call; they can only influence the data processed by the application, which does not dictate the existence or nil-ness of timer objects created by the application developer. Passing nil to `ReleaseTimer` is a violation of the library's usage contract (programmer error), not a result of processing malicious input. Under the threat model, missing parameter validation in a public API that requires trusted input (like a valid object reference from internal state) is considered a robustness issue or code quality flaw, not a security vulnerability, unless the input itself is untrusted data (which a function argument pointer is not in this context). Therefore, it is not attacker-reachable.\n\nCRUX: The nil pointer argument to ReleaseTimer is a result of host application programmer error (lifecycle misuse), not attacker-controlled untrusted data, making it a code quality issue rather than a security vulnerability under the library threat model.\nGREP: ReleaseTimer",
    "elapsed": 25.1,
    "tokens": 3680,
    "tokens_in": 3341,
    "tokens_out": 339,
    "cost_usd": 0.00242022,
    "file": "timer.go",
    "round": 4,
    "grep_used": true,
    "grep_results": "GREP `ReleaseTimer`:\n```\ntcpdialer.go:350:\t\t\tReleaseTimer(tc)\nclient.go:1101:\tReleaseTimer(tc)\nclient.go:1711:\t\tdefer ReleaseTimer(tc)\ntimer.go:33:// The returned Timer may be returned to the pool with ReleaseTimer\ntimer.go:45:// ReleaseTimer returns the time.Timer acquired via AcquireTimer to the pool\ntimer.go:50:func ReleaseTimer(t *time.Timer) {\n```"
  },
  {
    "finding_title": "ReleaseTimer panics on nil timer",
    "verdict": "INVALID",
    "reasoning": "The report claims `ReleaseTimer` panics if passed a nil pointer. While `stopTimer` indeed lacks a nil check, the threat model for this library defines the attacker as providing untrusted data to public APIs (e.g., network payloads, file contents). `ReleaseTimer` is a lifecycle management function intended to be paired with `AcquireTimer`. The argument `t` is a pointer to a `time.Timer` object, which is controlled by the host application's control flow (e.g., variables in `client.go` or `tcpdialer.go`). An external attacker cannot influence the Go control flow to inject a nil pointer into this specific function call; they can only influence the data processed by the application, which does not dictate the existence or nil-ness of timer objects created by the application developer. Passing nil to `ReleaseTimer` is a violation of the library's usage contract (programmer error), not a result of processing malicious input. Under the threat model, missing parameter validation in a public API that requires trusted input (like a valid object reference from internal state) is considered a robustness issue or code quality flaw, not a security vulnerability, unless the input itself is untrusted data (which a function argument pointer is not in this context). Therefore, it is not attacker-reachable.\n\nCRUX: The nil pointer argument to ReleaseTimer is a result of host application programmer error (lifecycle misuse), not attacker-controlled untrusted data, making it a code quality issue rather than a security vulnerability under the library threat model.\nGREP: ReleaseTimer",
    "elapsed": 24.5,
    "tokens": 4115,
    "tokens_in": 3780,
    "tokens_out": 335,
    "cost_usd": 0.0025926,
    "file": "timer.go",
    "round": 5,
    "grep_used": true,
    "grep_results": "GREP `ReleaseTimer`:\n```\ntcpdialer.go:350:\t\t\tReleaseTimer(tc)\nclient.go:1101:\tReleaseTimer(tc)\nclient.go:1711:\t\tdefer ReleaseTimer(tc)\ntimer.go:33:// The returned Timer may be returned to the pool with ReleaseTimer\ntimer.go:45:// ReleaseTimer returns the time.Timer acquired via AcquireTimer to the pool\ntimer.go:50:func ReleaseTimer(t *time.Timer) {\n```"
  }
]
Raw scan report for this file

## Security analysis

This file implements a pooled wrapper around `time.Timer` for fasthttp. It does not parse network data, use fixed-size buffers, perform integer arithmetic, or use `unsafe`. Therefore, the classic C-style memory corruption classes are not applicable here. The main security concerns are:

- public API robustness against invalid arguments,
- panic-DoS potential,
- timer lifetime/pool invariants,
- data races caused by incorrect caller usage.

The trust boundary is the exported surface:

- `AcquireTimer(timeout)`
- `ReleaseTimer(t)`

Internal helpers are only reachable through those:

- `initTimer`
- `stopTimer`

### `initTimer`

```go
func initTimer(t *time.Timer, timeout time.Duration) *time.Timer {
	if t == nil {
		return time.NewTimer(timeout)
	}
	if t.Reset(timeout) {
		// developer sanity-check
		panic("BUG: active timer trapped into initTimer()")
	}
	return t
}
```

This function is nil-safe: if `t == nil`, it allocates a new timer.

The dangerous behavior is the deliberate panic when `t.Reset(timeout)` returns `true`, meaning the timer was still active. Under correct usage, this should not happen because `ReleaseTimer` calls `stopTimer`, which stops the timer before returning it to the pool. However, if a timer is released twice, reused concurrently, or otherwise violates the pool invariant, `initTimer` can panic.

This is not directly attacker-controlled through untrusted payload bytes, but an in-process caller or buggy integration could trigger a panic-DoS in timeout-sensitive code paths.

Severity: low.

### `stopTimer`

```go
func stopTimer(t *time.Timer) {
	if !t.Stop() {
		select {
		case <-t.C:
		default:
		}
	}
}
```

This function assumes `t` is non-nil. If `t == nil`, `t.Stop()` panics with a nil pointer dereference. The drain logic is correct for a `time.Timer` channel, which has capacity one.

Because `stopTimer` is called from the public `ReleaseTimer`, a nil timer passed to `ReleaseTimer` becomes a panic.

Severity: medium if nil is reachable through public API misuse or malformed host logic.

### `AcquireTimer`

```go
func AcquireTimer(timeout time.Duration) *time.Duration) *time.Timer {
	v := timerPool.Get()
	if v == nil {
		return time.NewTimer(timeout)
	}
	t := v.(*time.Timer)
	initTimer(t, timeout)
	return t
}
```

Two issues are relevant:

1. **Unchecked interface type assertion**

   `timerPool.Get()` returns `interface{}`. The code casts it with:

   ```go
   t := v.(*time.Timer)
   ```

   without using the comma-ok form. If the pool ever contains a value that is not `*time.Timer`, this panics.

   In the current codebase, `timerPool` is unexported and only populated by `ReleaseTimer`, which puts `*time.Timer`. Therefore, this is not directly attacker-controlled through external untrusted data. It is mostly a robustness issue, but it could become exploitable if future code, a race, or a maintenance mistake injects the wrong type into the pool.

   Severity: low.

2. **No timeout validation**

   `timeout` is passed directly to `time.NewTimer` or `time.Timer.Reset`. Negative durations are generally clamped or treated as immediate expiry by the runtime, and huge durations may simply create long-pending timers. There is no explicit normalization, minimum, or maximum bound.

   This is not a memory-safety bug, but it can lead to surprising timeout behavior or resource retention if host code derives timeouts from untrusted input.

   Severity: low.

### `ReleaseTimer`

```go
func ReleaseTimer(t *time.Timer) {
	stopTimer(t)
	timerPool.Put(t)
}
```

This is the most significant public API issue.

It does not check whether `t` is nil. Passing nil causes `stopTimer(t)` to dereference a nil pointer, leading to a panic.

It also does not track whether a timer has already been released. Calling `ReleaseTimer` twice on the same timer can put the same timer into `sync.Pool` multiple times. Later, two different goroutines may acquire the same underlying `time.Timer`, causing concurrent use, data races, or the active-timer panic in `initTimer`.

Severity: medium for the nil dereference; low to medium for double-release lifecycle misuse.

### `timerPool`

```go
var timerPool sync.Pool
```

`sync.Pool` is untyped. The safety of the pool depends entirely on the invariant that only `*time.Timer` values are inserted. Currently that invariant is maintained by `ReleaseTimer`.

This is not an exploitable vulnerability by itself, but it is why the unchecked assertion in `AcquireTimer` is worth noting.

## Overall assessment

There is no classic parser memory corruption, integer overflow, out-of-bounds write, or unsafe pointer arithmetic in this file. The practical risks are panic-DoS and lifetime-management issues in the pooled timer API:

- nil timer passed to `ReleaseTimer`,
- duplicate release causing the same timer to be acquired concurrently,
- unchecked pool type assertion,
- deliberate panic if an active timer is reused,
- unvalidated timeout values.

```json
[
  {
    "severity": "medium",
    "title": "ReleaseTimer panics on nil timer",
    "function": "ReleaseTimer",
    "description": "ReleaseTimer calls stopTimer without checking whether t is nil. stopTimer dereferences t.Stop() and t.C, so passing a nil timer causes a nil pointer dereference panic. A public API caller can crash the host process if nil reaches this function through buggy or malicious usage."
  },
  {
    "severity": "low",
    "title": "Unchecked interface type assertion in AcquireTimer",
    "function": "AcquireTimer",
    "description": "AcquireTimer casts the value returned by timerPool.Get() to *time.Timer without using the comma-ok assertion form. If the sync.Pool ever contains a non-time.Timer value, the function panics. The risk is currently low because timerPool is unexported and normally populated only by ReleaseTimer, but it is a robustness hazard for a public pooled API."
  },
  {
    "severity": "low",
    "title": "initTimer can panic if a pooled timer is still active",
    "function": "initTimer",
    "description": "initTimer panics if t.Reset(timeout) returns true, indicating the timer was active. Under correct Acquire/Release usage this should not happen, but double ReleaseTimer, concurrent reuse, or use of a released timer can violate the pool invariant and trigger a panic-DoS in timeout code paths."
  },
  {
    "severity": "low",
    "title": "AcquireTimer does not validate timeout duration",
    "function": "AcquireTimer",
    "description": "The timeout argument is passed directly to time.NewTimer or time.Timer.Reset without normalization or bounds checking. Negative values may create immediately expired timers and very large values may keep timers pending longer than intended. If timeouts are derived from untrusted input, this can cause surprising behavior or resource retention."
  }
]
```