Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInformation leak in Geolocation
DescriptionInformation leak in Geolocation
ComponentGeolocation
Bug ClassLogic Error
Tracker533093250
Fix commit69e04014c2cd (chromium/src) +120/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Files Changed

  • device/base/features.cc
  • device/base/features.h
  • services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.java
  • services/device/geolocation/android/junit/src/org/chromium/device/geolocation/LocationProviderTest.java
  • services/device/public/cpp/device_feature_map.cc
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};
Loading diff…

Original Bug Report

reported by [email protected]

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.java
  • services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderFactory.java
  • services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAdapter.java
  • services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.java
  • services/device/geolocation/location_api_adapter_android.cc
  • services/device/geolocation/geolocation_provider_impl.cc
  • services/device/geolocation/geolocation_impl.cc
  • third_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:

  1. Host a page requesting geolocation with enableHighAccuracy: false:
    navigator.geolocation.watchPosition(successCallback, errorCallback, {enableHighAccuracy: false});
    
  2. 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.15 and ~22.44.x) where Chrome has system-level ACCESS_FINE_LOCATION permission.
  3. Prompt the user to grant “Approximate” location permission in the site-level dialog.
  4. When successCallback is 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:

  1. 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.
  2. Browser-side coarsening fallback: In the browser process’s GeolocationProviderImpl or GeolocationImpl, 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.

View on issue tracker