Give secondary users read-only physical cards.

Long ago, we mounted secondary physical cards as readable by all
users on the device, which enabled the use-case of loading media on
a card and viewing it from all users.

More recently, we started giving write access to these secondary
physical cards, but this created a one-directional channel for
communication across user boundaries; something that CDD disallows.

This change is designed to give us the best of both worlds: the
package-specific directories are writable for the user that mounted
the card, but access to those "Android" directories are blocked for
all other users.  Other users remain able to read content elsewhere
on the card.

Bug: 22787184
Change-Id: I4a04a1a857a65becf5fd37d775d927af022b40ca
diff --git a/sdcard/sdcard.c b/sdcard/sdcard.c
index ba4d70c..41bf045 100644
--- a/sdcard/sdcard.c
+++ b/sdcard/sdcard.c
@@ -151,7 +151,7 @@
     perm_t perm;
     userid_t userid;
     uid_t uid;
-    mode_t mode;
+    bool under_android;
 
     struct node *next;          /* per-dir sibling list */
     struct node *child;         /* first contained file by this dir */
@@ -419,11 +419,20 @@
         attr->gid = multiuser_get_uid(node->userid, fuse->gid);
     }
 
-    /* Filter requested mode based on underlying file, and
-     * pass through file type. */
-    int visible_mode = node->mode;
-    if (node->perm != PERM_PRE_ROOT) {
-        visible_mode = visible_mode & ~fuse->mask;
+    int visible_mode = 0775 & ~fuse->mask;
+    if (node->perm == PERM_PRE_ROOT) {
+        /* Top of multi-user view should always be visible to ensure
+         * secondary users can traverse inside. */
+        visible_mode = 0711;
+    } else if (node->under_android) {
+        /* Block "other" access to Android directories, since only apps
+         * belonging to a specific user should be in there; we still
+         * leave +x open for the default view. */
+        if (fuse->gid == AID_SDCARD_RW) {
+            visible_mode = visible_mode & ~0006;
+        } else {
+            visible_mode = visible_mode & ~0007;
+        }
     }
     int owner_mode = s->st_mode & 0700;
     int filtered_mode = visible_mode & (owner_mode | (owner_mode >> 3) | (owner_mode >> 6));
@@ -452,7 +461,7 @@
     node->perm = PERM_INHERIT;
     node->userid = parent->userid;
     node->uid = parent->uid;
-    node->mode = parent->mode;
+    node->under_android = parent->under_android;
 
     /* Derive custom permissions based on parent and current node */
     switch (parent->perm) {
@@ -463,33 +472,28 @@
         /* Legacy internal layout places users at top level */
         node->perm = PERM_ROOT;
         node->userid = strtoul(node->name, NULL, 10);
-        node->mode = 0771;
         break;
     case PERM_ROOT:
         /* Assume masked off by default. */
-        node->mode = 0770;
         if (!strcasecmp(node->name, "Android")) {
             /* App-specific directories inside; let anyone traverse */
             node->perm = PERM_ANDROID;
-            node->mode = 0771;
+            node->under_android = true;
         }
         break;
     case PERM_ANDROID:
         if (!strcasecmp(node->name, "data")) {
             /* App-specific directories inside; let anyone traverse */
             node->perm = PERM_ANDROID_DATA;
-            node->mode = 0771;
         } else if (!strcasecmp(node->name, "obb")) {
             /* App-specific directories inside; let anyone traverse */
             node->perm = PERM_ANDROID_OBB;
-            node->mode = 0771;
             /* Single OBB directory is always shared */
             node->graft_path = fuse->global->obb_path;
             node->graft_pathlen = strlen(fuse->global->obb_path);
         } else if (!strcasecmp(node->name, "media")) {
             /* App-specific directories inside; let anyone traverse */
             node->perm = PERM_ANDROID_MEDIA;
-            node->mode = 0771;
         }
         break;
     case PERM_ANDROID_DATA:
@@ -499,7 +503,6 @@
         if (appid != 0) {
             node->uid = multiuser_get_uid(parent->userid, appid);
         }
-        node->mode = 0770;
         break;
     }
 }
@@ -1730,6 +1733,7 @@
     ERROR("usage: sdcard [OPTIONS] <source_path> <label>\n"
             "    -u: specify UID to run as\n"
             "    -g: specify GID to run as\n"
+            "    -U: specify user ID that owns device\n"
             "    -m: source_path is multi-user\n"
             "    -w: runtime_write mount has full write access\n"
             "\n");
@@ -1763,7 +1767,7 @@
 }
 
 static void run(const char* source_path, const char* label, uid_t uid,
-        gid_t gid, bool multi_user, bool full_write) {
+        gid_t gid, userid_t userid, bool multi_user, bool full_write) {
     struct fuse_global global;
     struct fuse fuse_default;
     struct fuse fuse_read;
@@ -1796,18 +1800,17 @@
     global.root.refcount = 2;
     global.root.namelen = strlen(source_path);
     global.root.name = strdup(source_path);
-    global.root.userid = 0;
+    global.root.userid = userid;
     global.root.uid = AID_ROOT;
+    global.root.under_android = false;
 
     strcpy(global.source_path, source_path);
 
     if (multi_user) {
         global.root.perm = PERM_PRE_ROOT;
-        global.root.mode = 0711;
         snprintf(global.obb_path, sizeof(global.obb_path), "%s/obb", source_path);
     } else {
         global.root.perm = PERM_ROOT;
-        global.root.mode = 0771;
         snprintf(global.obb_path, sizeof(global.obb_path), "%s/Android/obb", source_path);
     }
 
@@ -1833,11 +1836,25 @@
 
     umask(0);
 
-    if (fuse_setup(&fuse_default, AID_SDCARD_RW, 0006)
-            || fuse_setup(&fuse_read, AID_EVERYBODY, 0027)
-            || fuse_setup(&fuse_write, AID_EVERYBODY, full_write ? 0007 : 0027)) {
-        ERROR("failed to fuse_setup\n");
-        exit(1);
+    if (multi_user) {
+        /* Multi-user storage is fully isolated per user, so "other"
+         * permissions are completely masked off. */
+        if (fuse_setup(&fuse_default, AID_SDCARD_RW, 0006)
+                || fuse_setup(&fuse_read, AID_EVERYBODY, 0027)
+                || fuse_setup(&fuse_write, AID_EVERYBODY, full_write ? 0007 : 0027)) {
+            ERROR("failed to fuse_setup\n");
+            exit(1);
+        }
+    } else {
+        /* Physical storage is readable by all users on device, but
+         * the Android directories are masked off to a single user
+         * deep inside attr_from_stat(). */
+        if (fuse_setup(&fuse_default, AID_SDCARD_RW, 0006)
+                || fuse_setup(&fuse_read, AID_EVERYBODY, full_write ? 0027 : 0022)
+                || fuse_setup(&fuse_write, AID_EVERYBODY, full_write ? 0007 : 0022)) {
+            ERROR("failed to fuse_setup\n");
+            exit(1);
+        }
     }
 
     /* Drop privs */
@@ -1875,6 +1892,7 @@
     const char *label = NULL;
     uid_t uid = 0;
     gid_t gid = 0;
+    userid_t userid = 0;
     bool multi_user = false;
     bool full_write = false;
     int i;
@@ -1882,7 +1900,7 @@
     int fs_version;
 
     int opt;
-    while ((opt = getopt(argc, argv, "u:g:mw")) != -1) {
+    while ((opt = getopt(argc, argv, "u:g:U:mw")) != -1) {
         switch (opt) {
             case 'u':
                 uid = strtoul(optarg, NULL, 10);
@@ -1890,6 +1908,9 @@
             case 'g':
                 gid = strtoul(optarg, NULL, 10);
                 break;
+            case 'U':
+                userid = strtoul(optarg, NULL, 10);
+                break;
             case 'm':
                 multi_user = true;
                 break;
@@ -1938,6 +1959,6 @@
         sleep(1);
     }
 
-    run(source_path, label, uid, gid, multi_user, full_write);
+    run(source_path, label, uid, gid, userid, multi_user, full_write);
     return 1;
 }