Snap for 7880955 from 10784ed07990087be79474c595093638c21377cd to sc-v2-release

Change-Id: Ia674a2b16f1e08e3253f5228e8f2840f59b18437
diff --git a/incfs/MountRegistry.cpp b/incfs/MountRegistry.cpp
index b7b53c8..4f16f73 100644
--- a/incfs/MountRegistry.cpp
+++ b/incfs/MountRegistry.cpp
@@ -302,6 +302,25 @@
     return true;
 }
 
+static std::string fixBackingDir(std::string_view dir, std::string_view mountDir) {
+    if (!dir.starts_with("/proc/"sv)) {
+        return std::string(dir);
+    }
+
+    // HACK:
+    // Vold uses a secure way of mounting incremental storages, where it passes in
+    // a virtual symlink in /proc/self/fd/... instead of the original path. Unfortunately,
+    // this symlink string gets preserved by the system and we can't resolve it later.
+    // But it's the only place that uses this symlink, and we know exactly how the
+    // mount and backing directory paths look in that case - so can recover one from
+    // another.
+    if (path::endsWith(mountDir, "mount"sv)) {
+        return path::join(path::dirName(mountDir), "backing_store"sv);
+    }
+    // Well, hack didn't work. Still may not return a /proc/ path
+    return {};
+}
+
 bool MountRegistry::Mounts::loadFrom(base::borrowed_fd fd, std::string_view filesystem) {
     struct MountInfo {
         std::string backing;
@@ -327,19 +346,21 @@
         }
         const auto groupId = items[2];
         auto subdir = items[3];
-        auto backingDir = items.rbegin()[1];
         auto mountPoint = std::string(items[4]);
         fixProcPath(mountPoint);
         mountPoint = path::normalize(mountPoint);
         auto& mount = mountsByGroup[std::string(groupId)];
-        if (mount.backing.empty()) {
-            mount.backing.assign(backingDir);
-        } else if (mount.backing != backingDir) {
-            LOG(WARNING) << "[incfs] root '" << *mount.roots.begin()
-                         << "' mounted in multiple places with different backing dirs, '"
-                         << mount.backing << "' vs new '" << backingDir
-                         << "'; updating to the new one";
-            mount.backing.assign(backingDir);
+        auto backingDir = fixBackingDir(items.rbegin()[1], mountPoint);
+        if (!backingDir.empty()) {
+            if (mount.backing.empty()) {
+                mount.backing = std::move(backingDir);
+            } else if (mount.backing != backingDir) {
+                LOG(WARNING) << "[incfs] root '" << *mount.roots.begin()
+                             << "' mounted in multiple places with different backing dirs, '"
+                             << mount.backing << "' vs new '" << backingDir
+                             << "'; updating to the new one";
+                mount.backing = std::move(backingDir);
+            }
         }
         if (subdir == "/"sv) {
             mount.roots.emplace(mountPoint);
@@ -368,6 +389,12 @@
                          << mount.bindPoints.size() << " bind(s), ignoring";
             continue;
         }
+        if (mount.backing.empty()) {
+            LOG(WARNING) << "[incfs] mount '" << *mount.roots.begin()
+                         << "' has no backing dir, but " << mount.bindPoints.size()
+                         << " bind(s), ignoring";
+            continue;
+        }
 
         Root& root = roots[index];
         auto& binds = root.binds;
diff --git a/incfs/incfs.cpp b/incfs/incfs.cpp
index 842e381..e13cd81 100644
--- a/incfs/incfs.cpp
+++ b/incfs/incfs.cpp
@@ -81,8 +81,13 @@
     return fd;
 }
 
-static ab::unique_fd openRaw(std::string_view dir, std::string_view name) {
-    return openRaw(path::join(dir, name));
+static ab::unique_fd openAt(int fd, std::string_view name, int flags = 0) {
+    auto res = ab::unique_fd(
+            ::openat(fd, details::c_str(name), O_RDONLY | O_CLOEXEC | O_NOFOLLOW | flags));
+    if (res < 0) {
+        return ab::unique_fd{-errno};
+    }
+    return res;
 }
 
 static std::string indexPath(std::string_view root, IncFsFileId fileId) {
@@ -334,32 +339,47 @@
     return 0;
 }
 
-static int rmDirContent(const char* path) {
-    auto dir = path::openDir(path);
+static int rmDirContent(int dirFd) {
+    auto dir = path::openDir(dirFd);
     if (!dir) {
-        return -EINVAL;
+        return -errno;
     }
     while (auto entry = ::readdir(dir.get())) {
         if (entry->d_name == "."sv || entry->d_name == ".."sv) {
             continue;
         }
-        auto fullPath = ab::StringPrintf("%s/%s", path, entry->d_name);
         if (entry->d_type == DT_DIR) {
-            if (const auto err = rmDirContent(fullPath.c_str()); err != 0) {
+            auto fd = openAt(dirFd, entry->d_name, O_DIRECTORY);
+            if (!fd.ok()) {
+                return -errno;
+            }
+            if (const auto err = rmDirContent(fd.get())) {
                 return err;
             }
-            if (const auto err = ::rmdir(fullPath.c_str()); err != 0) {
-                return err;
+            if (::unlinkat(fd.get(), entry->d_name, AT_REMOVEDIR)) {
+                return -errno;
             }
         } else {
-            if (const auto err = ::unlink(fullPath.c_str()); err != 0) {
-                return err;
+            auto fd = openAt(dirFd, entry->d_name);
+            if (!fd.ok()) {
+                return -errno;
+            }
+            if (::unlinkat(fd.get(), entry->d_name, 0)) {
+                return -errno;
             }
         }
     }
     return 0;
 }
 
+static int rmDirContent(const char* path) {
+    auto fd = openAt(-1, path, O_DIRECTORY);
+    if (!fd.ok()) {
+        return -errno;
+    }
+    return rmDirContent(fd.get());
+}
+
 static std::string makeMountOptionsString(IncFsMountOptions options) {
     auto opts = ab::StringPrintf("read_timeout_ms=%u,readahead=0,rlog_pages=%u,rlog_wakeup_cnt=1,",
                                  unsigned(options.defaultReadTimeoutMs),
@@ -375,8 +395,8 @@
     return opts;
 }
 
-static IncFsControl* makeControl(const char* root) {
-    auto cmd = openRaw(root, INCFS_PENDING_READS_FILENAME);
+static IncFsControl* makeControl(int fd) {
+    auto cmd = openAt(fd, INCFS_PENDING_READS_FILENAME);
     if (!cmd.ok()) {
         return nullptr;
     }
@@ -384,13 +404,13 @@
     if (!pendingReads.ok()) {
         return nullptr;
     }
-    auto logs = openRaw(root, INCFS_LOG_FILENAME);
+    auto logs = openAt(fd, INCFS_LOG_FILENAME);
     if (!logs.ok()) {
         return nullptr;
     }
     ab::unique_fd blocksWritten;
     if (features() & Features::v2) {
-        blocksWritten = openRaw(root, INCFS_BLOCKS_WRITTEN_FILENAME);
+        blocksWritten = openAt(fd, INCFS_BLOCKS_WRITTEN_FILENAME);
         if (!blocksWritten.ok()) {
             return nullptr;
         }
@@ -549,12 +569,30 @@
         return nullptr;
     }
 
-    if (!restoreconControlFiles(targetDir)) {
+    // in case when the path is given in a form of a /proc/.../fd/ link, we need to update
+    // it here: old fd refers to the original empty directory, not to the mount
+    std::string updatedTargetDir;
+    if (path::dirName(targetDir) == path::procfsFdDir) {
+        updatedTargetDir = path::readlink(targetDir);
+    } else {
+        updatedTargetDir = targetDir;
+    }
+
+    auto rootFd = ab::unique_fd(::open(updatedTargetDir.c_str(), O_PATH | O_CLOEXEC | O_DIRECTORY));
+    if (updatedTargetDir != targetDir) {
+        // ensure that the new directory is still the same after reopening
+        if (path::fromFd(rootFd) != updatedTargetDir) {
+            errno = EINVAL;
+            return nullptr;
+        }
+    }
+
+    if (!restoreconControlFiles(path::procfsForFd(rootFd))) {
         (void)IncFs_Unmount(targetDir);
         return nullptr;
     }
 
-    auto control = makeControl(targetDir);
+    auto control = makeControl(rootFd);
     if (control == nullptr) {
         (void)IncFs_Unmount(targetDir);
         return nullptr;
@@ -568,7 +606,8 @@
         errno = EINVAL;
         return nullptr;
     }
-    return makeControl(details::c_str(root));
+    auto rootFd = ab::unique_fd(::open(details::c_str(root), O_PATH | O_CLOEXEC | O_DIRECTORY));
+    return makeControl(rootFd);
 }
 
 IncFsFd IncFs_GetControlFd(const IncFsControl* control, IncFsFdType type) {
@@ -789,7 +828,7 @@
                       << " of " << params.size << " bytes";
         return -errno;
     }
-    if (::chmod(path::join(root, subpath).c_str(), mode)) {
+    if (::chmod(path::join(root, subdir, name).c_str(), mode)) {
         PLOG(WARNING) << "[incfs] couldn't change file mode to 0" << std::oct << mode;
     }
 
@@ -1325,13 +1364,21 @@
         return -ENOTSUP;
     }
 
-    auto [sourceRoot, subpath] = registry().rootAndSubpathFor(sourceDir);
-    if (sourceRoot.empty()) {
-        return -EINVAL;
-    }
-    if (subpath.empty()) {
-        LOG(WARNING) << "[incfs] Binding the root mount '" << sourceRoot << "' is not allowed";
-        return -EINVAL;
+    if (path::dirName(sourceDir) == path::procfsFdDir) {
+        // can't find such path in the mount registry, but still can verify the filesystem
+        // via the stat() call
+        if (!isIncFsPathImpl(sourceDir)) {
+            return -EINVAL;
+        }
+    } else {
+        auto [sourceRoot, subpath] = registry().rootAndSubpathFor(sourceDir);
+        if (sourceRoot.empty()) {
+            return -EINVAL;
+        }
+        if (subpath.empty()) {
+            LOG(WARNING) << "[incfs] Binding the root mount '" << sourceRoot << "' is not allowed";
+            return -EINVAL;
+        }
     }
 
     if (auto err = isValidMountTarget(targetDir); err != 0) {
@@ -1350,6 +1397,10 @@
     if (!enabled()) {
         return -ENOTSUP;
     }
+    if (!isIncFsPathImpl(dir)) {
+        LOG(WARNING) << __func__ << ": umount() called on non-incfs directory '" << dir << '\'';
+        return -EINVAL;
+    }
 
     errno = 0;
     if (::umount2(dir, MNT_FORCE) == 0 || errno == EINVAL || errno == ENOENT) {
diff --git a/incfs/include/path.h b/incfs/include/path.h
index 325eb1d..62f8b54 100644
--- a/incfs/include/path.h
+++ b/incfs/include/path.h
@@ -29,7 +29,11 @@
 void appendNextPath(std::string& res, std::string_view c);
 }
 
+constexpr auto procfsFdDir = std::string_view("/proc/self/fd");
+
 std::string fromFd(int fd);
+std::string procfsForFd(int fd);
+std::string readlink(std::string_view path);
 bool isAbsolute(std::string_view path);
 std::string normalize(std::string_view path);
 
@@ -94,11 +98,17 @@
 bool startsWith(std::string_view path, std::string_view prefix);
 bool endsWith(std::string_view path, std::string_view prefix);
 
+struct PathDirCloser {
+    void operator()(DIR* d) const { ::closedir(d); }
+};
+
 inline auto openDir(const char* path) {
-    struct closer {
-        void operator()(DIR* d) const { ::closedir(d); }
-    };
-    auto dir = std::unique_ptr<DIR, closer>(::opendir(path));
+    auto dir = std::unique_ptr<DIR, PathDirCloser>(::opendir(path));
+    return dir;
+}
+
+inline auto openDir(int dirFd) {
+    auto dir = std::unique_ptr<DIR, PathDirCloser>(::fdopendir(dirFd));
     return dir;
 }
 
diff --git a/incfs/path.cpp b/incfs/path.cpp
index f6f87ae..51442db 100644
--- a/incfs/path.cpp
+++ b/incfs/path.cpp
@@ -114,12 +114,25 @@
     return result;
 }
 
+static constexpr char fdNameFormat[] = "/proc/self/fd/%d";
+
+std::string procfsForFd(int fd) {
+    char fdNameBuffer[std::size(fdNameFormat) + 11 + 1]; // max int length + '\0'
+    snprintf(fdNameBuffer, std::size(fdNameBuffer), fdNameFormat, fd);
+    return fdNameBuffer;
+}
+
 std::string fromFd(int fd) {
-    static constexpr auto kDeletedSuffix = " (deleted)"sv;
-    static constexpr char fdNameFormat[] = "/proc/self/fd/%d";
     char fdNameBuffer[std::size(fdNameFormat) + 11 + 1]; // max int length + '\0'
     snprintf(fdNameBuffer, std::size(fdNameBuffer), fdNameFormat, fd);
 
+    return readlink(fdNameBuffer);
+}
+
+std::string readlink(std::string_view path) {
+    static constexpr auto kDeletedSuffix = " (deleted)"sv;
+
+    auto cPath = c_str(path);
     std::string res;
     // We used to call lstat() here to preallocate the buffer to the exact required size; turns out
     // that call is significantly more expensive than anything else, so doing a couple extra
@@ -127,9 +140,9 @@
     auto bufSize = 256;
     for (;;) {
         res.resize(bufSize - 1, '\0');
-        auto size = ::readlink(fdNameBuffer, &res[0], res.size());
+        auto size = ::readlink(cPath, &res[0], res.size());
         if (size < 0) {
-            PLOG(ERROR) << "readlink failed for " << fdNameBuffer;
+            PLOG(ERROR) << "readlink failed for " << path;
             return {};
         }
         if (size >= ssize_t(res.size())) {