CVE-2026-79075
Overview
Background
- Approximate geolocation permission
- a Chrome-on-Android permission mode, gated by
PermissionsAndroidFeatureList.APPROXIMATE_GEOLOCATION_PERMISSION, in which a site is granted only coarse location. The provider then runs withmEffectiveHighAccuracy == false. - `LocationProviderGmsCore`
- the Android location provider that gets positions from Google Play Services’ Fused Location Provider (
FusedLocationProviderClient) and passes them on throughLocationProviderAdapter. - GMS Core `Granularity` (`setGranularity(COARSE)`)
- a
LocationRequestparameter that asks Play Services to coarsen results. The patch’s comment says Chrome relies on it when it holds OS-levelACCESS_FINE_LOCATIONbut the site was granted only approximate location, and that it is guaranteed only on GMS Core 22.44+ (MIN_GMS_CORE_VERSION_WITH_GRANULARITY = 224400000). - Fail-closed
- when the provider cannot guarantee a coarse result, it stops and reports a position error (
LocationProviderAdapter.newErrorAvailable) instead of passing the reading on.
Root Cause Analysis
When a site had only approximate permission but Chrome held the app-level ACCESS_FINE_LOCATION permission, LocationProviderGmsCore relied entirely on GMS Core honoring the coarse Granularity request. It never checked the readings that came back. If GMS Core did not support Granularity (older APKs) or did not enforce it, the Fused Location Provider could return a precise fix, and the location callback forwarded it to the web page as if it were approximate. This breaks the rule that a site granted approximate location never receives precise coordinates.
The fix adds a check in the onLocationResult callback, which the commit message names; the hunk does not show the enclosing method. When approximate permission is enabled, high accuracy is off and Chrome holds ACCESS_FINE_LOCATION, any reading whose getAccuracy() is below PRECISE_LOCATION_ACCURACY_THRESHOLD_METERS (2000 m) is treated as a leak. The leak is recorded in UMA together with whether GMS Core claims Granularity support. If sGmsCoreFailClosedOnPreciseLeak (default on) is enabled, the provider calls stop(), reports an error, and returns before the location is forwarded. The check is stricter than before; it adds a new rejection path that did not exist.
Attack Path
- Approximate grant on Android, with the approximate geolocation permission feature enabled, a user grants a site only approximate location, while the Chrome app itself holds OS-level precise location permission.
- Coarse request
the site calls the Geolocation API.
LocationProviderGmsCoreruns withmEffectiveHighAccuracy == falseand asks the Fused Location Provider for coarse granularity. - Precise reading returned
a GMS Core version that does not support or enforce
Granularityreturns a fix with accuracy better than 2000 m. - Precise position disclosed
before the fix, the callback forwarded this reading through
LocationProviderAdapter, so the site’s script received coordinates more precise than the user agreed to share.
Impact Assessment
APPROXIMATE_GEOLOCATION_PERMISSION feature must be enabled; the user must have granted the site approximate location (a user decision); Chrome must hold OS ACCESS_FINE_LOCATION; and the installed GMS Core must fail to coarsen the result. Readings without accuracy information (hasAccuracy() false) are not caught by the new check, and enforcement can be turned off through the kGmsCoreFailClosedOnPreciseLeak kill switch.Files Changed
services/device/public/java/src/org/chromium/device/DeviceFeatureList.javatools/metrics/histograms/metadata/geolocation/enums.xmlservices/device/geolocation/android/junit/src/org/chromium/device/geolocation/LocationProviderTest.javaservices/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.javatools/metrics/histograms/metadata/geolocation/histograms.xmlservices/device/public/cpp/device_feature_map.ccdevice/base/features.ccdevice/base/features.h
Audit Directions
- Trusting external services to enforce privacy reductionslook for other places where Chrome asks an OS or Play Services API to reduce precision or scope (location granularity, sensor rates, contact or media pickers) and passes results on without checking that the reduction was applied.
- Permission-level vs. app-level mismatchreview paths where Chrome’s OS-level permission is broader than the site’s grant, such as precise vs. approximate location or full vs. partial media access. Each result needs to be checked against the site’s grant, not Chrome’s.
- Heuristic gaps in the fail-closed checkcheck how readings without an accuracy value, or with a mis-reported accuracy, are handled, and whether other providers (platform
LocationManager, network provider) apply the same approximate-mode check.
Patch
From 69e04014c2cd3820015e395adb2866af8c22c824 Mon Sep 17 00:00:00 2001 From: Alvin Ji <[email protected]> Date: Wed, 15 Jul 2026 17:20:07 -0700 Subject: [PATCH] geolocation: Fail-closed for approximate requests with precise GMS Core leaks Intercept and enforce a fail-closed position error within onLocationResult when an approximate geolocation request unexpectedly receives a precise location coordinate from the Fused Location Provider on Android. This runtime verification closes the legacy precision leakage window across all GMS Core APK versions. Telemetry for intercepted leak states is consolidated into a single enumerated UMA histogram to monitor real-world service compliance, with enforcement gated behind a default-enabled Finch kill switch for operational safety. BUG=533093250 Change-Id: I7e11ffb4d1bc785c4a89b1bbe9f34bb1171136f7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8094782 Reviewed-by: Reilly Grant <[email protected]> Reviewed-by: Matt Reynolds <[email protected]> Commit-Queue: Alvin Ji <[email protected]> Cr-Commit-Position: refs/heads/main@{#1662961} --- diff --git a/device/base/features.cc b/device/base/features.cc index 0037dea..5cecaf62e 100644 --- a/device/base/features.cc +++ b/device/base/features.cc @@ -37,6 +37,10 @@ // LocationProviderGmsCore BASE_FEATURE(kGmsCoreLocationRequestParamOverride, base::FEATURE_DISABLED_BY_DEFAULT); + +// Controls whether to fail closed and report a position error when a precise +// location leak is detected in LocationProviderGmsCore. +BASE_FEATURE(kGmsCoreFailClosedOnPreciseLeak, base::FEATURE_ENABLED_BY_DEFAULT); #endif // BUILDFLAG(IS_ANDROID) } // namespace features diff --git a/device/base/features.h b/device/base/features.h index 9718f088..e8def8d 100644 --- a/device/base/features.h +++ b/device/base/features.h @@ -30,6 +30,7 @@ #if BUILDFLAG(IS_ANDROID) DEVICE_BASE_EXPORT BASE_DECLARE_FEATURE(kGmsCoreLocationRequestParamOverride); +DEVICE_BASE_EXPORT BASE_DECLARE_FEATURE(kGmsCoreFailClosedOnPreciseLeak); #endif // BUILDFLAG(IS_ANDROID) #if BUILDFLAG(IS_ANDROID) 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 cc3e9cdf..30f5904 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 @@ -10,6 +10,10 @@ import android.location.Location; import android.os.SystemClock; +import androidx.annotation.IntDef; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; import com.google.android.gms.location.FusedLocationProviderClient; import com.google.android.gms.location.Granularity; import com.google.android.gms.location.LocationCallback; @@ -27,6 +31,9 @@ import org.chromium.device.DeviceFeatureList; import org.chromium.gms.ChromiumPlayServicesAvailability; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * This is a LocationProvider using Google Play Services. * @@ -40,6 +47,25 @@ private static final long UPDATE_INTERVAL_MS = 1000; private static final long UPDATE_INTERVAL_FAST_MS = 500; + // Minimum GMS Core version that guaranteed supports and enforces LocationRequest Granularity + // (Year 2022, Week 44). + private static final int MIN_GMS_CORE_VERSION_WITH_GRANULARITY = 224400000; + + // Threshold below which a location accuracy (in meters) is considered "precise" for the + // purpose of leak detection on approximate location requests. + static final float PRECISE_LOCATION_ACCURACY_THRESHOLD_METERS = 2000f; + + @IntDef({ + GmsCoreGranularitySupportState.GRANULARITY_UNSUPPORTED, + GmsCoreGranularitySupportState.GRANULARITY_SUPPORTED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface GmsCoreGranularitySupportState { + int GRANULARITY_UNSUPPORTED = 0; + int GRANULARITY_SUPPORTED = 1; + int NUM_ENTRIES = 2; + } + private final Context mContext; private final FusedLocationProviderClient mClient; private boolean mEffectiveHighAccuracy; @@ -49,6 +75,20 @@ private @Nullable LocationCallback mLocationCallback; + private boolean isGranularitySupportedByGmsCore() { + try { + if (mContext.getPackageManager() == null) { + return false; + } + return GoogleApiAvailability.getInstance() + .isGooglePlayServicesAvailable( + mContext, MIN_GMS_CORE_VERSION_WITH_GRANULARITY) + == ConnectionResult.SUCCESS; + } catch (Exception e) { + return false; + } + } + public static boolean isGooglePlayServicesAvailable(Context context) { return ChromiumPlayServicesAvailability.isGooglePlayServicesAvailable(context); } @@ -152,6 +192,45 @@ RecordHistogram.recordCount100000Histogram( histogramName, (int) location.getAccuracy()); } + + // When approximate location is requested but Chrome holds the + // app-level precise location permission in the OS, we rely on + // GMS Core's `setGranularity(COARSE)` API to coarsen the location. + // However, if we detect a precise location leak (accuracy < 2000m) + // despite requesting coarse location, report a position error and + // fail closed for ALL GMS Core versions. We distinguish them in UMA + // to monitor for service-side leaks. + if (PermissionsAndroidFeatureMap.isEnabled( + PermissionsAndroidFeatureList + .APPROXIMATE_GEOLOCATION_PERMISSION) + && !mEffectiveHighAccuracy + && mContext.checkCallingOrSelfPermission( + Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED) { + if (location.hasAccuracy() + && location.getAccuracy() + < PRECISE_LOCATION_ACCURACY_THRESHOLD_METERS) { + int leakCase = + isGranularitySupportedByGmsCore() + ? GmsCoreGranularitySupportState + .GRANULARITY_SUPPORTED + : GmsCoreGranularitySupportState + .GRANULARITY_UNSUPPORTED; + RecordHistogram.recordEnumeratedHistogram( + "Geolocation.GMSCoreLocationProvider" + + ".CoarseRequest.PreciseLocationReceived", + leakCase, + GmsCoreGranularitySupportState.NUM_ENTRIES); + if (DeviceFeatureList.sGmsCoreFailClosedOnPreciseLeak + .isEnabled()) { + stop(); + LocationProviderAdapter.newErrorAvailable( + "Cannot generate approximate location."); + return; + } + } + } + // Using `requestedHighAccuracy` for location update cause // `mEffectiveHighAccuracy` can be override by app-level permission // check. diff --git a/services/device/geolocation/android/junit/src/org/chromium/device/geolocation/LocationProviderTest.java b/services/device/geolocation/android/junit/src/org/chromium/device/geolocation/LocationProviderTest.java index b430a77..6db2f4df 100644 --- a/services/device/geolocation/android/junit/src/org/chromium/device/geolocation/LocationProviderTest.java +++ b/services/device/geolocation/android/junit/src/org/chromium/device/geolocation/LocationProviderTest.java @@ -412,7 +412,16 @@ .requestLocationUpdates( any(LocationRequest.class), captor2.capture(), any(Looper.class)); LocationCallback secondCallback = captor2.getValue(); - secondCallback.onLocationResult(result); + Location coarseLocation = new Location("test"); + coarseLocation.setLatitude(1.23); + coarseLocation.setLongitude(4.56); + coarseLocation.setTime(System.currentTimeMillis()); + coarseLocation.setAccuracy( + LocationProviderGmsCore.PRECISE_LOCATION_ACCURACY_THRESHOLD_METERS); + com.google.android.gms.location.LocationResult coarseResult = + com.google.android.gms.location.LocationResult.create( + Arrays.asList(coarseLocation)); + secondCallback.onLocationResult(coarseResult); // 6. Verify that this callback is processed and correctly identifies the location // as not precise. diff --git a/services/device/public/cpp/device_feature_map.cc b/services/device/public/cpp/device_feature_map.cc index b21e20b7..4d866e03 100644 --- a/services/device/public/cpp/device_feature_map.cc +++ b/services/device/public/cpp/device_feature_map.cc @@ -24,7 +24,8 @@ const base::Feature* const kFeaturesExposedToJava[] = { &kGenericSensorExtraClasses, &kSensorsAllowAskBlockPermissionModel, &kBatteryStatusManagerBroadcastReceiverInBackground, - &device::features::kGmsCoreLocationRequestParamOverride}; + &device::features::kGmsCoreLocationRequestParamOverride, + &device::features::kGmsCoreFailClosedOnPreciseLeak}; // static base::android::FeatureMap* GetFeatureMap() { diff --git a/services/device/public/java/src/org/chromium/device/DeviceFeatureList.java b/services/device/public/java/src/org/chromium/device/DeviceFeatureList.java index df550ef..1b0dad6 100644 --- a/services/device/public/java/src/org/chromium/device/DeviceFeatureList.java +++ b/services/device/public/java/src/org/chromium/device/DeviceFeatureList.java @@ -27,8 +27,13 @@ public static final String BATTERY_STATUS_MANAGER_BROADCAST_RECEIVER_IN_BACKGROUND = "BatteryStatusManagerBroadcastReceiverInBackground"; + public static final String GMS_CORE_FAIL_CLOSED_ON_PRECISE_LEAK = + "GmsCoreFailClosedOnPreciseLeak"; + public static final MutableFlagWithSafeDefault sGmsCoreLocationRequestParamOverride = newMutableFlagWithSafeDefault("GmsCoreLocationRequestParamOverride", false); + public static final MutableFlagWithSafeDefault sGmsCoreFailClosedOnPreciseLeak = + newMutableFlagWithSafeDefault(GMS_CORE_FAIL_CLOSED_ON_PRECISE_LEAK, true); public static final MutableIntParamWithSafeDefault sGmsCoreLocationRequestUpdateInterval = sGmsCoreLocationRequestParamOverride.newIntParam( "location_request_min_update_interval_millis", diff --git a/tools/metrics/histograms/metadata/geolocation/enums.xml b/tools/metrics/histograms/metadata/geolocation/enums.xml index 4980cb5..a0be49be 100644 --- a/tools/metrics/histograms/metadata/geolocation/enums.xml +++ b/tools/metrics/histograms/metadata/geolocation/enums.xml @@ -92,6 +92,11 @@ <int value="4" label="Authorized when in-use"/> </enum> +<enum name="GmsCoreGranularitySupportState"> + <int value="0" label="Granularity Unsupported (< Week 44, 2022)"/> + <int value="1" label="Granularity Supported (>= Week 44, 2022)"/> +</enum> + <enum name="LocationProviderManagerMode"> <int value="0" label="kNetworkOnly"/> <int value="1" label="kPlatformOnly"/> diff --git a/tools/metrics/histograms/metadata/geolocation/histograms.xml b/tools/metrics/histograms/metadata/geolocation/histograms.xml index 67a58830..717a72e4 100644 --- a/tools/metrics/histograms/metadata/geolocation/histograms.xml +++ b/tools/metrics/histograms/metadata/geolocation/histograms.xml @@ -116,6 +116,20 @@ </histogram> <histogram + name="Geolocation.GMSCoreLocationProvider.CoarseRequest.PreciseLocationReceived" + enum="GmsCoreGranularitySupportState" expires_after="2026-11-15"> + <owner>[email protected]</owner> + <owner>[email protected]</owner> + <summary> + Recorded when Chrome receives a precise location reading despite having + requested an approximate (coarse) location. The value indicates whether the + leaked reading occurred on a legacy GMS Core APK that does not support the + request-level Granularity parameter, or on a modern GMS Core APK where it is + unexpected. Triggers a fail-closed position error. + </summary> +</histogram> + +<histogram name="Geolocation.GMSCoreLocationProvider.TimeToFirstPosition.Approximate" units="ms" expires_after="2026-11-15"> <owner>[email protected]</owner>
Original Bug Report
Potential bypass of approximate geolocation permission on Android with older GMS Core versions
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 privacy vulnerability exists in Chrome for Android where a website granted only approximate location permission can retrieve highly precise coordinates. This occurs on devices with older Google Play Services (GMS Core) versions because the granularity field is silently ignored during Binder IPC, causing GMS Core to return precise location data that Chrome then mis-tags as approximate and delivers to the page.
Affected files:
services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.javaservices/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderFactory.javaservices/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAdapter.javaservices/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.javaservices/device/geolocation/location_api_adapter_android.ccservices/device/geolocation/geolocation_provider_impl.ccservices/device/geolocation/geolocation_impl.ccthird_party/android_deps/util/org/chromium/gms/ChromiumPlayServicesAvailability.java
Estimated timestamp from git blame: 2025-10-22
Description / Root Cause
In Chrome for Android, the kApproximateGeolocationPermission feature enforces the approximate geolocation permission boundary by requesting coarse location updates from the underlying operating system or Google Play Services (GMS Core).
When using the Android platform-level location provider (LocationProviderAndroid.java), Chrome implements a fail-closed guard. If Chrome is granted precise system-level permission (ACCESS_FINE_LOCATION) but a website only requests approximate location, the provider refuses to register for coarse updates to avoid leaking precise details and reports a position error:
// services/device/geolocation/android/.../LocationProviderAndroid.java
if (PermissionsAndroidFeatureMap.isEnabled(
PermissionsAndroidFeatureList.APPROXIMATE_GEOLOCATION_PERMISSION)
&& !enableHighAccuracy
&& mContext.checkCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
unregisterFromLocationUpdates();
LocationProviderAdapter.newErrorAvailable("Cannot generate approximate location.");
return;
}
However, no analogous guard exists in the GMS Core path (LocationProviderGmsCore.java). Instead, the GMS provider sets a request-level hint and trusts GMS Core on the device to perform the coarsening:
// services/device/geolocation/android/.../LocationProviderGmsCore.java
if (PermissionsAndroidFeatureMap.isEnabled(
PermissionsAndroidFeatureList.APPROXIMATE_GEOLOCATION_PERMISSION)) {
LocationRequest.Builder builder =
new LocationRequest.Builder(priority, interval);
if (mEffectiveHighAccuracy) {
builder.setGranularity(Granularity.GRANULARITY_FINE);
} else {
builder.setGranularity(Granularity.GRANULARITY_COARSE);
}
locationRequest = builder.build();
} else { ... }
Granularity is a field added in play-services-location client library version 21.0.0 (Oct 2022). Although Chrome bundles a client library supporting this field, the actual coarsening is performed by the GMS Core APK service on the device via SafeParcelable serialization.
Under the SafeParcelable forward-compatibility specification, any older GMS Core service APK on the device that does not recognize the newer granularity field ID will silently skip and drop it during deserialization. Since Chrome holds the system-level ACCESS_FINE_LOCATION permission, the GMS Core service defaults to generating and returning highly precise GPS/fused coordinates.
When Chrome’s onLocationResult callback receives these precise coordinates, it passes them to the adapter with isPrecise = false (since requestedHighAccuracy was set to false during request creation):
LocationProviderAdapter.onNewLocationAvailable(location, requestedHighAccuracy /* == false */);
On the C++ side, this is mapped directly into mojom::GeopositionResult with position->is_precise = false in location_api_adapter_android.cc. Because is_precise is false, the coordinates bypass all downstream security filters (such as geolocation_impl.cc and geolocation_provider_impl.cc which check is_precise to filter out fine positions) and are delivered directly to the origin’s approximate/low-accuracy callback.
Because our tooling currently lacks the capability to execute runtime code or compile APKs, these steps and observations are based entirely on static analysis of the source code.
Potential Attacker Attack Path
An attacker wishing to exploit this potential boundary bypass would follow these steps:
- Host a page requesting geolocation with
enableHighAccuracy: false:navigator.geolocation.watchPosition(successCallback, errorCallback, {enableHighAccuracy: false}); - Serve this page to a user on an Android device running a GMS Core APK older than late 2022’s Granularity rollout (GMS Core version between
20.41.15and~22.44.x) where Chrome has system-levelACCESS_FINE_LOCATIONpermission. - Prompt the user to grant “Approximate” location permission in the site-level dialog.
- When
successCallbackis triggered, extract the raw, precise latitude/longitude/accuracy coordinates, which bypass coarsening and are sent directly to the renderer.
Suggested Fix
To prevent precise coordinate leakage on older GMS Core versions, we recommend implementing one of the following changes:
- Fail-closed guard in GMS path: In
LocationProviderGmsCore.java, implement a check to ensure GMS Core actually supports the requested granularity level before registering for updates, or add a similar check to fallback to an error or alternative safe coarsening method if GMS Core is older than the required version. - Browser-side coarsening fallback: In the browser process’s
GeolocationProviderImplorGeolocationImpl, if an origin only has approximate geolocation permissions, actively coarsen/fuzz the coordinates (e.g., by rounding or snapping them to a grid) before delivering them to the callback, rather than relying solely on the platform provider’s metadata tag.
Evaluated with Chrome root at commit: 84065d9121f6e48f67755f0ae963cc09617e5c85
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.