CVE-2025-26456 AI-generated analysis
platform/art, libartservice (ART Service) DexUseManagerLocal
Bounds the ART dex-use database in DexUseManagerLocal by capping secondary dex path length (4096), class loader context length (10000) and the number of secondary dex entries per owning package (500). Two follow-up commits first made the companion file-existence check symlink-safe and then removed it entirely because of sepolicy restrictions.
Root cause
Per its own class comment, DexUseManagerLocal collects data sent directly by apps. Before the fix, addSecondaryDexUse ran computeIfAbsent on mSecondaryDexUseByDexFile for any dexPath, with no cap on the number of distinct secondary dex paths per owning package. The input validation loop in notifyDexContainersLoaded only checked that the path was non-empty and absolute, with no length limit on the dex path or the class loader context strings. The resulting entries are persisted to /data/system/package-dex-usage.pb, so the in-memory map and the on-disk database could grow without bound. The commit message puts the size at O(owning package x dex path x loading package), with secondary dex paths potentially unbounded.
How the fix works
- Commit 1aedae6e1049 (Ensure the dex use database cannot grow unboundedly), DexUseManagerLocal.java: adds the constants MAX_PATH_LENGTH = 4096, MAX_CLASS_LOADER_CONTEXT_LENGTH = 10000 and MAX_SECONDARY_DEX_FILES_PER_OWNER = 500. The last one is exposed through a new Injector.getMaxSecondaryDexFilesPerOwner().
- Same commit, validation loop in DexUseManagerLocal.notifyDexContainersLoaded: throws IllegalArgumentException if dexPath.length() > MAX_PATH_LENGTH or classLoaderContext.length() > MAX_CLASS_LOADER_CONTEXT_LENGTH. The existing path check is kept (Paths.get(dexPath).isAbsolute() on 14, ArtJni.validateDexPath on 15).
- Same commit, DexUseManagerLocal.addSecondaryDexUse: the computeIfAbsent lambda for mSecondaryDexUseByDexFile returns null when packageDexUse.mSecondaryDexUseByDexFile.size() >= mInjector.getMaxSecondaryDexFilesPerOwner(). In that case no entry is created, a Log.w is written, and the method returns early. Loader records can still be added to an existing dex path entry after the cap is reached (test testLimitSecondaryDexFiles).
- Same commit, addSecondaryDexUse: also adds an early return when isLoaderOtherApp(loader, owningPackageName) && !mInjector.pathExists(dexPath), where Injector.pathExists is new File(path).exists(). dex_use.proto only gains '// key' comments.
- Commit 3c76194d116b (Fix SELinux denial on GMS Core's symlinks), DexUseManagerLocal.Injector.pathExists: changed to Files.exists(Paths.get(path), LinkOption.NOFOLLOW_LINKS), because system_server lacks permission to follow GMS Core's symlinks to its secondary dex files.
- Commit 06a99377e368 (Omit file existence check on notifyDexContainersLoaded), DexUseManagerLocal.addSecondaryDexUse and Injector: removes the pathExists check, the pathExists() method and the LinkOption import 'due to sepolicy restrictions on some platforms'. In the tests, testNonexistingExternalSecondaryDexPath and testInternalSecondaryDexPath are deleted and testExistingExternalSecondaryDexPath is renamed to testSecondaryDexPath. The length and per-owner count limits remain.
Completeness
- The final code bounds three things for secondary dex use: path string length, class loader context string length, and the number of distinct secondary dex paths per owning package. Per the commit message, owning and loading packages are already bounded by the package database, and primary dex paths by installed packages. The diff adds no limits there.
- Commit 06a99377e368 removes the existence check whose stated purpose was to stop a loading package from using up another package's SecondaryDexUse entries. After it, nothing in the shown code prevents another app's nonexistent paths from counting toward an owner's 500-entry cap. Growth stays bounded, and the material shows no other mitigation.
- The per-owner cap is applied only at insertion. The diff shows no pruning of entries persisted before the fix.
- Over-long inputs are rejected with IllegalArgumentException. The material does not show how the caller handles it.
Behavior changes
- notifyDexContainersLoaded throws IllegalArgumentException for a dex path longer than 4096 chars or a class loader context longer than 10000 chars. These inputs were previously accepted.
- New secondary dex path entries beyond 500 per owning package are dropped with a Log.w (AsLog.w on 15) warning. Records for existing dex paths can still be added or updated.
- Between the first and third commits, secondary dex loads by another app were not recorded if the path did not exist from system_server's point of view (with NOFOLLOW_LINKS after the second commit). In the final state they are recorded regardless of existence.
- Injector gains getMaxSecondaryDexFilesPerOwner(). pathExists() was added and then removed. The DexUseManagerTest stubs change accordingly.
Per major
- Android 14: Same fix: three commits 1aedae6e1049, 3c76194d116b and 06a99377e368 in android-security-14.0.0_r21 (cherry-picks of e29eb53ebf11, abeeacd90204 and d62d66437f3b). At android-security-14.0.0_r29 the patched region is fully present (8/8 lines), and the code context shows the limits present and the LinkOption import gone.
- Android 15: Adapted fix: ff0ca4365def in android-security-15.0.0_r9 is the limits commit adapted to the 15 code base. It uses AsLog.w instead of Log.w, and the length checks sit around ArtJni.validateDexPath and ArtJni.validateClassLoaderContext. The per-major facts show 8/8 patched lines at android-security-15.0.0_r17. The material does not explicitly show the 15 cherry-picks of the symlink and omit-existence-check commits.
- Android 16: Resolved in base (android-16.0.0_r2) by d62d66437f3b (Omit file existence check). Its pre-image already contains the existence check with the NOFOLLOW_LINKS pathExists and the per-owner limit. 7/8 patched lines are present at android-security-16.0.0_r8.
- Android 17: Not affected: the bulletin does not list Android 17. 7/8 patched lines are present at android-security-17.0.0_r1.
Unknowns
- How notifyDexContainersLoaded is reached from an app (the system_server caller and binder path).
- How the caller handles the new IllegalArgumentException.
- Which inputs notifyDexContainersLoaded routes to addSecondaryDexUse rather than primary dex handling.
- Whether Android 15 security tags carry the symlink and omit-existence-check follow-ups (only the limits commit diff is provided for 15).
- Whether entries persisted before the fix are ever pruned to the new cap.