Mount appfuse in process namespace.

BUG=26148108

Change-Id: I2297fd227a4c607054e0403e73bd9c857f580a1c
diff --git a/CommandListener.cpp b/CommandListener.cpp
index edbb776..8808065 100644
--- a/CommandListener.cpp
+++ b/CommandListener.cpp
@@ -19,6 +19,7 @@
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <sys/wait.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <dirent.h>
@@ -33,9 +34,9 @@
 
 #define LOG_TAG "VoldCmdListener"
 
+#include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <cutils/fs.h>
-#include <cutils/log.h>
 
 #include <sysutils/SocketClient.h>
 #include <private/android_filesystem_config.h>
@@ -626,114 +627,212 @@
 
 static size_t kAppFuseMaxMountPointName = 32;
 
-static bool isValidAppFuseMountName(const std::string& name) {
+static android::status_t getMountPath(uid_t uid, const std::string& name, std::string* path) {
     if (name.size() > kAppFuseMaxMountPointName) {
-        return false;
+        LOG(ERROR) << "AppFuse mount name is too long.";
+        return -EINVAL;
     }
     for (size_t i = 0; i < name.size(); i++) {
         if (!isalnum(name[i])) {
-            return false;
+            LOG(ERROR) << "AppFuse mount name contains invalid character.";
+            return -EINVAL;
         }
     }
-    return true;
+    *path = android::base::StringPrintf("/mnt/appfuse/%d_%s", uid, name.c_str());
+    return android::OK;
+}
+
+static android::status_t mountInNamespace(uid_t uid, int device_fd, const std::string& path) {
+    // Remove existing mount.
+    android::vold::ForceUnmount(path);
+
+    const auto opts = android::base::StringPrintf(
+            "fd=%i,"
+            "rootmode=40000,"
+            "default_permissions,"
+            "allow_other,"
+            "user_id=%d,group_id=%d",
+            device_fd,
+            uid,
+            uid);
+
+    const int result = TEMP_FAILURE_RETRY(mount(
+            "/dev/fuse", path.c_str(), "fuse",
+            MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts.c_str()));
+    if (result != 0) {
+        PLOG(ERROR) << "Failed to mount " << path;
+        return -errno;
+    }
+
+    return android::OK;
+}
+
+static android::status_t runCommandInNamespace(const std::string& command,
+                                               uid_t uid,
+                                               pid_t pid,
+                                               const std::string& path,
+                                               int device_fd) {
+    LOG(DEBUG) << "Run app fuse command " << command << " for the path " << path << " in namespace "
+               << uid;
+
+    const android::vold::ScopedDir dir(opendir("/proc"));
+    if (dir.get() == nullptr) {
+        PLOG(ERROR) << "Failed to open /proc";
+        return -errno;
+    }
+
+    // Obtains process file descriptor.
+    const std::string pid_str = android::base::StringPrintf("%d", pid);
+    const android::vold::ScopedFd pid_fd(
+            openat(dirfd(dir.get()), pid_str.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC));
+    if (pid_fd.get() == -1) {
+        PLOG(ERROR) << "Failed to open /proc/" << pid;
+        return -errno;
+    }
+
+    // Check UID of process.
+    {
+        struct stat sb;
+        const int result = fstat(pid_fd.get(), &sb);
+        if (result == -1) {
+            PLOG(ERROR) << "Failed to stat /proc/" << pid;
+            return -errno;
+        }
+        if (sb.st_uid != uid) {
+            LOG(ERROR) << "Mismatch UID expected=" << uid << ", actual=" << sb.st_uid;
+            return -EPERM;
+        }
+    }
+
+    // Matches so far, but refuse to touch if in root namespace
+    {
+        char rootName[PATH_MAX];
+        char pidName[PATH_MAX];
+        const int root_result =
+                android::vold::SaneReadLinkAt(dirfd(dir.get()), "1/ns/mnt", rootName, PATH_MAX);
+        const int pid_result =
+                android::vold::SaneReadLinkAt(pid_fd.get(), "ns/mnt", pidName, PATH_MAX);
+        if (root_result == -1) {
+            LOG(ERROR) << "Failed to readlink for /proc/1/ns/mnt";
+            return -EPERM;
+        }
+        if (pid_result == -1) {
+            LOG(ERROR) << "Failed to readlink for /proc/" << pid << "/ns/mnt";
+            return -EPERM;
+        }
+        if (!strcmp(rootName, pidName)) {
+            LOG(ERROR) << "Don't mount appfuse in root namespace";
+            return -EPERM;
+        }
+    }
+
+    // We purposefully leave the namespace open across the fork
+    android::vold::ScopedFd ns_fd(openat(pid_fd.get(), "ns/mnt", O_RDONLY));
+    if (ns_fd.get() < 0) {
+        PLOG(ERROR) << "Failed to open namespace for /proc/" << pid << "/ns/mnt";
+        return -errno;
+    }
+
+    int child = fork();
+    if (child == 0) {
+        if (setns(ns_fd.get(), CLONE_NEWNS) != 0) {
+            PLOG(ERROR) << "Failed to setns";
+            _exit(-errno);
+        }
+
+        if (command == "mount") {
+            _exit(mountInNamespace(uid, device_fd, path));
+        } else if (command == "unmount") {
+            android::vold::ForceUnmount(path);
+            _exit(android::OK);
+        } else {
+            LOG(ERROR) << "Unknown appfuse command " << command;
+            _exit(-EPERM);
+        }
+    }
+
+    if (child == -1) {
+        PLOG(ERROR) << "Failed to folk child process";
+        return -errno;
+    }
+
+    android::status_t status;
+    TEMP_FAILURE_RETRY(waitpid(child, &status, 0));
+
+    return status;
 }
 
 CommandListener::AppFuseCmd::AppFuseCmd() : VoldCommand("appfuse") {}
 
-int CommandListener::AppFuseCmd::runCommand(SocketClient *cli,
-                                            int argc,
-                                            char **argv) {
+int CommandListener::AppFuseCmd::runCommand(SocketClient *cli, int argc, char **argv) {
     if (argc < 2) {
-        cli->sendMsg(
-            ResponseCode::CommandSyntaxError, "Missing argument", false);
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
         return 0;
     }
 
     const std::string command(argv[1]);
 
-    if (command == "mount" && argc == 4) {
+    if (command == "mount" && argc == 5) {
         const uid_t uid = atoi(argv[2]);
-        const std::string name(argv[3]);
+        const pid_t pid = atoi(argv[3]);
+        const std::string name(argv[4]);
 
         // Check mount point name.
-        if (!isValidAppFuseMountName(name)) {
-            return cli->sendMsg(
-                    ResponseCode::CommandParameterError,
-                    "Invalid mount point name.",
-                    false);
+        std::string path;
+        if (getMountPath(uid, name, &path) != android::OK) {
+            return cli->sendMsg(ResponseCode::CommandParameterError,
+                                "Invalid mount point name.",
+                                false);
         }
 
         // Create directories.
-        char path[PATH_MAX];
         {
-            snprintf(path, PATH_MAX, "/mnt/appfuse/%d_%s", uid, name.c_str());
-            umount2(path, UMOUNT_NOFOLLOW | MNT_DETACH);
-            const int result = android::vold::PrepareDir(path, 0700, 0, 0);
-            if (result != 0) {
+            const android::status_t result = android::vold::PrepareDir(path, 0700, 0, 0);
+            if (result != android::OK) {
+                PLOG(ERROR) << "Failed to prepare directory " << path;
                 return sendGenericOkFail(cli, result);
             }
         }
 
         // Open device FD.
-        const int device_fd = open("/dev/fuse", O_RDWR);
-        if (device_fd < 0) {
-            sendGenericOkFail(cli, device_fd);
-            return 0;
+        android::vold::ScopedFd device_fd(open("/dev/fuse", O_RDWR));
+        if (device_fd.get() == -1) {
+            PLOG(ERROR) << "Failed to open /dev/fuse";
+            return sendGenericOkFail(cli, -errno);
         }
 
         // Mount.
         {
-            char opts[256];
-            snprintf(
-                    opts,
-                    sizeof(opts),
-                    "fd=%i,"
-                    "rootmode=40000,"
-                    "default_permissions,"
-                    "allow_other,"
-                    "user_id=%d,group_id=%d",
-                    device_fd,
-                    uid,
-                    uid);
-            // TODO: Make it bound mount in application namespace.
-            // TODO: Add context= option to opts.
-            const int result = mount(
-                    "/dev/fuse", path, "fuse",
-                    MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts);
-            if (result != 0) {
-                sendGenericOkFail(cli, 1);
-                return 0;
+            const android::status_t result =
+                    runCommandInNamespace(command, uid, pid, path, device_fd.get());
+            if (result != android::OK) {
+                return sendGenericOkFail(cli, result);
             }
         }
 
-        const int result = sendFd(cli, device_fd);
-        close(device_fd);
-        return result;
-    } else if (command == "unmount" && argc == 4) {
+        return sendFd(cli, device_fd.get());
+    } else if (command == "unmount" && argc == 5) {
         const uid_t uid = atoi(argv[2]);
-        const std::string name(argv[3]);
+        const uid_t pid = atoi(argv[3]);
+        const std::string name(argv[4]);
 
         // Check mount point name.
-        if (!isValidAppFuseMountName(name)) {
-            return cli->sendMsg(
-                    ResponseCode::CommandParameterError,
-                    "Invalid mount point name.",
-                    false);
+        std::string path;
+        if (getMountPath(uid, name, &path) != android::OK) {
+            return cli->sendMsg(ResponseCode::CommandParameterError,
+                                "Invalid mount point name.",
+                                false);
         }
 
-        // Unmount directory.
-        char path[PATH_MAX];
-        snprintf(path, PATH_MAX, "/mnt/appfuse/%d_%s", uid, name.c_str());
-        umount2(path, UMOUNT_NOFOLLOW | MNT_DETACH);
-
-        return sendGenericOkFail(cli, /* success */ 0);
+        const android::status_t result =
+                runCommandInNamespace(command, uid, pid, path, -1 /* device_fd */);
+        return sendGenericOkFail(cli, result);
     }
 
-    return cli->sendMsg(
-            ResponseCode::CommandSyntaxError,  "Unknown appfuse cmd", false);
+    return cli->sendMsg(ResponseCode::CommandSyntaxError,  "Unknown appfuse cmd", false);
 }
 
-int CommandListener::AppFuseCmd::sendFd(SocketClient *cli, int fd) {
+android::status_t CommandListener::AppFuseCmd::sendFd(SocketClient *cli, int fd) {
     struct iovec data;
     char dataBuffer[128];
     char controlBuffer[CMSG_SPACE(sizeof(int))];
@@ -761,5 +860,11 @@
     controlMessage->cmsg_len = CMSG_LEN(sizeof(int));
     *((int *) CMSG_DATA(controlMessage)) = fd;
 
-    return TEMP_FAILURE_RETRY(sendmsg(cli->getSocket(), &message, 0));
+    const int result = TEMP_FAILURE_RETRY(sendmsg(cli->getSocket(), &message, 0));
+    if (result == -1) {
+        PLOG(ERROR) << "Failed to send FD from vold";
+        return -errno;
+    }
+
+    return android::OK;
 }
diff --git a/CommandListener.h b/CommandListener.h
index aede465..f858ac0 100644
--- a/CommandListener.h
+++ b/CommandListener.h
@@ -80,7 +80,7 @@
         virtual ~AppFuseCmd() {}
         int runCommand(SocketClient *c, int argc, char ** argv);
     private:
-        int sendFd(SocketClient *c, int fd);
+        android::status_t sendFd(SocketClient *c, int fd);
     };
 };
 
diff --git a/Utils.cpp b/Utils.cpp
index aba3a53..9f0e0f3 100644
--- a/Utils.cpp
+++ b/Utils.cpp
@@ -618,5 +618,31 @@
     return StringPrintf("/fstab.%s", hardware);
 }
 
+status_t SaneReadLinkAt(int dirfd, const char* path, char* buf, size_t bufsiz) {
+    ssize_t len = readlinkat(dirfd, path, buf, bufsiz);
+    if (len < 0) {
+        return -1;
+    } else if (len == (ssize_t) bufsiz) {
+        return -1;
+    } else {
+        buf[len] = '\0';
+        return 0;
+    }
+}
+
+ScopedFd::ScopedFd(int fd) : fd_(fd) {}
+
+ScopedFd::~ScopedFd() {
+    close(fd_);
+}
+
+ScopedDir::ScopedDir(DIR* dir) : dir_(dir) {}
+
+ScopedDir::~ScopedDir() {
+    if (dir_ != nullptr) {
+        closedir(dir_);
+    }
+}
+
 }  // namespace vold
 }  // namespace android
diff --git a/Utils.h b/Utils.h
index 0a74af7..f717da5 100644
--- a/Utils.h
+++ b/Utils.h
@@ -32,6 +32,8 @@
     void operator=(const TypeName&) = delete
 #endif
 
+struct DIR;
+
 namespace android {
 namespace vold {
 
@@ -105,6 +107,28 @@
 
 std::string DefaultFstabPath();
 
+status_t SaneReadLinkAt(int dirfd, const char* path, char* buf, size_t bufsiz);
+
+class ScopedFd {
+    const int fd_;
+public:
+    ScopedFd(int fd);
+    ~ScopedFd();
+    int get() const { return fd_; }
+
+    DISALLOW_COPY_AND_ASSIGN(ScopedFd);
+};
+
+class ScopedDir {
+    DIR* const dir_;
+public:
+    ScopedDir(DIR* dir);
+    ~ScopedDir();
+    DIR* get() const { return dir_; }
+
+    DISALLOW_COPY_AND_ASSIGN(ScopedDir);
+};
+
 }  // namespace vold
 }  // namespace android
 
diff --git a/VolumeManager.cpp b/VolumeManager.cpp
index e26b6fe..9304630 100644
--- a/VolumeManager.cpp
+++ b/VolumeManager.cpp
@@ -471,18 +471,6 @@
     return 0;
 }
 
-static int sane_readlinkat(int dirfd, const char* path, char* buf, size_t bufsiz) {
-    ssize_t len = readlinkat(dirfd, path, buf, bufsiz);
-    if (len < 0) {
-        return -1;
-    } else if (len == (ssize_t) bufsiz) {
-        return -1;
-    } else {
-        buf[len] = '\0';
-        return 0;
-    }
-}
-
 static int unmount_tree(const char* path) {
     size_t path_len = strlen(path);
 
@@ -529,7 +517,7 @@
     }
 
     // Figure out root namespace to compare against below
-    if (sane_readlinkat(dirfd(dir), "1/ns/mnt", rootName, PATH_MAX) == -1) {
+    if (android::vold::SaneReadLinkAt(dirfd(dir), "1/ns/mnt", rootName, PATH_MAX) == -1) {
         PLOG(ERROR) << "Failed to readlink";
         closedir(dir);
         return -1;
@@ -554,7 +542,7 @@
 
         // Matches so far, but refuse to touch if in root namespace
         LOG(DEBUG) << "Found matching PID " << de->d_name;
-        if (sane_readlinkat(pidFd, "ns/mnt", pidName, PATH_MAX) == -1) {
+        if (android::vold::SaneReadLinkAt(pidFd, "ns/mnt", pidName, PATH_MAX) == -1) {
             PLOG(WARNING) << "Failed to read namespace for " << de->d_name;
             goto next;
         }