summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYurii Zubrytskyi <zyy@google.com>2021-10-21 05:19:09 -0700
committerYurii Zubrytskyi <zyy@google.com>2021-11-02 21:46:54 +0000
commit10784ed07990087be79474c595093638c21377cd (patch)
tree84acfbe4f954ef0b16158e194902ea9df6fe640c
parent7aa80337ab0fcee919df4febaf41060df25c8dba (diff)
downloadincremental_delivery-android12L-dev.tar.gz
vold needs to verify the paths it uses for incfs mounts, and the only secure way as of now is to open each path in vold and pass a special /proc/self/fd/<id> link further This ensures the path remains untouched during the operation, and no timing attack is possible between verification and the mounting Unfortunately, there's plenty of places where these paths differ in behavior from normal: - mountinfo doesn't resolve the symlink path and keeps returning it for the bakcing dir - after mounting fd keeps pointing to the original empty directory, and not to the new incfs instance - some operations fail on these paths with EPERM This CL adds support for such paths to all mounting operations Bug: 198657657 Test: manual Change-Id: I1b703ba7750302fc808299bfaa67293a2bfa4784 Merged-In: I1b703ba7750302fc808299bfaa67293a2bfa4784
-rw-r--r--incfs/MountRegistry.cpp45
-rw-r--r--incfs/incfs.cpp103
-rw-r--r--incfs/include/path.h18
-rw-r--r--incfs/path.cpp21
4 files changed, 144 insertions, 43 deletions
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 @@ static bool forEachLine(base::borrowed_fd fd, Callback&& cb) {
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 @@ bool MountRegistry::Mounts::loadFrom(base::borrowed_fd fd, std::string_view file
}
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 @@ bool MountRegistry::Mounts::loadFrom(base::borrowed_fd fd, std::string_view file
<< 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 @@ static ab::unique_fd openRaw(std::string_view file) {
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 @@ static int isValidMountTarget(const char* path) {
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) {
- return err;
+ auto fd = openAt(dirFd, entry->d_name, O_DIRECTORY);
+ if (!fd.ok()) {
+ return -errno;
}
- if (const auto err = ::rmdir(fullPath.c_str()); err != 0) {
+ if (const auto err = rmDirContent(fd.get())) {
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 @@ static std::string makeMountOptionsString(IncFsMountOptions options) {
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 @@ static IncFsControl* makeControl(const char* root) {
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 @@ IncFsControl* IncFs_Mount(const char* backingPath, const char* targetDir,
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 @@ IncFsControl* IncFs_Open(const char* dir) {
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 @@ IncFsErrorCode IncFs_MakeFile(const IncFsControl* control, const char* path, int
<< " 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 @@ IncFsErrorCode IncFs_BindMount(const char* sourceDir, const char* targetDir) {
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 @@ IncFsErrorCode IncFs_Unmount(const char* dir) {
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 @@ namespace details {
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 @@ int isEmptyDir(std::string_view dir);
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 @@ std::string normalize(std::string_view path) {
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 @@ std::string fromFd(int fd) {
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())) {