Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3f64ec57 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Sanity check raw paths against requested volume.

When callers are inserting or updating raw "_data" paths, we need to
sanity check them to make sure they're not "crossing the streams"
between storage devices.  For example, it would be really broken to
insert a file on the SD card into the "internal" storage volume.

This also enforces that callers don't "cross the streams" between
multiple storage volumes on devices that support them, since
otherwise they'd end up with very confusing behavior, such as
the same underlying file being inserted into multiple databases.

Also, the "internal" storage volume should really only be used for
common media (such as ringtones), and it shouldn't be allowed to
point into private app data directories, since MODE_WORLD_READABLE
has been deprecated for many years now.

Bug: 117932814
Test: atest MediaProviderTests
Test: atest cts/tests/tests/media/src/android/media/cts/MediaScanner*
Test: atest cts/tests/tests/provider/src/android/provider/cts/MediaStore*
Change-Id: I267eacd45bbd270b8ce9b28de9d6e209f780f31a
parent ce476415
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -72,6 +72,7 @@ import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -831,6 +832,16 @@ public class FileUtils {
        return false;
    }

    /** {@hide} */
    public static boolean contains(Collection<File> dirs, File file) {
        for (File dir : dirs) {
            if (contains(dir, file)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Test if a file lives under the given directory, either as a direct child
     * or a distant grandchild.
+6 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import com.android.internal.util.Preconditions;

import java.io.CharArrayWriter;
import java.io.File;
import java.util.Locale;

/**
 * Information about a shared/external storage volume for a specific user.
@@ -263,6 +264,11 @@ public final class StorageVolume implements Parcelable {
        return mFsUuid;
    }

    /** {@hide} */
    public @Nullable String getNormalizedUuid() {
        return mFsUuid != null ? mFsUuid.toLowerCase(Locale.US) : null;
    }

    /**
     * Parse and return volume UUID as FAT volume ID, or return -1 if unable to
     * parse or UUID is unknown.
+5 −0
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import com.android.internal.util.Preconditions;
import java.io.CharArrayWriter;
import java.io.File;
import java.util.Comparator;
import java.util.Locale;
import java.util.Objects;

/**
@@ -254,6 +255,10 @@ public class VolumeInfo implements Parcelable {
        return fsUuid;
    }

    public @Nullable String getNormalizedFsUuid() {
        return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
    }

    @UnsupportedAppUsage
    public int getMountUserId() {
        return mountUserId;
+40 −9
Original line number Diff line number Diff line
@@ -70,6 +70,8 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -1224,7 +1226,7 @@ public final class MediaStore {
                if (sv.isPrimary()) {
                    return VOLUME_EXTERNAL;
                } else {
                    return checkArgumentVolumeName(sv.getUuid());
                    return checkArgumentVolumeName(sv.getNormalizedUuid());
                }
            }
            throw new IllegalStateException("Unknown volume at " + path);
@@ -2919,7 +2921,7 @@ public final class MediaStore {
                if (vi.isPrimary()) {
                    volumeNames.add(VOLUME_EXTERNAL);
                } else {
                    volumeNames.add(vi.getFsUuid());
                    volumeNames.add(vi.getNormalizedFsUuid());
                }
            }
        }
@@ -2953,8 +2955,7 @@ public final class MediaStore {
        // When not one of the well-known values above, it must be a hex UUID
        for (int i = 0; i < volumeName.length(); i++) {
            final char c = volumeName.charAt(i);
            if (('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
                    || ('0' <= c && c <= '9') || (c == '-')) {
            if (('a' <= c && c <= 'f') || ('0' <= c && c <= '9') || (c == '-')) {
                continue;
            } else {
                throw new IllegalArgumentException("Invalid volume name: " + volumeName);
@@ -2963,23 +2964,26 @@ public final class MediaStore {
        return volumeName;
    }

    /** {@hide} */
    /**
     * Return path where the given volume is mounted. Not valid for
     * {@link #VOLUME_INTERNAL}.
     *
     * @hide
     */
    public static @NonNull File getVolumePath(@NonNull String volumeName)
            throws FileNotFoundException {
        if (TextUtils.isEmpty(volumeName)) {
            throw new IllegalArgumentException();
        }

        if (VOLUME_INTERNAL.equals(volumeName)) {
            return Environment.getDataDirectory();
        } else if (VOLUME_EXTERNAL.equals(volumeName)) {
        if (VOLUME_EXTERNAL.equals(volumeName)) {
            return Environment.getExternalStorageDirectory();
        }

        final StorageManager sm = AppGlobals.getInitialApplication()
                .getSystemService(StorageManager.class);
        for (VolumeInfo vi : sm.getVolumes()) {
            if (Objects.equals(vi.getFsUuid(), volumeName)) {
            if (Objects.equals(vi.getNormalizedFsUuid(), volumeName)) {
                final File path = vi.getPathForUser(UserHandle.myUserId());
                if (path != null) {
                    return path;
@@ -2991,6 +2995,33 @@ public final class MediaStore {
        throw new FileNotFoundException("Failed to find path for " + volumeName);
    }

    /**
     * Return paths that should be scanned for the given volume.
     *
     * @hide
     */
    public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
            throws FileNotFoundException {
        if (TextUtils.isEmpty(volumeName)) {
            throw new IllegalArgumentException();
        }

        final ArrayList<File> res = new ArrayList<>();
        if (VOLUME_INTERNAL.equals(volumeName)) {
            res.add(new File(Environment.getRootDirectory(), "media"));
            res.add(new File(Environment.getOemDirectory(), "media"));
            res.add(new File(Environment.getProductDirectory(), "media"));
        } else {
            res.add(getVolumePath(volumeName));
            final UserManager um = AppGlobals.getInitialApplication()
                    .getSystemService(UserManager.class);
            if (VOLUME_EXTERNAL.equals(volumeName) && um.isDemoUser()) {
                res.add(Environment.getDataPreloadsMediaDirectory());
            }
        }
        return res;
    }

    /**
     * Uri for querying the state of the media scanner.
     */