CVE-2026-106311
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fthird_party/blink/renderer/core/html/html_capability_element_base_test.cc |
modified |
Files Changed
third_party/blink/renderer/core/css/css_properties.json5third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/invalid-css-properties.htmlthird_party/blink/web_tests/external/wpt/html/semantics/permission-element/install/invalid-css-properties.tentative.htmlthird_party/blink/renderer/core/html/html_capability_element_base_test.ccthird_party/blink/web_tests/external/wpt/html/semantics/permission-element/geolocation-element/invalid-css-properties.tentative.html
Patch
From 5312209646e57701b18af27318d41c6a98d8d0a5 Mon Sep 17 00:00:00 2001 From: Thomas Nguyen <[email protected]> Date: Mon, 07 Sep 2026 07:58:15 -0700 Subject: [PATCH] [PEPC] Disallow container-type, container-name, and contain-intrinsic* container-type: size enables size containment, which causes the element's fit-content width to resolve from contain-intrinsic-width instead of the user-agent controlled shadow-DOM label. The width bounds applied in AdjustStyle() are expressed as calc-size(fit-content, k * size), so an author-supplied intrinsic width can replace the label width those bounds are meant to track, allowing clickjacking boundaries to be bypassed. Since contain is already disallowed, remove valid_for_permission_element: true from container-type, container-name, and contain-intrinsic-* (width, height, inline-size, block-size, size) as well. Fixed: 514061289 Change-Id: I09daff39d72a39da4ebe687bd33b0a9d37fae9dc Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8363545 Reviewed-by: Rune Lillesveen <[email protected]> Commit-Queue: Thomas Nguyen <[email protected]> Cr-Commit-Position: refs/heads/main@{#1693346} --- diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5 index 90099f9..65aa6abf 100644 --- a/third_party/blink/renderer/core/css/css_properties.json5 +++ b/third_party/blink/renderer/core/css/css_properties.json5 @@ -3202,7 +3202,6 @@ default_value: "StyleIntrinsicLength()", type_name: "StyleIntrinsicLength", converter: "ConvertIntrinsicDimension", - valid_for_permission_element: true, invalidate: ["layout", "scroll-anchor"], affected_by_zoom: true, }, @@ -3218,7 +3217,6 @@ default_value: "StyleIntrinsicLength()", type_name: "StyleIntrinsicLength", converter: "ConvertIntrinsicDimension", - valid_for_permission_element: true, invalidate: ["layout", "scroll-anchor"], affected_by_zoom: true, }, @@ -3234,7 +3232,6 @@ keywords: ["none"], typedom_types: ["Keyword"], typedom_custom_ident: true, - valid_for_permission_element: true, is_animation_affecting: true, }, { @@ -3248,7 +3245,6 @@ type_name: "unsigned", converter: "ConvertFlags<EContainerType, CSSValueID::kNormal>", typedom_types: ["Keyword"], - valid_for_permission_element: true, invalidate: ["layout"], is_animation_affecting: true, }, @@ -9479,7 +9475,6 @@ resolver: "inline", }, typedom_types: ["Keyword", "Length"], - valid_for_permission_element: true, keywords: ["none"], }, { @@ -9490,7 +9485,6 @@ resolver: "block", }, typedom_types: ["Keyword", "Length"], - valid_for_permission_element: true, keywords: ["none"], }, { @@ -9500,7 +9494,6 @@ ], property_methods: ["ParseShorthand", "CSSValueFromComputedStyleInternal"], computable: true, - valid_for_permission_element: true, }, { name: "container", diff --git a/third_party/blink/renderer/core/html/html_capability_element_base_test.cc b/third_party/blink/renderer/core/html/html_capability_element_base_test.cc index 13d58e0..cceb977 100644 --- a/third_party/blink/renderer/core/html/html_capability_element_base_test.cc +++ b/third_party/blink/renderer/core/html/html_capability_element_base_test.cc @@ -846,6 +846,42 @@ } } +TEST_F(HTMLCapabilityElementBaseSimTest, + ContainIntrinsicSizeDoesNotAffectWidthBounds) { + SimRequest resource("https://example.test", "text/html"); + LoadURL("https://example.test"); + resource.Complete(R"( + <body> + </body> + )"); + constexpr char kBaseStyle[] = + "border: none; color: black; background-color: white;"; + auto* control = CreatePermissionElement(GetDocument(), "geolocation"); + control->setAttribute(html_names::kStyleAttr, AtomicString(kBaseStyle)); + auto* element = CreatePermissionElement(GetDocument(), "geolocation"); + element->setAttribute( + html_names::kStyleAttr, + AtomicString(String(kBaseStyle) + + "container-type: size; contain-intrinsic-size: 700px 100px;" + "min-width: 700px;")); + GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); + GetDocument().View()->UpdateAllLifecyclePhasesForTest(); + + const ComputedStyle* style = element->GetComputedStyle(); + ASSERT_TRUE(style); + EXPECT_FALSE(style->IsContainerForSizeContainerQueries()); + EXPECT_TRUE(style->ContainIntrinsicWidth().IsNoOp()); + EXPECT_TRUE(style->ContainIntrinsicHeight().IsNoOp()); + + DOMRect* control_rect = control->GetBoundingClientRect(); + DOMRect* element_rect = element->GetBoundingClientRect(); + EXPECT_GT(control_rect->width(), 0); + // Without a distinct border the element's width is bounded by 3x the + // intrinsic content width, which `contain-intrinsic-width` should not be + // able to override. + EXPECT_LE(element_rect->width(), 3 * control_rect->width()); +} + TEST_F(HTMLCapabilityElementBaseSimTest, BlockedByPermissionsPolicy) { GetDocument().GetSettings()->SetDefaultFontSize(12); SimRequest main_resource("https://example.test", "text/html"); diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/geolocation-element/invalid-css-properties.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/geolocation-element/invalid-css-properties.tentative.html index 8260cc3f..d95ac9d 100644 --- a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/geolocation-element/invalid-css-properties.tentative.html +++ b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/geolocation-element/invalid-css-properties.tentative.html @@ -20,6 +20,9 @@ cursor: none; content-visibility: hidden; contain: size; + container-type: size; + container-name: test; + contain-intrinsic-size: 700px 100px; corner-shape: notch; corner-top-left-shape: scoop; corner-top-right-shape: bevel; @@ -43,6 +46,10 @@ assert_equals(getComputedStyle(el_with_negatives).cursor, "pointer", "cursor"); assert_equals(getComputedStyle(el_with_negatives).contentVisibility, "visible", "content-visibility"); assert_equals(getComputedStyle(el_with_negatives).contain, "none", "contain"); + assert_equals(getComputedStyle(el_with_negatives).containerType, "normal", "container-type"); + assert_equals(getComputedStyle(el_with_negatives).containerName, "none", "container-name"); + assert_equals(getComputedStyle(el_with_negatives).containIntrinsicWidth, "none", "contain-intrinsic-width"); + assert_equals(getComputedStyle(el_with_negatives).containIntrinsicHeight, "none", "contain-intrinsic-height"); assert_equals(getComputedStyle(el_with_negatives).cornerShape, "superellipse(1)", "corner-shape"); assert_equals(getComputedStyle(el_with_negatives).cornerTopLeftShape, "superellipse(1)", "corner-top-left-shape"); assert_equals(getComputedStyle(el_with_negatives).cornerTopRightShape, "superellipse(1)", "corner-top-right-shape"); diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/install/invalid-css-properties.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/install/invalid-css-properties.tentative.html index 16681a8..f93e92a 100644 --- a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/install/invalid-css-properties.tentative.html +++ b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/install/invalid-css-properties.tentative.html @@ -20,6 +20,9 @@ cursor: none; content-visibility: hidden; contain: size; + container-type: size; + container-name: test; + contain-intrinsic-size: 700px 100px; corner-shape: notch; corner-top-left-shape: scoop; corner-top-right-shape: bevel; @@ -43,6 +46,10 @@ assert_equals(getComputedStyle(el_with_negatives).cursor, "pointer", "cursor"); assert_equals(getComputedStyle(el_with_negatives).contentVisibility, "visible", "content-visibility"); assert_equals(getComputedStyle(el_with_negatives).contain, "none", "contain"); + assert_equals(getComputedStyle(el_with_negatives).containerType, "normal", "container-type"); + assert_equals(getComputedStyle(el_with_negatives).containerName, "none", "container-name"); + assert_equals(getComputedStyle(el_with_negatives).containIntrinsicWidth, "none", "contain-intrinsic-width"); + assert_equals(getComputedStyle(el_with_negatives).containIntrinsicHeight, "none", "contain-intrinsic-height"); assert_equals(getComputedStyle(el_with_negatives).cornerShape, "superellipse(1)", "corner-shape"); assert_equals(getComputedStyle(el_with_negatives).cornerTopLeftShape, "superellipse(1)", "corner-top-left-shape"); assert_equals(getComputedStyle(el_with_negatives).cornerTopRightShape, "superellipse(1)", "corner-top-right-shape"); diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/invalid-css-properties.html b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/invalid-css-properties.html index 23d3f58..2938980 100644 --- a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/invalid-css-properties.html +++ b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/invalid-css-properties.html @@ -20,6 +20,9 @@ cursor: none; content-visibility: hidden; contain: size; + container-type: size; + container-name: test; + contain-intrinsic-size: 700px 100px; corner-shape: notch; corner-top-left-shape: scoop; corner-top-right-shape: bevel; @@ -43,6 +46,10 @@ assert_equals(getComputedStyle(el_with_negatives).cursor, "pointer", "cursor"); assert_equals(getComputedStyle(el_with_negatives).contentVisibility, "visible", "content-visibility"); assert_equals(getComputedStyle(el_with_negatives).contain, "none", "contain"); + assert_equals(getComputedStyle(el_with_negatives).containerType, "normal", "container-type"); + assert_equals(getComputedStyle(el_with_negatives).containerName, "none", "container-name"); + assert_equals(getComputedStyle(el_with_negatives).containIntrinsicWidth, "none", "contain-intrinsic-width"); + assert_equals(getComputedStyle(el_with_negatives).containIntrinsicHeight, "none", "contain-intrinsic-height"); assert_equals(getComputedStyle(el_with_negatives).cornerShape, "superellipse(1)", "corner-shape"); assert_equals(getComputedStyle(el_with_negatives).cornerTopLeftShape, "superellipse(1)", "corner-top-left-shape"); assert_equals(getComputedStyle(el_with_negatives).cornerTopRightShape, "superellipse(1)", "corner-top-right-shape");
Regression Test / PoC
diff --git a/third_party/blink/renderer/core/html/html_capability_element_base_test.cc b/third_party/blink/renderer/core/html/html_capability_element_base_test.cc
index 13d58e0..cceb977 100644
--- a/third_party/blink/renderer/core/html/html_capability_element_base_test.cc
+++ b/third_party/blink/renderer/core/html/html_capability_element_base_test.cc
@@ -846,6 +846,42 @@
}
}
+TEST_F(HTMLCapabilityElementBaseSimTest,
+ ContainIntrinsicSizeDoesNotAffectWidthBounds) {
+ SimRequest resource("https://example.test", "text/html");
+ LoadURL("https://example.test");
+ resource.Complete(R"(
+ <body>
+ </body>
+ )");
+ constexpr char kBaseStyle[] =
+ "border: none; color: black; background-color: white;";
+ auto* control = CreatePermissionElement(GetDocument(), "geolocation");
+ control->setAttribute(html_names::kStyleAttr, AtomicString(kBaseStyle));
+ auto* element = CreatePermissionElement(GetDocument(), "geolocation");
+ element->setAttribute(
+ html_names::kStyleAttr,
+ AtomicString(String(kBaseStyle) +
+ "container-type: size; contain-intrinsic-size: 700px 100px;"
+ "min-width: 700px;"));
+ GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+ GetDocument().View()->UpdateAllLifecyclePhasesForTest();
+
+ const ComputedStyle* style = element->GetComputedStyle();
+ ASSERT_TRUE(style);
+ EXPECT_FALSE(style->IsContainerForSizeContainerQueries());
+ EXPECT_TRUE(style->ContainIntrinsicWidth().IsNoOp());
+ EXPECT_TRUE(style->ContainIntrinsicHeight().IsNoOp());
+
+ DOMRect* control_rect = control->GetBoundingClientRect();
+ DOMRect* element_rect = element->GetBoundingClientRect();
+ EXPECT_GT(control_rect->width(), 0);
+ // Without a distinct border the element's width is bounded by 3x the
+ // intrinsic content width, which `contain-intrinsic-width` should not be
+ // able to override.
+ EXPECT_LE(element_rect->width(), 3 * control_rect->width());
+}
+
TEST_F(HTMLCapabilityElementBaseSimTest, BlockedByPermissionsPolicy) {
GetDocument().GetSettings()->SetDefaultFontSize(12);
SimRequest main_resource("https://example.test", "text/html");
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/geolocation-element/invalid-css-properties.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/geolocation-element/invalid-css-properties.tentative.html
index 8260cc3f..d95ac9d 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/geolocation-element/invalid-css-properties.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/geolocation-element/invalid-css-properties.tentative.html
@@ -20,6 +20,9 @@
cursor: none;
content-visibility: hidden;
contain: size;
+ container-type: size;
+ container-name: test;
+ contain-intrinsic-size: 700px 100px;
corner-shape: notch;
corner-top-left-shape: scoop;
corner-top-right-shape: bevel;
@@ -43,6 +46,10 @@
assert_equals(getComputedStyle(el_with_negatives).cursor, "pointer", "cursor");
assert_equals(getComputedStyle(el_with_negatives).contentVisibility, "visible", "content-visibility");
assert_equals(getComputedStyle(el_with_negatives).contain, "none", "contain");
+ assert_equals(getComputedStyle(el_with_negatives).containerType, "normal", "container-type");
+ assert_equals(getComputedStyle(el_with_negatives).containerName, "none", "container-name");
+ assert_equals(getComputedStyle(el_with_negatives).containIntrinsicWidth, "none", "contain-intrinsic-width");
+ assert_equals(getComputedStyle(el_with_negatives).containIntrinsicHeight, "none", "contain-intrinsic-height");
assert_equals(getComputedStyle(el_with_negatives).cornerShape, "superellipse(1)", "corner-shape");
assert_equals(getComputedStyle(el_with_negatives).cornerTopLeftShape, "superellipse(1)", "corner-top-left-shape");
assert_equals(getComputedStyle(el_with_negatives).cornerTopRightShape, "superellipse(1)", "corner-top-right-shape");
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/install/invalid-css-properties.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/install/invalid-css-properties.tentative.html
index 16681a8..f93e92a 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/install/invalid-css-properties.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/install/invalid-css-properties.tentative.html
@@ -20,6 +20,9 @@
cursor: none;
content-visibility: hidden;
contain: size;
+ container-type: size;
+ container-name: test;
+ contain-intrinsic-size: 700px 100px;
corner-shape: notch;
corner-top-left-shape: scoop;
corner-top-right-shape: bevel;
@@ -43,6 +46,10 @@
assert_equals(getComputedStyle(el_with_negatives).cursor, "pointer", "cursor");
assert_equals(getComputedStyle(el_with_negatives).contentVisibility, "visible", "content-visibility");
assert_equals(getComputedStyle(el_with_negatives).contain, "none", "contain");
+ assert_equals(getComputedStyle(el_with_negatives).containerType, "normal", "container-type");
+ assert_equals(getComputedStyle(el_with_negatives).containerName, "none", "container-name");
+ assert_equals(getComputedStyle(el_with_negatives).containIntrinsicWidth, "none", "contain-intrinsic-width");
+ assert_equals(getComputedStyle(el_with_negatives).containIntrinsicHeight, "none", "contain-intrinsic-height");
assert_equals(getComputedStyle(el_with_negatives).cornerShape, "superellipse(1)", "corner-shape");
assert_equals(getComputedStyle(el_with_negatives).cornerTopLeftShape, "superellipse(1)", "corner-top-left-shape");
assert_equals(getComputedStyle(el_with_negatives).cornerTopRightShape, "superellipse(1)", "corner-top-right-shape");
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/invalid-css-properties.html b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/invalid-css-properties.html
index 23d3f58..2938980 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/invalid-css-properties.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/invalid-css-properties.html
@@ -20,6 +20,9 @@
cursor: none;
content-visibility: hidden;
contain: size;
+ container-type: size;
+ container-name: test;
+ contain-intrinsic-size: 700px 100px;
corner-shape: notch;
corner-top-left-shape: scoop;
corner-top-right-shape: bevel;
@@ -43,6 +46,10 @@
assert_equals(getComputedStyle(el_with_negatives).cursor, "pointer", "cursor");
assert_equals(getComputedStyle(el_with_negatives).contentVisibility, "visible", "content-visibility");
assert_equals(getComputedStyle(el_with_negatives).contain, "none", "contain");
+ assert_equals(getComputedStyle(el_with_negatives).containerType, "normal", "container-type");
+ assert_equals(getComputedStyle(el_with_negatives).containerName, "none", "container-name");
+ assert_equals(getComputedStyle(el_with_negatives).containIntrinsicWidth, "none", "contain-intrinsic-width");
+ assert_equals(getComputedStyle(el_with_negatives).containIntrinsicHeight, "none", "contain-intrinsic-height");
assert_equals(getComputedStyle(el_with_negatives).cornerShape, "superellipse(1)", "corner-shape");
assert_equals(getComputedStyle(el_with_negatives).cornerTopLeftShape, "superellipse(1)", "corner-top-left-shape");
assert_equals(getComputedStyle(el_with_negatives).cornerTopRightShape, "superellipse(1)", "corner-top-right-shape");
Original Bug Report
PEPC anti-clickjacking bounds bypass via CSS size containment
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: The anti-clickjacking restrictions for Page Embedded Permission Control (PEPC) elements can be subverted using CSS properties that override intrinsic sizing. An attacker can potentially expand the clickable area of a permission request to deceptive dimensions by exploiting incorrectly permitted properties.
Affected files:
third_party/blink/renderer/core/css/css_properties.json5third_party/blink/renderer/core/html/html_capability_element_base.ccthird_party/blink/renderer/core/html/html_permission_element_utils.cc
Estimated timestamp from git blame: 2024-03-01
Background
The Page Embedded Permission Control (PEPC) system (e.g., <usermedia>, <geolocation>) uses a security model that restricts the element’s size to its User-Agent controlled label text. This prevents clickjacking by ensuring the clickable area matches the visible, browser-generated label.
In third_party/blink/renderer/core/html/html_capability_element_base.cc, the AdjustStyle method enforces these bounds by setting the MaxWidth and MinWidth to a multiple of the element’s intrinsic size. This is implemented using a calc-size expression: calc-size(fit-content, factor * size), where size represents the resolved value of the fit-content basis.
Vulnerability Details
A vulnerability exists because several CSS properties related to Container Queries and size containment are incorrectly marked as allowed for PEPC elements. In third_party/blink/renderer/core/css/css_properties.json5, the properties container-type and contain-intrinsic-* (width, height, inline-size, block-size) are flagged with valid_for_permission_element: true.
When an attacker applies container-type: size to a PEPC element, it triggers size containment. According to the CSS Containment spec and Blink’s implementation in third_party/blink/renderer/core/layout/length_utils.cc (CalculateMinMaxSizesIgnoringChildren), a node under size containment bypasses the measurement of its children and instead uses the value provided by contain-intrinsic-size (via node.OverrideIntrinsicContentInlineSize()).
Because the security bounding logic relies on the intrinsic size (fit-content) as the basis for its calculation, an attacker can provide an arbitrary intrinsic size. The resulting calc-size expression will use the attacker-controlled override as its basis, allowing the clickable area of the PEPC element to be expanded to extreme dimensions (e.g., the width of the entire viewport).
By matching the element’s background-color to the page and removing borders, an attacker can create a massive, invisible clickable overlay. A user clicking anywhere in this region would unknowingly trigger a browser-anchored permission prompt for sensitive capabilities like the camera or microphone.
Potential Reproduction Steps
- Host a page containing a PEPC element:
<usermedia type="camera">. - Apply the following CSS to the element:
permission { container-type: size; contain-intrinsic-width: 5000px; background-color: transparent; /* or matching page bg */ border: none; } - Observe that the clickable area of the element expands to match the
5000pxbasis (multiplied by the internal factor), covering a large portion of the page. - Click in the expanded area to trigger the permission prompt.
Proposed Fix
In third_party/blink/renderer/core/css/css_properties.json5, set valid_for_permission_element: false (or remove the line) for the following properties:
container-typecontainer-namecontain-intrinsic-widthcontain-intrinsic-heightcontain-intrinsic-inline-sizecontain-intrinsic-block-sizecontain-intrinsic-size
Only properties essential for the element’s basic appearance (colors, fonts, margins) should be permitted.
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.