CVE-2026-106410
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forcontent/browser/permissions/permission_service_impl.cc |
modified | |
ifcontent/browser/permissions/permission_service_impl.cc |
modified |
Files Changed
content/browser/permissions/permission_service_impl.cccontent/browser/permissions/permission_service_impl_unittest.cccontent/browser/permissions/embedded_permission_control_checker_unittest.cc
Patch
From 86b9dd14f73fc596fa79246658474bb72881a456 Mon Sep 17 00:00:00 2001 From: Thomas Nguyen <[email protected]> Date: Sun, 06 Sep 2026 04:46:42 -0700 Subject: [PATCH] [CE] Small refactorings to follow coding guideline Bug: 517801814 Change-Id: Iacb2e03bef7fce188556c11342cdc4ef043cbbbf Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8314750 Reviewed-by: Judith Hemp <[email protected]> Commit-Queue: Thomas Nguyen <[email protected]> Cr-Commit-Position: refs/heads/main@{#1693027} --- diff --git a/content/browser/permissions/permission_service_impl.cc b/content/browser/permissions/permission_service_impl.cc index 97e77edb..062fb40 100644 --- a/content/browser/permissions/permission_service_impl.cc +++ b/content/browser/permissions/permission_service_impl.cc @@ -150,25 +150,32 @@ bool CheckPageEmbeddedPermissionTypes( const std::vector<PermissionDescriptorPtr>& permissions, const EmbeddedPermissionRequestDescriptorPtr& descriptor) { + if (permissions.empty()) { + return false; + } for (const auto& permission : permissions) { - auto type = blink::PermissionDescriptorToPermissionType(permission); + std::optional<blink::PermissionType> type = + blink::MaybePermissionDescriptorToPermissionType(permission); + if (!type.has_value()) { + return false; + } switch (descriptor->detail->which()) { case blink::mojom::EmbeddedPermissionControlDescriptorExtension::Tag:: kGeolocation: - if (type != blink::PermissionType::GEOLOCATION) { + if (*type != blink::PermissionType::GEOLOCATION) { return false; } break; case blink::mojom::EmbeddedPermissionControlDescriptorExtension::Tag:: kInstall: - if (type != blink::PermissionType::WEB_APP_INSTALLATION) { + if (*type != blink::PermissionType::WEB_APP_INSTALLATION) { return false; } break; case blink::mojom::EmbeddedPermissionControlDescriptorExtension::Tag:: kUserMedia: - if (type != blink::PermissionType::AUDIO_CAPTURE && - type != blink::PermissionType::VIDEO_CAPTURE) { + if (*type != blink::PermissionType::AUDIO_CAPTURE && + *type != blink::PermissionType::VIDEO_CAPTURE) { return false; } break; diff --git a/content/browser/permissions/embedded_permission_control_checker_unittest.cc b/content/browser/permissions/embedded_permission_control_checker_unittest.cc index 77c1823..173dfe9 100644 --- a/content/browser/permissions/embedded_permission_control_checker_unittest.cc +++ b/content/browser/permissions/embedded_permission_control_checker_unittest.cc @@ -157,7 +157,7 @@ }; TEST_F(EmbeddedPermissionControlCheckerTest, - IgnoreRegisteregisterPageEmbeddedPermissionCheck) { + IgnoreRegisterPageEmbeddedPermissionCheck) { base::test::ScopedFeatureList features; features.InitAndEnableFeature(blink::features::kBypassPepcSecurityForTesting); for (const auto& [name, source] : diff --git a/content/browser/permissions/permission_service_impl_unittest.cc b/content/browser/permissions/permission_service_impl_unittest.cc index 494ce43..40fa1c5 100644 --- a/content/browser/permissions/permission_service_impl_unittest.cc +++ b/content/browser/permissions/permission_service_impl_unittest.cc @@ -337,6 +337,8 @@ Tag tag; std::vector<blink::mojom::PermissionName> permissions; } kCases[] = { + {Tag::kGeolocation, {}}, + {Tag::kGeolocation, {blink::mojom::PermissionName::FULLSCREEN}}, {Tag::kGeolocation, {blink::mojom::PermissionName::WEB_APP_INSTALLATION}}, {Tag::kGeolocation, {blink::mojom::PermissionName::AUDIO_CAPTURE}}, {Tag::kInstall, {blink::mojom::PermissionName::GEOLOCATION}}, @@ -366,6 +368,10 @@ Tag tag; std::vector<blink::mojom::PermissionName> permissions; } kCases[] = { + {Tag::kGeolocation, {}}, + {Tag::kInstall, {}}, + {Tag::kUserMedia, {}}, + {Tag::kGeolocation, {blink::mojom::PermissionName::FULLSCREEN}}, {Tag::kGeolocation, {blink::mojom::PermissionName::WEB_APP_INSTALLATION}}, {Tag::kInstall, {blink::mojom::PermissionName::AUDIO_CAPTURE}}, {Tag::kUserMedia, {blink::mojom::PermissionName::GEOLOCATION}},
Regression Test / PoC
diff --git a/content/browser/permissions/embedded_permission_control_checker_unittest.cc b/content/browser/permissions/embedded_permission_control_checker_unittest.cc
index 77c1823..173dfe9 100644
--- a/content/browser/permissions/embedded_permission_control_checker_unittest.cc
+++ b/content/browser/permissions/embedded_permission_control_checker_unittest.cc
@@ -157,7 +157,7 @@
};
TEST_F(EmbeddedPermissionControlCheckerTest,
- IgnoreRegisteregisterPageEmbeddedPermissionCheck) {
+ IgnoreRegisterPageEmbeddedPermissionCheck) {
base::test::ScopedFeatureList features;
features.InitAndEnableFeature(blink::features::kBypassPepcSecurityForTesting);
for (const auto& [name, source] :
diff --git a/content/browser/permissions/permission_service_impl_unittest.cc b/content/browser/permissions/permission_service_impl_unittest.cc
index 494ce43..40fa1c5 100644
--- a/content/browser/permissions/permission_service_impl_unittest.cc
+++ b/content/browser/permissions/permission_service_impl_unittest.cc
@@ -337,6 +337,8 @@
Tag tag;
std::vector<blink::mojom::PermissionName> permissions;
} kCases[] = {
+ {Tag::kGeolocation, {}},
+ {Tag::kGeolocation, {blink::mojom::PermissionName::FULLSCREEN}},
{Tag::kGeolocation, {blink::mojom::PermissionName::WEB_APP_INSTALLATION}},
{Tag::kGeolocation, {blink::mojom::PermissionName::AUDIO_CAPTURE}},
{Tag::kInstall, {blink::mojom::PermissionName::GEOLOCATION}},
@@ -366,6 +368,10 @@
Tag tag;
std::vector<blink::mojom::PermissionName> permissions;
} kCases[] = {
+ {Tag::kGeolocation, {}},
+ {Tag::kInstall, {}},
+ {Tag::kUserMedia, {}},
+ {Tag::kGeolocation, {blink::mojom::PermissionName::FULLSCREEN}},
{Tag::kGeolocation, {blink::mojom::PermissionName::WEB_APP_INSTALLATION}},
{Tag::kInstall, {blink::mojom::PermissionName::AUDIO_CAPTURE}},
{Tag::kUserMedia, {blink::mojom::PermissionName::GEOLOCATION}},
Original Bug Report
Permission element feature flag and embargo bypass via descriptor mismatch
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: A potential logic flaw in PermissionServiceImpl::RequestPageEmbeddedPermission allows a compromised renderer to pair an enabled permission element descriptor with mismatched permissions. This bypasses feature-flag checks for disabled permission elements, spoofs user gestures, and overrides active user embargoes. Consequently, an attacker can spam highly visible permission prompts without real user interaction.
Affected files:
content/browser/permissions/permission_service_impl.cccontent/public/browser/permission_request_description.cccomponents/permissions/permission_util.cccomponents/permissions/permission_request_manager.cccomponents/permissions/permission_context_base.cc
Estimated timestamp from git blame: 2025-09-11
Analysis of the Potential Vulnerability
In content/browser/permissions/permission_service_impl.cc, the method RequestPageEmbeddedPermission handles permission requests initiated by Page Embedded Permission Controls (PEPCs):
void PermissionServiceImpl::RequestPageEmbeddedPermission(
std::vector<PermissionDescriptorPtr> permissions,
EmbeddedPermissionRequestDescriptorPtr descriptor,
RequestPageEmbeddedPermissionCallback callback) {
...
To prevent features that are disabled or experimental from being accessed, the method checks the feature flag of the requested element based on the descriptor’s detail tag:
const base::Feature* required_feature = nullptr;
switch (descriptor->detail->which()) {
case blink::mojom::EmbeddedPermissionControlDescriptorExtension::Tag::
kGeolocation:
required_feature = &blink::features::kGeolocationElement;
break;
case blink::mojom::EmbeddedPermissionControlDescriptorExtension::Tag::
kInstall:
required_feature = &blink::features::kInstallElement;
break;
case blink::mojom::EmbeddedPermissionControlDescriptorExtension::Tag::
kUserMedia:
required_feature = &blink::features::kUserMediaElement;
break;
}
if (!base::FeatureList::IsEnabled(*required_feature)) {
bad_message::ReceivedBadMessage(
context_->render_frame_host()->GetProcess(),
bad_message::PSI_REQUEST_EMBEDDED_PERMISSION_WITHOUT_FEATURE);
return;
}
It also verifies that the requested permissions are supported for page embedded permissions via CheckPageEmbeddedPermissionTypes(permissions) (which permits GEOLOCATION, WEB_APP_INSTALLATION, AUDIO_CAPTURE, and VIDEO_CAPTURE).
However, there is no validation ensuring that the requested permissions actually correspond to/align with the tag of the descriptor.
Potential Impact and PEPC Privilege Bypasses
Since kGeolocationElement is enabled by default, but other features like kInstallElement are disabled, a compromised renderer can craft a Mojo request with:
descriptor->detailset tokGeolocation(gaining a successful feature-flag check pass).permissionsset to[WEB_APP_INSTALLATION](which is a supported embedded permission but corresponds to the disabledkInstallElement).
This bypasses the feature-gating check entirely. Once the request is accepted, it triggers powerful PEPC-exclusive bypass mechanisms that are meant only for highly trusted, user-clicked elements:
- User Gesture Spoofing: In
content/public/browser/permission_request_description.cc, the PEPC constructor automatically forcesuser_gesture = true, assigning a spoofed user gesture status to the request. - Active Embargo/Block Bypass: In
components/permissions/permission_util.cc(line 706),CanPermissionRequestIgnoreStatusreturnstrueforMULTIPLE_DISMISSALS,MULTIPLE_IGNORES,UNSPECIFIED(manual blocks), andAPP_LEVEL_SETTINGS. This allows the prompt to ignore previous user-denial choices. - Queue Preemption: In
components/permissions/permission_request_manager.cc(line 399), the spoofed request preempts and displaces any active permission prompts currently shown to the user. - Quiet UI Bypass: In
components/permissions/permission_request_manager.cc(lines 1047 & 1086), quiet UI selectors and gesture-gating checks are completely bypassed, displaying the prompt with high-visibility normal-alert UI. - Anti-Abuse/Embargo Avoidance: Dismissals or ignores of these requests do not invoke
RecordDismissAndEmbargoorRecordIgnoreAndEmbargoincomponents/permissions/permission_request_manager.cc(lines 1847 & 1854), allowing the attacker to spam prompts infinitely without ever triggering safety rate limits. - No Element Verification:
RequestPageEmbeddedPermissiondoes not consultEmbeddedPermissionControlCheckerto verify if a PEPC element actually exists or was registered on the frame.
Suggested Attack Sequence
Below are the conceptual steps a potential attacker would follow to trigger this vulnerability. Note that these are suggested steps as our analysis tooling does not have the ability to execute code.
- Achieve code execution within a sandboxed renderer process.
- Obtain a handle to the
blink::mojom::PermissionServiceMojo pipe. - Construct a Mojo request for
RequestPageEmbeddedPermissionwith mismatched arguments:- Set
descriptor->detailtoEmbeddedPermissionControlDescriptorExtension::NewGeolocation(...). - Set
permissionsto[PermissionDescriptor{name: WEB_APP_INSTALLATION}].
- Set
- Invoke the Mojo call.
- Observe that the high-visibility installation prompt is displayed, bypassing the disabled feature-flag check, ignoring active user settings blocks, and bypassing user interaction requirements.
Suggested Fix
Introduce strict cross-validation in PermissionServiceImpl::RequestPageEmbeddedPermission to ensure that the requested permissions are aligned with the specified descriptor->detail tag type:
- If
descriptor->detail->is_geolocation()is true, all permissions must beGEOLOCATION. - If
descriptor->detail->is_install()is true, all permissions must beWEB_APP_INSTALLATION. - If
descriptor->detail->is_user_media()is true, all permissions must beAUDIO_CAPTUREorVIDEO_CAPTURE. - Any mismatch should immediately invoke
ReceivedBadMessage()to terminate the renderer connection.
Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379
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.