microg/GmsCore · error · SecurityException
Caller must hold $permission for location bypass
Error message
Caller must hold $permission for location bypass
What it means
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.
Source
Thrown at play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/extensions.kt:85
}
fun ClientIdentity.isGoogle(context: Context) = PackageUtils.isGooglePackage(context, packageName)
fun ClientIdentity.isSelfProcess() = pid == Process.myPid()
fun ClientIdentity.isSelfUser() = uid == Process.myUid()
fun Context.granularityFromPermission(clientIdentity: ClientIdentity): @Granularity Int = when (PackageManager.PERMISSION_GRANTED) {
packageManager.checkPermission(Manifest.permission.ACCESS_FINE_LOCATION, clientIdentity.packageName) -> Granularity.GRANULARITY_FINE
packageManager.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, clientIdentity.packageName) -> Granularity.GRANULARITY_COARSE
else -> Granularity.GRANULARITY_PERMISSION_LEVEL
}
fun LocationRequest.verify(context: Context, clientIdentity: ClientIdentity) {
GranularityUtil.checkValidGranularity(granularity)
if (isBypass && !clientIdentity.isSelfUser()) {
val permission = if (SDK_INT >= 33) "android.permission.LOCATION_BYPASS" else Manifest.permission.WRITE_SECURE_SETTINGS
if (context.checkPermission(permission, clientIdentity.pid, clientIdentity.uid) != PackageManager.PERMISSION_GRANTED) {
throw SecurityException("Caller must hold $permission for location bypass")
}
}
if (impersonation != null && !clientIdentity.isSelfUser()) {
Log.w(TAG, "${clientIdentity.packageName} wants to impersonate ${impersonation!!.packageName}. Ignoring.")
}
}
fun checkAppOpFromEffectiveGranularity(effectiveGranularity: @Granularity Int) = when (effectiveGranularity) {
Granularity.GRANULARITY_FINE -> AppOpsManager.OPSTR_FINE_LOCATION
Granularity.GRANULARITY_COARSE -> AppOpsManager.OPSTR_COARSE_LOCATION
else -> throw IllegalArgumentException()
}
fun persistAppOpsFromEffectiveGranularity(effectiveGranularity: @Granularity Int) = when (effectiveGranularity) {
Granularity.GRANULARITY_FINE -> listOf(AppOpsManager.OPSTR_MONITOR_LOCATION, AppOpsManager.OPSTR_MONITOR_HIGH_POWER_LOCATION)
Granularity.GRANULARITY_COARSE -> listOf(AppOpsManager.OPSTR_MONITOR_LOCATION)
else -> throw IllegalArgumentException()View on GitHub (pinned to 157c9d86ac)
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
Example fix
// before val request = LocationRequest.Builder(...).setBypass(true).build() // after val request = LocationRequest.Builder(...).build() // drop bypass unless system-privileged
Defensive patterns
Strategy: validation
Validate before calling
val perm = if (Build.VERSION.SDK_INT >= 33) "android.permission.LOCATION_BYPASS"
else Manifest.permission.WRITE_SECURE_SETTINGS
val bypassAllowed = !request.isBypass ||
ctx.checkPermission(perm, android.os.Process.myPid(), android.os.Process.myUid()) == PackageManager.PERMISSION_GRANTED Type guard
fun LocationRequest.bypassPermitted(ctx: Context, uid: Int, pid: Int): Boolean =
!isBypass || ctx.checkPermission(
if (Build.VERSION.SDK_INT >= 33) "android.permission.LOCATION_BYPASS" else Manifest.permission.WRITE_SECURE_SETTINGS,
pid, uid) == PackageManager.PERMISSION_GRANTED Try / catch
try {
client.requestLocationUpdates(request, listener, looper)
} catch (e: SecurityException) {
if (e.message?.contains("location bypass") == true) {
request = request.toBuilder().setBypass(false).build(); retry()
} else throw e
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- Access denied, missing google package permission or GET_ACCO
- Caller must hold $permission for location bypass
- app op denied
- Access denied, missing google package permission for
- suggested UID [
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/79ebfbe1ba062e8f.
Report an issue: GitHub.