# Node.js: use-after-free in TLSWrap

TLSWrap::DoWrite frees the WriteWrap on a failed underlying write but returns success, causing a use-after-free in StreamBase::Writev.

- Payout: hidden
- Severity: high
- Program: Node.js
- Found by: fwilhelm
- Disclosed: 2021-01-05
- CVE: CVE-2020-8265
- Write-up quality: 9/10
- Original: https://hackerone.com/reports/988103
- Library page: https://bugbountylibrary.com/r/h1%3A988103
- Tags: area:oss domain:oss impact:dos impact:rce kind:bounty platform:hackerone sev:high tech:nodejs technique:source-review vuln:dos vuln:memory-corruption

**Target:** Node.js TLS socket write path (tls_wrap.cc / stream_base.cc)
**Root cause:** TLSWrap::DoWrite calls EncOut(), which on underlying_stream()->Write() failure invokes InvokeQueued() and frees the WriteWrap via StreamReq::Dispose, yet DoWrite still returns 0 (success), so StreamBase::Write hands the freed pointer back in StreamWriteResult.
**Steps:**
1. Run an HTTPS server (server.js) on Node v14.11.0.
2. Client connects via tls.connect, sends several HTTP requests, then calls socket.destroy() on first data to close the socket.
3. Server's next write hits a broken pipe in underlying_stream()->Write inside EncOut().
4. EncOut calls InvokeQueued(err) which frees the WriteWrap through StreamReq::Done -> WriteWrap::OnDone -> StreamReq::Dispose.
5. DoWrite returns 0, so StreamBase::Write returns the freed wrap; StreamBase::Writev calls res.wrap->SetAllocatedStorage(), a UAF (ASAN heap-use-after-free).
**How found:** Code reading of StreamBase::Write/DoWrite contract: DoWrite is expected to return an error if it frees req_wrap, but the EncOut failure path frees via InvokeQueued while still returning 0. Noticed the broken-pipe path is common in the real world yet never crashed, suggesting heap layout masked it.
**Exploitation:** Attacker closes the TLS connection just before the server writes, forcing a broken pipe in EncOut and freeing the WriteWrap while it is still returned as valid; the subsequent SetAllocatedStorage writes to freed memory. Report claims potential RCE with a suitable heap layout, but no working exploit is demonstrated.
**Insight:** When auditing C++ async write APIs, check every internal early-return path that can free the request wrapper (e.g. error callbacks like InvokeQueued) against the caller's assumption that a zero return means the wrapper is still alive; broken-pipe/error paths on sockets are a reliable trigger.

