← All issues

[24] [grid layout] Don't call viewportContentsChanged() from scroll updates during render tree layout

Severity: Low | Component: WebCore rendering | bf80e3e

diff에서 isInRenderTreeLayout()이 true인 상태로 scrollTo()viewportContentsChanged()를 호출했음이 확인됩니다. debug 빌드에서는 assertion이 유발되고, release 빌드에서는 부분적으로 초기화된 grid track 상태가 읽힙니다. 손상된 상태는 memory-copy primitive가 아닌 layout 연산으로 흘러 들어가므로, 관찰 가능한 가장 현실적인 영향은 공격자가 조작한 HTML/CSS에 의한 renderer crash 재현입니다.

RenderLayerScrollableArea::scrollTo()LocalFrameView::viewportContentsChanged()를 호출합니다. 이 함수는 renderer geometry를 조회하여 visibility rect를 계산합니다. layout 도중(grid pre-layout 중 updateScrollInfoAfterLayout을 통한 경우 등) 호출되면 containing block이 아직 완료되지 않은 상태입니다. 이 상태에서 absolutely positioned iframe의 percentage padding을 아직 채워지지 않은 grid area에 대해 해석하려 하면 gridAreaRangeForOutOfFlow에서 assertion 실패가 발생합니다. 이 호출은 isInRenderTreeLayout()으로 보호되었습니다.

Source/WebCore/rendering/RenderLayerScrollableArea.cpp

if (scrollsOverflow())
view.frameView().didChangeScrollOffset();
 
- view.frameView().viewportContentsChanged();
+ if (!view.frameView().layoutContext().isInRenderTreeLayout())
+ view.frameView().viewportContentsChanged();

Layout-phase invariant 위반: layout 진행 중 발생한 scroll update에서 post-layout visibility-rect 조회가 호출되어, containing block이 layout을 완료하기 전에 renderer geometry를 읽는 패턴.

scrollTo() 안의 viewportContentsChanged() 호출에 !layoutContext().isInRenderTreeLayout() 조건이 추가되었습니다. 이 호출 지연은 안전합니다. performPostLayoutTasks()가 매 layout 완료 후 무조건적으로 viewportContentsChanged()를 이미 호출하고 있기 때문입니다. regression test에서는 percentage padding-left를 가진 absolutely positioned <iframe>을 CSS grid container 안에 중첩합니다. 이 container는 scaled descendant를 가진 vertical-rl 스크롤 가능한 자식을 포함합니다.

Render tree layout은 단계적으로 진행됩니다. engine은 top-down 방식으로 box geometry를 계산하며, 일부 helper는 geometry를 읽기 전에 layout이 완료된 상태를 전제로 합니다. LocalFrameView::layoutContext().isInRenderTreeLayout()은 layout pass 중에 true를 반환합니다. viewportContentsChanged()는 scroll 이후의 visibility를 계산하기 위해 renderer tree를 순회하는 함수로, 일반적으로 layout 완료 후 performPostLayoutTasks()에서 호출됩니다. CSS grid는 item 배치 전 pre-layout pass를 통해 track 크기를 결정합니다. out-of-flow grid item은 gridAreaRangeForOutOfFlow를 통해 containing block을 해석하며, 이 과정에서 track range를 읽습니다. percentage padding은 containing block의 inline size를 기준으로 해석됩니다.

패치 이전 scrollTo()viewportContentsChanged()를 무조건적으로 호출했습니다. grid pre-layout 중 updateScrollInfoAfterLayout을 통해 이 경로에 도달하면 containing block이 완료되지 않아 geometry가 부분적으로 구성된 상태입니다. 이 테스트 구성에서는 gridAreaRangeForOutOfFlow가 track이 채워지지 않은 grid area를 대상으로 percentage padding-left를 해석하려 합니다. 결과적으로 debug 빌드에서는 assertion이 유발되고, release 빌드에서는 초기화되지 않은 grid track 상태가 읽힙니다.

트리거는 HTML/CSS만으로 구성할 수 있습니다. display: grid container 안에 grid pre-layout 중 scroll offset이 갱신되는 스크롤 가능한 inner box가 필요합니다(regression test에서는 scaled descendant를 가진 vertical-rl overflow:scroll 사용). 여기에 더해, track이 채워지기 전에 gridAreaRangeForOutOfFlow 해석을 강제하는 percentage padding을 가진 absolutely positioned grid item이 있어야 합니다. layout 도중 발생한 scrollToviewportContentsChanged()를 호출하고, 이 함수가 renderer geometry를 재귀적으로 조회하면서 완료되지 않은 grid area를 참조하게 됩니다.

release 빌드에서 가장 현실적으로 관찰 가능한 영향은 percentage padding 해석에 사용되는 grid track range 상태의 초기화되지 않은 읽기입니다. 해당 값의 소비처는 layout 연산이며 JS에서 접근 가능한 buffer로의 복사가 아니므로, write primitive는 드러나지 않습니다. 최소한으로 관찰 가능한 결과는 신뢰할 수 없는 CSS에 의한 renderer crash입니다. 이는 updateScrollInfoAfterLayout 내의 scroll offset 조정이 layout 완료를 전제로 하는 helper를 재진입하는 반복적인 문제 유형에 해당합니다.

이 취약점은 trust boundary가 아닌 renderer process의 안정성을 약화시키는 수준입니다.