Let's reinvent storage, yet again!

Now that we're treating storage as a runtime permission, we need to
grant read/write access without killing the app.  This is really
tricky, since we had been using GIDs for access control, and they're
set in stone once Zygote drops privileges.

The only thing left that can change dynamically is the filesystem
itself, so let's do that.  This means changing the FUSE daemon to
present itself as three different views:

/mnt/runtime_default/foo - view for apps with no access
/mnt/runtime_read/foo - view for apps with read access
/mnt/runtime_write/foo - view for apps with write access

There is still a single location for all the backing files, and
filesystem permissions are derived the same way for each view, but
the file modes are masked off differently for each mountpoint.

During Zygote fork, it wires up the appropriate storage access into
an isolated mount namespace based on the current app permissions.  When
the app is granted permissions dynamically at runtime, the system
asks vold to jump into the existing mount namespace and bind mount
the newly granted access model into place.

Bug: 21858077
Change-Id: I62fb25d126dd815aea699b33d580e3afb90f8fd2
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index cb68d74..c9853df 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -504,4 +504,5 @@
 
     void grantDefaultPermissions(int userId);
     void setCarrierAppPackagesProvider(in IPackagesProvider provider);
+    int getMountExternalMode(int uid);
 }
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index f9c50f3..7234e98 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -643,6 +643,10 @@
             }
             if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
                 argsForZygote.add("--mount-external-default");
+            } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
+                argsForZygote.add("--mount-external-read");
+            } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
+                argsForZygote.add("--mount-external-write");
             }
             argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
 
@@ -802,7 +806,12 @@
      * @hide
      */
     public static final boolean isIsolated() {
-        int uid = UserHandle.getAppId(myUid());
+        return isIsolated(myUid());
+    }
+
+    /** {@hide} */
+    public static final boolean isIsolated(int uid) {
+        uid = UserHandle.getAppId(uid);
         return uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID;
     }
 
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index e55ae99..84a879c 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -1177,6 +1177,21 @@
                     _data.recycle();
                 }
             }
+
+            @Override
+            public void remountUid(int uid) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeInt(uid);
+                    mRemote.transact(Stub.TRANSACTION_remountUid, _data, _reply, 0);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
         }
 
         private static final String DESCRIPTOR = "IMountService";
@@ -1292,6 +1307,8 @@
         static final int TRANSACTION_benchmark = IBinder.FIRST_CALL_TRANSACTION + 59;
         static final int TRANSACTION_setDebugFlags = IBinder.FIRST_CALL_TRANSACTION + 60;
 
+        static final int TRANSACTION_remountUid = IBinder.FIRST_CALL_TRANSACTION + 61;
+
         /**
          * Cast an IBinder object into an IMountService interface, generating a
          * proxy if needed.
@@ -1845,6 +1862,13 @@
                     reply.writeNoException();
                     return true;
                 }
+                case TRANSACTION_remountUid: {
+                    data.enforceInterface(DESCRIPTOR);
+                    int uid = data.readInt();
+                    remountUid(uid);
+                    reply.writeNoException();
+                    return true;
+                }
             }
             return super.onTransact(code, data, reply, flags);
         }
@@ -2154,4 +2178,6 @@
     public String getPrimaryStorageUuid() throws RemoteException;
     public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback)
             throws RemoteException;
+
+    public void remountUid(int uid) throws RemoteException;
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 9b26f24..aab68e9 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -871,6 +871,15 @@
     }
 
     /** {@hide} */
+    public void remountUid(int uid) {
+        try {
+            mMountService.remountUid(uid);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
     private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
     private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
     private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index c97fdf4..197004c 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -46,8 +46,12 @@
 
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = 0;
-    /** Default user-specific external storage should be mounted. */
+    /** Default external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_DEFAULT = 1;
+    /** Read-only external storage should be mounted. */
+    public static final int MOUNT_EXTERNAL_READ = 2;
+    /** Read-write external storage should be mounted. */
+    public static final int MOUNT_EXTERNAL_WRITE = 3;
 
     private static final ZygoteHooks VM_HOOKS = new ZygoteHooks();
 
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index fa870b9..3e86fac 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -519,6 +519,10 @@
                     niceName = arg.substring(arg.indexOf('=') + 1);
                 } else if (arg.equals("--mount-external-default")) {
                     mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
+                } else if (arg.equals("--mount-external-read")) {
+                    mountExternal = Zygote.MOUNT_EXTERNAL_READ;
+                } else if (arg.equals("--mount-external-write")) {
+                    mountExternal = Zygote.MOUNT_EXTERNAL_WRITE;
                 } else if (arg.equals("--query-abi-list")) {
                     abiListQuery = true;
                 } else if (arg.startsWith("--instruction-set=")) {