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

Commit 2d8b4e80 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Delegate mkdirs() to vold when lacking perms.

Apps without sdcard_r or sdcard_rw are still able to write to
their package-specific directory, but someone needs to first make
that directory on their behalf.  This change will delegate the
mkdirs() call through to vold when an app fails to create directly.

MountService validates that the path belongs to the calling user, and
that it's actually on external storage, before passing to vold.

Update Environment to make app-vs-vold paths clearer.

Bug: 10577808
Change-Id: I43b4a77fd6d2b9af2a0d899790da8d9d89386776
parent 6df7d4a5
Loading
Loading
Loading
Loading
+18 −4
Original line number Diff line number Diff line
@@ -92,6 +92,7 @@ import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.SystemVibrator;
import android.os.UserManager;
import android.os.storage.IMountService;
import android.os.storage.StorageManager;
import android.print.IPrintManager;
import android.print.PrintManager;
@@ -864,7 +865,9 @@ class ContextImpl extends Context {
            if (mExternalObbDirs == null) {
                mExternalObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
            }
            return mExternalObbDirs;

            // Create dirs if needed
            return ensureDirsExistOrFilter(mExternalObbDirs);
        }
    }

@@ -2127,16 +2130,27 @@ class ContextImpl extends Context {
     * Ensure that given directories exist, trying to create them if missing. If
     * unable to create, they are filtered by replacing with {@code null}.
     */
    private static File[] ensureDirsExistOrFilter(File[] dirs) {
    private File[] ensureDirsExistOrFilter(File[] dirs) {
        File[] result = new File[dirs.length];
        for (int i = 0; i < dirs.length; i++) {
            File dir = dirs[i];
            if (!dir.exists()) {
                if (!dir.mkdirs()) {
                    // Failing to mkdir() may be okay, since we might not have
                    // enough permissions; ask vold to create on our behalf.
                    final IMountService mount = IMountService.Stub.asInterface(
                            ServiceManager.getService("mount"));
                    int res = -1;
                    try {
                        res = mount.mkdirs(getPackageName(), dir.getAbsolutePath());
                    } catch (RemoteException e) {
                    }
                    if (res != 0) {
                        Log.w(TAG, "Failed to ensure directory: " + dir);
                        dir = null;
                    }
                }
            }
            result[i] = dir;
        }
        return result;
+42 −22
Original line number Diff line number Diff line
@@ -109,29 +109,36 @@ public class Environment {
        // TODO: generalize further to create package-specific environment
        // TODO: add support for secondary external storage

        private final File[] mExternalDirs;
        private final File mMediaDir;
        /** External storage dirs, as visible to vold */
        private final File[] mExternalDirsForVold;
        /** External storage dirs, as visible to apps */
        private final File[] mExternalDirsForApp;
        /** Primary emulated storage dir for direct access */
        private final File mEmulatedDirForDirect;

        public UserEnvironment(int userId) {
            // See storage config details at http://source.android.com/tech/storage/
            String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE);
            String rawEmulatedStorageTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);
            String rawEmulatedSource = System.getenv(ENV_EMULATED_STORAGE_SOURCE);
            String rawEmulatedTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);
            String rawMediaStorage = System.getenv(ENV_MEDIA_STORAGE);
            if (TextUtils.isEmpty(rawMediaStorage)) {
                rawMediaStorage = "/data/media";
            }

            if (!TextUtils.isEmpty(rawEmulatedStorageTarget)) {
            if (!TextUtils.isEmpty(rawEmulatedTarget)) {
                // Device has emulated storage; external storage paths should have
                // userId burned into them.
                final String rawUserId = Integer.toString(userId);
                final File emulatedBase = new File(rawEmulatedStorageTarget);
                final File emulatedSourceBase = new File(rawEmulatedSource);
                final File emulatedTargetBase = new File(rawEmulatedTarget);
                final File mediaBase = new File(rawMediaStorage);

                // /storage/emulated/0
                mExternalDirs = new File[] { buildPath(emulatedBase, rawUserId) };
                mExternalDirsForVold = new File[] { buildPath(emulatedSourceBase, rawUserId) };
                mExternalDirsForApp = new File[] { buildPath(emulatedTargetBase, rawUserId) };
                // /data/media/0
                mMediaDir = buildPath(mediaBase, rawUserId);
                mEmulatedDirForDirect = buildPath(mediaBase, rawUserId);

            } else {
                // Device has physical external storage; use plain paths.
@@ -141,15 +148,16 @@ public class Environment {
                }

                // /storage/sdcard0
                mExternalDirs = new File[] { new File(rawExternalStorage) };
                mExternalDirsForVold = new File[] { new File(rawExternalStorage) };
                mExternalDirsForApp = new File[] { new File(rawExternalStorage) };
                // /data/media
                mMediaDir = new File(rawMediaStorage);
                mEmulatedDirForDirect = new File(rawMediaStorage);
            }
        }

        @Deprecated
        public File getExternalStorageDirectory() {
            return mExternalDirs[0];
            return mExternalDirsForApp[0];
        }

        @Deprecated
@@ -157,44 +165,56 @@ public class Environment {
            return buildExternalStoragePublicDirs(type)[0];
        }

        public File[] getExternalDirs() {
            return mExternalDirs;
        public File[] getExternalDirsForVold() {
            return mExternalDirsForVold;
        }

        public File[] getExternalDirsForApp() {
            return mExternalDirsForApp;
        }

        public File getMediaDir() {
            return mMediaDir;
            return mEmulatedDirForDirect;
        }

        public File[] buildExternalStoragePublicDirs(String type) {
            return buildPaths(mExternalDirs, type);
            return buildPaths(mExternalDirsForApp, type);
        }

        public File[] buildExternalStorageAndroidDataDirs() {
            return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA);
            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA);
        }

        public File[] buildExternalStorageAndroidObbDirs() {
            return buildPaths(mExternalDirs, DIR_ANDROID, DIR_OBB);
            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB);
        }

        public File[] buildExternalStorageAppDataDirs(String packageName) {
            return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA, packageName);
            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName);
        }

        public File[] buildExternalStorageAppDataDirsForVold(String packageName) {
            return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_DATA, packageName);
        }

        public File[] buildExternalStorageAppMediaDirs(String packageName) {
            return buildPaths(mExternalDirs, DIR_ANDROID, DIR_MEDIA, packageName);
            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_MEDIA, packageName);
        }

        public File[] buildExternalStorageAppObbDirs(String packageName) {
            return buildPaths(mExternalDirs, DIR_ANDROID, DIR_OBB, packageName);
            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB, packageName);
        }

        public File[] buildExternalStorageAppObbDirsForVold(String packageName) {
            return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_OBB, packageName);
        }

        public File[] buildExternalStorageAppFilesDirs(String packageName) {
            return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
        }

        public File[] buildExternalStorageAppCacheDirs(String packageName) {
            return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE);
            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE);
        }
    }

@@ -344,7 +364,7 @@ public class Environment {
     */
    public static File getExternalStorageDirectory() {
        throwIfUserRequired();
        return sCurrentUser.getExternalDirs()[0];
        return sCurrentUser.getExternalDirsForApp()[0];
    }

    /** {@hide} */
+38 −2
Original line number Diff line number Diff line
@@ -20,9 +20,7 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.storage.StorageVolume;

/**
 * WARNING! Update IMountService.h and IMountService.cpp if you change this
@@ -737,7 +735,25 @@ public interface IMountService extends IInterface {
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public int mkdirs(String callingPkg, String path) throws RemoteException {
                Parcel _data = Parcel.obtain();
                Parcel _reply = Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(callingPkg);
                    _data.writeString(path);
                    mRemote.transact(Stub.TRANSACTION_mkdirs, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

@@ -811,6 +827,8 @@ public interface IMountService extends IInterface {

        static final int TRANSACTION_fixPermissionsSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 33;

        static final int TRANSACTION_mkdirs = IBinder.FIRST_CALL_TRANSACTION + 34;

        /**
         * Cast an IBinder object into an IMountService interface, generating a
         * proxy if needed.
@@ -1154,6 +1172,15 @@ public interface IMountService extends IInterface {
                    reply.writeInt(resultCode);
                    return true;
                }
                case TRANSACTION_mkdirs: {
                    data.enforceInterface(DESCRIPTOR);
                    String callingPkg = data.readString();
                    String path = data.readString();
                    int result = mkdirs(callingPkg, path);
                    reply.writeNoException();
                    reply.writeInt(result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
@@ -1376,4 +1403,13 @@ public interface IMountService extends IInterface {
     */
    public int fixPermissionsSecureContainer(String id, int gid, String filename)
            throws RemoteException;

    /**
     * Ensure that all directories along given path exist, creating parent
     * directories as needed. Validates that given path is absolute and that it
     * contains no relative "." or ".." paths or symlinks. Also ensures that
     * path belongs to a volume managed by vold, and that path is either
     * external storage data or OBB directory belonging to calling app.
     */
    public int mkdirs(String callingPkg, String path) throws RemoteException;
}
+81 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;

import android.Manifest;
import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -2127,6 +2128,85 @@ class MountService extends IMountService.Stub
        }
    }

    @Override
    public int mkdirs(String callingPkg, String appPath) {
        final int userId = UserHandle.getUserId(Binder.getCallingUid());
        final UserEnvironment userEnv = new UserEnvironment(userId);

        // Validate that reported package name belongs to caller
        final AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
                Context.APP_OPS_SERVICE);
        appOps.checkPackage(Binder.getCallingUid(), callingPkg);

        try {
            appPath = new File(appPath).getCanonicalPath();
        } catch (IOException e) {
            Slog.e(TAG, "Failed to resolve " + appPath + ": " + e);
            return -1;
        }

        // Try translating the app path into a vold path, but require that it
        // belong to the calling package.
        String voldPath = maybeTranslatePathForVold(appPath,
                userEnv.buildExternalStorageAppDataDirs(callingPkg),
                userEnv.buildExternalStorageAppDataDirsForVold(callingPkg));
        if (voldPath != null) {
            try {
                mConnector.execute("volume", "mkdirs", voldPath);
                return 0;
            } catch (NativeDaemonConnectorException e) {
                return e.getCode();
            }
        }

        voldPath = maybeTranslatePathForVold(appPath,
                userEnv.buildExternalStorageAppObbDirs(callingPkg),
                userEnv.buildExternalStorageAppObbDirsForVold(callingPkg));
        if (voldPath != null) {
            try {
                mConnector.execute("volume", "mkdirs", voldPath);
                return 0;
            } catch (NativeDaemonConnectorException e) {
                return e.getCode();
            }
        }

        throw new SecurityException("Invalid mkdirs path: " + appPath);
    }

    /**
     * Translate the given path from an app-visible path to a vold-visible path,
     * but only if it's under the given whitelisted paths.
     *
     * @param path a canonicalized app-visible path.
     * @param appPaths list of app-visible paths that are allowed.
     * @param voldPaths list of vold-visible paths directly corresponding to the
     *            allowed app-visible paths argument.
     * @return a vold-visible path representing the original path, or
     *         {@code null} if the given path didn't have an app-to-vold
     *         mapping.
     */
    @VisibleForTesting
    public static String maybeTranslatePathForVold(
            String path, File[] appPaths, File[] voldPaths) {
        if (appPaths.length != voldPaths.length) {
            throw new IllegalStateException("Paths must be 1:1 mapping");
        }

        for (int i = 0; i < appPaths.length; i++) {
            final String appPath = appPaths[i].getAbsolutePath();
            if (path.startsWith(appPath)) {
                path = new File(voldPaths[i], path.substring(appPath.length() + 1))
                        .getAbsolutePath();
                if (!path.endsWith("/")) {
                    path = path + "/";
                }
                return path;
            }
        }
        return null;
    }

    @Override
    public StorageVolume[] getVolumeList() {
        final int callingUserId = UserHandle.getCallingUserId();
@@ -2651,7 +2731,7 @@ class MountService extends IMountService.Stub
        if (forVold) {
            return new File(Environment.getEmulatedStorageSource(userId), path).getAbsolutePath();
        } else {
            return new File(userEnv.getExternalDirs()[0], path).getAbsolutePath();
            return new File(userEnv.getExternalDirsForApp()[0], path).getAbsolutePath();
        }
    }