Support umount in helper_process
Currently mount points cleanup is called directly in imageloader_main
without sandboxing. In order for cleanup to work in non-root user in
sandboxed environment (dbus call), I provide a method in helper_process
to perform umount in sandbox as root.
It also fixes a bug in message deserialization where message could be
cut shorter accidentally due to encoding with string delimeter in the
middle.
BUG=chromium:784031,chromium:782334
TEST=unittest, mount/umount images on DuT
Change-Id: Ib4c7522c96165c6ffae4cb0342137f8a0bdcc07b
Reviewed-on: https://chromium-review.googlesource.com/767024
Commit-Ready: Xiaochu Liu <xiaochu@chromium.org>
Tested-by: Xiaochu Liu <xiaochu@chromium.org>
Reviewed-by: Greg Kerr <kerrnel@chromium.org>
diff --git a/helper_process.cc b/helper_process.cc
index 583d606..de107fd 100644
--- a/helper_process.cc
+++ b/helper_process.cc
@@ -46,31 +46,11 @@
pid_ = p.Pid();
}
-bool HelperProcess::SendMountCommand(int fd, const std::string& path,
- FileSystem fs_type,
- const std::string& table) {
- struct msghdr msg = {0};
- char fds[CMSG_SPACE(sizeof(fd))];
- memset(fds, '\0', sizeof(fds));
-
- MountImage msg_proto;
- msg_proto.set_mount_path(path);
- msg_proto.set_table(table);
-
- // Convert the internal enum to the protobuf enum.
- switch (fs_type) {
- case FileSystem::kExt4:
- msg_proto.set_fs_type(MountImage::EXT4);
- break;
- case FileSystem::kSquashFS:
- msg_proto.set_fs_type(MountImage::SQUASH);
- break;
- default:
- LOG(FATAL) << "Unknown file system type passed to helper process.";
- }
-
+std::unique_ptr<CommandResponse> HelperProcess::SendCommand(
+ const ImageCommand& image_command, struct msghdr* msg) {
+ // Serialize message object into string.
std::string msg_str;
- if (!msg_proto.SerializeToString(&msg_str))
+ if (!image_command.SerializeToString(&msg_str))
LOG(FATAL) << "error serializing protobuf";
// iov takes a non-const pointer.
@@ -81,9 +61,39 @@
iov[0].iov_base = buffer;
iov[0].iov_len = sizeof(buffer);
- msg.msg_iov = iov;
- msg.msg_iovlen = sizeof(iov) / sizeof(iov[0]);
+ msg->msg_iov = iov;
+ msg->msg_iovlen = sizeof(iov) / sizeof(iov[0]);
+ if (sendmsg(control_fd_.get(), msg, 0) < 0) PLOG(FATAL) << "sendmsg failed";
+
+ return WaitForResponse();
+}
+
+bool HelperProcess::SendMountCommand(int fd, const std::string& path,
+ FileSystem fs_type,
+ const std::string& table) {
+ struct msghdr msg = {0};
+ char fds[CMSG_SPACE(sizeof(fd))];
+ memset(fds, '\0', sizeof(fds));
+
+ // 1. Construct message object.
+ ImageCommand image_command;
+ image_command.mutable_mount_command()->set_mount_path(path);
+ image_command.mutable_mount_command()->set_table(table);
+
+ // Convert the internal enum to the protobuf enum.
+ switch (fs_type) {
+ case FileSystem::kExt4:
+ image_command.mutable_mount_command()->set_fs_type(MountCommand::EXT4);
+ break;
+ case FileSystem::kSquashFS:
+ image_command.mutable_mount_command()->set_fs_type(MountCommand::SQUASH);
+ break;
+ default:
+ LOG(FATAL) << "Unknown file system type passed to helper process.";
+ }
+
+ // 2. Encode the fd into message.
msg.msg_control = fds;
msg.msg_controllen = sizeof(fds);
@@ -96,12 +106,43 @@
memmove(CMSG_DATA(cmsg), &fd, sizeof(fd));
msg.msg_controllen = cmsg->cmsg_len;
- if (sendmsg(control_fd_.get(), &msg, 0) < 0) PLOG(FATAL) << "sendmsg failed";
-
- return WaitForResponse();
+ // 3. Send the command.
+ return SendCommand(image_command, &msg)->success();
}
-bool HelperProcess::WaitForResponse() {
+bool HelperProcess::SendUnmountAllCommand(bool dry_run,
+ const std::string& rootpath,
+ std::vector<std::string>* paths) {
+ struct msghdr msg = {0};
+
+ // 1. Construct message object.
+ ImageCommand image_command;
+ image_command.mutable_unmount_all_command()->set_dry_run(dry_run);
+ image_command.mutable_unmount_all_command()->set_unmount_rootpath(rootpath);
+
+ // 2. Send the command.
+ std::unique_ptr<CommandResponse> response = SendCommand(image_command, &msg);
+
+ // 3. Process return value.
+ for (int i = 0; i < response->paths_size(); i++) {
+ std::string path(response->paths(i));
+ paths->push_back(path);
+ }
+ return response->success();
+}
+
+bool HelperProcess::SendUnmountCommand(const std::string& path) {
+ struct msghdr msg = {0};
+
+ // 1. Construct message object.
+ ImageCommand image_command;
+ image_command.mutable_unmount_command()->set_unmount_path(path);
+
+ // 2. Send the command.
+ return SendCommand(image_command, &msg)->success();
+}
+
+std::unique_ptr<CommandResponse> HelperProcess::WaitForResponse() {
struct pollfd pfd;
pfd.fd = control_fd_.get();
pfd.events = POLLIN;
@@ -109,20 +150,20 @@
int rc = poll(&pfd, 1, /*timeout=*/ 2000);
PCHECK(rc >= 0 || errno == EINTR);
+ std::unique_ptr<CommandResponse> response =
+ std::make_unique<CommandResponse>();
if (pfd.revents & POLLIN) {
char buffer[4096];
memset(buffer, '\0', sizeof(buffer));
ssize_t bytes = read(control_fd_.get(), buffer, sizeof(buffer));
PCHECK(bytes != -1);
- MountResponse response;
- if (!response.ParseFromArray(buffer, bytes)) {
+ if (!response->ParseFromArray(buffer, bytes)) {
LOG(FATAL) << "could not deserialize protobuf: " << buffer;
}
- return response.success();
}
- return false;
+ return response;
}
} // namespace imageloader
diff --git a/helper_process.h b/helper_process.h
index a6a7da4..e78a01d 100644
--- a/helper_process.h
+++ b/helper_process.h
@@ -10,11 +10,18 @@
#include <base/files/scoped_file.h>
#include <base/macros.h>
+// Forward declare classes in sys/socket.h
+struct msghdr;
+
namespace imageloader {
// Forward declare the FileSystem enum.
enum class FileSystem;
+// Forward declare classes in ipc.pb.h
+class ImageCommand;
+class CommandResponse;
+
// Tracks a helper subprocess. Handles forking, cleaning up on termination, and
// IPC.
class HelperProcess {
@@ -33,17 +40,33 @@
FileSystem fs_type,
const std::string& table);
+ // Sends a message telling the helper process to enumerate all mount point
+ // paths with prefix of |rootpath| and returns them with |paths|. If
+ // |dry_run| is true, no mount points are unmounted. If |dry_run| is false,
+ // all mount points returned in |paths| are unmounted.
+ virtual bool SendUnmountAllCommand(bool dry_run,
+ const std::string& rootpath,
+ std::vector<std::string>* paths);
+
+ // Sends a message telling the helper process to umount mount point at
+ // |path|.
+ virtual bool SendUnmountCommand(const std::string& path);
+
const pid_t pid() { return pid_; }
protected:
// Waits for a reply from the helper process indicating if the mount succeeded
// or failed.
- virtual bool WaitForResponse();
+ virtual std::unique_ptr<CommandResponse> WaitForResponse();
pid_t pid_{0};
base::ScopedFD control_fd_;
private:
+ // Constructs msghdr and sends it.
+ virtual std::unique_ptr<CommandResponse> SendCommand(
+ const ImageCommand& msg_proto, struct msghdr* msg);
+
DISALLOW_COPY_AND_ASSIGN(HelperProcess);
};
diff --git a/imageloader_impl.cc b/imageloader_impl.cc
index 28081c2..ff85c10 100644
--- a/imageloader_impl.cc
+++ b/imageloader_impl.cc
@@ -105,6 +105,18 @@
return RemoveComponentAtPath(name, component_root, component_path);
}
+bool ImageLoaderImpl::CleanupAll(bool dry_run,
+ const base::FilePath& parent_dir,
+ std::vector<std::string>* paths,
+ HelperProcess* process) {
+ return process->SendUnmountAllCommand(dry_run, parent_dir.value(), paths);
+}
+
+bool ImageLoaderImpl::Cleanup(const base::FilePath& path,
+ HelperProcess* process) {
+ return process->SendUnmountCommand(path.value());
+}
+
bool ImageLoaderImpl::RemoveComponentAtPath(
const std::string& name,
const base::FilePath& component_root,
diff --git a/imageloader_impl.h b/imageloader_impl.h
index 7c84431..7692b87 100644
--- a/imageloader_impl.h
+++ b/imageloader_impl.h
@@ -40,8 +40,20 @@
bool RegisterComponent(const std::string& name, const std::string& version,
const std::string& component_folder_abs_path);
+ // Remove a component.
bool RemoveComponent(const std::string& name);
+ // Enumerates all mount point paths with prefix of |parent_dir| and returns
+ // them with |paths|. If |dry_run| is true, no mount points are unmounted.
+ // If |dry_run| is false, all mount points returned in |paths| are unmounted.
+ bool CleanupAll(bool dry_run,
+ const base::FilePath& parent_dir,
+ std::vector<std::string>* paths,
+ HelperProcess* process);
+
+ // Cleanup a mount point at |path|.
+ bool Cleanup(const base::FilePath& path, HelperProcess* process);
+
// Get component version given component name.
std::string GetComponentVersion(const std::string& name);
diff --git a/imageloader_main.cc b/imageloader_main.cc
index 79bcacc..d72c99c 100644
--- a/imageloader_main.cc
+++ b/imageloader_main.cc
@@ -90,33 +90,6 @@
return 1;
}
- if (FLAGS_unmount_all) {
- imageloader::VerityMounter mounter;
- std::vector<base::FilePath> paths;
- const base::FilePath parent_dir(FLAGS_loaded_mounts_base);
- bool success = mounter.CleanupAll(FLAGS_dry_run, parent_dir, &paths);
- if (FLAGS_dry_run) {
- for (const auto& path : paths) {
- std::cout << path.value() << "\n";
- }
- }
- if (!success) {
- LOG(ERROR) << "--unmount_all failed!";
- return 1;
- }
- return 0;
- }
-
- if (FLAGS_unmount) {
- if (FLAGS_mount_point.empty()) {
- LOG(ERROR) << "--mount_point=path must be set with --unmount";
- return 1;
- }
-
- imageloader::VerityMounter mounter;
- return mounter.Cleanup(base::FilePath(FLAGS_mount_point)) ? 0 : 1;
- }
-
// Executing this as the helper process if specified.
if (FLAGS_mount_helper_fd >= 0) {
CHECK_GT(FLAGS_mount_helper_fd, -1);
@@ -171,6 +144,48 @@
return 0;
}
+ // Unmount all component mount points and exit.
+ if (FLAGS_unmount_all) {
+ // Run with minimal privilege.
+ imageloader::ImageLoader::EnterSandbox();
+
+ imageloader::ImageLoaderImpl loader(std::move(config));
+ std::vector<std::string> paths;
+ const base::FilePath parent_dir(FLAGS_loaded_mounts_base);
+ bool success = loader.CleanupAll(FLAGS_dry_run,
+ parent_dir, &paths, helper_process.get());
+ if (FLAGS_dry_run) {
+ for (const auto& path : paths) {
+ std::cout << path << "\n";
+ }
+ }
+ if (!success) {
+ LOG(ERROR) << "--unmount_all failed!";
+ return 1;
+ }
+ return 0;
+ }
+
+ // Unmount a component mount point and exit.
+ if (FLAGS_unmount) {
+ // Run with minimal privilege.
+ imageloader::ImageLoader::EnterSandbox();
+
+ if (FLAGS_mount_point.empty()) {
+ LOG(ERROR) << "--mount_point=path must be set with --unmount";
+ return 1;
+ }
+
+ imageloader::ImageLoaderImpl loader(std::move(config));
+ const base::FilePath path(FLAGS_mount_point);
+ bool success = loader.Cleanup(path, helper_process.get());
+ if (!success) {
+ LOG(ERROR) << "--unmount failed!";
+ return 1;
+ }
+ return 0;
+ }
+
// Run as a daemon and wait for dbus requests.
imageloader::ImageLoader daemon(std::move(config), std::move(helper_process));
daemon.Run();
diff --git a/imageloader_unittest.cc b/imageloader_unittest.cc
index 03ef911..b7876ab 100644
--- a/imageloader_unittest.cc
+++ b/imageloader_unittest.cc
@@ -172,6 +172,50 @@
EXPECT_EQ(expected_path, mnt_path);
}
+TEST_F(ImageLoaderTest, CleanupAll) {
+ Keys keys;
+ keys.push_back(
+ std::vector<uint8_t>(std::begin(kDevPublicKey), std::end(kDevPublicKey)));
+
+ auto helper_mock = std::make_unique<MockHelperProcess>();
+ EXPECT_CALL(*helper_mock, SendUnmountAllCommand(_, _, _))
+ .Times(1);
+ ON_CALL(*helper_mock, SendUnmountAllCommand(_, _, _))
+ .WillByDefault(testing::Return(true));
+
+ base::ScopedTempDir scoped_mount_dir;
+ ASSERT_TRUE(scoped_mount_dir.CreateUniqueTempDir());
+
+ ImageLoaderConfig config(keys, temp_dir_.value().c_str(),
+ scoped_mount_dir.path().value().c_str());
+ ImageLoaderImpl loader(std::move(config));
+
+ base::FilePath rootpath("/");
+ std::vector<std::string> paths;
+ EXPECT_EQ(loader.CleanupAll(true, rootpath, &paths, helper_mock.get()), true);
+}
+
+TEST_F(ImageLoaderTest, Cleanup) {
+ Keys keys;
+ keys.push_back(
+ std::vector<uint8_t>(std::begin(kDevPublicKey), std::end(kDevPublicKey)));
+
+ auto helper_mock = std::make_unique<MockHelperProcess>();
+ EXPECT_CALL(*helper_mock, SendUnmountCommand(_)).Times(1);
+ ON_CALL(*helper_mock, SendUnmountCommand(_))
+ .WillByDefault(testing::Return(true));
+
+ base::ScopedTempDir scoped_mount_dir;
+ ASSERT_TRUE(scoped_mount_dir.CreateUniqueTempDir());
+
+ ImageLoaderConfig config(keys, temp_dir_.value().c_str(),
+ scoped_mount_dir.path().value().c_str());
+ ImageLoaderImpl loader(std::move(config));
+
+ base::FilePath path("/");
+ EXPECT_EQ(loader.Cleanup(path, helper_mock.get()), true);
+}
+
TEST_F(ImageLoaderTest, LoadExt4Image) {
Keys keys;
keys.push_back(
diff --git a/ipc.proto b/ipc.proto
index e2aeb83..efd4d09 100644
--- a/ipc.proto
+++ b/ipc.proto
@@ -7,21 +7,36 @@
package imageloader;
-// Optional fields are considered best practice, but since the client and server
-// come from the same code base, this uses required fields to save on
-// validation.
+message ImageCommand {
+ oneof op {
+ MountCommand mount_command = 1;
+ UnmountAllCommand unmount_all_command = 2;
+ UnmountCommand unmount_command = 3;
+ }
+}
-message MountImage {
- required string mount_path = 1;
- required string table = 2;
+message MountCommand {
enum FileSystem {
- UNKNOWN = 0;
SQUASH = 1;
EXT4 = 2;
}
- required FileSystem fs_type = 3 [default = UNKNOWN];
+ required string mount_path = 2;
+ required string table = 3;
+ required FileSystem fs_type = 4 [default = SQUASH];
}
-message MountResponse {
- required bool success = 1;
+message UnmountAllCommand {
+ required bool dry_run = 5 [default = true];
+ required string unmount_rootpath = 6;
+}
+
+message UnmountCommand {
+ required string unmount_path = 7;
+}
+
+message CommandResponse {
+ required bool success = 1 [default = false];
+ // |paths| are only set for response of UnmountAllCommand: paths that can be
+ // unmounted.
+ repeated string paths = 2;
}
diff --git a/mock_helper_process.h b/mock_helper_process.h
index 66bf699..61c54c2 100644
--- a/mock_helper_process.h
+++ b/mock_helper_process.h
@@ -22,6 +22,12 @@
MOCK_METHOD4(SendMountCommand, bool(int, const std::string&,
FileSystem, const std::string&));
+ MOCK_METHOD3(SendUnmountAllCommand, bool(bool,
+ const std::string&,
+ std::vector<std::string>* paths));
+
+ MOCK_METHOD1(SendUnmountCommand, bool(const std::string&));
+
private:
DISALLOW_COPY_AND_ASSIGN(MockHelperProcess);
};
diff --git a/mount_helper.cc b/mount_helper.cc
index a5d6ea2..447b3a6 100644
--- a/mount_helper.cc
+++ b/mount_helper.cc
@@ -80,49 +80,73 @@
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- if (cmsg == nullptr) LOG(FATAL) << "no cmsg";
-
- if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS)
- LOG(FATAL) << "cmsg is wrong type";
-
- memmove(&pending_fd_, CMSG_DATA(cmsg), sizeof(pending_fd_));
-
- MountImage command;
- if (!command.ParseFromArray(buffer, strlen(buffer)))
+ ImageCommand command;
+ std::string msg_str;
+ msg_str.assign(buffer, bytes - 1);
+ if (!command.ParseFromString(msg_str))
LOG(FATAL) << "error parsing protobuf";
// Handle the command to mount the image.
- MountResponse response = HandleCommand(command);
+ CommandResponse response = HandleCommand(command, cmsg);
// Reply to the parent process with the success or failure.
SendResponse(response);
}
-MountResponse MountHelper::HandleCommand(MountImage& command) {
- // Convert the fs type to a string.
- std::string fs_type;
- switch (command.fs_type()) {
- case MountImage::EXT4:
- fs_type = "ext4";
- break;
- case MountImage::SQUASH:
- fs_type = "squashfs";
- break;
- default:
- LOG(FATAL) << "unknown filesystem type";
+CommandResponse MountHelper::HandleCommand(ImageCommand& imageCommand,
+ struct cmsghdr* cmsg) {
+ CommandResponse response;
+ if (imageCommand.has_mount_command()) {
+ MountCommand command = imageCommand.mount_command();
+ if (cmsg == nullptr)
+ LOG(FATAL) << "no cmsg";
+
+ if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS)
+ LOG(FATAL) << "cmsg is wrong type";
+
+ memmove(&pending_fd_, CMSG_DATA(cmsg), sizeof(pending_fd_));
+
+ // Convert the fs type to a string.
+ std::string fs_type;
+ switch (command.fs_type()) {
+ case MountCommand::EXT4:
+ fs_type = "ext4";
+ break;
+ case MountCommand::SQUASH:
+ fs_type = "squashfs";
+ break;
+ default:
+ LOG(FATAL) << "unknown filesystem type";
+ }
+
+ bool status = mounter_.Mount(base::ScopedFD(pending_fd_),
+ base::FilePath(command.mount_path()),
+ fs_type,
+ command.table());
+ if (!status)
+ LOG(ERROR) << "mount failed";
+
+ response.set_success(status);
+ } else if (imageCommand.has_unmount_all_command()) {
+ UnmountAllCommand command = imageCommand.unmount_all_command();
+ std::vector<base::FilePath> paths;
+ const base::FilePath root_dir(command.unmount_rootpath());
+ response.set_success(mounter_.CleanupAll(command.dry_run(),
+ root_dir, &paths));
+ for (const auto& path : paths) {
+ const std::string path_(path.value());
+ response.add_paths(path_);
+ }
+ } else if (imageCommand.has_unmount_command()) {
+ UnmountCommand command = imageCommand.unmount_command();
+ const base::FilePath path(command.unmount_path());
+ response.set_success(mounter_.Cleanup(path));
+ } else {
+ LOG(ERROR) << "unknown operations";
}
-
- bool status = mounter_.Mount(base::ScopedFD(pending_fd_),
- base::FilePath(command.mount_path()),
- fs_type,
- command.table());
- if (!status) LOG(ERROR) << "mount failed";
-
- MountResponse response;
- response.set_success(status);
return response;
}
-void MountHelper::SendResponse(MountResponse& response) {
+void MountHelper::SendResponse(CommandResponse& response) {
std::string response_str;
if (!response.SerializeToString(&response_str))
LOG(FATAL) << "failed to serialize protobuf";
diff --git a/mount_helper.h b/mount_helper.h
index 499a5b3..7fc0b06 100644
--- a/mount_helper.h
+++ b/mount_helper.h
@@ -14,6 +14,8 @@
using MessageLoopForIO = base::MessageLoopForIO;
+struct cmsghdr;
+
namespace imageloader {
// Main loop for the Mount helper process.
@@ -32,8 +34,9 @@
void OnFileCanWriteWithoutBlocking(int fd) override {}
private:
- MountResponse HandleCommand(MountImage& command);
- void SendResponse(MountResponse& response);
+ CommandResponse HandleCommand(ImageCommand& command,
+ struct cmsghdr* cmsg);
+ void SendResponse(CommandResponse& response);
base::ScopedFD control_fd_;
MessageLoopForIO::FileDescriptorWatcher control_watcher_;
diff --git a/seccomp/imageloader-helper-seccomp-amd64.policy b/seccomp/imageloader-helper-seccomp-amd64.policy
index c13d485..d51eced 100644
--- a/seccomp/imageloader-helper-seccomp-amd64.policy
+++ b/seccomp/imageloader-helper-seccomp-amd64.policy
@@ -70,6 +70,7 @@
statfs: 1
sysinfo: 1
tgkill: 1
+umount2: 1
uname: 1
unlink: 1
wait4: 1
diff --git a/seccomp/imageloader-helper-seccomp-arm.policy b/seccomp/imageloader-helper-seccomp-arm.policy
index 53f7da2..d4f2942 100644
--- a/seccomp/imageloader-helper-seccomp-arm.policy
+++ b/seccomp/imageloader-helper-seccomp-arm.policy
@@ -71,6 +71,7 @@
sysinfo: 1
tgkill: 1
ugetrlimit: 1
+umount2: 1
uname: 1
wait4: 1
write: 1
diff --git a/seccomp/imageloader-helper-seccomp-x86.policy b/seccomp/imageloader-helper-seccomp-x86.policy
index cfcba52..62cb278 100644
--- a/seccomp/imageloader-helper-seccomp-x86.policy
+++ b/seccomp/imageloader-helper-seccomp-x86.policy
@@ -72,6 +72,7 @@
tgkill: 1
time: 1
ugetrlimit: 1
+umount2: 1
uname: 1
unlink: 1
wait4: 1