[24] [grid layout] Don't call viewportContentsChanged() from scroll updates during render tree layout
Severity: Low | Component: WebCore rendering | bf80e3e
Rated Low because the diff confirms scrollTo() invoked viewportContentsChanged() while isInRenderTreeLayout() was true, tripping an assertion in debug and reading partially-initialized grid track state in release; the corrupted state flows into layout arithmetic rather than a memory-copy primitive, so the most plausible observable impact is a reliable renderer crash from attacker-shaped HTML/CSS.
RenderLayerScrollableArea::scrollTo() called LocalFrameView::viewportContentsChanged(), which computes visibility rects by querying renderer geometry. When invoked during layout (e.g. via updateScrollInfoAfterLayout during grid pre-layout), containing blocks have not finished, causing an assertion failure in gridAreaRangeForOutOfFlow when resolving percentage padding on an absolutely positioned iframe against a grid area not yet populated. The call is guarded with 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 violation: a post-layout visibility-rect query is invoked from a scroll update that fires mid-layout, reading renderer geometry before containing blocks have completed layout.
Patch Details
The viewportContentsChanged() call in scrollTo() is guarded with !layoutContext().isInRenderTreeLayout(). The deferral is safe because performPostLayoutTasks() already calls viewportContentsChanged() unconditionally after every layout completes. A regression test nests an absolutely positioned <iframe> with percentage padding-left inside a CSS grid container holding a vertical-rl scrollable child with a scaled descendant.
Background
Render tree layout is phased: the engine computes box geometry top-down, and certain helpers assume layout has completed before reading geometry. LocalFrameView::layoutContext().isInRenderTreeLayout() returns true during the layout pass. viewportContentsChanged() walks the renderer tree to compute visibility post-scroll, normally invoked from performPostLayoutTasks() once layout finishes. CSS grid uses a pre-layout pass to size tracks before placing items; out-of-flow grid items resolve their containing block through gridAreaRangeForOutOfFlow, which reads track ranges. Percentage padding resolves against the inline size of the containing block.
Analysis
Pre-fix scrollTo() unconditionally called viewportContentsChanged(). When reached via updateScrollInfoAfterLayout during grid pre-layout, containing blocks had not finished and geometry was partially constructed. In the test configuration this led gridAreaRangeForOutOfFlow to resolve percentage padding-left against a grid area whose tracks were unpopulated, tripping the assertion in debug and reading uninitialized grid track state in release.
Aaa Aaaaaaa Aaaaaaaa Aaaa Aaaaaaaaa a Aaaaaaaaa Aaaaa Aaaaaaaaa Aaaaaaa a Aaaaaaaaaa Aaaaa Aaa Aaaaa Aaaaaa Aaaaaaa Aaa Aaaaaa Aaaaaa Aaaaaa Aaaa Aaaaaaaaaa Aaaa Aaaaaaaaaa Aaaa Aaaa Aaaaaaaaaaaaa Aaaaaaaaaaaaaaa Aaaa a Aaaaaa Aaaaaaaaaaa Aaa Aa Aaaaaaaaaa Aaaaaaaaaa Aaaa Aaaa Aaaa Aaaaaaaaaa Aaaaaaa Aaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaa Aaaaaa Aaaaaa Aaa Aaaaaaaaaa Aaa Aaaaaaaaaa Aaaaaaaaaa Aaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaaaaaaa Aaaa Aaaaaaaa Aaaaaaaa Aaa Aaaaaaa Aaa Aaaaaaaaaa Aaaa Aaaaa
Aaa Aaaa Aaaaaaaaa Aaaaaaaaaaaaa Aaaaaa Aa Aa Aaaaaaaaaaaaa Aaaa Aa Aaaa Aaaaa Aaaaa Aaaaa Aaaa Aa Aaaaaaa Aaa Aaaaaaaaaa Aaaaaaaa Aaa Aaaaaaaa Aa Aaaaaa Aaaaaaaaaaa Aaa a Aaaa Aaaa a Aaaaaaaaaa Aaaaaaa Aa Aa Aaaaa Aaaaaaaaa Aa Aaaaaaaa Aaa Aaaaaaa Aaaaaaaaaa Aaaaaaaaaaa Aa Aaaaaaaa Aaaaaaaa Aaaaa Aaaa Aaaaaaaaa Aaaa Aaaa Aa Aaaa Aa a Aaaaaaaaa Aaaaa Aaaaa Aaaaaaaaaaaaa Aaaaaaaaaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaa Aaaaaaa Aaaaaaaa Aaaaaa Aaaaaaaaaaa
Aaaa Aaaaaaa Aaaaaaaaaaaaaaaa Aaaaaaaaaa Aaaaaa Aaaa a Aaaaa Aaaaaaaaa
🔒The phase-ordering invariant behind this layout assertion is examined, along with what the partially-constructed grid state could plausibly mean in release builds.
Subscribe to read more
Audit directions
a Aaaaaaaaaaaaa Aaaaaaa Aaaaaaa Aaaa Aaaaaaaaaaaaa Aaaaaaaaaaa Aaaa Aaaa Aaaaaa Aaaaaa Aaaa Aaaaaaaaa Aaaaa Aaaaa Aaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaa Aaa Aaaaaa Aaa Aa Aaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaa Aaaaa Aaaa Aaa Aaaaaaaaaa a Aaaaaaaaaaaaaaaaaaa Aaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaa Aaaaaaaaaaaaa Aaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaa Aa Aaaaa Aaaaaaaa Aaa Aaaaaaaa Aaaaaa Aaaaa
a Aaaaaaaaaaaaa Aaaa Aaaaa Aaaaaaaaa Aaaaaaa Aaaa Aaaaa Aaaaa Aaaaaa Aaaaaa Aaa Aaaaaaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaaaaa Aa Aaaaaaaaaaaa Aaa Aaaaa Aaaaaa Aaaa Aaa Aaa Aaaaaa Aaaaaaaaaa a Aaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaa Aaaaaaaaaa Aa Aaaaaaaaaa Aaaaaaaaaa Aaaa Aaaaaa
a Aaaaaaaaaaaaaa Aaa Aaaaaaaaaaa Aaaaaa Aaaaaaaaaaaa Aaaaaaaaaa Aaaaaa Aaaaaaa Aaaaaa Aaaaaaa Aaaaaaaaa Aaaaaaaaaaa Aaaaa Aaaaaa Aaaaa Aaaaa a Aaaaaaa Aaaaaaaaa Aaaaaaaa Aaaaaaaa Aaa Aaaaaa Aaa Aaaaaaaa Aaaaaa Aaaaaaaa Aaaaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaa Aaaaaa Aaaaaa Aaaaaaaaaaaaaa
a Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaa Aaaaaa Aaaaaaaaaaaaaaa Aaaaa Aaa Aaaaaaa Aaaaa Aaaaaa Aaaaaaa Aaaa Aaaaaaaa Aaaaaaaa Aaaaaaa Aaaa Aaaaaaa Aaaaaa Aaaaaaaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaa Aa Aaaaaaaa Aaaaaa
🔒Multiple reusable audit patterns across scroll-update entry points and grid out-of-flow resolution, with concrete WebCore starting points for variant discovery.
Subscribe to read more