← All reports

[6] GFC `layoutGridItems` redundant measurement layouts

LowWebCore GFC (Grid Formatting Context)LogicError

97a5bd9

Low. 메모리 안전성 문제가 아니라 동작 불일치(behavioral divergence)를 바로잡는 fix입니다. fit-content grid item을 측정하는 과정에서 강제 layout이 추가로 수행되었고, 그때마다 item이 repaint되어 legacy RenderGrid와 동작이 달라졌습니다. exploit 가능성은 낮은 것으로 보입니다. 관찰되는 영향은 불필요한 repaint invalidation이며, 현재 확인 가능한 근거로는 메모리 안전성 측면의 확장 방향이 보이지 않습니다.

GFC는 LFC 기반으로 CSS Grid를 다시 구현한 것으로, Source/WebCore/layout/formattingContexts/grid 아래에 있습니다. sizing algorithm을 실행한 뒤 IntegrationUtils bridge를 거쳐 legacy render tree에 grid item을 배치합니다. 이 과정은 모두 WebContent process에서 수행됩니다. fit-content item의 경우, 먼저 helper를 통해 block size를 결정했습니다. 이 helper는 실제 renderer 위에서 intrinsic size를 측정하기 위한 probe layout을 수행합니다. 이후 측정된 height를 강제로 적용한 상태로 item을 다시 layout했습니다. 원래 지켜져야 할 invariant는 단순합니다. item의 최종 size는 단 한 번의 authoritative layout에서 나와야 하며, 이를 다시 계산하려고 render tree를 추가로 변경해서는 안 됩니다.

관전 포인트: 공격 측면의 의미는 없습니다. fit-content grid item에서 repaint invalidation이 추가로 발생하며, paint rect 개수로 이를 관찰할 수 있습니다. 점검 관점에서 가치 있는 부분은 intrinsic size probe가 실제 render tree 상태를 변경하는 패턴입니다.

Commit message와 주석에 따르면, fit-content item에서는 blockUsedSize가 min/max-content size를 계산하기 위해 layout까지 수행했습니다. 반면 RenderGrid는 "just calls layout on it"이라고 설명되어 있습니다. Fix는 이런 item을 block 축으로 강제하지 않는 한 번의 layout으로 처리하고, height는 geometry에서 다시 조회하도록 변경했습니다.

Source/WebCore/layout/formattingContexts/grid/GridLayout.cpp

- auto blockSize = GridLayoutUtils::blockUsedSize(...);
- integrationUtils.layoutGridItem(..., blockSize /* forced */, ...);
+ integrationUtils.layoutGridItem(..., inlineUsedSize, { }, gridAreaInlineSize);
+ auto blockSize = box.contentBoxHeight() + blockBorderAndPadding;

fit-content grid item의 block size를 더 이상 측정 helper로 결정하지 않도록 변경되었습니다. 대신 block 축으로는 강제하지 않는 layout을 한 번만 수행합니다. 이때 layoutGridItem(..., inlineUsedSize, { }, gridAreaInlineSize)는 여전히 inline size를 강제합니다. 이후 결과 box geometry에서 contentBoxHeight()를 읽고, blockBorderAndPadding은 별도로 더합니다. geometry에 포함된 padding이 grid area가 아니라 grid container를 기준으로 계산되었기 때문입니다.

실제 render tree 상태 위에서 수행되는 중복 측정 layout으로, repaint invalidation과 dirty bit 변동 같은 side effect가 authoritative 단일 layout 경로와 어긋나는 패턴.

GFC와 integration bridge. GFC는 grid sizing algorithm(GridSizer)을 실행하고, IntegrationUtils를 거쳐 legacy render tree에 item을 배치합니다. 계산된 rect는 computeGridItemRects/setGridItemGeometries를 통해 반영됩니다.

Intrinsic size probe의 side effect. 이 integration layer에서는 측정 호출이 live renderer를 수정합니다. blockSizeForGridItem은 실제 renderer에 대해 setGridAreaContentLogicalWidth(...), setNeedsLayout(MarkOnlyThis), layoutWithFormattingContextForBox(...), clearGridAreaContentSize()를 차례로 호출합니다. 그 뒤에야 geometryForBox(box).borderBoxHeight()를 읽습니다. blockUsedSize가 이 helper로 이어진다는 관계는 이번 패치가 추가한 주석을 근거로 삼은 내용입니다.

공존하는 legacy 경로. 추가된 주석은 이번 변경을 "RenderGrid just calls layout on it"의 동작에 맞추는 것으로 설명합니다. migration 기간 동안 legacy RenderGrid 구현이 함께 존재한다는 점을 시사하는 대목입니다.

로직 오류, 즉 동작 불일치(behavioral divergence) 유형에 해당합니다. 실제 render tree 상태 위에서 측정 pass가 중복으로 수행되고, 그 side effect를 외부에서 관찰할 수 있습니다. 현재 확인 가능한 근거로 보면 메모리 안전성 버그는 아닙니다.

측정 layout은 매번 item을 layout 대상으로 표시한 뒤 실제로 layout합니다. 추가된 test의 설명에 따르면, item에 대한 layout pass가 한 번 수행될 때마다 이전 위치에서 repaint가 발생합니다. 결과적으로 최종 layout 앞에 intrinsic size 측정 layout이 붙으면서, GFC 경로는 fit-content grid item에 대해 더 많은 repaint invalidation을 발생시켰습니다. 비교 기준은 주석이 RenderGrid의 동작으로 설명하는 경로입니다.

새 test는 rect 개수를 3개로 고정합니다. align-self가 start에서 end로 바뀐 뒤, 2개는 item의 이전 위치인 y=50에, 1개는 새 위치인 y=100에 해당합니다. fix 이전의 rect 개수는 diff에 명시되어 있지 않습니다 — expectation 파일 자체가 새로 추가된 것이므로, 이번 수정은 integration-layer 경로를 RenderGrid가 이미 수행하는 layout 횟수에 맞추는 것이지 특정 숫자로 줄이는 것이 아닙니다. 메모리 안전성 측면으로 확장될 방향은 보이지 않습니다.