CVE-2026-9945
Overview
Background
- `MediaFoundationStreamWrapper`
- A Windows media class (
media/renderers/win/media_foundation_stream_wrapper.cc) implementingIMFMediaStreamviaMicrosoft::WRL::RuntimeClass(ClassicCom). It bridges the Chromium media pipeline and the asynchronous Media Foundation (MF) pipeline, and runs in theMediaFoundationServiceutility process. - COM reference counting
- WRL manages the object’s lifetime by
AddRef/Release. By default, when the final reference is dropped the object deletes itself on whatever thread dropped it — here potentially a background MF threadpool thread. - `base::WeakPtr` / `weak_factory_`
- The wrapper posts work to the Chromium media
task_runner_viabase::BindOnce(&…::ProcessRequestsIfPossible, weak_factory_.GetWeakPtr()).base::WeakPtris sequence-affine: its invalidation (on destruction) and its dereference must happen on the same sequence, and its pointer isRAW_PTR_EXCLUSION(not BackupRefPtr-protected). - `task_runner_->DeleteSoon` / `RunsTasksInCurrentSequence`
- The sequenced-task-runner primitives the fix uses to force destruction back onto the media sequence.
Root Cause Analysis
The invariant violated is single-sequence ownership of a base::WeakPtrFactory: an object that hands out WeakPtrs bound to a task runner must be destroyed on that same sequence, so that weak-pointer invalidation and the posted tasks that dereference it are serialized. MediaFoundationStreamWrapper instead let default WRL COM Release() run its destructor on whichever thread dropped the last reference. When the MF pipeline released its final COM reference on a background MF threadpool thread, ~MediaFoundationStreamWrapper() (and thus weak_factory_) was destroyed off-sequence while a ProcessRequestsIfPossible task was concurrently being resolved on the Chromium media thread — a cross-thread TOCTOU between WeakPtr validity check and the subsequent method invocation, yielding a use-after-free on the freed wrapper. Because the WeakPtr’s backing pointer is RAW_PTR_EXCLUSION, the freed block is not quarantined by MiraclePtr, so the dangling access hits reusable memory.
The patch adds an IFACEMETHODIMP_(ULONG) Release() override to both MediaFoundationStreamWrapper and MediaFoundationSourceWrapper (media/renderers/win/*.cc / .h). The override calls WRL InternalRelease(), and when the refcount reaches 0 it checks task_runner_->RunsTasksInCurrentSequence(): if not on the media sequence it defers teardown via task_runner_->DeleteSoon(FROM_HERE, this), otherwise it deletes inline. This guarantees the destructor — and weak_factory_ invalidation — always runs on the media sequence, so a queued task can no longer observe the factory being destroyed concurrently. The header is also annotated MEDIA_EXPORT and the Release() override declared; new unit tests (DestructionOnTaskRunner for both wrappers) trigger a final COM release from a background thread and assert the deletion bounces to the task runner. The diff fully substantiates the advisory’s UAF class.
base::WeakPtrs onto a task runner was allowed to self-destruct on an arbitrary background thread, so its weak-pointer factory could be torn down on one thread at the exact moment a posted task dereferenced it on another — a cross-thread UAF rooted in missing sequence-bound destruction.Attack Path
- Start MF playback
Web content loads media (the advisory cites DRM/MF-backed codecs) that spins up
MediaFoundationRendererin theMediaFoundationServiceutility process and creates stream wrappers. - Queue weak-bound tasks
The MF pipeline repeatedly calls
RequestSample(), postingProcessRequestsIfPossibletasks bound to the wrapper’sWeakPtronto the media sequence. - Force teardown
Rapidly destroy the
<video>/source so Chromium drops its COM references while MF is still finishing, so the final reference is dropped by a background MF threadpool thread. - Race the TOCTOU window
On the media sequence a queued task validates the
WeakPtrand resolves it to a raw pointer; concurrently the MF thread runs~MediaFoundationStreamWrapper, freeing the object between the validity check and the method call. - Reclaim the block (inferred)
Because the
WeakPtrpointer isRAW_PTR_EXCLUSION, the freed memory is not quarantined; the attacker grooms the utility-process heap (via Mojo IPC) to reallocate it with forged contents. - Achieve control-flow/RCE (inferred) The task executes on the reclaimed memory, acquiring a forged lock and making a virtual call through a controlled member pointer, yielding code execution in the utility process. Steps 5–6 (heap grooming, control of the freed block, virtual-call hijack) are plausible exploitation, not demonstrated by the diff.
Impact Assessment
MediaFoundationService utility process (Windows) and is reachable from web content that drives MF-backed playback, with the UAF requiring a cross-thread destruction/execution race (high attack complexity, matching the advisory’s AC:H). Confirmed by the diff: a cross-thread use-after-free on a non-MiraclePtr-protected object. Escalation to arbitrary code execution in the utility process is (inferred) — it depends on winning the race and controlling the reallocated heap block, neither of which the diff proves; absent that, the realized outcome is a UAF (likely crash). Severity is nonetheless high because the freed object is immediately reusable (no BRP quarantine).Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmedia/renderers/win/media_foundation_source_wrapper.cc |
modified | |
MediaFoundationSourceWrappermedia/renderers/win/media_foundation_source_wrapper.h |
modified | |
MEDIA_EXPORTmedia/renderers/win/media_foundation_source_wrapper.h |
modified | |
MediaFoundationSourceWrapperTestmedia/renderers/win/media_foundation_source_wrapper_unittest.cc |
modified | |
MediaFoundationSourceWrapperTestmedia/renderers/win/media_foundation_source_wrapper_unittest.cc |
modified | |
TEST_Fmedia/renderers/win/media_foundation_source_wrapper_unittest.cc |
modified | |
ifmedia/renderers/win/media_foundation_stream_wrapper.cc |
modified |
Files Changed
media/renderers/BUILD.gnmedia/renderers/win/media_foundation_source_wrapper.ccmedia/renderers/win/media_foundation_source_wrapper.hmedia/renderers/win/media_foundation_source_wrapper_unittest.ccmedia/renderers/win/media_foundation_stream_wrapper.ccmedia/renderers/win/media_foundation_stream_wrapper.h
Audit Directions
- Other WRL/COM classes vending WeakPtrsAudit
media/renderers/winand other COM-visible Chromium classes that inheritRuntimeClassand hold aWeakPtrFactoryor post to a task runner; any without a thread-hoppingRelease()have the same cross-thread destruction hazard. - `RAW_PTR_EXCLUSION` dangling surfacesLook for objects whose only safety net would be MiraclePtr but whose pointers are excluded (e.g. inside
base::WeakPtr), where a UAF is immediately exploitable rather than a safe crash. - Default COM lifetime on sequence-affine objectsFlag any class that mixes default WRL self-delete with sequence-bound Chromium primitives (
SequencedTaskRunner,WeakPtr,base::Lockassumed held on one sequence). - DeleteSoon correctnessVerify the fix’s assumption that
task_runner_outlives the object atRelease()time; audit shutdown ordering where the task runner could itself be gone.
Patch
From 41ffa38dcb7f3ce786130a2834c51b00cc0e78a9 Mon Sep 17 00:00:00 2001 From: Sangbaek Park <[email protected]> Date: Tue, 28 Apr 2026 11:59:07 -0700 Subject: [PATCH] media: Fix cross-thread use-after-free in MediaFoundationStreamWrapper MediaFoundationStreamWrapper uses default COM reference counting for lifetime management, allowing it to be destructed on a background MF thread. This introduces a Time-of-Check to Time-of-Use (TOCTOU) race condition when executing tasks posted to the Chromium media thread via base::WeakPtr, potentially leading to a use-after-free. This CL enforces that MediaFoundationStreamWrapper is always destroyed on the correct sequenced task runner by overriding Release() to use DeleteSoon(). The same solution is applied to MediaFoundationSourceWrapper. Tests: { MediaFoundationStreamWrapperTest.DestructionOnTaskRunner, MediaFoundationSourceWrapperTest.DestructionOnTaskRunner } Bug: 503565293 Change-Id: Ib9044672195157f30e29905c8f1565a63c411ab2 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7787693 Reviewed-by: Dale Curtis <[email protected]> Commit-Queue: Sangbaek Park <[email protected]> Cr-Commit-Position: refs/heads/main@{#1621947} --- diff --git a/media/renderers/BUILD.gn b/media/renderers/BUILD.gn index 67642cb1..74361be 100644 --- a/media/renderers/BUILD.gn +++ b/media/renderers/BUILD.gn @@ -153,6 +153,7 @@ sources += [ "win/media_foundation_renderer_integration_test.cc", "win/media_foundation_renderer_unittest.cc", + "win/media_foundation_source_wrapper_unittest.cc", "win/media_foundation_stream_wrapper_unittest.cc", ] deps += [ "//media/test:pipeline_integration_test_base" ] diff --git a/media/renderers/win/media_foundation_source_wrapper.cc b/media/renderers/win/media_foundation_source_wrapper.cc index 2f7ade0..a06b3d7 100644 --- a/media/renderers/win/media_foundation_source_wrapper.cc +++ b/media/renderers/win/media_foundation_source_wrapper.cc @@ -35,6 +35,18 @@ } } +IFACEMETHODIMP_(ULONG) MediaFoundationSourceWrapper::Release() { + ULONG ref_count = InternalRelease(); + if (ref_count == 0) { + if (!task_runner_->RunsTasksInCurrentSequence()) { + task_runner_->DeleteSoon(FROM_HERE, this); + } else { + delete this; + } + } + return ref_count; +} + HRESULT MediaFoundationSourceWrapper::RuntimeClassInitialize( MediaResource* media_resource, MediaLog* media_log, diff --git a/media/renderers/win/media_foundation_source_wrapper.h b/media/renderers/win/media_foundation_source_wrapper.h index 4bc0657..1a14e52e 100644 --- a/media/renderers/win/media_foundation_source_wrapper.h +++ b/media/renderers/win/media_foundation_source_wrapper.h @@ -14,6 +14,7 @@ #include "base/memory/scoped_refptr.h" #include "base/task/sequenced_task_runner.h" +#include "media/base/media_export.h" #include "media/base/media_resource.h" #include "media/base/win/media_foundation_cdm_proxy.h" #include "media/renderers/win/media_foundation_stream_wrapper.h" @@ -31,7 +32,7 @@ // Note: The methods in this class can be called on two different threads - // Chromium thread and MF threadpool thread. // -class MediaFoundationSourceWrapper +class MEDIA_EXPORT MediaFoundationSourceWrapper : public Microsoft::WRL::RuntimeClass< Microsoft::WRL::RuntimeClassFlags< Microsoft::WRL::RuntimeClassType::ClassicCom>, @@ -44,6 +45,8 @@ MediaFoundationSourceWrapper(); ~MediaFoundationSourceWrapper() override; + IFACEMETHODIMP_(ULONG) Release() override; + // This is only called on |task_runner|. void DetachResource(); diff --git a/media/renderers/win/media_foundation_source_wrapper_unittest.cc b/media/renderers/win/media_foundation_source_wrapper_unittest.cc new file mode 100644 index 0000000..fd90b344 --- /dev/null +++ b/media/renderers/win/media_foundation_source_wrapper_unittest.cc @@ -0,0 +1,74 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "media/renderers/win/media_foundation_source_wrapper.h" + +#include <mfapi.h> + +#include <memory> + +#include "base/synchronization/waitable_event.h" +#include "base/task/sequenced_task_runner.h" +#include "base/task/thread_pool.h" +#include "base/test/task_environment.h" +#include "media/base/media_util.h" +#include "media/base/mock_filters.h" +#include "media/base/test_helpers.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +using Microsoft::WRL::ComPtr; +using Microsoft::WRL::MakeAndInitialize; + +class MediaFoundationSourceWrapperTest : public testing::Test { + public: + MediaFoundationSourceWrapperTest() { + source_wrapper_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner( + {base::MayBlock(), base::TaskPriority::BEST_EFFORT}); + + null_media_log_ = std::make_unique<NullMediaLog>(); + + MakeAndInitialize<MediaFoundationSourceWrapper>( + &mf_source_wrapper_, &media_resource_, null_media_log_.get(), + source_wrapper_task_runner_); + } + + ~MediaFoundationSourceWrapperTest() override { + mf_source_wrapper_.Reset(); + task_environment_.RunUntilIdle(); + } + + protected: + ComPtr<MediaFoundationSourceWrapper> mf_source_wrapper_; + base::test::TaskEnvironment task_environment_; + scoped_refptr<base::SequencedTaskRunner> source_wrapper_task_runner_; + testing::NiceMock<MockMediaResource> media_resource_; + std::unique_ptr<MediaLog> null_media_log_; +}; + +// Initializes a MediaFoundationSourceWrapper inside a simulated task runner +// environment, then manually triggers its destruction from a background thread +// to verify that it successfully bounces the destruction back to the sequenced +// task runner. +TEST_F(MediaFoundationSourceWrapperTest, DestructionOnTaskRunner) { + auto wrapper = mf_source_wrapper_; + mf_source_wrapper_.Reset(); + + base::WaitableEvent event; + base::ThreadPool::PostTask( + FROM_HERE, base::BindOnce( + [](ComPtr<MediaFoundationSourceWrapper> wrapper, + base::WaitableEvent* event) { + wrapper.Reset(); + event->Signal(); + }, + std::move(wrapper), &event)); + event.Wait(); + + // Wait for the task runner to process the deletion. + task_environment_.RunUntilIdle(); +} + +} // namespace media diff --git a/media/renderers/win/media_foundation_stream_wrapper.cc b/media/renderers/win/media_foundation_stream_wrapper.cc index 18bb9c19..3633ea79b9 100644 --- a/media/renderers/win/media_foundation_stream_wrapper.cc +++ b/media/renderers/win/media_foundation_stream_wrapper.cc @@ -39,6 +39,18 @@ MediaFoundationStreamWrapper::MediaFoundationStreamWrapper() = default; MediaFoundationStreamWrapper::~MediaFoundationStreamWrapper() = default; +IFACEMETHODIMP_(ULONG) MediaFoundationStreamWrapper::Release() { + ULONG ref_count = InternalRelease(); + if (ref_count == 0) { + if (!task_runner_->RunsTasksInCurrentSequence()) { + task_runner_->DeleteSoon(FROM_HERE, this); + } else { + delete this; + } + } + return ref_count; +} + /*static*/ HRESULT MediaFoundationStreamWrapper::Create( int stream_id, diff --git a/media/renderers/win/media_foundation_stream_wrapper.h b/media/renderers/win/media_foundation_stream_wrapper.h index a60e60ff..5a4d44d 100644 --- a/media/renderers/win/media_foundation_stream_wrapper.h +++ b/media/renderers/win/media_foundation_stream_wrapper.h
Regression Test / PoC
diff --git a/media/renderers/win/media_foundation_source_wrapper_unittest.cc b/media/renderers/win/media_foundation_source_wrapper_unittest.cc
new file mode 100644
index 0000000..fd90b344
--- /dev/null
+++ b/media/renderers/win/media_foundation_source_wrapper_unittest.cc
@@ -0,0 +1,74 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include "media/renderers/win/media_foundation_source_wrapper.h"
+
+#include <mfapi.h>
+
+#include <memory>
+
+#include "base/synchronization/waitable_event.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/thread_pool.h"
+#include "base/test/task_environment.h"
+#include "media/base/media_util.h"
+#include "media/base/mock_filters.h"
+#include "media/base/test_helpers.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+using Microsoft::WRL::ComPtr;
+using Microsoft::WRL::MakeAndInitialize;
+
+class MediaFoundationSourceWrapperTest : public testing::Test {
+ public:
+ MediaFoundationSourceWrapperTest() {
+ source_wrapper_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
+ {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
+
+ null_media_log_ = std::make_unique<NullMediaLog>();
+
+ MakeAndInitialize<MediaFoundationSourceWrapper>(
+ &mf_source_wrapper_, &media_resource_, null_media_log_.get(),
+ source_wrapper_task_runner_);
+ }
+
+ ~MediaFoundationSourceWrapperTest() override {
+ mf_source_wrapper_.Reset();
+ task_environment_.RunUntilIdle();
+ }
+
+ protected:
+ ComPtr<MediaFoundationSourceWrapper> mf_source_wrapper_;
+ base::test::TaskEnvironment task_environment_;
+ scoped_refptr<base::SequencedTaskRunner> source_wrapper_task_runner_;
+ testing::NiceMock<MockMediaResource> media_resource_;
+ std::unique_ptr<MediaLog> null_media_log_;
+};
+
+// Initializes a MediaFoundationSourceWrapper inside a simulated task runner
+// environment, then manually triggers its destruction from a background thread
+// to verify that it successfully bounces the destruction back to the sequenced
+// task runner.
+TEST_F(MediaFoundationSourceWrapperTest, DestructionOnTaskRunner) {
+ auto wrapper = mf_source_wrapper_;
+ mf_source_wrapper_.Reset();
+
+ base::WaitableEvent event;
+ base::ThreadPool::PostTask(
+ FROM_HERE, base::BindOnce(
+ [](ComPtr<MediaFoundationSourceWrapper> wrapper,
+ base::WaitableEvent* event) {
+ wrapper.Reset();
+ event->Signal();
+ },
+ std::move(wrapper), &event));
+ event.Wait();
+
+ // Wait for the task runner to process the deletion.
+ task_environment_.RunUntilIdle();
+}
+
+} // namespace media
diff --git a/media/renderers/win/media_foundation_stream_wrapper_unittest.cc b/media/renderers/win/media_foundation_stream_wrapper_unittest.cc
index 38bbc4e..0b317d75 100644
--- a/media/renderers/win/media_foundation_stream_wrapper_unittest.cc
+++ b/media/renderers/win/media_foundation_stream_wrapper_unittest.cc
@@ -86,6 +86,7 @@
~MediaFoundationStreamWrapperTest() override {
mf_video_stream_wrapper_.Reset();
+ task_environment_.RunUntilIdle();
}
// Polls the MediaFoundationStreamWrapper object for queued media events.
@@ -342,4 +343,27 @@
VerifyBufferedPostFlushSamplesProcessed(/*startEvent*/ false);
}
+// Initializes a MediaFoundationStreamWrapper inside a simulated task runner
+// environment, then manually triggers its destruction from a background thread
+// to verify that it successfully bounces the destruction back to the sequenced
+// task runner.
+TEST_F(MediaFoundationStreamWrapperTest, DestructionOnTaskRunner) {
+ auto wrapper = mf_video_stream_wrapper_;
+ mf_video_stream_wrapper_.Reset();
+
+ base::WaitableEvent event;
+ base::ThreadPool::PostTask(
+ FROM_HERE, base::BindOnce(
+ [](ComPtr<MediaFoundationStreamWrapper> wrapper,
+ base::WaitableEvent* event) {
+ wrapper.Reset();
+ event->Signal();
+ },
+ std::move(wrapper), &event));
+ event.Wait();
+
+ // Wait for the task runner to process the deletion.
+ task_environment_.RunUntilIdle();
+}
+
} // namespace media
Original Bug Report
Potential Cross-Thread Use-After-Free in MediaFoundationStreamWrapper
Flapjack, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team. Please see go/chrome-ai-generated-security-bugs-faq for more information.
Overview: MediaFoundationStreamWrapper’s lifetime is managed by COM reference counting, permitting its destruction on a background Media Foundation thread. This creates a Time-of-Check to Time-of-Use (TOCTOU) race condition when executing tasks posted to the Chromium media thread via base::WeakPtr. A successful race can lead to a Use-After-Free (UAF) and arbitrary code execution in the MediaFoundationService utility process.
Affected files:
media/renderers/win/media_foundation_stream_wrapper.ccmedia/renderers/win/media_foundation_stream_wrapper.h
Estimated timestamp from git blame: 2025-11-24
Summary
A potential cross-thread Use-After-Free (UAF) vulnerability exists in MediaFoundationStreamWrapper (media/renderers/win/media_foundation_stream_wrapper.cc). The class relies on default COM reference counting for lifetime management, meaning its destruction can occur on a background Windows Media Foundation (MF) threadpool thread. However, it uses a base::WeakPtrFactory to post tasks to the Chromium media thread. Because base::WeakPtr is not thread-safe for concurrent destruction and dereferencing, a Time-of-Check to Time-of-Use (TOCTOU) race condition exists that allows the media thread to execute methods on freed memory.
Technical Details
MediaFoundationStreamWrapper implements IMFMediaStream and inherits from Microsoft::WRL::RuntimeClass. It interacts with both the Chromium media pipeline and the asynchronous MF pipeline. When the MF pipeline requests a sample (RequestSample), the wrapper posts a task to the Chromium media thread:
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&MediaFoundationStreamWrapper::ProcessRequestsIfPossible,
weak_factory_.GetWeakPtr()));
Because there is no custom Release() override that trampolines destruction to the task_runner_, if the MF pipeline drops the final COM reference, ~MediaFoundationStreamWrapper() executes synchronously on the MF background thread. This destroys the weak_factory_.
Concurrently, when the task executes on the Chromium media thread, the base::internal::InvokeHelper evaluates the WeakPtr. It unwraps the pointer, checks its validity, and then invokes the bound method. If the MF thread invalidates the WeakPtr and frees the MediaFoundationStreamWrapper’s memory after the WeakPtr’s validity has been confirmed but before or during the execution of the target method, a UAF occurs.
Notably, the underlying pointer within base::WeakPtr is declared as RAW_PTR_EXCLUSION T* ptr_; (base/memory/weak_ptr.h). Therefore, the freed memory is not quarantined by MiraclePtr (BackupRefPtr), leaving it immediately exploitable.
Potential Attacker Steps
Note: These are suggested steps; our tooling has not executed a live Proof of Concept.
- Trigger playback: An attacker creates a webpage hosting DRM-protected media or specific codecs that initialize the
MediaFoundationRendererinside theMediaFoundationServiceutility process. - Queue tasks: The MF pipeline asynchronously calls
RequestSample(), queuingProcessRequestsIfPossible()tasks bound toWeakPtrs on the Chromium media thread. - Initiate Teardown: The attacker rapidly destroys the
<video>element. The Chromium pipeline drops its COM references to theMediaFoundationSourceWrapperand its streams. - Race Condition: The MF pipeline subsequently shuts down and drops its final reference to the
MediaFoundationStreamWrapperon a background thread. - TOCTOU Gap: On the Chromium media thread, the queued task begins execution.
InvokeHelperconfirms theWeakPtris valid and resolves it to a raw C++ reference. - Object Destruction: Before the method fully executes, the MF thread destroys the
MediaFoundationStreamWrapperand its memory is deallocated. - Heap Grooming: The attacker leverages cross-process heap grooming (via Mojo IPCs to the utility process) to immediately reallocate the freed memory block with forged contents (e.g., an unlocked
base::Lockstate and a controlleddemuxer_stream_pointer). - Exploitation:
ProcessRequestsIfPossible()executes on the attacker-controlled memory block. It acquires the forged lock, reads the forgeddemuxer_stream_pointer, and makes a virtual method call (demuxer_stream_->Read()), redirecting control flow and achieving Remote Code Execution (RCE) in the utility process.
Suggested Fix
Implement a custom thread-hopping Release() override in MediaFoundationStreamWrapper (and similar COM-visible classes in this directory, like MediaFoundationSourceWrapper) to ensure the object is always destructed on the correct Chromium sequence.
For example:
ULONG STDMETHODCALLTYPE Release() override {
ULONG ref_count = Microsoft::WRL::RuntimeClass<...>::Release();
if (ref_count == 0) {
// Note: requires modifying the class to not auto-delete in the base Release(),
// or using a custom Deleter with a smart pointer instead of strict WRL inheritance.
}
return ref_count;
}
Alternatively, use base::RefCountedDeleteOnSequence paradigms, or wrap the WRL RuntimeClass inside an inner object so the outer Chromium-facing class controls the teardown strictly on the task_runner_.
Evaluated with Chrome root at commit: c0eb5541aebfa4ea08806eaf6e94bcc69f87ab2f
Results 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.