BudgetScan

Unbounded user-value storage and linear lookup

low swesweep-fasthttp · userdata.go · Set() · run #4

Description

userData has no limit on the number of entries, and Get/Remove perform linear scans. If a host application stores attacker-influenced keys, such as parsed body fields or generated identifiers, without validation or limits, an attacker can cause excessive memory allocation or CPU consumption.

Triage

Not triaged.

Raw scan report for this file

`userData` is an unexported per-request/response key/value store, but it is reachable through exported `Request`/`Response`/`RequestCtx` user-value APIs. The main risk is not classic memory corruption, but availability and state corruption caused by unvalidated `any` keys, uncontrolled resource lifecycle, and unbounded growth.

Key observations:

- `Set`, `Get`, and `Remove` compare `kv.key == key`, where both values are `any`. If the dynamic type is uncomparable, such as a slice, map, function, or a struct containing any of those, the comparison panics.
- `Set` normalizes exact `[]byte` keys to `string`, but it does not reject or normalize other uncomparable key types.
- `Get` and `Remove` use `b2s` for `[]byte` keys. That is usually safe in this context because the converted string is only used for comparison and is not stored, but it depends on `b2s` behaving as expected.
- `Reset` calls `Close()` on any user value implementing `io.Closer`. If a stored value is attacker-influenced or a typed nil pointer implementing `io.Closer`, cleanup can panic or leak state.
- There is no bound on the number of user values, and lookup is linear, so an application that stores many attacker-influenced keys can be made to allocate excessive memory or spend excessive CPU.

```json
[
  {
    "severity": "High",
    "title": "Panic from comparing uncomparable any keys",
    "function": "Set()/Get()/Remove()",
    "description": "The code compares interface values with kv.key == key without verifying that the dynamic key type is comparable. If a caller stores or looks up a key whose dynamic type is a slice, map, function, or a struct containing an uncomparable field, Go panics at runtime. This is reachable through public user-value APIs that accept key as any, and can be used to cause denial of service if an attacker can influence the key object passed to the application."
  },
  {
    "severity": "Medium",
    "title": "Reset may invoke malicious or invalid io.Closer values",
    "function": "Reset()",
    "description": "Reset iterates over stored user values and calls Close() on any value implementing io.Closer. If an application stores attacker-influenced values, or typed nil pointers whose pointer type implements io.Closer, Close may panic, perform unintended side effects, or leave the userData slice in an inconsistent state. Error return values from Close are also ignored."
  },
  {
    "severity": "Low",
    "title": "Replacing or clearing a Closer user value without closing it",
    "function": "Set()",
    "description": "Set overwrites an existing value with the new value, including nil, without checking whether the old value implements io.Closer. If an application uses Set(key, nil) or Set(key, other) to replace a resource-holding value, the old resource is not closed, leading to resource leaks."
  },
  {
    "severity": "Low",
    "title": "Unbounded user-value storage and linear lookup",
    "function": "Set()",
    "description": "userData has no limit on the number of entries, and Get/Remove perform linear scans. If a host application stores attacker-influenced keys, such as parsed body fields or generated identifiers, without validation or limits, an attacker can cause excessive memory allocation or CPU consumption."
  }
]
```