Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Ozone
DescriptionUse after free in Ozone
ComponentOzone
Bug ClassUAF
Tracker433027577
Fix commit454dc266cc04 (chromium/src) +50/-30
CISA KEVNot listed
CreditedWei Yuan of MoyunSec VLab
Disclosed2025-10-28

Changed Functions

FunctionChangeNotes
if
ui/ozone/platform/wayland/host/wayland_screen.cc
modified
OrgGnomeMutterIdleMonitor
ui/ozone/platform/wayland/host/wayland_screen.h
modified
WaylandScreen
ui/ozone/platform/wayland/host/wayland_screen.h
modified
if
ui/ozone/platform/wayland/host/zwp_idle_inhibit_manager.cc
modified
WaylandConnection
ui/ozone/platform/wayland/host/zwp_idle_inhibit_manager.h
modified
WaylandWindow
ui/ozone/platform/wayland/host/zwp_idle_inhibit_manager.h
modified

Files Changed

  • ui/ozone/platform/wayland/host/wayland_screen.cc
  • ui/ozone/platform/wayland/host/wayland_screen.h
  • ui/ozone/platform/wayland/host/zwp_idle_inhibit_manager.cc
  • ui/ozone/platform/wayland/host/zwp_idle_inhibit_manager.h
From 454dc266cc0437da6371c65720b722322ef8eecc Mon Sep 17 00:00:00 2001
From: Kramer Ge <[email protected]>
Date: Wed, 24 Sep 2025 19:20:27 -0700
Subject: [PATCH] [Ozone/Wayland]Remove idle_inhibitor_ object from WaylandScreen

..to prevent UAF on chrome shutdown. Unlike most objects created by
WaylandConnection, WaylandScreen is owned by ChromeBrowserMainExtraParts
and outlives WaylandConnection.

To prevent accessing wl_display internals after destruction of
WaylandConnection, WaylandScreen should either destroy wl::Object when
connection resets, or reference wl::Object indirectly.

Manage inhibitor in zwp_idle_inhibit_manager. Also fix a logic in
IsScreenSaverActive() where `inhibitor` mean screen saver is blocked.

Bug: 433027577, 433643249
Change-Id: If02755ddced08f8cf795ac21ed144387d0aa4077
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6979565
Commit-Queue: Kramer Ge <[email protected]>
Reviewed-by: Thomas Anderson <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1520174}
---

diff --git a/ui/ozone/platform/wayland/host/wayland_screen.cc b/ui/ozone/platform/wayland/host/wayland_screen.cc
index c3e6b7df..cc9225dd 100644
--- a/ui/ozone/platform/wayland/host/wayland_screen.cc
+++ b/ui/ozone/platform/wayland/host/wayland_screen.cc
@@ -128,10 +128,7 @@
   }
 }
 
-WaylandScreen::~WaylandScreen() {
-  // Destroy the idle inhibitor early.  See https://crbug.com/433643249
-  idle_inhibitor_.reset();
-}
+WaylandScreen::~WaylandScreen() = default;
 
 void WaylandScreen::OnOutputAddedOrUpdated(
     const WaylandOutput::Metrics& metrics) {
@@ -448,31 +445,19 @@
     return false;
 
   if (suspend) {
-    // Wayland inhibits idle behaviour on certain output, and implies that a
-    // surface bound to that output should obtain the inhibitor and hold it
-    // until it no longer needs to prevent the output to go idle.
-    // We assume that the idle lock is initiated by the user, and therefore the
-    // surface that we should use is the one owned by the window that is focused
-    // currently.
-    const auto* window_manager = connection_->window_manager();
-    DCHECK(window_manager);
-    const auto* current_window = window_manager->GetCurrentFocusedWindow();
-    if (!current_window) {
-      LOG(WARNING) << "Cannot inhibit going idle when no window is focused";
-      return false;
-    }
-    DCHECK(current_window->root_surface());
-    idle_inhibitor_ = connection_->zwp_idle_inhibit_manager()->CreateInhibitor(
-        current_window->root_surface()->surface());
+    connection_->zwp_idle_inhibit_manager()->CreateInhibitor();
   } else {
-    idle_inhibitor_.reset();
+    connection_->zwp_idle_inhibit_manager()->RemoveInhibitor();
   }
 
   return true;
 }
 
 bool WaylandScreen::IsScreenSaverActive() const {
-  return idle_inhibitor_ != nullptr;
+  // idle_inhibitor prevents screen saver from engaging, but does not indicate
+  // whether screen saver is active or not. Assume not here.
+  NOTIMPLEMENTED_LOG_ONCE();
+  return false;
 }
 
 base::TimeDelta WaylandScreen::CalculateIdleTime() const {
diff --git a/ui/ozone/platform/wayland/host/wayland_screen.h b/ui/ozone/platform/wayland/host/wayland_screen.h
index e09aa150..23f0903 100644
--- a/ui/ozone/platform/wayland/host/wayland_screen.h
+++ b/ui/ozone/platform/wayland/host/wayland_screen.h
@@ -38,7 +38,8 @@
 class OrgGnomeMutterIdleMonitor;
 #endif
 
-// A PlatformScreen implementation for Wayland.
+// A PlatformScreen implementation for Wayland. Note that this object outlives
+// WaylandConnection.
 class WaylandScreen : public PlatformScreen, public DeviceScaleFactorObserver {
  public:
   explicit WaylandScreen(WaylandConnection* connection);
@@ -136,7 +137,6 @@
       org_gnome_mutter_idle_monitor_;
 #endif
 
-  wl::Object<zwp_idle_inhibitor_v1> idle_inhibitor_;
   uint32_t screen_saver_suspension_count_ = 0;
 
   base::ScopedObservation<ui::LinuxUi, DeviceScaleFactorObserver>
diff --git a/ui/ozone/platform/wayland/host/zwp_idle_inhibit_manager.cc b/ui/ozone/platform/wayland/host/zwp_idle_inhibit_manager.cc
index 4ca26f5..cd4fdf3 100644
--- a/ui/ozone/platform/wayland/host/zwp_idle_inhibit_manager.cc
+++ b/ui/ozone/platform/wayland/host/zwp_idle_inhibit_manager.cc
@@ -8,6 +8,8 @@
 
 #include "base/logging.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
+#include "ui/ozone/platform/wayland/host/wayland_surface.h"
+#include "ui/ozone/platform/wayland/host/wayland_window.h"
 
 namespace ui {
 
@@ -45,14 +47,37 @@
 ZwpIdleInhibitManager::ZwpIdleInhibitManager(
     zwp_idle_inhibit_manager_v1* manager,
     WaylandConnection* connection)
-    : manager_(manager) {}
+    : connection_(connection), manager_(manager) {}
 
 ZwpIdleInhibitManager::~ZwpIdleInhibitManager() = default;
 
-wl::Object<zwp_idle_inhibitor_v1> ZwpIdleInhibitManager::CreateInhibitor(
-    wl_surface* surface) {
-  return wl::Object<zwp_idle_inhibitor_v1>(
-      zwp_idle_inhibit_manager_v1_create_inhibitor(manager_.get(), surface));
+bool ZwpIdleInhibitManager::CreateInhibitor() {
+  // Wayland inhibits idle behaviour on certain output, and implies that a
+  // surface bound to that output should obtain the inhibitor and hold it
+  // until it no longer needs to prevent the output to go idle.
+  // We assume that the idle lock is initiated by the user, and therefore the
+  // surface that we should use is the one owned by the window that is focused
+  // currently.
+  const auto* window_manager = connection_->window_manager();
+  DCHECK(window_manager);
+  auto* current_window = window_manager->GetCurrentFocusedWindow();
+  if (!current_window) {
+    LOG(WARNING) << "Cannot inhibit going idle when no window is focused";
+    return false;
+  }
+
+  DCHECK(current_window->root_surface());
+  auto new_inhibitor = wl::Object<zwp_idle_inhibitor_v1>(
+      zwp_idle_inhibit_manager_v1_create_inhibitor(
+          manager_.get(), current_window->root_surface()->surface()));
+
+  idle_inhibitor_.swap(new_inhibitor);
+  return true;
+}
+
+void ZwpIdleInhibitManager::RemoveInhibitor() {
+  inhibiting_window_ = nullptr;
+  idle_inhibitor_.reset();
 }
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/zwp_idle_inhibit_manager.h b/ui/ozone/platform/wayland/host/zwp_idle_inhibit_manager.h
index f7af500..b0a72d4 100644
--- a/ui/ozone/platform/wayland/host/zwp_idle_inhibit_manager.h
+++ b/ui/ozone/platform/wayland/host/zwp_idle_inhibit_manager.h
@@ -5,11 +5,13 @@
 #ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_ZWP_IDLE_INHIBIT_MANAGER_H_
 #define UI_OZONE_PLATFORM_WAYLAND_HOST_ZWP_IDLE_INHIBIT_MANAGER_H_
 
+#include "base/memory/weak_ptr.h"
 #include "ui/ozone/platform/wayland/common/wayland_object.h"
 
 namespace ui {
 
 class WaylandConnection;
+class WaylandWindow;
 
 // Wraps the idle inhibit manager, which is provided via
 // zwp_idle_inhibit_manager_v1 interface.
@@ -30,11 +32,19 @@
   ZwpIdleInhibitManager& operator=(const ZwpIdleInhibitManager&) = delete;
   ~ZwpIdleInhibitManager();
 
-  wl::Object<zwp_idle_inhibitor_v1> CreateInhibitor(wl_surface* surface);
+  bool CreateInhibitor();
+  void RemoveInhibitor();
+
+  bool is_inhibiting() const { return !!inhibiting_window_; }
 
  private:
+  const raw_ptr<WaylandConnection> connection_;
+
   // Wayland object wrapped by this class.
   wl::Object<zwp_idle_inhibit_manager_v1> manager_;
+
+  base::WeakPtr<WaylandWindow> inhibiting_window_;
+  wl::Object<zwp_idle_inhibitor_v1> idle_inhibitor_;
 };
 
 }  // namespace ui
Loading diff…

Original Bug Report

reported by [email protected]

heap-use-after-free in wl_proxy_marshal_array_flags

Steps to reproduce the problem

The crash occasionally occurs in my fuzzing system and there is currently no stable reproduction case. You can refer to the RCA to fix the issue.

Problem Description

BISECT https://chromium-review.googlesource.com/c/chromium/src/+/3008414

RCA

  1. When Chrome opens on Ubuntu, a ui::WaylandConnection object is created, and display_ is initialized in WaylandConnection::Initialize().
bool WaylandConnection::Initialize(bool use_threaded_polling) {
  ...
  display_.reset(wl_display_connect(nullptr));      // [1]
  if (!display_) {
    PLOG(ERROR) << "Failed to connect to Wayland display";
    return false;
  }
  ...
}

https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/wayland/host/wayland_connection.cc;l=195;drc=5c47d5e475326944a7be161b049c73c831cb830e;bpv=1;bpt=1

  1. When a screen saver view is created and suspended, a WaylandScreen object is also created and owned by that view. The idle_inhibitor_ member of WaylandScreen will be initialized, holding a proxy pointer to ui::WaylandConnection’s zwp_idle_inhibit_manager. This proxy pointer will be used in Wayland to interact with the display in [1].
bool WaylandScreen::SetScreenSaverSuspended(bool suspend) {
  if (!connection_->zwp_idle_inhibit_manager())
    return false;

  if (suspend) {
    ...
    idle_inhibitor_ = connection_->zwp_idle_inhibit_manager()->CreateInhibitor(
        current_window->root_surface()->surface());     // [2]
  } else {
    idle_inhibitor_.reset();
  }

  return true;
}

https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/wayland/host/wayland_screen.cc;l=451;drc=5c47d5e475326944a7be161b049c73c831cb830e;bpv=1;bpt=1

  1. If the above two conditions are met, when Chrome closes, the WaylandConnection and its display_ (which points to a wl_display) may be destroyed before ChromeBrowserMainExtraPartsViews. As a result, a heap-use-after-free will occur when the WaylandScreen is destroyed and try to interact the display object [3].
WL_EXPORT struct wl_proxy *
wl_proxy_marshal_array_flags(struct wl_proxy *proxy, uint32_t opcode,
			     const struct wl_interface *interface, uint32_t version,
			     uint32_t flags, union wl_argument *args)
{
    ...
	if (proxy->display->last_error) {       // [3]
		goto err_unlock;
	}
    ...
}

https://source.chromium.org/chromium/chromium/src/+/main:third_party/wayland/src/src/wayland-client.c;l=911;drc=5c47d5e475326944a7be161b049c73c831cb830e;bpv=1;bpt=1

Summary

heap-use-after-free in wl_proxy_marshal_array_flags

Custom Questions

Type of crash:

browser

Crash state:

heap-use-after-free

Reporter credit:

V0

Additional Data

Category: Security
Chrome Channel: Stable
Regression: N/A \

View on issue tracker