Overview

Medium
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Ozone
DescriptionUse after free in Ozone
ComponentOzone
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker461877477
Fix commit2cc3c5b2b5f1 (chromium/src) +17/-1
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2025-11-18 (per vendor advisory)
Fix landed2025-12-08 (public git)
Disclosed2026-02-10
Patch gap64 days — fix public in source before the advisory

Background

`WaylandWpColorManager`
Ozone/Wayland host object wrapping the wp_color_manager_v1 global; it is the observed subject that other color-management objects register against.
`WaylandWpColorManagementOutput`
Per-output color-management object owned by WaylandOutput that registers as an Observer of WaylandWpColorManager through a ScopedObservation.
`ScopedObservation`
RAII helper that adds an observer on construction and calls RemoveObserver on the still-referenced subject at destruction, requiring the subject to outlive it.
Destruction ordering
Because WaylandWpColorManager sits earlier in the owning member list than the WaylandOutput-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.

Key insight
A 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

  1. Color-management setup A compositor advertises wp_color_manager_v1, causing the browser to create WaylandWpColorManager and a WaylandWpColorManagementOutput observing it.
  2. Teardown trigger Output or connection teardown destroys WaylandWpColorManager before the WaylandOutput-owned observer, per member ordering.
  3. Dangling observation The observer’s ScopedObservation retains a raw pointer to the now-freed WaylandWpColorManager.
  4. 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

This is a browser-process use-after-free in Ozone’s Wayland color-management path, reachable during normal output/connection teardown on Linux/ChromeOS Wayland platforms. Exploitability is constrained because the trigger is driven by Wayland lifecycle events rather than direct renderer input, but a UAF in the privileged browser process can lead to memory corruption and potential code execution. Practical impact depends on the ability to influence allocation timing and reuse of the freed WaylandWpColorManager.

Changed Functions

FunctionChangeNotes
if
ui/ozone/platform/wayland/host/wayland_wp_color_management_output.cc
modified
for
ui/ozone/platform/wayland/host/wayland_wp_color_manager.cc
modified

Files Changed

  • ui/ozone/platform/wayland/host/wayland_wp_color_management_output.cc
  • ui/ozone/platform/wayland/host/wayland_wp_color_management_output.h
  • ui/ozone/platform/wayland/host/wayland_wp_color_management_surface.cc
  • ui/ozone/platform/wayland/host/wayland_wp_color_management_surface.h
  • ui/ozone/platform/wayland/host/wayland_wp_color_manager.cc
  • ui/ozone/platform/wayland/host/wayland_wp_color_manager.h

Audit Directions

  • Observer/observed lifetime ordering
    Audit every ScopedObservation/ObserverList in 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 discipline
    Look 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 order
    Review member/destruction ordering in WaylandOutput, WaylandConnection, and related owners so observed globals are declared after (destroyed after) the objects that observe them.
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";
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.