{"record":{"id":"7fde216ba1eb6db9","repo":"microg/GmsCore","slug":"caller-must-hold-permission-for-location-bypass","errorCode":null,"errorMessage":"Caller must hold $permission for location bypass","messagePattern":"Caller must hold \\$permission for location bypass","errorType":"exception","errorClass":"SecurityException","httpStatus":null,"severity":"error","filePath":"play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationManager.kt","lineNumber":76,"sourceCode":"    private var activePermissionRequest: Deferred<Boolean>? = null\n    private var lastGpsLocation: Location? = null\n    private var lastNetworkLocation: Location? = null\n\n    private var currentGpsInterval: Long = -1\n    private var currentNetworkInterval: Long = -1\n\n    val deviceOrientationManager = DeviceOrientationManager(context, lifecycle) { updateLocationRequests() }\n\n    var started: Boolean = false\n        private set\n\n    suspend fun getLastLocation(clientIdentity: ClientIdentity, request: LastLocationRequest): Location? {\n        if (request.maxUpdateAgeMillis < 0) throw IllegalArgumentException()\n        GranularityUtil.checkValidGranularity(request.granularity)\n        if (request.isBypass) {\n            val permission = if (SDK_INT >= 33) \"android.permission.LOCATION_BYPASS\" else Manifest.permission.WRITE_SECURE_SETTINGS\n            if (context.checkPermission(permission, clientIdentity.pid, clientIdentity.uid) != PackageManager.PERMISSION_GRANTED) {\n                throw SecurityException(\"Caller must hold $permission for location bypass\")\n            }\n        }\n        if (request.impersonation != null) {\n            Log.w(TAG, \"${clientIdentity.packageName} wants to impersonate ${request.impersonation!!.packageName}. Ignoring.\")\n        }\n        val permissionGranularity = context.granularityFromPermission(clientIdentity)\n        var effectiveGranularity = getEffectiveGranularity(request.granularity, permissionGranularity)\n        if (effectiveGranularity == GRANULARITY_FINE && database.getForceCoarse(clientIdentity.packageName) && !clientIdentity.isSelfUser()) effectiveGranularity = GRANULARITY_COARSE\n        val returnedLocation = if (effectiveGranularity > permissionGranularity) {\n            // No last location available at requested granularity due to lack of permission\n            null\n        } else {\n            ensurePermissions()\n            val preLocation = lastLocationCapsule.getLocation(effectiveGranularity, request.maxUpdateAgeMillis)\n            val processedLocation = postProcessor.process(preLocation, effectiveGranularity, clientIdentity.isGoogle(context))\n            if (!context.noteAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) {\n                // App Op denied\n                null","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationManager.kt#L58-L94","documentation":"LocationManager.getLastLocation throws this SecurityException when LastLocationRequest.isBypass is true but the calling process does not hold the required bypass permission: android.permission.LOCATION_BYPASS on Android 13+ (SDK 33+), or android.permission.WRITE_SECURE_SETTINGS on older versions. Bypass is a privileged escape hatch that skips location permission checks, so microG requires the caller to hold a system-level permission before honoring it. It is thrown synchronously inside the suspend function before any location is returned.","triggerScenarios":"Calling FusedLocationProviderApi.getCurrentLocation/LastLocation with a LastLocationRequest built via setBypass(true) (e.g. LocationRequest.Builder#setBypass) while the app's uid/pid lacks android.permission.LOCATION_BYPASS (SDK>=33) or android.permission.WRITE_SECURE_SETTINGS (SDK<33).","commonSituations":"Apps enabling bypass after copying sample code that sets isBypass; apps testing on Android 12 with WRITE_SECURE_SETTINGS granted via adb then moving to Android 13 where LOCATION_BYPASS is needed; system/privileged apps whose privileges were not declared in the platform's privapp-permissions whitelist.","solutions":["Remove setBypass(true) from the LastLocationRequest and request normal location permissions (ACCESS_FINE_LOCATION) instead.","If bypass is genuinely needed (system app), grant the required permission: adb shell pm grant <pkg> android.permission.WRITE_SECURE_SETTINGS (SDK<33) or add android.permission.LOCATION_BYPASS to the privapp-permissions whitelist (SDK>=33).","Runtime-check before calling: context.checkPermission(permission, pid, uid) == PERMISSION_GRANTED, mirroring the library's own logic."],"exampleFix":"// before\nval request = LastLocationRequest.Builder().setBypass(true).build()\nval location = fusedLocationClient.getLastLocation(request)\n// after\nval request = LastLocationRequest.Builder().build() // no bypass\nval location = fusedLocationClient.getLastLocation(request)","handlingStrategy":"try-catch","validationCode":"val needed = if (Build.VERSION.SDK_INT >= 33) \"android.permission.LOCATION_BYPASS\" else android.Manifest.permission.WRITE_SECURE_SETTINGS\nval ok = context.checkPermission(needed, android.os.Process.myPid(), android.os.Process.myUid()) == PackageManager.PERMISSION_GRANTED","typeGuard":"fun canBypass(ctx: Context): Boolean {\n    val p = if (Build.VERSION.SDK_INT >= 33) \"android.permission.LOCATION_BYPASS\" else android.Manifest.permission.WRITE_SECURE_SETTINGS\n    return ctx.checkPermission(p, android.os.Process.myPid(), android.os.Process.myUid()) == PackageManager.PERMISSION_GRANTED\n}","tryCatchPattern":"try {\n    client.getLastLocation(request)\n} catch (e: SecurityException) {\n    if (e.message?.contains(\"bypass\") == true) fallbackToNonBypassRequest()\n    else throw e\n}","preventionTips":["Do not set bypass=true unless you are a privileged/system app","Branch permission checks on Build.VERSION.SDK_INT (33+ needs LOCATION_BYPASS)","Keep privileged permissions in the platform privapp-permissions whitelist","Prefer normal ACCESS_FINE_LOCATION flow over bypass"],"tags":["security","location","permission","android"],"backgroundTag":"permission-denied","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}