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

  1. Remove setBypass(true) from the LocationRequest unless the app is privileged
  2. 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
  3. Verify the calling identity: bypass is only allowed for the self/system user
  4. 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

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


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/79ebfbe1ba062e8f. Report an issue: GitHub.