{"record":{"id":"79ebfbe1ba062e8f","repo":"microg/GmsCore","slug":"caller-must-hold-permission-for-location-bypass-79ebfb","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/extensions.kt","lineNumber":85,"sourceCode":"}\n\nfun ClientIdentity.isGoogle(context: Context) = PackageUtils.isGooglePackage(context, packageName)\n\nfun ClientIdentity.isSelfProcess() = pid == Process.myPid()\nfun ClientIdentity.isSelfUser() = uid == Process.myUid()\n\nfun Context.granularityFromPermission(clientIdentity: ClientIdentity): @Granularity Int = when (PackageManager.PERMISSION_GRANTED) {\n    packageManager.checkPermission(Manifest.permission.ACCESS_FINE_LOCATION, clientIdentity.packageName) -> Granularity.GRANULARITY_FINE\n    packageManager.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, clientIdentity.packageName) -> Granularity.GRANULARITY_COARSE\n    else -> Granularity.GRANULARITY_PERMISSION_LEVEL\n}\n\nfun LocationRequest.verify(context: Context, clientIdentity: ClientIdentity) {\n    GranularityUtil.checkValidGranularity(granularity)\n    if (isBypass && !clientIdentity.isSelfUser()) {\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 (impersonation != null && !clientIdentity.isSelfUser()) {\n        Log.w(TAG, \"${clientIdentity.packageName} wants to impersonate ${impersonation!!.packageName}. Ignoring.\")\n    }\n\n}\n\nfun checkAppOpFromEffectiveGranularity(effectiveGranularity: @Granularity Int) = when (effectiveGranularity) {\n    Granularity.GRANULARITY_FINE -> AppOpsManager.OPSTR_FINE_LOCATION\n    Granularity.GRANULARITY_COARSE -> AppOpsManager.OPSTR_COARSE_LOCATION\n    else -> throw IllegalArgumentException()\n}\n\nfun persistAppOpsFromEffectiveGranularity(effectiveGranularity: @Granularity Int) = when (effectiveGranularity) {\n    Granularity.GRANULARITY_FINE -> listOf(AppOpsManager.OPSTR_MONITOR_LOCATION, AppOpsManager.OPSTR_MONITOR_HIGH_POWER_LOCATION)\n    Granularity.GRANULARITY_COARSE -> listOf(AppOpsManager.OPSTR_MONITOR_LOCATION)\n    else -> throw IllegalArgumentException()","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/extensions.kt#L67-L103","documentation":"LocationRequest.verify() validates a caller-supplied LocationRequest. The isBypass flag requests bypassing location throttling/settings restrictions, a privileged capability. If the caller is not microG itself (isSelfUser()) and does not hold android.permission.LOCATION_BYPASS (API 33+) or WRITE_SECURE_SETTINGS (older), a SecurityException is thrown.","triggerScenarios":"An app builds a LocationRequest with setBypass(true) (or reflection-set isBypass) without holding LOCATION_BYPASS/WRITE_SECURE_SETTINGS and submits it via requestLocationUpdates; a non-privileged UID/PID calls with bypass enabled.","commonSituations":"Third-party apps copying bypass usage from system/priv-app code; testing code that enabled bypass during development and shipped it; apps targeting API 33 expecting WRITE_SECURE_SETTINGS semantics after the permission changed.","solutions":["Remove setBypass(true) from the LocationRequest unless the app is privileged","Grant the required permission (LOCATION_BYPASS on API 33+, WRITE_SECURE_SETTINGS below) via adb pm grant or priv-app installation only if legitimately entitled","Verify the calling identity: bypass is only allowed for the self/system user","Gate the bypass flag behind a permission check in your own code before building the request"],"exampleFix":"// before\nval request = LocationRequest.Builder(...).setBypass(true).build()\n// after\nval request = LocationRequest.Builder(...).build() // drop bypass unless system-privileged","handlingStrategy":"validation","validationCode":"val perm = if (Build.VERSION.SDK_INT >= 33) \"android.permission.LOCATION_BYPASS\"\n            else Manifest.permission.WRITE_SECURE_SETTINGS\nval bypassAllowed = !request.isBypass ||\n    ctx.checkPermission(perm, android.os.Process.myPid(), android.os.Process.myUid()) == PackageManager.PERMISSION_GRANTED","typeGuard":"fun LocationRequest.bypassPermitted(ctx: Context, uid: Int, pid: Int): Boolean =\n    !isBypass || ctx.checkPermission(\n        if (Build.VERSION.SDK_INT >= 33) \"android.permission.LOCATION_BYPASS\" else Manifest.permission.WRITE_SECURE_SETTINGS,\n        pid, uid) == PackageManager.PERMISSION_GRANTED","tryCatchPattern":"try {\n    client.requestLocationUpdates(request, listener, looper)\n} catch (e: SecurityException) {\n    if (e.message?.contains(\"location bypass\") == true) {\n        request = request.toBuilder().setBypass(false).build(); retry()\n    } else throw e\n}","preventionTips":["Never set bypass(true) in production app code unless the app is system-privileged","Gate bypass behind a Build/permission check at request-construction time","Remember the required permission changed at API 33 (LOCATION_BYPASS replaces WRITE_SECURE_SETTINGS)","Verify with adb shell dumpsys package that the permission is actually granted when developing privileged tools"],"tags":["location","android","security","permissions","bypass"],"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"}