CVE-2026-8545
Overview
Background
- `DisplayResourceProvider`
- The viz/compositor service component that imports compositor resources handed up from child clients and tracks them in its
resources_map keyed by a parent-localResourceId. - `ResourceIdGenerator`
- A monotonic generator (
GenerateNextId) that assigns each imported resource a new localResourceId, which wraps around once it hits the maximum value. - `child_to_parent_map`
- A per-child mapping from the child’s
TransferableResourceid to the parent-localResourceIdallocated for it. - `std::map/unordered_map::emplace`
- Returns a pair whose
.secondis false (and leaves the existing entry untouched) if a key already exists, so an ignored return value silently drops the new value on a key collision.
Root Cause Analysis
When importing a child resource, the code generated a local id with resource_id_generator_.GenerateNextId() and called resources_.emplace(local_id, ChildResource(...)) while discarding the returned .second success flag. If ResourceIdGenerator had wrapped past its maximum and produced a ResourceId already present in resources_, the emplace would fail and leave the pre-existing ChildResource in place, yet the code then unconditionally set child_info.child_to_parent_map[transferable_resource.id] = local_id, binding the new child resource id to a parent-local id whose ChildResource actually belongs to a different (stale) resource. This breaks the invariant that each live ResourceId in resources_ corresponds to exactly one owning child resource, so later lookups via child_to_parent_map resolve to the wrong ChildResource (object confusion).
The fix captures the inserted flag from emplace(...).second and CHECK(inserted), converting a silent id collision into an immediate, controlled abort before the mismatched mapping is established.
emplace’s success flag, so a wrapped-around ResourceId collision silently aliased a new child resource onto a stale ChildResource; the fix CHECKs that the insertion actually happened.Attack Path
- Exhaust the id space
A compositor client drives enough resource imports that
resource_id_generator_reaches its maximumResourceIdand wraps around (the patch states this is the only way a collision is possible). - Force a collision
A newly generated
local_idmatches an id still live inresources_, soemplacefails and the existingChildResourceis retained unchanged. - Install a mismatched mapping (pre-patch)
child_to_parent_map[transferable_resource.id] = local_idis set anyway, pointing the new child resource at a parent-local id owned by an unrelated resource. - Trigger object confusion
Subsequent compositing operations resolve the child’s resource through the aliased mapping and operate on the wrong
ChildResource, i.e. object corruption (any further memory-safety consequence is not shown by the diff and is (inferred)).
Impact Assessment
ResourceId generator to wrap can cause DisplayResourceProvider to alias a child resource onto an unrelated ChildResource, producing object corruption in the viz compositing service; the precondition is enough resource churn to wrap the id space and hit a live collision. The vendor advisory rates this high severity and describes it as object corruption in Compositing. Post-patch the condition is a deterministic CHECK crash rather than silent corruption; escalation beyond the object confusion/crash is not demonstrated by the diff and is (inferred).Files Changed
components/viz/service/display/display_resource_provider.cc
Audit Directions
- Ignored `emplace`/`insert` return valuesAudit map/set insertions keyed by generated or externally influenced ids where
.secondis discarded, especially when a downstream map is then updated as if the insert succeeded. - Wrap-around of monotonic id generatorsReview id/handle generators (
ResourceId, sequence numbers, token counters) for uniqueness assumptions that break once the counter wraps, and whether collisions are detected. - Cross-map consistencyLook for paired structures (here
resources_andchild_to_parent_map) that must stay in sync and verify both are updated atomically only when the primary insertion actually occurs.
Patch
From 92a617477bcb53e15d84cad0cec866ce9d0c153e Mon Sep 17 00:00:00 2001
From: kylechar <kylechar@chromium.org>
Date: Wed, 01 Apr 2026 06:59:51 -0700
Subject: [PATCH] Guard against ResourceId collisions
Have DisplayResourceProvider check for ResourceId collisions. In theory
a collision is possible after ResourceIdGenerator wraps at the max
value.
Fixed: 497486030
Change-Id: Id652aeea7211dceba7557235658e9b5c0b788e17
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7719378
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
Commit-Queue: Kyle Charbonneau <kylechar@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1608494}
---
diff --git a/components/viz/service/display/display_resource_provider.cc b/components/viz/service/display/display_resource_provider.cc
index 4bf9bad2..b0e5505 100644
--- a/components/viz/service/display/display_resource_provider.cc
+++ b/components/viz/service/display/display_resource_provider.cc
@@ -263,8 +263,13 @@
}
ResourceId local_id = resource_id_generator_.GenerateNextId();
- resources_.emplace(local_id,
- ChildResource(child_id, transferable_resource));
+ bool inserted =
+ resources_
+ .emplace(local_id, ChildResource(child_id, transferable_resource))
+ .second;
+ // Verify there wasn't a ResourceId collision. A collision is only possible
+ // after `resource_id_generator_` hit the max ID and wrapped around.
+ CHECK(inserted);
child_info.child_to_parent_map[transferable_resource.id] = local_id;
}
}
Original Bug Report
Potential Cross-Origin GPU Texture Leak via ResourceIdGenerator Wraparound in Viz
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A lack of collision detection in ResourceIdGenerator::GenerateNextId() combined with improper duplicate key handling in DisplayResourceProvider::ReceiveFromChild allows a compromised renderer to force a 32-bit ID wraparound. This creates an alias to a cross-origin victim’s GPU resource, which the attacker can then render and exfiltrate.
Affected files:
components/viz/common/resources/resource_id.hcomponents/viz/service/display/display_resource_provider.cccomponents/viz/service/display/resolved_frame_data.cccomponents/viz/service/display/display_resource_provider_skia.cc
Estimated timestamp from git blame: 2021-08-20
Summary
A potential vulnerability in the viz component’s resource management allows a compromised renderer to access and render GPU textures belonging to cross-origin surfaces (such as iframes or other windows) that share the same Display. The issue stems from a lack of collision detection in ResourceIdGenerator::GenerateNextId() when the 32-bit ID counter wraps around, combined with improper handling of duplicate resource IDs in DisplayResourceProvider::ReceiveFromChild.
Technical Details
In the Chromium compositor (Viz), DisplayResourceProvider is responsible for managing resources (like GPU textures) used for compositing a Display. A single DisplayResourceProvider instance is shared across all surfaces within a Display (e.g., the root surface and all its embedded iframes).
Resources are identified by a ResourceId, which is a 32-bit unsigned integer. When a child (a renderer process) sends resources to Viz via SubmitCompositorFrame, Viz assigns a local_id to each resource using a ResourceIdGenerator.
The generator simply increments a 32-bit next_id_. When next_id_ reaches the reserved range (near UINT32_MAX), it wraps back to start_id_. There is no check to ensure that the new local_id is not already in use by another resource in the DisplayResourceProvider.
In DisplayResourceProvider::ReceiveFromChild, the code handles new resources:
ResourceId local_id = resource_id_generator_.GenerateNextId();
resources_.emplace(local_id,
ChildResource(child_id, transferable_resource));
child_info.child_to_parent_map[transferable_resource.id] = local_id;
If local_id already exists in the resources_ map (because it is currently held by a victim surface), resources_.emplace will fail because std::unordered_map::emplace does not overwrite existing entries on duplicate keys. However, the code proceeds to update child_info.child_to_parent_map[transferable_resource.id] = local_id;. This creates an alias: the attacker’s child-side resource ID now maps to the same local_id as the victim’s resource.
When the compromised renderer subsequently submits a CompositorFrame containing a TextureDrawQuad that references this aliased resource ID, the Viz service resolves the ID to the victim’s local_id and retrieves the victim’s ChildResource and its associated GPU mailbox. The victim’s texture is then sampled and rendered into the attacker’s frame.
Potential Attack Scenario
Note: This is a suggested attack path; our tooling agent does not have the ability to run code to confirm this end-to-end.
- Renderer Compromise: An attacker compromises a renderer process (e.g., via a V8 bug) and gains the ability to send custom
CompositorFrameIPCs to the Viz service. - Victim Setup: The attacker embeds a cross-origin iframe (the victim) that holds a long-lived GPU resource (e.g., a static WebGL canvas or a video frame). This resource occupies a
local_idin the sharedDisplayResourceProvider. - Resource Flooding: The attacker churns
ResourceIds by repeatedly callingSubmitCompositorFramewith a large number of dummy resources. SinceSubmitCompositorFrameallows[UnlimitedSize]arrays, the attacker can quickly cycle through the 4.29 billion possible IDs without triggering out-of-memory errors by ensuring the resources are unused and immediately released. - Forcing a Collision: At 60fps with 100,000 resources per frame, the 32-bit
ResourceIdGeneratorwraps around in approximately 12 minutes. The attacker continues until it generates alocal_idthat collides with the victim’s resource. - Malicious Aliasing: When the collision occurs,
resources_.emplacefails, but the attacker’schild_to_parent_mapis successfully updated to alias the victim’slocal_id. - Unauthorized Rendering: The attacker submits a
TextureDrawQuadusing the aliased child-side ID. The victim’s cross-origin texture is rendered within the attacker’s controlled region. - Data Exfiltration: The attacker can then use timing side-channels (such as applying computationally expensive CSS/SVG filters to the leaked texture and measuring rendering performance) to slowly exfiltrate the visual contents of the cross-origin iframe back to their JavaScript environment.
Suggested Fix
In DisplayResourceProvider::ReceiveFromChild, check the result of resources_.emplace to ensure the ID was successfully inserted. If it fails due to a collision, the code should generate a new ID and try again, or crash the child process if it attempts to submit an excessive number of resources that exhaust the 32-bit ID space.
ResourceId local_id;
bool inserted = false;
while (!inserted) {
local_id = resource_id_generator_.GenerateNextId();
auto result = resources_.emplace(local_id,
ChildResource(child_id, transferable_resource));
inserted = result.second;
// Optionally add a safeguard against infinite loops if the ID space is fully exhausted.
}
child_info.child_to_parent_map[transferable_resource.id] = local_id;
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.