0c12ca0334960d633654a8c197d5a134ccfeb946 hid: Support Feature and Output reports on Android
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
- 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.
- Trigger — A site calls WebHID sendReport/sendFeatureReport/receiveFeatureReport, invoking the new openDevice and report paths (inferred).
- Primitive — No vulnerability primitive in the diff; the design specifically prevents the UAF that unguarded callbacks would otherwise create.
- 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
- 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.