CVE-2026-2321
Overview
Background
- `WaylandWpColorManager`
- Ozone/Wayland host object wrapping the
wp_color_manager_v1global; it is the observed subject that other color-management objects register against. - `WaylandWpColorManagementOutput`
- Per-output color-management object owned by
WaylandOutputthat registers as anObserverofWaylandWpColorManagerthrough aScopedObservation. - `ScopedObservation`
- RAII helper that adds an observer on construction and calls
RemoveObserveron the still-referenced subject at destruction, requiring the subject to outlive it. - Destruction ordering
- Because
WaylandWpColorManagersits earlier in the owning member list than theWaylandOutput-owned observer, the subject is freed first, leaving the observer holding a dangling reference.
Root Cause Analysis
WaylandWpColorManagementOutput observes WaylandWpColorManager via a ScopedObservation, but the two objects have mismatched lifetimes: the observer is owned by WaylandOutput and destroyed later in the member list than WaylandWpColorManager. When WaylandWpColorManager is destroyed first, its original destructor did not notify or detach its observers, so the observer’s ScopedObservation continued to hold a raw pointer to the freed subject. When the observer was subsequently torn down (or otherwise ran), the ScopedObservation attempted to call RemoveObserver on the already-freed WaylandWpColorManager, dereferencing freed memory and producing a use-after-free in the browser process. The defect is purely a lifetime/ownership-ordering bug in Ozone host code, not attacker-controlled memory corruption.
The fix inverts the notification direction so the subject proactively informs observers before it disappears.
ScopedObservation is only safe when the observed subject outlives the observer; here the guaranteed destruction order is reversed, so the RAII detach fired against a freed subject. The remedy is to have the subject’s destructor drive an explicit OnColorManagerDestroyed() callback so observers reset their observation while the subject pointer is still valid.Attack Path
- Color-management setup
A compositor advertises
wp_color_manager_v1, causing the browser to createWaylandWpColorManagerand aWaylandWpColorManagementOutputobserving it. - Teardown trigger
Output or connection teardown destroys
WaylandWpColorManagerbefore theWaylandOutput-owned observer, per member ordering. - Dangling observation
The observer’s
ScopedObservationretains a raw pointer to the now-freedWaylandWpColorManager. - Use-after-free When the observer runs or is destroyed, the stale observation dereferences the freed subject, corrupting or reading freed browser-process memory.
Impact Assessment
WaylandWpColorManager.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/ozone/platform/wayland/host/wayland_wp_color_management_output.cc |
modified | |
forui/ozone/platform/wayland/host/wayland_wp_color_manager.cc |
modified |
Files Changed
ui/ozone/platform/wayland/host/wayland_wp_color_management_output.ccui/ozone/platform/wayland/host/wayland_wp_color_management_output.hui/ozone/platform/wayland/host/wayland_wp_color_management_surface.ccui/ozone/platform/wayland/host/wayland_wp_color_management_surface.hui/ozone/platform/wayland/host/wayland_wp_color_manager.ccui/ozone/platform/wayland/host/wayland_wp_color_manager.h
Audit Directions
- Observer/observed lifetime orderingAudit every
ScopedObservation/ObserverListin Ozone Wayland host code where subject and observer are owned by different parents; confirm the subject is guaranteed to outlive the observer, or that the subject’s destructor notifies observers to detach. - Destructor detach disciplineLook for subject destructors that free themselves without notifying registered observers; prefer an explicit
On<Subject>Destroyed()callback pattern over relying on RAII detach against a possibly-freed subject. - Member declaration orderReview member/destruction ordering in
WaylandOutput,WaylandConnection, and related owners so observed globals are declared after (destroyed after) the objects that observe them.
Patch
From 2cc3c5b2b5f16287fc8464e3506a5c6607340270 Mon Sep 17 00:00:00 2001
From: Kramer Ge <fangzhoug@chromium.org>
Date: Mon, 08 Dec 2025 14:05:38 -0800
Subject: [PATCH] Resolve WaylandWpColorManager::Observer UAF
`WaylandWpColorManagementOutput` is observing `WaylandWpColorManager`.
The former outlives the latter because former is owned by
`WaylandOutput` and is destroyed in member list.
Fixed: 462468374, 461877477
Change-Id: Ida658bf700a8686b3f87c233e627d87981454436
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7239547
Commit-Queue: Kramer Ge <fangzhoug@chromium.org>
Reviewed-by: Thomas Anderson <thomasanderson@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1555723}
---
diff --git a/ui/ozone/platform/wayland/host/wayland_wp_color_management_output.cc b/ui/ozone/platform/wayland/host/wayland_wp_color_management_output.cc
index e80056c..e730f6f3 100644
--- a/ui/ozone/platform/wayland/host/wayland_wp_color_management_output.cc
+++ b/ui/ozone/platform/wayland/host/wayland_wp_color_management_output.cc
@@ -58,6 +58,10 @@
weak_factory_.GetWeakPtr()));
}
+void WaylandWpColorManagementOutput::OnColorManagerDestroyed() {
+ color_manager_observation_.Reset();
+}
+
void WaylandWpColorManagementOutput::OnImageDescription(
scoped_refptr<WaylandWpImageDescription> image_description) {
if (!image_description) {
diff --git a/ui/ozone/platform/wayland/host/wayland_wp_color_management_output.h b/ui/ozone/platform/wayland/host/wayland_wp_color_management_output.h
index 8f235dd..f01976d 100644
--- a/ui/ozone/platform/wayland/host/wayland_wp_color_management_output.h
+++ b/ui/ozone/platform/wayland/host/wayland_wp_color_management_output.h
@@ -46,6 +46,7 @@
// WaylandWpColorManager::Observer:
void OnHdrEnabledChanged(bool hdr_enabled) override;
+ void OnColorManagerDestroyed() override;
void GetCurrentColorSpace();
void OnImageDescription(
diff --git a/ui/ozone/platform/wayland/host/wayland_wp_color_management_surface.cc b/ui/ozone/platform/wayland/host/wayland_wp_color_management_surface.cc
index 9c245b4..8089dc5 100644
--- a/ui/ozone/platform/wayland/host/wayland_wp_color_management_surface.cc
+++ b/ui/ozone/platform/wayland/host/wayland_wp_color_management_surface.cc
@@ -8,6 +8,7 @@
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/notimplemented.h"
+#include "base/notreached.h"
#include "ui/gfx/display_color_spaces.h"
#include "ui/ozone/platform/wayland/host/wayland_connection.h"
#include "ui/ozone/platform/wayland/host/wayland_surface.h"
@@ -124,4 +125,8 @@
: base::MakeRefCounted<gfx::DisplayColorSpacesRef>());
}
+void WaylandWpColorManagementSurface::OnColorManagerDestroyed() {
+ NOTREACHED();
+}
+
} // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_wp_color_management_surface.h b/ui/ozone/platform/wayland/host/wayland_wp_color_management_surface.h
index 4d63600..3db62dde 100644
--- a/ui/ozone/platform/wayland/host/wayland_wp_color_management_surface.h
+++ b/ui/ozone/platform/wayland/host/wayland_wp_color_management_surface.h
@@ -49,6 +49,7 @@
// WaylandWpColorManager::Observer:
void OnHdrEnabledChanged(bool hdr_enabled) override;
+ void OnColorManagerDestroyed() override;
void OnSetColorSpace(
scoped_refptr<WaylandWpImageDescription> image_description);
diff --git a/ui/ozone/platform/wayland/host/wayland_wp_color_manager.cc b/ui/ozone/platform/wayland/host/wayland_wp_color_manager.cc
index 7c4d29bb..8ac5b579 100644
--- a/ui/ozone/platform/wayland/host/wayland_wp_color_manager.cc
+++ b/ui/ozone/platform/wayland/host/wayland_wp_color_manager.cc
@@ -169,7 +169,11 @@
wp_color_manager_v1_add_listener(manager_.get(), &kListener, this);
}
-WaylandWpColorManager::~WaylandWpColorManager() = default;
+WaylandWpColorManager::~WaylandWpColorManager() {
+ for (auto& observer : observers_) {
+ observer.OnColorManagerDestroyed();
+ }
+}
void WaylandWpColorManager::GetImageDescription(
const gfx::ColorSpace& color_space,
diff --git a/ui/ozone/platform/wayland/host/wayland_wp_color_manager.h b/ui/ozone/platform/wayland/host/wayland_wp_color_manager.h
index 0599b06..e7e52dfb 100644
--- a/ui/ozone/platform/wayland/host/wayland_wp_color_manager.h
+++ b/ui/ozone/platform/wayland/host/wayland_wp_color_manager.h
@@ -34,6 +34,7 @@
~Observer() override = default;
virtual void OnHdrEnabledChanged(bool hdr_enabled) = 0;
+ virtual void OnColorManagerDestroyed() = 0;
};
static constexpr char kInterfaceName[] = "wp_color_manager_v1";