CVE-2023-40127 AI-generated analysis
platform/packages/providers/MediaProvider: MediaProvider.call() SCAN_FILE_CALL handling, plus ModernMediaScanner / FileUtils path handling
Canonicalizes the caller-supplied file path in MediaProvider's SCAN_FILE_CALL handler and in ModernMediaScanner's public scanFile(), scanDirectory() and onDirectoryDirty() entry points, so that non-canonical paths ("..", symlinks) are not scanned as-is under MediaProvider's cleared calling identity.
Root cause
In MediaProvider.call(), the SCAN_FILE_CALL branch ran `new File(arg)` on the raw string supplied by the caller. It passed the result straight to scanFile(file, REASON_DEMAND) after clearLocalCallingIdentity()/clearCallingIdentity(), so the scan ran with MediaProvider's own identity on whatever path the caller named. The resulting Uri was returned to the caller in Intent.EXTRA_STREAM. Neither the provider nor ModernMediaScanner.scanFile()/scanDirectory()/onDirectoryDirty() canonicalized the path, so paths containing ".." segments or symlinks reached the scanner in non-canonical form. The bulletin classifies the issue as information disclosure (ID), High.
How the fix works
- src/com/android/providers/media/MediaProvider.java:call(), case MediaStore.SCAN_FILE_CALL: split out of the block it shared with SCAN_VOLUME_CALL into its own block. The arg string goes to FileUtils.getCanonicalFile(filePath). If that throws IOException, file is set to null and no scan happens (uri = null). Only the canonical File is passed to scanFile(file, REASON_DEMAND). The identity is still cleared before the scan and restored after it. A TODO (b/262244882) says enforceCallingPermissionInternal(uri, ...) might still be added. The commit message says the permission check was added, but the diff does not implement it.
- src/com/android/providers/media/MediaProvider.java:call(), case MediaStore.SCAN_VOLUME_CALL: rewritten as a standalone block with the same logic (findVolume, then MediaService.onScanVolume). It now returns Bundle.EMPTY instead of a new empty Bundle().
- src/com/android/providers/media/scan/ModernMediaScanner.java:scanDirectory(): after requireNonNull, replaces `file` with file.getCanonicalFile(). On IOException it logs and returns without scanning. The redundant `return` in the FileNotFoundException catch is removed.
- src/com/android/providers/media/scan/ModernMediaScanner.java:scanFile(): same canonicalization. On IOException it logs and returns null.
- src/com/android/providers/media/scan/ModernMediaScanner.java:onDirectoryDirty(): the parameter is annotated @NonNull, requireNonNull(dir) is added and dir is replaced by dir.getCanonicalFile(). On IOException it logs and returns. As a result, mPendingCleanDirectories.remove() and FileUtils.setDirectoryDirty() operate on the canonical path.
- src/com/android/providers/media/util/FileUtils.java: the private getCanonicalPath(String), which accepted null and swallowed exceptions, is replaced by two public @NonNull helpers that throw IOException: getCanonicalFile(String) (new, used by MediaProvider) and getCanonicalPath(String). extractRelativePath() already canonicalized before the fix. It now calls the new getCanonicalPath inside its own try/catch, so it still returns null on failure and keeps the original `data` unchanged for logging.
Completeness
- Covers the SCAN_FILE_CALL method and the three public ModernMediaScanner entry points (scanFile, scanDirectory, onDirectoryDirty). Internal callers such as MediaProvider.scanFileAsMediaProvider()/scanRenamedDirectoryForFuse() (per index search) still build `new File(path)` and call MediaProvider.scanFile(). They benefit from the scanner-level canonicalization only if MediaProvider.scanFile() delegates to ModernMediaScanner.scanFile(), which the material does not show.
- The permission check named in the commit message (enforceCallingPermissionInternal() in SCAN_FILE_CALL) is not in the diff; only a TODO comment referencing the same bug is added. The handler still returns the scanned file's Uri to the caller without an access check after the scan.
- FileUtils.extractVolumePath() and extractVolumeName() (shown in the r29 context) still run PATTERN_RELATIVE_PATH / PATTERN_VOLUME_NAME on the raw string without canonicalizing it. Of the three, only extractRelativePath() canonicalizes.
Behavior changes
- If the supplied path cannot be canonicalized (IOException), SCAN_FILE_CALL now returns a Bundle with a null Intent.EXTRA_STREAM instead of attempting the scan on the raw path.
- SCAN_VOLUME_CALL returns Bundle.EMPTY instead of a newly allocated empty Bundle.
- When getCanonicalFile() fails, ModernMediaScanner.scanDirectory()/onDirectoryDirty() return silently (with an error log) and scanFile() returns null. Before the fix, the raw File was processed.
- onDirectoryDirty() now throws NullPointerException on a null argument (requireNonNull) and records the dirty state under the canonical directory path.
- FileUtils.getCanonicalPath() becomes public and @NonNull and throws IOException. FileUtils.getCanonicalFile(String) is a new public helper.
Per major
- Android 14: same fix: commits e7561e10a58e (android-security-14.0.0_r1) and 0bbf3f4c3e75 (android-security-14.0.0_r2) are the same cherry-pick (Change-Id I61e77d69ae857984b819fa0ea27bec5c26a34842) and differ only in the FileUtils hunk offsets/base. At android-security-14.0.0_r29, 8/8 patched lines are present. Per index search, in android-14.0.0_r75 the SCAN_FILE_CALL body is in the helper MediaProvider.getResultForScanFile() with the same logic.
- Android 15: not affected (not listed by the bulletin); the patched code is present at android-security-15.0.0_r17 (8/8 lines).
- Android 16: not affected (not listed by the bulletin); the patched code is present at android-security-16.0.0_r8 (8/8 lines).
- Android 17: not affected (not listed by the bulletin); the patched code is present at android-security-17.0.0_r1 (8/8 lines).
Unknowns
- What permission or identity checks, if any, run before the method switch in MediaProvider.call() for SCAN_FILE_CALL (the material shows only the case body).
- Which path-based checks inside ModernMediaScanner.Scan or downstream were bypassed by non-canonical paths, and exactly what information could be disclosed; Scan internals are not in the material.
- Whether MediaProvider.scanFile() delegates to ModernMediaScanner.scanFile(), which would determine whether internal callers get canonicalized.
- Whether a follow-up change under b/262244882 ever added the enforceCallingPermissionInternal() check described in the commit message.