[24] ReadableStreamDefaultReader GC-thread UAF on m_stream
Rated Medium because the diff fixes the same cross-thread UAF class as #23 in a different DOM object: GC-thread visitors read
m_streamwhile the main thread can null it viareleaseLock/genericRelease.
Sister fix to the XMLHttpRequest commit. m_stream becomes WTF_GUARDED_BY_LOCK(m_streamLock); every access serialises through the lock.
Source/WebCore/Modules/streams/ReadableStreamDefaultReader.h
Unsynchronized access from a GC visitor thread to a main-thread-mutable reference-counted member, allowing the visitor to dereference a pointer concurrently being released.
The commit message explicitly notes "we cannot easily ref the stream on the GC thread" — atomic ref does not solve the race because the object could be destroying mid-ref.
A WHATWG ReadableStream may have at most one active reader; the reader holds m_stream back to the stream. releaseLock() and stream-side release nulls this. The lock serializes nulling against GC reads — the GC visit either sees a stable live pointer or nullptr.
This vulnerability weakens the invariant that data structures visited by the GC are either immutable or accessed under synchronization with main-thread mutators.
Audit directions
- Sister Streams classes likely missed.
ReadableStreamBYOBReader,WritableStreamDefaultWriter,ReadableStreamDefaultController,ReadableByteStreamController— same opaque-root pattern. - Cross-thread
RefPtraccess without a lock. Grep WebCore forRefPtr<T> m_foopatterns nearstd::exchange(m_foo, { })orm_foo = nullptrin objects participating inWebCoreOpaqueRoot. - Lock released too early around
m_field = nullptr. IngenericRelease,stream->setDefaultReader(nullptr)runs unlocked; verify no path where GC observes incoherent (stream->defaultReader(),reader->m_stream) state. SUPPRESS_NODELETEannotations added with the fix. Confirm they don't mask a previously-enforced inlining-related lifetime invariant.