Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Gamepad
DescriptionUse after free in Gamepad
ComponentGamepad
Bug ClassUAF
Tracker516975148
Fix commita5e41fa34b2c (chromium/src) +6/-12
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-08

Changed Functions

FunctionChangeNotes
if
device/gamepad/hid_writer_win.cc
modified

Files Changed

  • device/gamepad/hid_writer_win.cc
From a5e41fa34b2c37843ba3e82b51b7323f32b3763a Mon Sep 17 00:00:00 2001
From: Rob Pitkin <robpitkin@google.com>
Date: Tue, 02 Jun 2026 12:46:44 -0700
Subject: [PATCH] gamepad: Fix Use-After-Return in HidWriterWin

HidWriterWin::WriteOutputReport uses a stack-allocated OVERLAPPED
structure for asynchronous writes. When a write operation times out, the
function calls CancelIo and then waits for either the file handle or the
event handle to be signaled using WaitForMultipleObjects with bWaitAll =
false.

If the file handle was signaled by a previous operation,
WaitForMultipleObjects returns immediately, before the current operation
is fully cancelled. The function then returns, destroying the stack
frame and the OVERLAPPED structure. When the driver eventually completes
the cancellation, it writes status information back to the now-invalid
OVERLAPPED address, causing memory corruption.

This CL fixes the issue by removing the WaitForMultipleObjects call and
instead calling GetOverlappedResult with `bWait = true` after
potentially cancelling the I/O. This ensures that the operation is fully
retired before the function returns and the stack frame is destroyed.

Bug: 516975148
Change-Id: I92116cef7454e35de8d40ccb55b15819eddff79b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7892686
Reviewed-by: Matt Reynolds <mattreynolds@chromium.org>
Commit-Queue: Rob Pitkin <robpitkin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1640392}
---

diff --git a/device/gamepad/hid_writer_win.cc b/device/gamepad/hid_writer_win.cc
index 420f637..7dc61c0 100644
--- a/device/gamepad/hid_writer_win.cc
+++ b/device/gamepad/hid_writer_win.cc
@@ -57,19 +57,13 @@
       // Wait for the write to complete. This causes WriteOutputReport to behave
       // synchronously.
       DWORD wait_object = ::WaitForSingleObject(overlapped.hEvent, 100);
-      if (wait_object == WAIT_OBJECT_0) {
-        ::GetOverlappedResult(hid_handle_.Get(), &overlapped, &bytes_written,
-                              true);
-      } else {
-        // Wait failed, or the timeout was exceeded before the write completed.
-        // Cancel the write request.
-        if (::CancelIo(hid_handle_.Get())) {
-          HANDLE handles[2];
-          handles[0] = hid_handle_.Get();
-          handles[1] = overlapped.hEvent;
-          ::WaitForMultipleObjects(2, handles, false, INFINITE);
-        }
+      if (wait_object != WAIT_OBJECT_0) {
+        ::CancelIo(hid_handle_.Get());
       }
+      // This blocks until the specific overlapped operation completes or
+      // aborts.
+      write_success = ::GetOverlappedResult(hid_handle_.Get(), &overlapped,
+                                            &bytes_written, true);
     }
   }
   return write_success ? bytes_written : 0;
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential stack Use-After-Return in HidWriterWin::WriteOutputReport during I/O cancellation

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: An asynchronous write operation in HidWriterWin::WriteOutputReport utilizes a stack-allocated OVERLAPPED structure. If the write times out and is cancelled, a flawed WaitForMultipleObjects call can return prematurely before the I/O cancellation is complete. This allows the kernel to write status information into a stale stack frame, potentially leading to memory corruption in the unsandboxed browser process.

Affected files:

  • device/gamepad/hid_writer_win.cc

Estimated timestamp from git blame: 2018-02-09

Root Cause Analysis

In device/gamepad/hid_writer_win.cc, HidWriterWin::WriteOutputReport manages asynchronous writing to a Windows HID device opened with FILE_FLAG_OVERLAPPED (device/gamepad/hid_writer_win.cc, line 31). It utilizes a stack-allocated OVERLAPPED structure and a stack-scoped event helper object:

base::win::ScopedHandle event_handle(
    ::CreateEvent(nullptr, false, false, L""));
OVERLAPPED overlapped = {0};
overlapped.hEvent = event_handle.Get();

DWORD bytes_written = 0;
BOOL write_success =
    ::WriteFile(hid_handle_.Get(), report.data(), report.size_bytes(),
                &bytes_written, &overlapped);

If the write operation is pending, the function blocks on the event for up to 100 milliseconds. If the timeout is exceeded, the code attempts to cancel the I/O operation via CancelIo and wait for completion:

if (::CancelIo(hid_handle_.Get())) {
  HANDLE handles[2];
  handles[0] = hid_handle_.Get();
  handles[1] = overlapped.hEvent;
  ::WaitForMultipleObjects(2, handles, false, INFINITE); // bWaitAll = false
}

There is a critical race condition in how ::WaitForMultipleObjects is utilized here:

  1. When an overlapped operation is initiated with a valid hEvent in its OVERLAPPED structure, the OS resets the event handle (overlapped.hEvent) to the non-signaled state, but the signaled state of the file handle itself (hid_handle_) remains unchanged.
  2. When a prior overlapped operation completes successfully, the OS signals both the event associated with that operation and the file handle.
  3. On a subsequent write operation (“Call 2”), if a timeout occurs, CancelIo is invoked, and WaitForMultipleObjects is called with bWaitAll = false.
  4. Since the file handle (handles[0]) was signaled by the previous operation and never reset (because Call 2 passed an explicit event handle), WaitForMultipleObjects evaluates immediately as completed and returns.

This premature return occurs before the driver has actually completed the cancellation of Call 2. The stack frame of WriteOutputReport is then unwound, deallocating the local overlapped structure. When the driver eventually cancels/aborts the IRP, the Windows I/O manager writes completion status metadata back to the deallocated OVERLAPPED memory address, corrupting the active stack of the gamepad polling thread in the unsandboxed browser process.

Potential Attacker Steps / Trigger Scenario

Note: The following scenario describes potential steps to trigger this behavior, as our automated security tools do not have the capability to execute code or verify runtime state.

  1. A compromised renderer or standard web page calls the Gamepad Haptics API (navigator.getGamepads()[i].vibrationActuator.playEffect(...)).
  2. The request is bound to the browser process and dispatched to the gamepad polling thread, where HidWriterWin::WriteOutputReport is invoked.
  3. A connected USB or Bluetooth HID device (such as a custom or simulated virtual gamepad) intentionally delays its response on the haptic output endpoint, causing the 100ms timeout to trigger.
  4. A previous successful operation (or another concurrent operation on the handle) ensures that the file handle itself is already signaled in kernel-space.
  5. When CancelIo is called, WaitForMultipleObjects immediately returns due to the signaled file handle, unwinding the stack frame.
  6. The device driver completes the abort/cancellation after the function has exited, causing the kernel to write status flags directly into the modified/reused stack frames of the gamepad polling thread.

Suggested Remediation

To ensure that the stack-allocated OVERLAPPED structure is not deallocated until the I/O operation is guaranteed to have finished or cancelled, avoid waiting on the raw file handle. Instead, use GetOverlappedResult with bWait = TRUE after triggering cancellation, which safely blocks until the specific overlapped transaction has been retired by the kernel:

::CancelIo(hid_handle_.Get());
DWORD bytes = 0;
// This blocks until the specific overlapped operation completes or aborts
::GetOverlappedResult(hid_handle_.Get(), &overlapped, &bytes, TRUE);

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


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.

View on issue tracker