# Stale handle state across reused operations

Reused handles or pooled connections carrying prior per-request state, or shared statics, into a new operation

_Area: Memory corruption and native code. Seen in 4 disclosed reports; 2 confirmed bounties: $480 and $2,400; latest 2023-03-20. Updated 2026-10-23._

## How it works
A handle or pooled connection is reused for a new operation, but not all per-request state is reset or compared. The code assumes "same handle means same configuration," so a later operation inherits options, buffers, or security settings from an earlier one. The same failure appears in two shapes: per-handle staleness (a reused handle keeps old state) and cross-handle sharing (a `static` buffer lets one handle overwrite another's settings). Either way the wrong callback fires, the wrong buffer is read, or a weaker security setting silently applies.

## Where it shows up
- Per-connection setters writing into `static` arrays or buffers instead of per-handle storage [E4](https://bugbountylibrary.com/r/h1%3A1172857.md)
- Option transitions on one handle: upload/read-callback to postfields, PUT to POST [E2](https://bugbountylibrary.com/r/h1%3A1752146.md)
- Connection-pool match logic that omits some per-transfer options from the reuse key [E3](https://bugbountylibrary.com/r/h1%3A1912782.md)
- Handles holding raw pointers to connection objects that can be freed by timeout while the handle stays live [E1](https://bugbountylibrary.com/r/h1%3A948876.md)
- Callbacks invoked against fields belonging to a previous request [E2](https://bugbountylibrary.com/r/h1%3A1752146.md)

## How to review
Enumerate every piece of per-request state and confirm it is either fully reset on reuse or included in the reuse match key. The invariant: a reused handle or pooled connection must behave exactly as a fresh one configured with only the current request's options. Checklist of option categories to verify in reset and match logic: auth/credentials, GSSAPI delegation, cipher/TLS settings, and callback/body-source options (read function vs postfields). Confirm setters store state per-handle, not in shared statics [E4](https://bugbountylibrary.com/r/h1%3A1172857.md); that switching modes clears the old callback path [E2](https://bugbountylibrary.com/r/h1%3A1752146.md); that every security-relevant option is in the pool match key [E3](https://bugbountylibrary.com/r/h1%3A1912782.md); and that handles do not retain pointers to connections that timeout/age-out can free [E1](https://bugbountylibrary.com/r/h1%3A948876.md).

## Variants seen
- Connect-only handle keeps a dangling connection pointer; a new connection at the same address is silently used [E1](https://bugbountylibrary.com/r/h1%3A948876.md)
- PUT-to-POST switch on a reused handle still calls the old read callback, sending the wrong buffer [E2](https://bugbountylibrary.com/r/h1%3A1752146.md)
- Pool matching without the GSSAPI delegation setting, allowing reuse across differing delegation levels [E3](https://bugbountylibrary.com/r/h1%3A1912782.md)
- Shared static cipher array across concurrent handles, last writer wins [E4](https://bugbountylibrary.com/r/h1%3A1172857.md)

## Impact and severity
Data sent to the wrong server or leaked from wrong buffer reads, including use-after-free [E1](https://bugbountylibrary.com/r/h1%3A948876.md)[E2](https://bugbountylibrary.com/r/h1%3A1752146.md); silent cipher downgrade across handles [E4](https://bugbountylibrary.com/r/h1%3A1172857.md); a stricter transfer reusing a laxer connection, weakening delegation guarantees [E3](https://bugbountylibrary.com/r/h1%3A1912782.md). Severity rises when the stale state is security-relevant and the mismatch is silent, since neither caller nor peer sees the change.

## Why fixes fail
Fixes that reset only the obvious option miss others. The recurring omission classes: (a) the pool match key is updated for one option but not the full set of security-relevant ones [E3](https://bugbountylibrary.com/r/h1%3A1912782.md); (b) `static` storage in a setter is overlooked because it looks local [E4](https://bugbountylibrary.com/r/h1%3A1172857.md); (c) mode transitions clear the new path but not the old callback [E2](https://bugbountylibrary.com/r/h1%3A1752146.md); (d) lifecycle pointers are not invalidated when the referenced object is freed [E1](https://bugbountylibrary.com/r/h1%3A948876.md). The same class then recurs in a different option.

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A948876.md
- E2: https://bugbountylibrary.com/r/h1%3A1752146.md
- E3: https://bugbountylibrary.com/r/h1%3A1912782.md
- E4: https://bugbountylibrary.com/r/h1%3A1172857.md
