CVE-2026-45521 AI-generated analysis
platform/frameworks/base, services/core StorageManagerService / AppFuseBridge (AppFuse proxy file descriptors)
StorageManagerService.openProxyFileDescriptor now captures Binder.getCallingUid() and AppFuseBridge.openFile rejects the request unless the calling UID equals the uid recorded in the AppFuse MountScope for the requested mountId, preventing cross-app access to another app's AppFuse proxy file descriptors.
Root cause
IStorageManager.openProxyFileDescriptor(mountId, fileId, mode) in StorageManagerService looked up the AppFuse mount only by the caller-supplied mountId via AppFuseBridge.openFile(mountId, fileId, mode), which only checked that mScopes.get(mountId) was non-null before calling scope.waitForMount() and scope.openFile(mountId, fileId, flags). Mounts are created in StorageManagerService with new AppFuseMountScope(uid, name), where name comes from mNextAppFuseName++, so each scope carries an owner uid, but that uid was never compared against the caller at open time. A caller reaching the Binder method could therefore obtain a ParcelFileDescriptor for a file in an AppFuse mount owned by another UID.
How the fix works
- services/core/java/com/android/server/StorageManagerService.java:openProxyFileDescriptor: adds `final int uid = Binder.getCallingUid();` at method entry (before the mode mask) and passes it as the new first argument to mAppFuseBridge.openFile(uid, mountId, fileId, mode). The mAppFuseLock synchronization, null-bridge check and catch of FuseUnavailableMountException | InterruptedException (returning null) are unchanged.
- services/core/java/com/android/server/storage/AppFuseBridge.java:openFile: signature changes to openFile(int uid, int mountId, int fileId, int mode). Inside the synchronized(this) block the check `scope == null` becomes `scope == null || scope.uid != uid`, throwing FuseUnavailableMountException(mountId) when the caller is not the mount owner. The rest of the method (waitForMount, translateModePfdToPosix, scope.openFile) is unchanged, so the ownership check happens before any wait or open.
Completeness
- The check is applied at the single lookup point in AppFuseBridge.openFile, the only consumer of mScopes for opening files shown in the material; the other mScopes user shown, AppFuseBridge.onMount, is a native-driven callback and is not changed.
- The comparison is an exact UID match against MountScope.uid (the uid passed to new AppFuseMountScope(uid, name) when the mount is created); there is no allowance for system/root or other principals, and no additional permission check was added to openProxyFileDescriptor itself.
- The mismatch case is folded into the pre-existing FuseUnavailableMountException path, so to the caller it is indistinguishable from a stale or unknown mountId (null return).
Behavior changes
- openProxyFileDescriptor returns null (with a verbose log 'The mount point has already been invalid') when the caller's UID differs from the mount owner's UID, instead of returning a ParcelFileDescriptor.
- AppFuseBridge.openFile gains a leading int uid parameter; any out-of-tree caller of the old 4-argument signature will fail to compile.
Per major
- Android 14: same fix: 0d2ee8e16afc in android-security-14.0.0_r29, two-file change (StorageManagerService.java + AppFuseBridge.java), StorageManagerService hunk at line 3571; 7/8 lines of the patched region present at that tag.
- Android 15: same fix: bca099fcb293 in android-security-15.0.0_r17, identical diff content, StorageManagerService hunk offset 3729; 7/8 lines of patched region present.
- Android 16: same fix: 63f908ebd9ee in android-security-16.0.0_r8, identical diff content, hunk offset 3829; 7/8 lines of patched region present.
- Android 17: same fix: 03988cf95e56 in android-security-17.0.0_r1, identical diff content, hunk offset 3793; 7/8 lines of patched region present.
Unknowns
- Whether openProxyFileDescriptor is gated by any permission or SELinux policy at the Binder/AIDL layer; the method body shows none.
- The name of the StorageManagerService method that creates AppFuseMountScope(uid, name) and where its uid comes from are not shown in the excerpt.
- The commit lists two Cherrypick-From sources; the material does not show what, if anything, the second upstream change contained beyond this diff.
- No test was added (Test: EXEMPT tested manually); the material does not show a regression test.