Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Blink
DescriptionUse after free in Blink
ComponentBlink
Bug ClassUAF
Tracker409059706
Fix commitf1e6422a355c (chromium/src) +105/-21
CISA KEVNot listed
CreditedWalkman
Disclosed2025-06-02

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/core/workers/worker_thread.cc
modified

Files Changed

  • third_party/blink/common/features.cc
  • third_party/blink/public/common/features.h
  • third_party/blink/renderer/core/workers/worker_thread.cc
From f1e6422a355c016e5f2c22619181f7f121d1f511 Mon Sep 17 00:00:00 2001
From: Yoshisato Yanagisawa <yyanagisawa@chromium.org>
Date: Wed, 21 May 2025 23:25:12 -0700
Subject: [PATCH] Enforce SharedWorker::Terminate() procedure order

During the investigation of crbug.com/409059706, we observed that
PerformShutdownOnWorkerThread() is called during the status is
running.

I suppose the root cause is race condition between `Terminate()`
procedure and a child process termination procedure in different
thread.  WorkerThread can be terminated if two conditions are met;
`Terminate()` is called and all child worker threads have been
terminated.  Both `Terminate()` and the child process termination
procedure may call `PerformShutdownOnWorkerThread()`, and former
is executed regardless of two conditions are met.  The latter
is called if `Terminate()` is called and no child processes.
To be clear, "`Terminate()` is called" does not mean
`PrepareForShutdownOnWorkerThread()` is executed.  `Terminate()`
queues it after the flag to tell `Terminate()` call.  And, when
the issue happen, I am quite sure the flag is set but,
`PrepareForShutdownOnWorkerThread()` won't be executed yet.

The fix is that:
1. The "Terminate() is called" flag to be multi staged.
   The flag is used for two purpose; a. avoid re-enter of
  `Terminate()`, and b. `PrepareForShutdownOnWorkerThread()` is
  in flight. The CL changed the flag to enum to represent
  the stage properly.
2. `PerformShutdownOnWorkerThread()` is queued even if it is
   called within the child process termination procedure.
   It avoid the execution order flip between
   `PrepareForShutdownOnWorkerThread()` and
   `PerformShutdownOnWorkerThread()`.

In addition, this change ensures `PerformShutdownOnWorkerThread()`
is called once.  While `PerformShutdownOnWorkerThread()` touches
fields inside, the fields must not be touched at some point within
the function, the function is actually not re-entrant when it reaches
to the end.  Upon mikt@ suggestion, I made
`PerformShutdownOnWorkerThread()` is called only when two conditions
are fulfilled. i.e. `Terminate()` is called and the number of child
threads is 0.  Also, the CL uses the enum to show
`PerformShutdownOnWorkerThread()` is in-flight to avoid re-entrance
in this level.

Bug: 409059706
Change-Id: I81a1c3b1a34e827fa75ec2d1a9b37023965dbe27
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6543412
Reviewed-by: Hiroki Nakagawa <nhiroki@chromium.org>
Commit-Queue: Yoshisato Yanagisawa <yyanagisawa@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1463892}
---

diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 2d13943f..0440e931 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -2816,6 +2816,13 @@
              "WebviewAccelerateSmallCanvases",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// WorkerThread termination procedure (prepare and shutdown) runs sequentially
+// in the same task without calling another cross thread post task.
+// Kill switch for crbug.com/409059706.
+BASE_FEATURE(kWorkerThreadSequentialShutdown,
+             "WorkerThreadSequentialShutdown",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 BASE_FEATURE(kNoReferrerForPreloadFromSubresource,
              "NoReferrerForPreloadFromSubresource",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 7e3c024e..97923fb 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -1827,6 +1827,8 @@
 
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kWebviewAccelerateSmallCanvases);
 
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kWorkerThreadSequentialShutdown);
+
 // Kill switch for https://crbug.com/415810136.
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kNoReferrerForPreloadFromSubresource);
 
diff --git a/third_party/blink/renderer/core/workers/worker_thread.cc b/third_party/blink/renderer/core/workers/worker_thread.cc
index e4d70bc..ba5aa4c 100644
--- a/third_party/blink/renderer/core/workers/worker_thread.cc
+++ b/third_party/blink/renderer/core/workers/worker_thread.cc
@@ -263,9 +263,10 @@
   DCHECK_CALLED_ON_VALID_THREAD(parent_thread_checker_);
   {
     base::AutoLock locker(lock_);
-    if (requested_to_terminate_)
+    if (termination_progress_ != TerminationProgress::kNotRequested) {
       return;
-    requested_to_terminate_ = true;
+    }
+    termination_progress_ = TerminationProgress::kRequested;
   }
 
   // Schedule a task to forcibly terminate the script execution in case that the
@@ -281,10 +282,33 @@
       *task_runner, FROM_HERE,
       CrossThreadBindOnce(&WorkerThread::PrepareForShutdownOnWorkerThread,
                           CrossThreadUnretained(this)));
-  PostCrossThreadTask(
-      *task_runner, FROM_HERE,
-      CrossThreadBindOnce(&WorkerThread::PerformShutdownOnWorkerThread,
-                          CrossThreadUnretained(this)));
+
+  if (!base::FeatureList::IsEnabled(
+          blink::features::kWorkerThreadSequentialShutdown)) {
+    PostCrossThreadTask(
+        *task_runner, FROM_HERE,
+        CrossThreadBindOnce(&WorkerThread::PerformShutdownOnWorkerThread,
+                            CrossThreadUnretained(this)));
+    return;
+  }
+
+  bool perform_shutdown = false;
+  {
+    base::AutoLock locker(lock_);
+    CHECK_EQ(TerminationProgress::kRequested, termination_progress_);
+    termination_progress_ = TerminationProgress::kPrepared;
+    if (num_child_threads_ == 0) {
+      termination_progress_ = TerminationProgress::kPerforming;
+      perform_shutdown = true;
+    }
+  }
+
+  if (perform_shutdown) {
+    PostCrossThreadTask(
+        *task_runner, FROM_HERE,
+        CrossThreadBindOnce(&WorkerThread::PerformShutdownOnWorkerThread,
+                            CrossThreadUnretained(this)));
+  }
 }
 
 void WorkerThread::TerminateForTesting() {
@@ -421,20 +445,48 @@
 
 void WorkerThread::ChildThreadStartedOnWorkerThread(WorkerThread* child) {
   DCHECK(IsCurrentThread());
-#if DCHECK_IS_ON()
+  child_threads_.insert(child);
   {
     base::AutoLock locker(lock_);
     DCHECK_EQ(ThreadState::kRunning, thread_state_);
+    CHECK_EQ(TerminationProgress::kNotRequested, termination_progress_);
+    if (base::FeatureList::IsEnabled(
+            blink::features::kWorkerThreadSequentialShutdown)) {
+      ++num_child_threads_;
+      CHECK_EQ(child_threads_.size(), num_child_threads_);
+    }
   }
-#endif
-  child_threads_.insert(child);
 }
 
 void WorkerThread::ChildThreadTerminatedOnWorkerThread(WorkerThread* child) {
   DCHECK(IsCurrentThread());
   child_threads_.erase(child);
-  if (child_threads_.empty() && CheckRequestedToTerminate())
-    PerformShutdownOnWorkerThread();
+  if (!base::FeatureList::IsEnabled(
+          blink::features::kWorkerThreadSequentialShutdown)) {
+    if (child_threads_.empty() && CheckRequestedToTerminate()) {
+      PerformShutdownOnWorkerThread();
+    }
+    return;
+  }
+
+  bool perform_shutdown = false;
+  {
+    base::AutoLock locker(lock_);
+    --num_child_threads_;
+    CHECK_EQ(child_threads_.size(), num_child_threads_);
+    if (num_child_threads_ == 0 &&
+        termination_progress_ == TerminationProgress::kPrepared) {
+      termination_progress_ = TerminationProgress::kPerforming;
+      perform_shutdown = true;
+    }
+  }
+  if (perform_shutdown) {
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner =
+        GetWorkerBackingThread().BackingThread().GetTaskRunner();
+    GetWorkerBackingThread().BackingThread().GetTaskRunner()->PostTask(
+        FROM_HERE, WTF::BindOnce(&WorkerThread::PerformShutdownOnWorkerThread,
+                                 WTF::Unretained(this)));
+  }
 }
 
 WorkerThread::WorkerThread(WorkerReportingProxy& worker_reporting_proxy)
@@ -772,18 +824,32 @@
   DCHECK(IsCurrentThread());
   {
     base::AutoLock locker(lock_);
-    DCHECK(requested_to_terminate_);
+    if (!base::FeatureList::IsEnabled(
Loading diff…

Original Bug Report

reported by [Deleted User]

Use After Free in CompressedPointer::Load inside WorkerThread::DidProcessTask

deleted
View on issue tracker