CVE-2026-12011
Overview
Background
- `MidiManagerWinrt` / `MidiPortManager`
- the WinRT-based Web MIDI backend on Windows (
media/midi/midi_manager_winrt.cc); aMidiManagerWinrtownsMidiPortManagerobjects that watch device arrivals/removals via asynchronous WinRTDeviceWatcherand async-operation callbacks. - `TaskService` instance binding
- a
MidiManagerWinrtcallsBindInstance()to obtain anInstanceId, andPostBoundTask/PostBoundDelayedTaskstamp posted tasks soTaskServicecan drop tasks whose boundInstanceIdno longer matches the active instance afterUnbindInstance(). - WinRT completion callback
- a
WRL::Callbacklambda (add_Added,add_EnumerationCompleted,add_Removed,put_Completed,add_MessageReceived) that fires asynchronously and re-posts work ontokComTaskRunnerviabase::Unretained(port_manager). - `base::Unretained`
- binds a raw, non-owning pointer into a callback, so the callee (
MidiPortManager::OnAdded,OnRemoved, etc.) is dereferenced with no lifetime guarantee, making correctInstanceIdvalidation the sole thing preventing a use-after-free.
Root Cause Analysis
Each MidiPortManager WinRT callback lambda captured only [port_manager, task_service] and re-posted work with task_service->PostBoundTask(kComTaskRunner, ...), the overload that stamps the task with whatever InstanceId is currently active at post time rather than the ID that was active when the port manager was created. During rapid session re-binding, an old MidiPortManager could be torn down while its asynchronous WinRT callbacks were still outstanding; if a new MidiManagerWinrt had already called BindInstance() and become the active instance, a stale callback from the destroyed object would stamp its posted task with the new active InstanceId, so TaskService’s validation passed and the task ran MidiPortManager::OnAdded/OnRemoved/OnCompletedGetPortFromIdAsync on the freed port_manager via base::Unretained — a use-after-free.
The fix stores the construction-time ID in a new const TaskService::InstanceId instance_id_ member, captures a local const TaskService::InstanceId instance_id = instance_id_; by value in every callback lambda, and calls the new explicit-ID overload PostBoundTask(instance_id, kComTaskRunner, ...); a stale callback therefore stamps its task with the old ID, which fails validation and is dropped instead of dereferencing freed memory. The destructor additionally now iterates async_ops_ and calls async_op->Release() so outstanding async operations are cleaned up at teardown.
InstanceId at post time instead of the port manager’s own creation-time identity, so a stale callback from a freed instance was validated against — and executed on behalf of — the new live instance; the fix pins each callback to the construction-time instance_id_ captured by value so stale tasks fail TaskService validation.Attack Path
- Trigger rapid re-binding of the WinRT MIDI backend
repeatedly obtain and release Web MIDI access so an old
MidiManagerWinrt/MidiPortManageris destroyed while a newMidiManagerWinrtcallsBindInstance()and becomes the active instance before the old cleanup completes (the exact renderer-side sequence, e.g. repeatednavigator.requestMIDIAccess(), is(inferred)). - Leave an async WinRT callback outstanding on the old instance
a
DeviceWatcher(add_Added/add_Removed/add_EnumerationCompleted) or async-operation (put_Completed) callback belonging to the destroyedMidiPortManageris still pending; because it captured only[port_manager, task_service], it will re-post using the current-active-IDPostBoundTask(kComTaskRunner, ...). - Stale callback stamps its task with the new active `InstanceId`
when the pending callback fires,
PostBoundTaskbinds the task to the now-active new instance’s ID, soTaskService’s ID check passes and the task is dispatched instead of dropped. - Use-after-free on the freed port manager
the dispatched task invokes
MidiPortManager::OnAdded/OnRemoved/OnCompletedGetPortFromIdAsynconbase::Unretained(port_manager), dereferencing the already-destroyedMidiPortManager. - Escalation
controlling the freed allocation’s contents to convert the UAF into a read/write primitive or code execution is
(inferred); the diff proves only the use-after-free.
Impact Assessment
MidiPortManager, dereferenced on kComTaskRunner in the process hosting the MIDI backend (the browser or MIDI/audio service process; the exact process is (inferred)). Preconditions are the Windows WinRT MIDI backend and a timing window where a new instance binds before the old instance’s outstanding asynchronous callbacks are drained. The vendor advisory rates this critical severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.3. The advisory metadata classifies this as a WebMIDI use-after-free and does not state exploited-in-the-wild status or a severity rating in the provided material.Changed Functions
| Function | Change | Notes |
|---|---|---|
MidiManagerWinrtmedia/midi/midi_manager_winrt.cc |
modified | |
instance_id_media/midi/midi_manager_winrt.cc |
modified | |
formedia/midi/midi_manager_winrt.cc |
modified | |
ifmedia/midi/midi_manager_winrt.cc |
modified |
Files Changed
media/midi/midi_manager_winrt.cc
Audit Directions
- Ambient-ID task postingaudit every
PostBoundTask/PostBoundDelayedTaskcall site that omits an explicitInstanceId; any that runs from an asynchronous callback capturingbase::Unretained(this)can stamp a stale task with a newer active instance and re-introduce the same UAF. - Async callbacks outliving their ownerreview WinRT/COM handlers (
add_*,put_Completed) and otherWRL::Callback/async lambdas that capture a raw owner pointer; confirm the owner’s identity (not just its address) is captured by value and re-validated before the callback dereferences it. - Teardown vs. outstanding operationscheck destructors of objects that register async operations (analogous to the new
async_ops_Release()loop) to ensure all in-flight operations are cancelled/released before the object is freed, closing the window where a callback fires post-destruction.
Patch
From c5162675305c91532d60c33f6f860d093c445557 Mon Sep 17 00:00:00 2001 From: Hongchan Choi <[email protected]> Date: Wed, 03 Jun 2026 23:08:01 -0700 Subject: [PATCH] [WebMIDI] Resolve instance binding lifetime issue in MidiManagerWinrt Address a lifetime issue in Chrome's WinRT-based MIDI manager (`MidiManagerWinrt`) on Windows during rapid session re-binding. The issue occurred due to stale asynchronous WinRT callbacks firing from a destroyed `MidiPortManager` instance. If a new `MidiManagerWinrt` instance bound to `TaskService` before the old port managers' cleanup tasks were completed, the stale callbacks would stamp their posted tasks with the new active `InstanceId`, bypassing validation and executing on the outdated object. Fix this by: 1. Modifying `TaskService::BindInstance()` to return `std::optional<InstanceId>`, which is `nullopt` on failure. This avoids breaking otherplatform managers. 2. Overloading `PostBoundTask` and `PostBoundDelayedTask` to accept an explicit `InstanceId`. 3. Storing the construction-time `InstanceId` in `MidiPortManager`, capturing it by value in all WinRT callback lambdas, and using the explicit-ID overloads. Added unit tests in `media/midi/task_service_unittest.cc` to verify the explicit-ID behavior. CONV=ef5ca2e2-c51f-4594-a8bb-6055af840666 TAG=agy Bug: 518108291 Change-Id: I16aa19425ea93afffec395697edfd7752c6db8fa Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7890706 Commit-Queue: Hongchan Choi <[email protected]> Reviewed-by: Michael Wilson <[email protected]> Cr-Commit-Position: refs/heads/main@{#1641483} --- diff --git a/media/midi/midi_manager_winrt.cc b/media/midi/midi_manager_winrt.cc index 594c873f..0b3a4e63 100644 --- a/media/midi/midi_manager_winrt.cc +++ b/media/midi/midi_manager_winrt.cc @@ -25,6 +25,7 @@ #include <iomanip> #include <memory> +#include <optional> #include "base/check.h" #include "base/containers/heap_array.h" @@ -219,11 +220,17 @@ class MidiManagerWinrt::MidiPortManager { public: // MidiPortManager instances should be constructed on the kComTaskRunner. - MidiPortManager(MidiManagerWinrt* midi_manager) - : midi_service_(midi_manager->service()), midi_manager_(midi_manager) {} + MidiPortManager(MidiManagerWinrt* midi_manager, + TaskService::InstanceId instance_id) + : midi_service_(midi_manager->service()), + midi_manager_(midi_manager), + instance_id_(instance_id) {} virtual ~MidiPortManager() { DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner)); + for (auto* async_op : async_ops_) { + async_op->Release(); + } } bool StartWatcher() { @@ -267,12 +274,13 @@ // we can handle raw pointers safely in the following blocks. MidiPortManager* port_manager = this; TaskService* task_service = midi_service_->task_service(); + const TaskService::InstanceId instance_id = instance_id_; hr = watcher_->add_Added( WRL::Callback<ITypedEventHandler< DeviceWatcher*, Win::Devices::Enumeration::DeviceInformation*>>( - [port_manager, task_service](IDeviceWatcher* watcher, - IDeviceInformation* info) { + [port_manager, task_service, instance_id]( + IDeviceWatcher* watcher, IDeviceInformation* info) { if (!info) { VLOG(1) << "DeviceWatcher.Added callback provides null " "pointer, ignoring"; @@ -284,13 +292,14 @@ if (IsMicrosoftSynthesizer(info)) return S_OK; - std::string dev_id = GetIdString(info), - dev_name = GetNameString(info); + const std::string dev_id = GetIdString(info); + const std::string dev_name = GetNameString(info); task_service->PostBoundTask( - kComTaskRunner, base::BindOnce(&MidiPortManager::OnAdded, - base::Unretained(port_manager), - dev_id, dev_name)); + instance_id, kComTaskRunner, + base::BindOnce(&MidiPortManager::OnAdded, + base::Unretained(port_manager), dev_id, + dev_name)); return S_OK; }) @@ -303,10 +312,10 @@ hr = watcher_->add_EnumerationCompleted( WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>( - [port_manager, task_service](IDeviceWatcher* watcher, - IInspectable* insp) { + [port_manager, task_service, instance_id](IDeviceWatcher* watcher, + IInspectable* insp) { task_service->PostBoundTask( - kComTaskRunner, + instance_id, kComTaskRunner, base::BindOnce(&MidiPortManager::OnEnumerationCompleted, base::Unretained(port_manager))); @@ -322,8 +331,8 @@ hr = watcher_->add_Removed( WRL::Callback< ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>( - [port_manager, task_service](IDeviceWatcher* watcher, - IDeviceInformationUpdate* update) { + [port_manager, task_service, instance_id]( + IDeviceWatcher* watcher, IDeviceInformationUpdate* update) { if (!update) { VLOG(1) << "DeviceWatcher.Removed callback provides null " "pointer, ignoring"; @@ -333,7 +342,7 @@ std::string dev_id = GetIdString(update); task_service->PostBoundTask( - kComTaskRunner, + instance_id, kComTaskRunner, base::BindOnce(&MidiPortManager::OnRemoved, base::Unretained(port_manager), dev_id)); @@ -451,6 +460,8 @@ // from tasks that are invoked by TaskService. raw_ptr<MidiManagerWinrt> midi_manager_; + const TaskService::InstanceId instance_id_; + private: // DeviceWatcher callbacks: void OnAdded(std::string dev_id, std::string dev_name) { @@ -474,16 +485,17 @@ MidiPortManager* port_manager = this; TaskService* task_service = midi_service_->task_service(); + const TaskService::InstanceId instance_id = instance_id_; hr = async_op->put_Completed( WRL::Callback< Win::Foundation::IAsyncOperationCompletedHandler<RuntimeType*>>( - [port_manager, task_service]( + [port_manager, task_service, instance_id]( IAsyncOperation<RuntimeType*>* async_op, AsyncStatus status) { // A reference to |async_op| is kept in |async_ops_|, safe to pass // outside. task_service->PostBoundTask( - kComTaskRunner, + instance_id, kComTaskRunner, base::BindOnce( &MidiPortManager::OnCompletedGetPortFromIdAsync, base::Unretained(port_manager), @@ -640,8 +652,9 @@ Win::Devices::Midi::IMidiInPortStatics, RuntimeClass_Windows_Devices_Midi_MidiInPort> { public: - MidiInPortManager(MidiManagerWinrt* midi_manager) - : MidiPortManager(midi_manager) {} + MidiInPortManager(MidiManagerWinrt* midi_manager, + TaskService::InstanceId instance_id) + : MidiPortManager(midi_manager, instance_id) {} MidiInPortManager(const MidiInPortManager&) = delete; MidiInPortManager& operator=(const MidiInPortManager&) = delete; @@ -654,17 +667,18 @@ MidiInPortManager* port_manager = this; TaskService* task_service = midi_service_->task_service(); + const TaskService::InstanceId instance_id = instance_id_; HRESULT hr = handle->add_MessageReceived( WRL::Callback<ITypedEventHandler< Win::Devices::Midi::MidiInPort*, Win::Devices::Midi::MidiMessageReceivedEventArgs*>>( - [port_manager, task_service]( + [port_manager, task_service, instance_id]( Win::Devices::Midi::IMidiInPort* handle, Win::Devices::Midi::IMidiMessageReceivedEventArgs* args) { const base::TimeTicks now = base::TimeTicks::Now(); - std::string dev_id = GetDeviceIdString(handle); + const std::string dev_id = GetDeviceIdString(handle);
Regression Test / PoC
diff --git a/media/midi/task_service_unittest.cc b/media/midi/task_service_unittest.cc
index cc5eac0..fe780ac 100644
--- a/media/midi/task_service_unittest.cc
+++ b/media/midi/task_service_unittest.cc
@@ -5,6 +5,7 @@
#include "media/midi/task_service.h"
#include <memory>
+#include <optional>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
@@ -61,9 +62,23 @@
TaskServiceClient(const TaskServiceClient&) = delete;
TaskServiceClient& operator=(const TaskServiceClient&) = delete;
- bool Bind() { return task_service()->BindInstance(); }
+ bool Bind() {
+ std::optional<TaskService::InstanceId> instance_id =
+ task_service()->BindInstance();
+ if (instance_id) {
+ instance_id_ = *instance_id;
+ return true;
+ }
+ instance_id_ = TaskService::kInvalidInstanceId;
+ return false;
+ }
- bool Unbind() { return task_service()->UnbindInstance(); }
+ bool Unbind() {
+ instance_id_ = TaskService::kInvalidInstanceId;
+ return task_service()->UnbindInstance();
+ }
+
+ TaskService::InstanceId instance_id() const { return instance_id_; }
void PostBoundTask(TaskService::RunnerId runner_id) {
task_service()->PostBoundTask(
@@ -71,6 +86,22 @@
base::Unretained(this)));
}
+ void PostBoundTaskWithExplicitId(TaskService::InstanceId instance_id,
+ TaskService::RunnerId runner_id) {
+ task_service()->PostBoundTask(
+ instance_id, runner_id,
+ base::BindOnce(&TaskServiceClient::IncrementCount,
+ base::Unretained(this)));
+ }
+
+ void PostBoundSignalTaskWithExplicitId(TaskService::InstanceId instance_id,
+ TaskService::RunnerId runner_id) {
+ task_service()->PostBoundTask(
+ instance_id, runner_id,
+ base::BindOnce(&TaskServiceClient::SignalEvent,
+ base::Unretained(this)));
+ }
+
void PostBoundSignalTask(TaskService::RunnerId runner_id) {
task_service()->PostBoundTask(
runner_id, base::BindOnce(&TaskServiceClient::SignalEvent,
@@ -91,6 +122,27 @@
base::Milliseconds(100));
}
+ void PostBoundDelayedTaskWithExplicitId(TaskService::InstanceId instance_id,
+ TaskService::RunnerId runner_id,
+ base::TimeDelta delay) {
+ task_service()->PostBoundDelayedTask(
+ instance_id, runner_id,
+ base::BindOnce(&TaskServiceClient::IncrementCount,
+ base::Unretained(this)),
+ delay);
+ }
+
+ void PostBoundDelayedSignalTaskWithExplicitId(
+ TaskService::InstanceId instance_id,
+ TaskService::RunnerId runner_id,
+ base::TimeDelta delay) {
+ task_service()->PostBoundDelayedTask(
+ instance_id, runner_id,
+ base::BindOnce(&TaskServiceClient::SignalEvent,
+ base::Unretained(this)),
+ delay);
+ }
+
void WaitTask() { wait_task_event_->Wait(); }
size_t count() {
@@ -121,6 +173,7 @@
raw_ptr<TaskService> task_service_;
std::unique_ptr<base::WaitableEvent> wait_task_event_;
size_t count_;
+ TaskService::InstanceId instance_id_ = TaskService::kInvalidInstanceId;
};
class MidiTaskServiceTest : public ::testing::Test {
@@ -310,6 +363,82 @@
EXPECT_TRUE(client->Unbind());
}
+// Tests if bound tasks with explicit InstanceId are handled correctly.
+TEST_F(MidiTaskServiceTest, RunBoundTaskWithExplicitInstanceId) {
+ std::unique_ptr<TaskServiceClient> client =
+ std::make_unique<TaskServiceClient>(task_service());
+
+ EXPECT_TRUE(client->Bind());
+ TaskService::InstanceId id1 = client->instance_id();
+ EXPECT_NE(TaskService::kInvalidInstanceId, id1);
+
+ // Task with correct ID should run.
+ EXPECT_EQ(0u, client->count());
+ client->PostBoundTaskWithExplicitId(id1, kFirstRunner);
+ client->PostBoundSignalTaskWithExplicitId(id1, kFirstRunner);
+ WaitEvent();
+ EXPECT_EQ(2u, client->count());
+
+ // Unbind.
+ EXPECT_TRUE(client->Unbind());
+
+ // Bind again to get a new ID.
+ ResetEvent();
+ EXPECT_TRUE(client->Bind());
+ TaskService::InstanceId id2 = client->instance_id();
+ EXPECT_NE(id1, id2);
+
+ // Task with old ID should be ignored.
+ client->PostBoundTaskWithExplicitId(id1, kFirstRunner);
+
+ // Task with new ID should run.
+ client->PostBoundSignalTaskWithExplicitId(id2, kFirstRunner);
+ WaitEvent();
+ // Count should only be incremented by the new task (2 -> 3).
+ EXPECT_EQ(3u, client->count());
+
+ EXPECT_TRUE(client->Unbind());
+}
+
+// Tests if bound delayed tasks with explicit InstanceId are handled correctly.
+TEST_F(MidiTaskServiceTest, RunBoundDelayedTaskWithExplicitInstanceId) {
+ std::unique_ptr<TaskServiceClient> client =
+ std::make_unique<TaskServiceClient>(task_service());
+
+ EXPECT_TRUE(client->Bind());
+ TaskService::InstanceId id1 = client->instance_id();
+ EXPECT_NE(TaskService::kInvalidInstanceId, id1);
+
+ // Task with correct ID should run.
+ EXPECT_EQ(0u, client->count());
+ client->PostBoundDelayedSignalTaskWithExplicitId(id1, kFirstRunner,
+ base::Milliseconds(10));
+ WaitEvent();
+ EXPECT_EQ(1u, client->count());
+
+ // Unbind.
+ EXPECT_TRUE(client->Unbind());
+
+ // Bind again to get a new ID.
+ ResetEvent();
+ EXPECT_TRUE(client->Bind());
+ TaskService::InstanceId id2 = client->instance_id();
+ EXPECT_NE(id1, id2);
+
+ // Task with old ID should be ignored.
+ client->PostBoundDelayedTaskWithExplicitId(id1, kFirstRunner,
+ base::Milliseconds(10));
+
+ // Task with new ID should run.
+ client->PostBoundDelayedSignalTaskWithExplicitId(id2, kFirstRunner,
+ base::Milliseconds(10));
+ WaitEvent();
+ // Count should only be incremented by the new task (1 -> 2).
+ EXPECT_EQ(2u, client->count());
+
+ EXPECT_TRUE(client->Unbind());
+}
+
} // namespace
} // namespace midi
Original Bug Report
Potential Use-after-free in MidiManagerWinrt via stale TaskService callback execution
Project Fortify, 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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A potential use-after-free (UAF) vulnerability exists in Chrome’s Windows-specific MIDI implementation, MidiManagerWinrt, during rapid session re-binding. Stale WinRT callbacks can capture and run under a newly bound instance ID, defeating the TaskService validation gate and calling methods on a freed MidiPortManager. This could lead to potential heap corruption in the browser process.
Affected files:
media/midi/midi_manager_winrt.ccmedia/midi/task_service.cc
Estimated timestamp from git blame: 2018-01-17
Root Cause Analysis
In media/midi/task_service.cc, TaskService::PostBoundTask() stamps posted tasks with the current bound_instance_id_ instead of the ID that was active when the caller’s callback was registered:
void TaskService::PostBoundTask(RunnerId runner_id, base::OnceClosure task) {
InstanceId instance_id;
{
base::AutoLock lock(lock_);
if (bound_instance_id_ == kInvalidInstanceId)
return;
instance_id = bound_instance_id_;
}
GetTaskRunner(runner_id)->PostTask(
FROM_HERE, base::BindOnce(&TaskService::RunTask, base::Unretained(this),
instance_id, runner_id, std::move(task)));
}
MidiManagerWinrt registers WinRT callbacks that execute on WinRT-owned thread pool threads outside of Chrome’s immediate control. These callbacks capture a raw MidiPortManager* pointer and call PostBoundTask() directly when events (such as device addition/removal or message receipt) occur:
MidiPortManager* port_manager = this;
TaskService* task_service = midi_service_->task_service();
hr = watcher_->add_Added(
WRL::Callback<...>
[port_manager, task_service](IDeviceWatcher*, IDeviceInformation* info) {
task_service->PostBoundTask(
kComTaskRunner, base::BindOnce(&MidiPortManager::OnAdded,
base::Unretained(port_manager),
dev_id, dev_name));
return S_OK;
}).Get(), &token_Added_);
These callbacks are torn down in StopWatcher(), which is called inside FinalizeOnComRunner (a static task posted by the destructor on kComTaskRunner after UnbindInstance() completes).
If a new MIDI session is initiated before the deferred cleanup task FinalizeOnComRunner executes on kComTaskRunner, a new MidiManagerWinrt instance binds to the TaskService, setting bound_instance_id_ to a new ID.
If a stale WinRT callback from the first session fires before FinalizeOnComRunner runs, it calls PostBoundTask(). This call reads the newly bound instance ID, stamping the task with the active ID and successfully posting it to kComTaskRunner.
Because FinalizeOnComRunner is queued before this stale task, it executes first, unregistering the watcher and deleting the old MidiPortManager instance. When the stale task subsequently runs, it bypasses the IsInstanceIdStillBound check (since the active instance ID matches) and dereferences the freed MidiPortManager pointer via base::Unretained, causing a Use-After-Free (UAF).
Potential Steps to Reproduce
Note: The following steps are potential/suggested vectors of exploitation. Our security analysis tooling does not currently have the capability to execute live code or verify this via a working proof-of-concept.
- From a compromised renderer process, request a MIDI session using the Mojo interface
midi::mojom::MidiSessionProvider. This constructsMidiManagerWinrt(Instance #1) and registers the WinRT callbacks. - Rapidly terminate the session (e.g., by closing the Mojo pipe). This invokes the destructor, which calls
UnbindInstance()and queues the cleanup taskFinalizeOnComRunneronkComTaskRunnerto stop the watchers and delete the port managers. - Immediately initiate a new MIDI session. This constructs a second
MidiManagerWinrt(Instance #2), which binds toTaskServiceand setsbound_instance_id_to a new valid ID. - If a stale WinRT callback from Instance #1 fires before
FinalizeOnComRunnerhas executed onkComTaskRunner, it callsPostBoundTask(). This stamps the task with the ID of Instance #2 and posts it to the queue. - On the
kComTaskRunnerthread:FinalizeOnComRunnerruns first, deleting the port managers of Instance #1.- The stale callback task runs next, bypassing validation because its stamped ID matches the active Instance #2 ID, and dereferences the deleted port manager of Instance #1.
Impact and Severity
This issue allows for a potential Use-After-Free (UAF) on a heap-allocated MidiInPortManager or MidiOutPortManager in the browser process (which is unsandboxed). An attacker with control over a compromised renderer could potentially leverage this heap corruption to escape the sandbox and achieve Remote Code Execution (RCE) with the privileges of the browser process. Because this is a high-severity browser-process memory corruption issue, it is classified as Severity 0 (Critical).
Suggested Fix
To resolve this issue, TaskService::PostBoundTask (and delayed tasks) should not rely on reading a dynamically changing global bound_instance_id_ at the time of posting. Instead, classes posting bound tasks should track and supply their own bound instance ID associated with their specific registration lifetime, or MidiPortManager should use weak pointers or ref-counted callbacks to ensure that tasks cannot execute on a freed instance. Alternatively, ensuring that WinRT watchers are stopped synchronously on the calling sequence during unbinding would prevent stale callbacks from firing altogether.
Evaluated with Chrome root at commit: fb72408a8493c46bc75fae1c70d03daec96b3040
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
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.