CVE-2026-58834 AI-generated analysis
platform/frameworks/base, services/devicepolicy (DevicePolicyManagerService, DevicePolicyEngine)
Adds an argument check in DevicePolicyManagerService.setPermissionGrantState that rejects any grantState other than GRANTED, DENIED or DEFAULT, and wraps the per-policy enforcePolicy calls in DevicePolicyEngine's policy re-apply loop in try/catch so that a bad persisted policy is logged with Slogf.wtf instead of crashing system_server.
Root cause
DevicePolicyManagerService.setPermissionGrantState accepted an arbitrary int grantState from the calling admin without range-checking it. Per the commit message, the value was saved to disk, and on later system_server boots the policy re-application in DevicePolicyEngine called enforcePolicy(policyDefinition, policyValue, ...) for each global and local policy with no exception handling. The invalid stored value threw an uncaught IllegalArgumentException and crashed system_server during boot, repeatedly, until a factory reset.
How the fix works
- services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java:setPermissionGrantState: immediately after Objects.requireNonNull(callback) and before getCallerIdentity / checkCanExecuteOrThrowUnsafe, adds Preconditions.checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED || grantState == PERMISSION_GRANT_STATE_DEFAULT). Any other value now throws IllegalArgumentException to the caller before anything is stored. In the diff the check is unconditional at method entry; the commit message says it applies when the coexistence flag is enabled, but no flag condition appears in the added lines.
- services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java (re-apply loop over mGlobalPolicies and mLocalPolicies; the hunk header does not show the method name): the global-policy call enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL) and the local-policy call enforcePolicy(policyDefinition, policyValue, userId) are each wrapped in try { ... } catch (Exception e) { Slogf.wtf(TAG, "Failed to reapply policy " + policy [+ " for user " + userId], e); }. A failure for one policy no longer aborts the loop or propagates out of it.
- services/tests/servicestests/.../DevicePolicyManagerTest.java: adds testSetPermissionGrantState_coexistence_invalidState_throwsException, which sets a device owner and asserts IllegalArgumentException for grantState 999.
Completeness
- Input validation covers only the setPermissionGrantState entry point. It is placed before the caller identity check, so it applies to every caller of this binder method regardless of admin type.
- The try/catch is applied only to the two enforcePolicy calls in the re-apply loop (global and local). It catches Exception generally, so it also absorbs exceptions from other policies whose enforcement throws at re-apply time, not only permission grant state.
- Other DevicePolicyEngine paths visible in the code context, for example transferPolicies calling setGlobalPolicy/setLocalPolicy, are not wrapped. Whether those methods call enforcePolicy is not shown in the material.
- The fix does not remove or repair an invalid value that was persisted before the patch. With the patch, an exception from enforcing that value in the re-apply loop is logged via wtf instead of escaping the loop.
- The material does not show the enforcement code that actually throws IllegalArgumentException for an out-of-range grant state.
Behavior changes
- setPermissionGrantState now throws IllegalArgumentException for any grantState outside {GRANTED, DENIED, DEFAULT}, before permission and caller checks run.
- During policy re-application, an exception from enforcePolicy for a single policy is caught and logged via Slogf.wtf with the policy key (and the user id for local policies); the remaining policies are still re-applied.
Per major
- Android 14: same fix: commit 057ff07691a6 in android-security-14.0.0_r29; same three-file change. The DevicePolicyEngine hunks at ~1528/1539 have no shouldSkipEnforcementIfNotChanged() guard around the calls. 8/8 patched lines present at r30.
- Android 15: same fix: commit 49e1a4ba935f in android-security-15.0.0_r17; identical logic, with hunks at ~1799/1812 inside an existing if (!policyDefinition.shouldSkipEnforcementIfNotChanged()) block. 8/8 present at r18.
- Android 16: same fix: commit f4e7001b21fb in android-security-16.0.0_r8; identical to 15, with hunks at ~1818/1831. 8/8 present at r9.
- Android 17: same fix: commit c715c992cad0 in android-security-17.0.0_r1; identical logic, but the local-policy loop iterates a copied Set<Integer> userIds instead of indexing mLocalPolicies. 8/8 present at r2.
Unknowns
- Name of the DevicePolicyEngine method containing the patched re-apply loop (the hunk header shows only the class).
- Where the IllegalArgumentException is thrown during enforcement of an invalid grant state (enforcement/persistence code not included).
- The exact coexistence flag gating referenced in the commit message; the added check itself is unconditional in the diff.
- Whether any other binder entry point can persist a permission-grant policy value without going through setPermissionGrantState.
- Whether setGlobalPolicy/setLocalPolicy (e.g. via transferPolicies) enforce policies in a way that could still throw unhandled.
- Whether the commit depends on other changes.