[6] [WebCore] Fix UAF in Range::createContextualFragment via Trusted Types policy callback
Range::createContextualFragment의 Trusted Types createHTML callback 구간에 걸쳐 raw Node*/Element* 캡처가 Ref/RefPtr로 승격되었기 때문에 High로 분류됩니다. 승격이 없는 상태에서는 attacker가 policy callback 내의 JS를 통해 모든 reference를 해제하고 GC를 강제할 수 있었습니다. callback이 반환된 이후 해당 pointer를 역참조하면 이미 해제된 메모리를 대상으로 동작하게 됩니다.
Range::createContextualFragment에서 캡처된 Node/Element reference가 Ref/RefPtr로 변환되어, Trusted Types policy 호출 구간 전반에 걸쳐 lifetime이 고정됩니다.
Source/WebCore/dom/Range.cpp
Re-entrancy UAF: Trusted Types policy callback 호출 전 raw Node pointer를 캡처하고, attacker JavaScript 동기 실행 이후 해당 pointer를 역참조하는 패턴.
Patch Details
context node와 start container에 대한 함수의 로컬 캡처가 reference-counted 형태로 변경되었습니다. policy callback 자체에는 수정이 없습니다.
Background
Range::createContextualFragment는 Range의 start container 컨텍스트 안에서 HTML fragment를 파싱합니다. Trusted Types의 createHTML policy는 입력 문자열 경로상에 위치하며, 파싱 전에 사용자가 등록한 JavaScript callback을 실행하여 제공된 HTML을 sanitize합니다.
Analysis
패치 이전에는 함수가 Range의 boundary에서 raw Node*/Element*를 추출한 뒤, createHTML 호출을 거치는 동안에도 그대로 사용하는 구조였습니다. 이 callback은 attacker가 제어하는 JS입니다. callback 내에서 Range를 변경할 수 있습니다. 예를 들어 range.setEnd(document, 0)을 호출하면, 구현상 순서가 역전될 때 setEnd가 start를 end 위치로 이동시킵니다. 이 밖에도 살아있는 모든 reference를 해제하고 GC를 강제하는 것도 가능합니다.
createHTML이 반환되는 시점에는 캐시된 raw pointer가 이미 소멸된 Node를 가리키게 됩니다. 이후 파싱 컨텍스트 조회, fragment 생성, 혹은 attach 과정이 해제된 메모리를 대상으로 동작합니다. 캡처를 Ref/RefPtr로 승격하면 stack frame이 유지되는 동안 Node의 lifetime이 보장되어, 이 invariant가 복구됩니다.
이 버그는 JS re-entrancy 지점 전반에서 DOM lifetime invariant를 약화시킵니다. exploit primitive는 script에서 도달 가능한 DOM Node에 대한 UAF입니다.
Audit directions
- 사용자가 제어 가능한 callback을 호출하는 DOM 함수 (Trusted Types policy, MutationObserver, custom-element callback,
requestIdleCallback) 중 raw Node/Element pointer를 보유한 채 호출하는 경우..get()이후 알려진 callback dispatch 지점이 바로 이어지는 패턴을 검색합니다. - JS로 재진입하는 다른 Range mutator.
Range::extractContents,Range::cloneContents,Range::deleteContents는 MutationObserver와 DOM 레벨 event 경계를 교차합니다. HTMLFragmentParser호출 지점 중 context node를 캡처한 채 파싱 과정에서 script로 재진입하는 경우.