DivestOS/Patches/LineageOS-17.1/android_packages_providers_MediaProvider/368071.patch

268 lines
10 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Sergey Nikolaienkov <sergeynv@google.com>
Date: Tue, 28 Mar 2023 12:22:31 +0200
Subject: [PATCH] Fix path traversal vulnerabilities in MediaProvider
Canonicalize filepath provided by the caller when hanling SCAN_FILE_CALL
method call in MediaProvider.
Additionally, make sure to check access permission in SCAN_FILE_CALL
(using enforceCallingPermissionInternal()).
Preemptively canonicalize Files provided as an arguments to the public
API methods in ModernMediaScanner (scanFile(), scanDirectory() and
onDirectoryDirty()) to prevent path traversal attacks.
Bug: 262244882
Test: atest MediaProviderTests
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5d2808f30c9dbe35ddbefeda4845328557569a93)
Merged-In: I61e77d69ae857984b819fa0ea27bec5c26a34842
Change-Id: I61e77d69ae857984b819fa0ea27bec5c26a34842
Change-Id: Idf0babc3ab4cae14e69ab7448b914510861a47d7
---
.../providers/media/MediaProvider.java | 88 +++++++++++++------
.../media/scan/LegacyMediaScanner.java | 25 +++++-
.../media/scan/ModernMediaScanner.java | 23 ++++-
3 files changed, 108 insertions(+), 28 deletions(-)
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 822d69537..0887bd6ae 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -2317,18 +2317,25 @@ public class MediaProvider extends ContentProvider {
}
private static @Nullable String extractRelativePath(@Nullable String data) {
- data = getCanonicalPath(data);
if (data == null) return null;
- final Matcher matcher = PATTERN_RELATIVE_PATH.matcher(data);
+ final String path;
+ try {
+ path = getCanonicalPath(data);
+ } catch (IOException e) {
+ Log.d(TAG, "Unable to get canonical path from invalid data path: " + data, e);
+ return null;
+ }
+
+ final Matcher matcher = PATTERN_RELATIVE_PATH.matcher(path);
if (matcher.find()) {
- final int lastSlash = data.lastIndexOf('/');
+ final int lastSlash = path.lastIndexOf('/');
if (lastSlash == -1 || lastSlash < matcher.end()) {
// This is a file in the top-level directory, so relative path is "/"
// which is different than null, which means unknown path
return "/";
} else {
- return data.substring(matcher.end(), lastSlash + 1);
+ return path.substring(matcher.end(), lastSlash + 1);
}
} else {
return null;
@@ -4193,30 +4200,45 @@ public class MediaProvider extends ContentProvider {
@Override
public Bundle call(String method, String arg, Bundle extras) {
switch (method) {
- case MediaStore.SCAN_FILE_CALL:
+ case MediaStore.SCAN_FILE_CALL: {
+ final LocalCallingIdentity token = clearLocalCallingIdentity();
+ final CallingIdentity providerToken = clearCallingIdentity();
+
+ final Uri uri;
+ try {
+ final Uri fileUri = extras.getParcelable(Intent.EXTRA_STREAM);
+ File file;
+ try {
+ file = getCanonicalFile(fileUri.getPath());
+ } catch (IOException e) {
+ file = null;
+ }
+
+ uri = file != null ? MediaScanner.instance(getContext()).scanFile(file) : null;
+ } finally {
+ restoreCallingIdentity(providerToken);
+ restoreLocalCallingIdentity(token);
+ }
+
+ final Bundle res = new Bundle();
+ res.putParcelable(Intent.EXTRA_STREAM, uri);
+ return res;
+ }
case MediaStore.SCAN_VOLUME_CALL: {
final LocalCallingIdentity token = clearLocalCallingIdentity();
final CallingIdentity providerToken = clearCallingIdentity();
+
try {
final Uri uri = extras.getParcelable(Intent.EXTRA_STREAM);
final File file = new File(uri.getPath());
- final Bundle res = new Bundle();
- switch (method) {
- case MediaStore.SCAN_FILE_CALL:
- res.putParcelable(Intent.EXTRA_STREAM,
- MediaScanner.instance(getContext()).scanFile(file));
- break;
- case MediaStore.SCAN_VOLUME_CALL:
- MediaService.onScanVolume(getContext(), Uri.fromFile(file));
- break;
- }
- return res;
+ MediaService.onScanVolume(getContext(), Uri.fromFile(file));
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
restoreCallingIdentity(providerToken);
restoreLocalCallingIdentity(token);
}
+ return Bundle.EMPTY;
}
case MediaStore.UNHIDE_CALL: {
throw new UnsupportedOperationException();
@@ -6705,14 +6727,30 @@ public class MediaProvider extends ContentProvider {
return s.toString();
}
- @Nullable
- private static String getCanonicalPath(@Nullable String path) {
- if (path == null) return null;
- try {
- return new File(path).getCanonicalPath();
- } catch (IOException e) {
- Log.d(TAG, "Unable to get canonical path from invalid data path: " + path, e);
- return null;
- }
+ /**
+ * Returns the canonical {@link File} for the provided abstract pathname.
+ *
+ * @return The canonical pathname string denoting the same file or directory as this abstract
+ * pathname
+ * @see File#getCanonicalFile()
+ */
+ @NonNull
+ public static File getCanonicalFile(@NonNull String path) throws IOException {
+ Objects.requireNonNull(path);
+ return new File(path).getCanonicalFile();
}
+
+ /**
+ * Returns the canonical pathname string of the provided abstract pathname.
+ *
+ * @return The canonical pathname string denoting the same file or directory as this abstract
+ * pathname.
+ * @see File#getCanonicalPath()
+ */
+ @NonNull
+ public static String getCanonicalPath(@NonNull String path) throws IOException {
+ Objects.requireNonNull(path);
+ return new File(path).getCanonicalPath();
+ }
+
}
diff --git a/src/com/android/providers/media/scan/LegacyMediaScanner.java b/src/com/android/providers/media/scan/LegacyMediaScanner.java
index 5041265cb..eb28b5b59 100644
--- a/src/com/android/providers/media/scan/LegacyMediaScanner.java
+++ b/src/com/android/providers/media/scan/LegacyMediaScanner.java
@@ -16,16 +16,23 @@
package com.android.providers.media.scan;
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
import android.content.Context;
import android.net.Uri;
import android.os.Trace;
import android.provider.MediaStore;
+import android.util.Log;
import libcore.net.MimeUtils;
import java.io.File;
+import java.io.IOException;
public class LegacyMediaScanner implements MediaScanner {
+ private static final String TAG = "LegacyMediaScanner";
+
private final Context mContext;
public LegacyMediaScanner(Context context) {
@@ -39,6 +46,14 @@ public class LegacyMediaScanner implements MediaScanner {
@Override
public void scanDirectory(File file) {
+ requireNonNull(file);
+ try {
+ file = file.getCanonicalFile();
+ } catch (IOException e) {
+ Log.e(TAG, "Couldn't canonicalize directory to scan" + file, e);
+ return;
+ }
+
final String path = file.getAbsolutePath();
final String volumeName = MediaStore.getVolumeName(file);
@@ -52,7 +67,15 @@ public class LegacyMediaScanner implements MediaScanner {
}
@Override
- public Uri scanFile(File file) {
+ public Uri scanFile(@NonNull File file) {
+ requireNonNull(file);
+ try {
+ file = file.getCanonicalFile();
+ } catch (IOException e) {
+ Log.e(TAG, "Couldn't canonicalize file to scan" + file, e);
+ return null;
+ }
+
final String path = file.getAbsolutePath();
final String volumeName = MediaStore.getVolumeName(file);
diff --git a/src/com/android/providers/media/scan/ModernMediaScanner.java b/src/com/android/providers/media/scan/ModernMediaScanner.java
index fe0e5cf78..f8f29fb4e 100644
--- a/src/com/android/providers/media/scan/ModernMediaScanner.java
+++ b/src/com/android/providers/media/scan/ModernMediaScanner.java
@@ -39,6 +39,8 @@ import static android.provider.MediaStore.UNKNOWN_STRING;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.CurrentTimeMillisLong;
import android.annotation.CurrentTimeSecondsLong;
import android.annotation.NonNull;
@@ -161,7 +163,15 @@ public class ModernMediaScanner implements MediaScanner {
}
@Override
- public void scanDirectory(File file) {
+ public void scanDirectory(@NonNull File file) {
+ requireNonNull(file);
+ try {
+ file = file.getCanonicalFile();
+ } catch (IOException e) {
+ Log.e(TAG, "Couldn't canonicalize directory to scan" + file, e);
+ return;
+ }
+
try (Scan scan = new Scan(file)) {
scan.run();
} catch (OperationCanceledException ignored) {
@@ -169,7 +179,16 @@ public class ModernMediaScanner implements MediaScanner {
}
@Override
- public Uri scanFile(File file) {
+ @Nullable
+ public Uri scanFile(@NonNull File file) {
+ requireNonNull(file);
+ try {
+ file = file.getCanonicalFile();
+ } catch (IOException e) {
+ Log.e(TAG, "Couldn't canonicalize file to scan" + file, e);
+ return null;
+ }
+
try (Scan scan = new Scan(file)) {
scan.run();
return scan.mFirstResult;