CVE-2026-11145
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifservices/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.java |
modified |
Files Changed
services/BUILD.gnservices/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.javaservices/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.java
Patch
From 5da87b27f6225394630d153b99136adbf24ceb67 Mon Sep 17 00:00:00 2001
From: Alvin Ji <alvinji@chromium.org>
Date: Wed, 29 Apr 2026 08:57:43 -0700
Subject: [PATCH] Fix location accuracy labeling race condition on Android
Capture the requested accuracy intent at the time of the location
request in `LocationProviderGmsCore` and `LocationProviderAndroid`. This
prevents a race condition where an in-flight callback could misreport
its accuracy if a new request with different requirements was initiated
in the meantime.
Change-Id: Ia9aebb6b9dd4d791ea96dcf8e29770acdecdb266
Bug: 501683745
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7794376
Commit-Queue: Alvin Ji <alvinji@chromium.org>
Reviewed-by: Matt Reynolds <mattreynolds@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1622489}
---
diff --git a/services/BUILD.gn b/services/BUILD.gn
index c61236ee..07dd02ab 100644
--- a/services/BUILD.gn
+++ b/services/BUILD.gn
@@ -199,6 +199,7 @@
"//services/device/serial:java",
"//services/shape_detection:shape_detection_java",
"//skia/public/mojom:mojom_java",
+ "//third_party/androidx:androidx_annotation_annotation_java",
"//third_party/google-truth:google_truth_java",
]
diff --git a/services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.java b/services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.java
index 6726ce7..added8cb 100644
--- a/services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.java
+++ b/services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.java
@@ -29,20 +29,20 @@
import java.util.List;
/**
- * This is a LocationProvider using Android APIs [1]. It is a separate class for clarity
- * so that it can manage all processing completely on the UI thread. The container class
- * ensures that the start/stop calls into this class are done on the UI thread.
+ * This is a LocationProvider using Android APIs [1]. It is a separate class for clarity so that it
+ * can manage all processing completely on the UI thread. The container class ensures that the
+ * start/stop calls into this class are done on the UI thread.
*
- * [1] https://developer.android.com/reference/android/location/package-summary.html
+ * <p>[1] https://developer.android.com/reference/android/location/package-summary.html
*/
@NullMarked
-public class LocationProviderAndroid implements LocationListener, LocationProvider {
+public class LocationProviderAndroid implements LocationProvider {
private static final String TAG = "LocationProvider";
private @Nullable LocationManager mLocationManager;
+ private @Nullable LocationListener mLocationListener;
private boolean mIsRunning;
private boolean mEffectiveHighAccuracy;
- private boolean mRequestedHighAccuracy;
private final Context mContext;
@@ -57,8 +57,8 @@
@Override
public void start(boolean enableHighAccuracy) {
ThreadUtils.assertOnUiThread();
- mRequestedHighAccuracy = enableHighAccuracy;
- mEffectiveHighAccuracy = mRequestedHighAccuracy;
+ final boolean requestedHighAccuracy = enableHighAccuracy;
+ mEffectiveHighAccuracy = requestedHighAccuracy;
// Checking app-level permission here and override the `mEffectiveHighAccuracy`
// so we can make sure `Geolocation.AndroidLocationProvider.` is logged with
@@ -69,7 +69,7 @@
}
unregisterFromLocationUpdates();
- registerForLocationUpdates();
+ registerForLocationUpdates(requestedHighAccuracy);
}
@Override
@@ -84,37 +84,6 @@
return mIsRunning;
}
- @Override
- public void onLocationChanged(Location location) {
- // Callbacks from the system location service are queued to this thread, so it's
- // possible that we receive callbacks after unregistering. At this point, the
- // native object will no longer exist. Using `mRequestedHighAccuracy` for
- // location update because `mEffectiveHighAccuracy` can be overridden by app-level
- // permission check.
- if (mIsRunning) {
- if (location.hasAccuracy()) {
- final String histogramName =
- "Geolocation.AndroidLocationProvider"
- + (mEffectiveHighAccuracy
- ? ".HighAccuracyHint"
- : ".LowAccuracyHint")
- + ".Accuracy";
- RecordHistogram.recordCount100000Histogram(
- histogramName, (int) location.getAccuracy());
- }
- LocationProviderAdapter.onNewLocationAvailable(location, mRequestedHighAccuracy);
- }
- }
-
- @Override
- public void onStatusChanged(String provider, int status, Bundle extras) {}
-
- @Override
- public void onProviderEnabled(String provider) {}
-
- @Override
- public void onProviderDisabled(String provider) {}
-
public void setLocationManagerForTesting(LocationManager manager) {
var oldValue = mLocationManager;
mLocationManager = manager;
@@ -130,7 +99,7 @@
}
/** Registers this object with the location service. */
- private void registerForLocationUpdates() {
+ private void registerForLocationUpdates(boolean requestedHighAccuracy) {
createLocationManagerIfNeeded();
if (mLocationManager == null) {
Log.e(TAG, "Could not get location manager.");
@@ -141,6 +110,41 @@
assert !mIsRunning;
mIsRunning = true;
+ mLocationListener =
+ new LocationListener() {
+ @Override
+ public void onLocationChanged(Location location) {
+ // Callbacks from the system location service are queued to this thread, so
+ // it's possible that we receive callbacks after unregistering. At this
+ // point, the native object will no longer exist. Using
+ // `requestedHighAccuracy` for location update because
+ // `mEffectiveHighAccuracy` can be overridden by app-level permission check.
+ if (mIsRunning && mLocationListener == this) {
+ if (location.hasAccuracy()) {
+ final String histogramName =
+ "Geolocation.AndroidLocationProvider"
+ + (mEffectiveHighAccuracy
+ ? ".HighAccuracyHint"
+ : ".LowAccuracyHint")
+ + ".Accuracy";
+ RecordHistogram.recordCount100000Histogram(
+ histogramName, (int) location.getAccuracy());
+ }
+ LocationProviderAdapter.onNewLocationAvailable(
+ location, requestedHighAccuracy);
+ }
+ }
+
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {}
+
+ @Override
+ public void onProviderEnabled(String provider) {}
+
+ @Override
+ public void onProviderDisabled(String provider) {}
+ };
+
// We're running on the main thread. The C++ side is responsible to
// bounce notifications to the Geolocation thread as they arrive in the mainLooper.
try {
@@ -162,7 +166,7 @@
}
}
mLocationManager.requestLocationUpdates(
- 0, 0, criteria, this, ThreadUtils.getUiThreadLooper());
+ 0, 0, criteria, mLocationListener, ThreadUtils.getUiThreadLooper());
} catch (SecurityException e) {
Log.e(
TAG,
@@ -186,7 +190,10 @@
if (!mIsRunning) return;
mIsRunning = false;
assumeNonNull(mLocationManager);
- mLocationManager.removeUpdates(this);
+ if (mLocationListener != null) {
+ mLocationManager.removeUpdates(mLocationListener);
+ mLocationListener = null;
+ }
}
@RequiresNonNull("mLocationManager")
diff --git a/services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.java b/services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.java
index bc684aa7..cc3e9cdf 100644
--- a/services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.java
+++ b/services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.java
@@ -43,7 +43,6 @@
private final Context mContext;
private final FusedLocationProviderClient mClient;
Original Bug Report
Potential precise location leak to approximate-only origins via race condition on Android
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 without the Chrome Security team.
Overview: A potential race condition in Chrome’s Android geolocation service could allow websites with only approximate location permissions to receive precise GPS coordinates. If an approximate location request is initiated while a precise request is active, the provider changes its accuracy state, mislabeling already-queued precise OS callbacks as approximate. These uncoarsened precise coordinates are then routed to approximate-only clients, bypassing the permission boundary.
Affected files:
services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.javaservices/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.javaservices/device/geolocation/geolocation_provider_impl.ccservices/device/geolocation/geolocation_impl.ccservices/device/geolocation/location_api_adapter_android.cc
Estimated timestamp from git blame: 2025-10-22
Vulnerability Details
When the kApproximateGeolocationPermission feature is enabled, Chrome enforces location accuracy by routing updates based on an is_precise flag attached to the Geoposition object. In GeolocationProviderImpl::NotifyClients, if is_precise is false, the update is forwarded to clients with only approximate location permissions without any additional coarsening applied by C++.
On Android, the is_precise flag is populated based on the value of a mutable Java instance variable, mRequestedHighAccuracy, located in LocationProviderAndroid or LocationProviderGmsCore. This variable is read dynamically at the exact moment the Android OS location callback (e.g., onLocationChanged) executes on the UI thread.
A race condition occurs because mRequestedHighAccuracy can be mutated by a new incoming request while earlier OS callbacks are still pending in the UI thread’s MessageQueue.
Potential Sequence of Events
- High Accuracy Active: A trusted origin (Tab A) requests a precise location. The Java provider calls
start(true), settingmRequestedHighAccuracy = trueand requesting high-accuracy updates from the Android OS. - Callback Queued: The Android OS successfully acquires a precise GPS fix and queues a location callback onto the Android UI thread’s
MessageQueue. - Attacker Joins: Before the callback executes, an attacker-controlled origin (Tab B) with only Approximate Location permission calls
navigator.geolocation.getCurrentPosition(). - Accuracy Downgrade:
GeolocationProviderImpl::OnClientsChangeddetects the new approximate client. To prevent leaking precise locations, it drops the provider to low accuracy by issuing astart(false)command to the Java layer. - State Mutated: The
start(false)call executes on the UI thread, immediately settingmRequestedHighAccuracy = false. Crucially, this does not purge the previously queued OS callbacks. - Mislabeled Update: The UI thread executes the queued callback containing the precise coordinates (from Step 2). The
onLocationChangedmethod reads the current mutated value ofmRequestedHighAccuracy(nowfalse) and passes the precise coordinates to C++ over JNI withis_precise = false. - Permission Bypass:
GeolocationProviderImpl::NotifyClientsinspects the flag, concludes the location is approximate, and routes the exact GPS coordinates to Tab B.
Potential Attacker Steps
Note: Our tooling agent does not currently have the ability to run code, so these are potential steps and a working Proof of Concept has not been explicitly verified in a live environment.
- An attacker sets up a malicious page that the user grants ‘Approximate Location’ permissions to.
- The attacker uses JavaScript to rapidly toggle
navigator.geolocation.watchPositionandclearWatchin a tight loop. - If the user simultaneously has another application, Chrome tab, or background process actively using precise location, the rapid toggling will probabilistically align the
start(false)state mutation with a queued precise OS callback. - The attacker’s callback resolves with exact, uncoarsened coordinates.
Suggested Fix
To remediate this issue, the is_precise flag should not be derived from the global provider state (mRequestedHighAccuracy) at the time the callback is executed.
Instead, the requested accuracy could be bound to the LocationCallback instance itself. When start() replaces the callback, the old callback (if still in the queue) should retain its original accuracy context. Alternatively, Android’s Location object may be inspected directly to determine if the provider naturally supplied a high-accuracy fix (e.g., based on the provider string or actual accuracy radius), rather than relying on the intent flag.
Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b
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.