← All issues

[23] XMLHttpRequest GC-thread UAF on m_responseDocument

Severity: Medium | Component: WebCore XMLHttpRequest bindings | a814080

Rated Medium because the diff fixes a cross-thread race where the JSC GC marking thread dereferenced m_responseDocument without synchronization while the main thread could null it via responseXML()/clearResponseBuffers().

JSXMLHttpRequest::visitAdditionalChildrenInGCThread read wrapped().optionalResponseXML() and optionalUpload() with no synchronization while the main thread could perform m_responseDocument = nullptr; — a RefPtr reassignment that drops the previous ref.

Source/WebCore/xml/XMLHttpRequest.h

+ Lock m_gcLock;
+ const std::unique_ptr<XMLHttpRequestUpload> m_upload WTF_GUARDED_BY_LOCK(m_gcLock);
- RefPtr<Document> m_responseDocument;
+ RefPtr<Document> m_responseDocument WTF_GUARDED_BY_LOCK(m_gcLock);
+template<typename Visitor>
+void XMLHttpRequest::visitAdditionalChildrenInGCThread(Visitor& visitor)
+{
+ Locker locker { m_gcLock };
+ if (m_upload)
+ addWebCoreOpaqueRoot(visitor, *m_upload);
+ if (auto* document = m_responseDocument.get())
+ addWebCoreOpaqueRoot(visitor, *document);
+}

Unsynchronized GC-thread read of refcounted DOM pointers that the main thread can null or reassign without barriers, producing a data-race UAF on a JS opaque root.

Every main-thread reader/writer of those two members is wrapped in Locker { m_gcLock }. The header drops the now-unused inline getters optionalResponseXML() and optionalUpload().

🔒

A multi-threading hazard between JSC's marking phase and main-thread DOM mutation — the analysis walks through how the visitor's view of an owned DOM pointer can race a re-entrant nullification.

Subscribe to read more

🔒

Four reusable audit patterns covering GC-visitor thread-safety across WebKit JS bindings, with concrete grep starting points for variant discovery.

Subscribe to read more