[24] ReadableStreamDefaultReader GC-thread UAF on m_stream
[23]과 동일한 cross-thread UAF 유형이 다른 DOM 객체에서도 발견된 사례입니다. GC-thread visitor가
m_stream을 읽는 사이, main thread에서releaseLock/genericRelease를 통해 이를 null로 설정할 수 있습니다. Medium으로 평가된 이유입니다.
XMLHttpRequest commit에 대응하는 짝 패치입니다. m_stream은 WTF_GUARDED_BY_LOCK(m_streamLock)으로 보호되며, 모든 접근은 lock을 통해 직렬화됩니다.
Source/WebCore/Modules/streams/ReadableStreamDefaultReader.h
Main thread에서 수정 가능한 reference-counted 멤버에 대해, GC visitor thread가 동기화 없이 접근하는 패턴입니다. 이로 인해 visitor가 해제 중인 pointer를 역참조하게 됩니다.
commit message에는 "GC thread에서 stream을 쉽게 ref할 수 없다"는 내용이 명시되어 있습니다. atomic ref로는 이 race를 해결할 수 없습니다. ref가 진행되는 도중에 객체가 이미 소멸 중일 수 있기 때문입니다.
WHATWG ReadableStream은 동시에 하나의 active reader만 가질 수 있습니다. reader는 m_stream을 통해 stream에 대한 역참조를 유지합니다. releaseLock()이나 stream 측의 해제가 발생하면 이 참조가 null로 설정됩니다. lock은 GC 읽기와 null 설정 사이를 직렬화하여, GC visit 시에는 안정적인 live pointer 또는 nullptr만 관찰되도록 보장됩니다.
이 vulnerability는 GC가 방문하는 데이터 구조가 불변이거나 main thread mutator와 동기화된 상태에서 접근되어야 한다는 invariant를 약화시킵니다.
Audit directions
- Sister Streams classes likely missed.
ReadableStreamBYOBReader,WritableStreamDefaultWriter,ReadableStreamDefaultController,ReadableByteStreamController도 동일한 opaque-root 패턴을 갖고 있습니다. - Cross-thread
RefPtraccess without a lock.WebCoreOpaqueRoot에 참여하는 객체에서std::exchange(m_foo, { })나m_foo = nullptr근처의RefPtr<T> m_foo패턴을 WebCore 전반에서 검색합니다. - Lock released too early around
m_field = nullptr.genericRelease내부에서stream->setDefaultReader(nullptr)는 lock 없이 실행됩니다. GC가 (stream->defaultReader(),reader->m_stream) 상태를 incoherent하게 관찰하는 경로가 없는지 확인합니다. SUPPRESS_NODELETEannotations added with the fix. 이번 fix에서 추가된SUPPRESS_NODELETEannotation이 기존에 적용되던 inlining 관련 lifetime invariant를 숨기지 않는지 확인합니다.