CVE-2025-26454 AI-generated analysis
platform/packages/apps/ManagedProvisioning, provisioning parameter parser (DisclaimersParserImpl, disclaimer/terms content handling)
ManagedProvisioning's DisclaimersParserImpl now validates each caller-supplied EXTRA_PROVISIONING_DISCLAIMER_CONTENT Uri before copying its content into the provisioning disclaimer cache. The scheme must be content or android.resource, and for content URIs the Binder caller must hold read permission. Any disclaimer that fails validation is skipped.
Root cause
In src/com/android/managedprovisioning/parser/DisclaimersParserImpl.java, the parse loop took the Uri from each disclaimer bundle (EXTRA_PROVISIONING_DISCLAIMER_CONTENT) and, after only a null check, passed it straight to saveDisclaimerContentIntoFile(uri, i). Nothing verified the URI scheme, or whether the app that supplied the provisioning extras was itself allowed to read that URI. ManagedProvisioning therefore copied the content into its disclaimer cache on the caller's behalf. The terms UI later uses that cache (the commit title names termsActivity). This let ManagedProvisioning act as a confused deputy for content the caller could not access directly.
How the fix works
- Commit c341743982f0 (android-security-14.0.0_r21), DisclaimersParserImpl.java, parse loop: before saveDisclaimerContentIntoFile(uri, i), the loop now calls the new private method validateUriSchemeAndPermission(uri) inside a try/catch. On SecurityException it logs via ProvisionLogger.loge and calls `continue`, so that disclaimer is dropped and the loop moves on.
- Same commit, new DisclaimersParserImpl.validateUriSchemeAndPermission(Uri): any scheme other than "android.resource" (SCHEME_ANDROID_RESOURCE) or "content" (SCHEME_CONTENT) is rejected with a SecurityException. The method then calls mContext.checkUriPermission(uri, Binder.getCallingPid(), Binder.getCallingUid(), Intent.FLAG_GRANT_READ_URI_PERMISSION) and throws SecurityException unless the result is PackageManager.PERMISSION_GRANTED.
- Commit 51723855c430 (android-security-14.0.0_r22), same function: wraps the checkUriPermission block in `if (Objects.equals(scheme, SCHEME_CONTENT))`. The read-permission check now applies only to content: URIs, and android.resource: URIs pass after the scheme check alone. The commit message cites the DevicePolicyManager documentation stating that FLAG_GRANT_READ_URI_PERMISSION is required only for content URIs.
- Net result at android-security-14.0.0_r29 (lines 122-144): the scheme allowlist is {content, android.resource}; content URIs are checked against the Binder calling pid/uid; a failure skips that one disclaimer rather than aborting parsing.
Completeness
- Covers the only place in the diff where disclaimer content URIs are consumed: the parse loop in DisclaimersParserImpl, before saveDisclaimerContentIntoFile. Both the scheme restriction and the caller permission check sit on that path.
- After the second commit, android.resource: URIs are accepted with no permission check at all; only content: URIs go through checkUriPermission. The commit message indicates this was intentional, matching the DPM documentation it cites.
- Permission is evaluated against Binder.getCallingPid()/Binder.getCallingUid(). The material does not show the call site of parse(), so the diff alone cannot confirm that these identify the provisioning initiator on every entry path.
- Validation failure is non-fatal: the offending disclaimer is skipped with a log entry, the remaining disclaimers are still processed, and provisioning is not rejected.
- No other ManagedProvisioning file is touched. The material shows no sibling URI-consuming path, so no such path can be judged either way.
Behavior changes
- Disclaimer URIs with any scheme other than content: or android.resource: (for example file: or http:) are now dropped from the disclaimer list with a ProvisionLogger.loge entry. No error is returned to the caller.
- content: disclaimer URIs that the Binder caller lacks read permission for are dropped instead of being copied into the disclaimer cache.
- A new ProvisionLogger.logd line logs every disclaimer URI being validated.
- In android-security-14.0.0_r21 only (between the first and second commit), android.resource: URIs were also subject to checkUriPermission. From r22 onward they are accepted on scheme alone.
Per major
- Android 13: Listed as affected by the bulletin. No commit or tag material for Android 13 was provided, so how (or whether) the fix was applied there is unknown.
- Android 14: Fixed in two commits: c341743982f0 in android-security-14.0.0_r21 (scheme allowlist plus permission check) and 51723855c430 in android-security-14.0.0_r22 (permission check limited to the content scheme). The patched region is fully present (8/8 lines) at android-security-14.0.0_r29.
- Android 15: Same fix: resolved at android-security-15.0.0_r10 by commit 03cadb65c0b6, which carries the identical second-step diff (base index 542bd7ce5 -> 9d8bb3b9d). The matching base suggests the first step was already in the 15 branch. 8/8 lines present at android-security-15.0.0_r17.
- Android 16: Not affected (not listed by the bulletin). The fixed code is present (8/8 lines) at android-security-16.0.0_r8.
- Android 17: Not affected (not listed by the bulletin). The fixed code is present (8/8 lines) at android-security-17.0.0_r1.
Unknowns
- The call site and thread/Binder context of DisclaimersParserImpl.parse (the material only shows the parser file), so it is not shown whether Binder.getCallingPid()/getCallingUid() always identify the provisioning initiator.
- How saveDisclaimerContentIntoFile opens the content (only its beginning is shown), and how termsActivity consumes the cached file (it is only named in the commit title).
- Whether ManagedProvisioning holds privileges that widen what it can read compared with the caller.
- How the fix was applied to Android 13, which the bulletin lists as affected.
- The reporter's test app and the referenced go/ and hsv links are not available, so the exact pre-fix impact is not shown beyond the bulletin's EoP/High rating.