← Chrome — 2026-W34

0c12ca0334960d633654a8c197d5a134ccfeb946  hid: Support Feature and Output reports on Android

severity high class None CVSS 0.0 signal score 11.26 services/ AI review PLAUSIBLE
Structural signals: security_assert (+3.0), bounds_guard (+4.32), null_guard (+1.95), uaf_lifetime (+2.2), area:services/ (+1.8), large_churn (-2.0)

The bug at a glance

This is a feature addition (WebHID Output/Feature report support on Android), not a silent bug fix: it implements previously-stubbed methods and is deliberately designed with UAF-prevention patterns (native-pointer nulling and != 0 checks before every JNI callback), so no pre-existing vulnerability is being remediated in the diff.

A defender cares because it adds new attack surface: WebHID report I/O is now reachable on Android (API 37+) after a user grants HID access, bridging untrusted renderer-originated report data and IDs across JNI to the Android HidDevice framework; the mitigations here (pointer nulling, SecurityException handling) are what keep that surface safe.

Root cause

Previously HidConnectionAndroid report I/O were unimplemented stubs. The CL wires ChromeHidConnection.java (and openDevice in ChromeHidManager.java) to Android’s android.hardware.hid.HidDevice via jni_zero, routing sendOutputReport/sendFeatureReport/getFeatureReport and close through a single UI-thread SequencedTaskRunner for sequence-checker safety. Lifetime is managed by storing mNativeConnectionPointer, zeroing it in close()/shutdown(), and testing mNativeConnectionPointer != 0 (and mNativePointer != 0) before every onWriteComplete/onReadFeatureComplete/onConnectComplete JNI call, which is the explicit use-after-free guard against the native HidConnectionAndroid being destroyed while an OutcomeReceiver callback is still outstanding. SecurityException/IllegalStateException are caught because revoking HID access mid-connection makes HidDevice calls throw.

Key code

UAF prevention: null the native pointer on close, check before every JNI callback

private void close() {
    mNativeConnectionPointer = 0;
    ...
}
...
if (mNativeConnectionPointer != 0) {
    ChromeHidConnectionJni.get()
            .onWriteComplete(mNativeConnectionPointer, callbackId, true);
}

Patch walkthrough

  • services/device/hid/BUILD.gn — Adds hid_connection_android.cc/.h to the Android sources and ChromeHidConnection.java to java_sources_needing_jni, enabling the new native/Java connection bridge.
  • services/device/hid/android/java/src/org/chromium/device/hid/ChromeHidConnection.java — New Java wrapper over HidDevice performing all report I/O on the UI task runner; stores and nulls mNativeConnectionPointer and guards every JNI callback with a != 0 check to prevent calling into a freed native HidConnectionAndroid (UAF prevention).
  • services/device/hid/android/java/src/org/chromium/device/hid/ChromeHidManager.java — Adds openDevice() which opens the HidDevice and creates a ChromeHidConnection on success, passes the HidDevice up through onDeviceAdded/onConnectComplete, and nulls mNativePointer in shutdown(); all callbacks guarded by mNativePointer != 0.
  • services/device/hid/hid_connection_android.cc — New native connection implementation (diff truncated in this prompt) backing the Java bridge; the C++ side holds the native pointer passed to Java and is the object protected by the Java-side nulling.

Background

WebHID — Web API letting granted sites communicate with HID devices via input/output/feature reports, exposed to the renderer through the device service.

jni_zero / @CalledByNative / @NativeMethods — Chromium’s JNI bridge generating glue so native C++ can invoke Java methods and vice versa, here carrying a raw native pointer (long) across the boundary.

OutcomeReceiver + SequencedTaskRunner — Android async callback plus a serialized UI-thread runner used to keep all HidDevice interaction on one sequence for thread-safety with the C++ sequence checker.

Vulnerability window

  1. Reachability — Reachable only on Android API 37+ (Cinnamon Bun) after the user grants WebHID/HID Special App Access to a site, from ordinary (granted) web content.
  2. Trigger — A site calls WebHID sendReport/sendFeatureReport/receiveFeatureReport, invoking the new openDevice and report paths (inferred).
  3. Primitive — No vulnerability primitive in the diff; the design specifically prevents the UAF that unguarded callbacks would otherwise create.
  4. Escalation — None demonstrated; any future weakness would hinge on the native hid_connection_android.cc not shown here (inferred).

Proof of concept

No bug to trigger; conceptually the risky sequence this code guards against is a report callback (OutcomeReceiver) firing after the native HidConnectionAndroid has been destroyed, which the mNativeConnectionPointer nulling plus != 0 check neutralizes. No exploit code.

Exploitation

  1. N/A — Feature addition with built-in UAF mitigation; no exploitation path present in the shown diff (inferred).

Detection & hunting

For defenders and detection engineers:

  • grep: @CalledByNative callbacks invoking Jni.get().on(mNativePointer, …) without a preceding mNativePointer != 0 check — Audit JNI bridges that carry raw native pointers to ensure every async Java->native callback checks the pointer was not nulled by close()/shutdown().
  • Review of hid_connection_android.cc (native side, truncated here) for the destruction ordering that nulls the Java-held pointer — Confirm the native destructor calls the Java close() / clears the pointer before freeing, so no in-flight OutcomeReceiver can reach freed memory.

Audit directions

  • Other Android device-service JNI bridges (USB, serial, bluetooth) using raw native pointers across OutcomeReceiver callbacks — Verify each nulls its native pointer on teardown and guards every callback, matching the pattern established here.
  • The unshown native hid_connection_android.cc report-handling code — Review report-size/offset handling and vector<uint8_t> marshaling of renderer-controlled report IDs and data for OOB/validation issues.

Before / after

Loading diff…