Use privileged helper process to mount images.
Uses a privileged helper process to mount images, so that the untrusted
data is only read by the non-root, sandboxed main process.
BUG=chromium:682968
CQ-DEPEND=CL:434201
TEST=test_that -b ${BOARD} ${DUT_ip} platform_ImageLoaderServer
Change-Id: I7aa12d998065c0c2c81628f23ed52505333b4e8c
Reviewed-on: https://chromium-review.googlesource.com/434126
Commit-Ready: Greg Kerr <kerrnel@chromium.org>
Tested-by: Greg Kerr <kerrnel@chromium.org>
Reviewed-by: Jorge Lucangeli Obes <jorgelo@chromium.org>
diff --git a/.presubmitignore b/.presubmitignore
index d102f14..a901455 100644
--- a/.presubmitignore
+++ b/.presubmitignore
@@ -5,3 +5,4 @@
imageloader.conf
dbus_permissions/org.chromium.ImageLoader.conf
seccomp/imageloader-seccomp-x86.policy
+seccomp/imageloader-helper-seccomp-x86.policy
diff --git a/component.cc b/component.cc
index c71ec36..d2e7b56 100644
--- a/component.cc
+++ b/component.cc
@@ -24,6 +24,8 @@
#include <crypto/sha2.h>
#include <crypto/signature_verifier.h>
+#include "helper_process.h"
+
namespace imageloader {
namespace {
@@ -130,7 +132,7 @@
return manifest_;
}
-bool Component::Mount(VerityMounter* mounter, const base::FilePath& dest_dir) {
+bool Component::Mount(HelperProcess* mounter, const base::FilePath& dest_dir) {
if (!initialized_) {
NOTREACHED() << "Component not initialized";
return false;
@@ -151,12 +153,7 @@
}
base::ScopedFD image_fd(image.TakePlatformFile());
- // The mount point is not yet taken, so go ahead.
- if (!mounter->Mount(image_fd, dest_dir, table)) {
- LOG(ERROR) << "Failed to mount image.";
- return false;
- }
- return true;
+ return mounter->SendMountCommand(image_fd.get(), dest_dir.value(), table);
}
bool Component::ParseManifest() {
diff --git a/component.h b/component.h
index 03524a2..33ad211 100644
--- a/component.h
+++ b/component.h
@@ -24,7 +24,7 @@
#include <base/macros.h>
#include <crypto/secure_hash.h>
-#include "verity_mounter.h"
+#include "helper_process.h"
namespace imageloader {
@@ -56,7 +56,7 @@
bool CopyTo(const base::FilePath& dest_dir);
// Mounts the component into |mount_point|. |mount_point| must already exist.
- bool Mount(VerityMounter* mounter, const base::FilePath& mount_point);
+ bool Mount(HelperProcess* mounter, const base::FilePath& mount_point);
// Return a reference to the parsed manifest object, which is stored in
// memory.
diff --git a/component_unittest.cc b/component_unittest.cc
index 8080458..d194038 100644
--- a/component_unittest.cc
+++ b/component_unittest.cc
@@ -10,9 +10,8 @@
#include <string>
#include <vector>
-#include "mock_verity_mounter.h"
+#include "mock_helper_process.h"
#include "test_utilities.h"
-#include "verity_mounter.h"
#include <base/files/file_path.h>
#include <base/files/file_util.h>
@@ -143,10 +142,14 @@
ASSERT_TRUE(base::CreateDirectory(copied_dir));
ASSERT_TRUE(base::SetPosixFilePermissions(copied_dir, kComponentDirPerms));
- auto mount_mock = base::MakeUnique<MockVerityMounter>();
- ON_CALL(*mount_mock, Mount(_, _, _)).WillByDefault(testing::Return(true));
- EXPECT_CALL(*mount_mock, Mount(_, _, _)).Times(1);
- ASSERT_TRUE(copied_component.Mount(mount_mock.get(), mount_dir));
+ // Note: this fails to test the actual mounting process since it is just a
+ // mock here. The platform_ImageLoader autotest tests the real helper
+ // process running as a dbus service.
+ auto helper_mock = base::MakeUnique<MockHelperProcess>();
+ EXPECT_CALL(*helper_mock, SendMountCommand(_, _, _)).Times(1);
+ ON_CALL(*helper_mock, SendMountCommand(_, _, _))
+ .WillByDefault(testing::Return(true));
+ ASSERT_TRUE(copied_component.Mount(helper_mock.get(), mount_dir));
}
TEST_F(ComponentTest, CheckFilesAfterCopy) {
diff --git a/dbus_adaptors/org.chromium.ImageLoaderInterface.xml b/dbus_adaptors/org.chromium.ImageLoaderInterface.xml
index 68dc448..6b0c9e5 100644
--- a/dbus_adaptors/org.chromium.ImageLoaderInterface.xml
+++ b/dbus_adaptors/org.chromium.ImageLoaderInterface.xml
@@ -50,5 +50,21 @@
</tp:docstring>
</arg>
</method>
+ <method name="LoadComponent">
+ <tp:docstring>
+ Loads the component, if and only if the component verifies the
+ signature check, and returns the mount point.
+ </tp:docstring>
+ <arg name="name" type="s" direction="in">
+ <tp:docstring>
+ The name of the component.
+ </tp:docstring>
+ </arg>
+ <arg name="mount_point" type="s" direction="out">
+ <tp:docstring>
+ The mount point of the verified and mounted component.
+ </tp:docstring>
+ </arg>
+ </method>
</interface>
</node>
diff --git a/dbus_permissions/org.chromium.ImageLoader.conf b/dbus_permissions/org.chromium.ImageLoader.conf
index de66092..c2fdbbd 100644
--- a/dbus_permissions/org.chromium.ImageLoader.conf
+++ b/dbus_permissions/org.chromium.ImageLoader.conf
@@ -22,6 +22,9 @@
<allow send_destination="org.chromium.ImageLoader"
send_interface="org.chromium.ImageLoaderInterface"
send_member="GetComponentVersion" />
+ <allow send_destination="org.chromium.ImageLoader"
+ send_interface="org.chromium.ImageLoaderInterface"
+ send_member="LoadComponent" />
</policy>
<policy context="default">
<deny send_destination="org.chromium.ImageLoader"/>
diff --git a/helper_process.cc b/helper_process.cc
new file mode 100644
index 0000000..76db008
--- /dev/null
+++ b/helper_process.cc
@@ -0,0 +1,113 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "helper_process.h"
+
+#include <poll.h>
+#include <signal.h>
+#include <sys/socket.h>
+
+#include <utility>
+#include <vector>
+
+#include <base/process/launch.h>
+
+#include "ipc.pb.h"
+
+namespace imageloader {
+
+void HelperProcess::Start(int argc, char* argv[], const std::string& fd_arg) {
+ int control[2];
+
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, control) != 0)
+ PLOG(FATAL) << "socketpair failed";
+
+ control_fd_.reset(control[0]);
+ const int subprocess_fd = control[1];
+
+ CHECK_GE(argc, 1);
+ std::vector<std::string> child_argv;
+ for (int i = 0; i < argc; i++)
+ child_argv.push_back(argv[i]);
+
+ child_argv.push_back(fd_arg + "=" + std::to_string(subprocess_fd));
+
+ base::FileHandleMappingVector fd_mapping;
+ fd_mapping.push_back({subprocess_fd, subprocess_fd});
+
+ base::LaunchOptions options;
+ options.fds_to_remap = &fd_mapping;
+
+ base::Process p = base::LaunchProcess(child_argv, options);
+ CHECK(p.IsValid());
+ pid_ = p.Pid();
+}
+
+bool HelperProcess::SendMountCommand(int fd, const std::string& path,
+ 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);
+
+ std::string msg_str;
+ if (!msg_proto.SerializeToString(&msg_str))
+ LOG(FATAL) << "error serializing protobuf";
+
+ // iov takes a non-const pointer.
+ char buffer[msg_str.size() + 1];
+ memcpy(buffer, msg_str.c_str(), sizeof(buffer));
+
+ struct iovec iov[1];
+ 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_control = fds;
+ msg.msg_controllen = sizeof(fds);
+
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+
+ // Move the file descriptor into the payload.
+ 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();
+}
+
+bool HelperProcess::WaitForResponse() {
+ struct pollfd pfd;
+ pfd.fd = control_fd_.get();
+ pfd.events = POLLIN;
+
+ int rc = poll(&pfd, 1, /*timeout=*/ 2000);
+ PCHECK(rc >= 0 || errno == EINTR);
+
+ 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)) {
+ LOG(FATAL) << "could not deserialize protobuf: " << buffer;
+ }
+ return response.success();
+ }
+
+ return false;
+}
+
+} // namespace imageloader
diff --git a/helper_process.h b/helper_process.h
new file mode 100644
index 0000000..91a9627
--- /dev/null
+++ b/helper_process.h
@@ -0,0 +1,48 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IMAGELOADER_HELPER_PROCESS_H_
+#define IMAGELOADER_HELPER_PROCESS_H_
+
+#include <sys/types.h>
+
+#include <base/files/scoped_file.h>
+#include <base/macros.h>
+
+namespace imageloader {
+
+// Tracks a helper subprocess. Handles forking, cleaning up on termination, and
+// IPC.
+class HelperProcess {
+ public:
+ HelperProcess() = default;
+ virtual ~HelperProcess() = default;
+
+ // Re-execs imageloader with a new argument: "|fd_arg|=N", where N is the side
+ // of |control_fd|. This tells the subprocess to start up a different
+ // mainloop.
+ void Start(int argc, char* argv[], const std::string& fd_arg);
+
+ // Sends a message telling the helper process to mount the file backed by |fd|
+ // at the |path|.
+ virtual bool SendMountCommand(int fd, const std::string& path,
+ const std::string& table);
+
+ 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();
+
+ pid_t pid_{0};
+ base::ScopedFD control_fd_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HelperProcess);
+};
+
+} // namespace imageloader
+
+#endif // IMAGELOADER_HELPER_PROCESS_H_
diff --git a/imageloader.cc b/imageloader.cc
index 50e2944..bebd31b 100644
--- a/imageloader.cc
+++ b/imageloader.cc
@@ -1,4 +1,4 @@
-// Copyright 2016 The Chromium OS Authors. All rights reserved.
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -23,15 +23,17 @@
const char ImageLoader::kImageLoaderUserName[] = "imageloaderd";
const int ImageLoader::kShutdownTimeoutMilliseconds = 20000;
-ImageLoader::ImageLoader(ImageLoaderConfig config)
+ImageLoader::ImageLoader(ImageLoaderConfig config,
+ std::unique_ptr<HelperProcess> process)
: DBusServiceDaemon(kImageLoaderServiceName,
"/org/chromium/ImageLoader/Manager"),
- impl_(std::move(config)) {}
+ impl_(std::move(config)),
+ helper_process_(std::move(process)) {}
ImageLoader::~ImageLoader() {}
-int ImageLoader::OnInit() {
- // Run with minimal privileges.
+// static
+void ImageLoader::EnterSandbox() {
struct minijail* jail = minijail_new();
minijail_no_new_privs(jail);
minijail_use_seccomp_filter(jail);
@@ -39,16 +41,26 @@
minijail_reset_signal_mask(jail);
minijail_namespace_ipc(jail);
minijail_namespace_net(jail);
- minijail_namespace_user(jail);
minijail_remount_proc_readonly(jail);
CHECK_EQ(0, minijail_change_user(jail, kImageLoaderUserName));
CHECK_EQ(0, minijail_change_group(jail, kImageLoaderGroupName));
minijail_enter(jail);
+}
+
+int ImageLoader::OnInit() {
+ // Run with minimal privileges.
+ EnterSandbox();
int return_code = brillo::DBusServiceDaemon::OnInit();
if (return_code != EX_OK)
return return_code;
+ process_reaper_.Register(this);
+ process_reaper_.WatchForChild(
+ FROM_HERE, helper_process_->pid(),
+ base::Bind(&ImageLoader::OnSubprocessExited, weak_factory_.GetWeakPtr(),
+ helper_process_->pid()));
+
PostponeShutdown();
return EX_OK;
@@ -68,6 +80,10 @@
brillo::DBusServiceDaemon::OnShutdown(return_code);
}
+void ImageLoader::OnSubprocessExited(pid_t pid, const siginfo_t& info) {
+ LOG(FATAL) << "Subprocess " << pid << " exited unexpectedly.";
+}
+
void ImageLoader::PostponeShutdown() {
shutdown_callback_.Reset(base::Bind(&brillo::Daemon::Quit,
base::Unretained(this)));
@@ -95,7 +111,7 @@
bool ImageLoader::LoadComponent(brillo::ErrorPtr* err, const std::string& name,
std::string* out_mount_point) {
- *out_mount_point = impl_.LoadComponent(name);
+ *out_mount_point = impl_.LoadComponent(name, helper_process_.get());
PostponeShutdown();
return true;
}
diff --git a/imageloader.gyp b/imageloader.gyp
index 157aa02..2750d7e 100644
--- a/imageloader.gyp
+++ b/imageloader.gyp
@@ -8,11 +8,22 @@
'libbrillo-<(libbase_ver)',
'libchrome-<(libbase_ver)',
'libminijail',
+ 'protobuf-lite',
],
},
},
'targets': [
{
+ 'target_name': 'protos',
+ 'type': 'static_library',
+ 'variables': {
+ 'proto_in_dir': '.',
+ 'proto_out_dir': 'include',
+ },
+ 'sources': ['<(proto_in_dir)/ipc.proto'],
+ 'includes': ['../../platform2/common-mk/protoc.gypi'],
+ },
+ {
'target_name': 'imageloader-adaptors',
'type': 'none',
'variables': {
@@ -46,13 +57,18 @@
'type': 'static_library',
'dependencies': [
'imageloader-adaptors',
+ 'protos',
],
'sources': [
'component.cc',
'component.h',
+ 'helper_process.cc',
+ 'helper_process.h',
'imageloader.cc',
'imageloader.h',
'imageloader_impl.cc',
+ 'mount_helper.cc',
+ 'mount_helper.h',
'verity_mounter.h',
'verity_mounter.cc',
],
@@ -93,6 +109,7 @@
'imageloader_unittest.cc',
'imageloader.cc',
'imageloader.h',
+ 'mock_helper_process.h',
'test_utilities.cc',
'test_utilities.h',
],
diff --git a/imageloader.h b/imageloader.h
index 0b81b17..fe34794 100644
--- a/imageloader.h
+++ b/imageloader.h
@@ -6,13 +6,17 @@
#include <string>
+#include <signal.h>
+
#include <base/callback.h>
#include <base/cancelable_callback.h>
#include <base/memory/weak_ptr.h>
#include <brillo/daemons/dbus_daemon.h>
#include <brillo/errors/error.h>
+#include <brillo/process_reaper.h>
#include "dbus_adaptors/org.chromium.ImageLoaderInterface.h"
+#include "helper_process.h"
#include "imageloader_impl.h"
namespace imageloader {
@@ -27,7 +31,7 @@
static const char kImageLoaderGroupName[];
static const char kImageLoaderUserName[];
- ImageLoader(ImageLoaderConfig config);
+ ImageLoader(ImageLoaderConfig config, std::unique_ptr<HelperProcess> helper);
~ImageLoader();
// Implementations of the public methods interface.
@@ -44,7 +48,11 @@
// Load and mount a component.
bool LoadComponent(brillo::ErrorPtr* err, const std::string& name,
- std::string* out_mount_point);
+ std::string* out_mount_point) override;
+
+ // Sandboxes the runtime environment, using minijail. This is publicly exposed
+ // so that imageloader_main.cc can sandbox when not running as a daemon.
+ static void EnterSandbox();
protected:
int OnInit() override;
@@ -53,6 +61,11 @@
void OnShutdown(int* return_code) override;
private:
+ // Callback from ProcessReaper to notify ImageLoader that one of the
+ // subprocesses died.
+ void OnSubprocessExited(pid_t pid, const siginfo_t& info);
+ // ImageLoader exits after 20 seconds of inactivity. This function restarts
+ // the timer.
void PostponeShutdown();
// Daemon will automatically shutdown after this length of idle time.
@@ -60,6 +73,8 @@
std::unique_ptr<brillo::dbus_utils::DBusObject> dbus_object_;
ImageLoaderImpl impl_;
+ std::unique_ptr<HelperProcess> helper_process_;
+ brillo::ProcessReaper process_reaper_;
base::CancelableClosure shutdown_callback_;
org::chromium::ImageLoaderInterfaceAdaptor dbus_adaptor_{this};
diff --git a/imageloader_impl.cc b/imageloader_impl.cc
index e49cdbd..6eb6a2c 100644
--- a/imageloader_impl.cc
+++ b/imageloader_impl.cc
@@ -45,77 +45,11 @@
return mode == kComponentDirPerms;
}
-bool CreateDirectoryWithMode(const base::FilePath& full_path, int mode) {
- std::vector<base::FilePath> subpaths;
-
- // Collect a list of all parent directories.
- base::FilePath last_path = full_path;
- subpaths.push_back(full_path);
- for (base::FilePath path = full_path.DirName();
- path.value() != last_path.value(); path = path.DirName()) {
- subpaths.push_back(path);
- last_path = path;
- }
-
- // Iterate through the parents and create the missing ones.
- for (const auto& subpath : base::Reversed(subpaths)) {
- if (base::DirectoryExists(subpath)) continue;
- if (mkdir(subpath.value().c_str(), mode) == 0) continue;
- // Mkdir failed, but it might have failed with EEXIST, or some other error
- // due to the the directory appearing out of thin air. This can occur if
- // two processes are trying to create the same file system tree at the same
- // time. Check to see if it exists and make sure it is a directory.
- if (!base::DirectoryExists(subpath)) {
- PLOG(ERROR) << "Failed to create directory";
- return false;
- }
- }
- return true;
-}
-
-bool CreateMountPointIfNeeded(const base::FilePath& mount_point,
- bool* already_mounted) {
- *already_mounted = false;
- // Is this mount point somehow already taken?
- struct stat st;
- if (lstat(mount_point.value().c_str(), &st) == 0) {
- if (!S_ISDIR(st.st_mode)) {
- LOG(ERROR) << "Mount point exists but is not a directory.";
- return false;
- }
-
- base::FilePath mount_parent = mount_point.DirName();
- struct stat st2;
- if (stat(mount_parent.value().c_str(), &st2) != 0) {
- PLOG(ERROR) << "Could not stat the mount point parent";
- return false;
- }
- if (st.st_dev != st2.st_dev) {
- struct statfs st_fs;
- if (statfs(mount_point.value().c_str(), &st_fs) != 0) {
- PLOG(ERROR) << "statfs";
- return false;
- }
- if (st_fs.f_type != SQUASHFS_MAGIC || !(st_fs.f_flags & ST_NODEV) ||
- !(st_fs.f_flags & ST_NOSUID) || !(st_fs.f_flags & ST_RDONLY)) {
- LOG(ERROR) << "File system is not the expected type.";
- return false;
- }
- LOG(INFO) << "The mount point already exists: " << mount_point.value();
- *already_mounted = true;
- return true;
- }
- } else if (!CreateDirectoryWithMode(mount_point, kComponentDirPerms)) {
- LOG(ERROR) << "Failed to create mount point: " << mount_point.value();
- return false;
- }
- return true;
-}
-
} // namespace {}
bool ImageLoaderImpl::LoadComponent(const std::string& name,
- const std::string& mount_point_str) {
+ const std::string& mount_point_str,
+ HelperProcess* process) {
base::FilePath component_path;
if (!GetPathToCurrentComponentVersion(name, &component_path)) {
return false;
@@ -128,15 +62,11 @@
}
base::FilePath mount_point(mount_point_str);
- // First check if the component is already mounted and avoid unnecessary work.
- bool already_mounted = false;
- if (!CreateMountPointIfNeeded(mount_point, &already_mounted)) return false;
- if (already_mounted) return true;
-
- return component.Mount(config_.verity_mounter.get(), mount_point);
+ return component.Mount(process, mount_point);
}
-std::string ImageLoaderImpl::LoadComponent(const std::string& name) {
+std::string ImageLoaderImpl::LoadComponent(const std::string& name,
+ HelperProcess* process) {
base::FilePath component_path;
if (!GetPathToCurrentComponentVersion(name, &component_path)) {
return kBadResult;
@@ -150,15 +80,8 @@
base::FilePath mount_point(
GetMountPoint(config_.mount_path, name, component.manifest().version));
- // First check if the component is already mounted and avoid unnecessary work.
- bool already_mounted = false;
- if (!CreateMountPointIfNeeded(mount_point, &already_mounted))
- return kBadResult;
- if (already_mounted) return name;
-
- return component.Mount(config_.verity_mounter.get(), mount_point)
- ? mount_point.value()
- : kBadResult;
+ return component.Mount(process, mount_point) ? mount_point.value()
+ : kBadResult;
}
bool ImageLoaderImpl::RegisterComponent(
diff --git a/imageloader_impl.h b/imageloader_impl.h
index 63bc475..3fc0d23 100644
--- a/imageloader_impl.h
+++ b/imageloader_impl.h
@@ -11,22 +11,20 @@
#include <base/gtest_prod_util.h>
#include <base/macros.h>
-#include "verity_mounter.h"
+#include "helper_process.h"
namespace imageloader {
struct ImageLoaderConfig {
ImageLoaderConfig(const std::vector<uint8_t> key, const char* storage_path,
- const char* mount_path, std::unique_ptr<VerityMounter> ops)
+ const char* mount_path)
: key(key),
storage_dir(storage_path),
- mount_path(mount_path),
- verity_mounter(std::move(ops)) {}
+ mount_path(mount_path) {}
std::vector<uint8_t> key;
base::FilePath storage_dir;
base::FilePath mount_path;
- std::unique_ptr<VerityMounter> verity_mounter;
};
class ImageLoaderImpl {
@@ -44,10 +42,11 @@
// Load the specified component. This returns the mount point or an empty
// string on failure.
- std::string LoadComponent(const std::string& name);
+ std::string LoadComponent(const std::string& name, HelperProcess* process);
// Load the specified component at a set mount point.
- bool LoadComponent(const std::string& name, const std::string& mount_point);
+ bool LoadComponent(const std::string& name, const std::string& mount_point,
+ HelperProcess* process);
// The directory hierarchy for a component consists of the storage_root (i.e.
// `/var/lib/imageloader`), the component_root
diff --git a/imageloader_main.cc b/imageloader_main.cc
index b5f5f1e..9d073e4 100644
--- a/imageloader_main.cc
+++ b/imageloader_main.cc
@@ -8,9 +8,10 @@
#include <brillo/flag_helper.h>
#include <brillo/syslog_logging.h>
+#include "helper_process.h"
#include "imageloader.h"
#include "imageloader_impl.h"
-#include "verity_mounter.h"
+#include "mount_helper.h"
constexpr uint8_t kProdPublicKey[] = {
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
@@ -35,18 +36,33 @@
"Specifies the name of the component when using --mount.");
DEFINE_string(mount_point, "",
"Specifies the mountpoint when using --mount.");
+ DEFINE_int32(mount_helper_fd, -1,
+ "Control socket for starting an ImageLoader subprocess. Used "
+ "internally.");
brillo::FlagHelper::Init(argc, argv, "imageloader");
brillo::OpenLog("imageloader", true);
brillo::InitLog(brillo::kLogToSyslog);
+ // Executing this as the helper process if specified.
+ if (FLAGS_mount_helper_fd >= 0) {
+ CHECK_GT(FLAGS_mount_helper_fd, -1);
+ base::ScopedFD fd(FLAGS_mount_helper_fd);
+ imageloader::MountHelper mount_helper(std::move(fd));
+ return mount_helper.Run();
+ }
+
std::vector<uint8_t> key(std::begin(kProdPublicKey),
std::end(kProdPublicKey));
- auto verity_mounter = base::MakeUnique<imageloader::VerityMounter>();
- imageloader::ImageLoaderConfig config(key, kComponentsPath, kMountPath,
- std::move(verity_mounter));
+ imageloader::ImageLoaderConfig config(key, kComponentsPath, kMountPath);
+ auto helper_process = base::MakeUnique<imageloader::HelperProcess>();
+ helper_process->Start(argc, argv, "--mount_helper_fd");
+ // Load and mount the specified component and exit.
if (FLAGS_mount) {
+ // Run with minimal privilege.
+ imageloader::ImageLoader::EnterSandbox();
+
if (FLAGS_mount_point == "" || FLAGS_mount_component == "") {
LOG(ERROR) << "--mount_component=name and --mount_point=path must be set "
"with --mount";
@@ -55,14 +71,16 @@
// Access the ImageLoaderImpl directly to avoid needless dbus dependencies,
// which may not be available at early boot.
imageloader::ImageLoaderImpl loader(std::move(config));
- if (!loader.LoadComponent(FLAGS_mount_component, FLAGS_mount_point)) {
+ if (!loader.LoadComponent(FLAGS_mount_component, FLAGS_mount_point,
+ helper_process.get())) {
LOG(ERROR) << "Failed to verify and mount component.";
return 1;
}
return 0;
}
- imageloader::ImageLoader daemon(std::move(config));
+ // Run as a daemon and wait for dbus requests.
+ imageloader::ImageLoader daemon(std::move(config), std::move(helper_process));
daemon.Run();
return 0;
diff --git a/imageloader_unittest.cc b/imageloader_unittest.cc
index 575cadc..30aa2cc 100644
--- a/imageloader_unittest.cc
+++ b/imageloader_unittest.cc
@@ -11,7 +11,7 @@
#include <vector>
#include "component.h"
-#include "mock_verity_mounter.h"
+#include "mock_helper_process.h"
#include "test_utilities.h"
#include "verity_mounter.h"
@@ -37,8 +37,7 @@
ImageLoaderConfig GetConfig(const char* path) {
std::vector<uint8_t> key(std::begin(kDevPublicKey),
std::end(kDevPublicKey));
- auto ops = base::MakeUnique<MockVerityMounter>();
- ImageLoaderConfig config(key, path, "/foo", std::move(ops));
+ ImageLoaderConfig config(key, path, "/foo");
return config;
}
@@ -112,16 +111,16 @@
TEST_F(ImageLoaderTest, MountValidImage) {
std::vector<uint8_t> key(std::begin(kDevPublicKey), std::end(kDevPublicKey));
- auto mount_mock = base::MakeUnique<MockVerityMounter>();
- ON_CALL(*mount_mock, Mount(_, _, _)).WillByDefault(testing::Return(true));
- EXPECT_CALL(*mount_mock, Mount(_, _, _)).Times(2);
+ auto helper_mock = base::MakeUnique<MockHelperProcess>();
+ EXPECT_CALL(*helper_mock, SendMountCommand(_, _, _)).Times(2);
+ ON_CALL(*helper_mock, SendMountCommand(_, _, _))
+ .WillByDefault(testing::Return(true));
base::ScopedTempDir scoped_mount_dir;
ASSERT_TRUE(scoped_mount_dir.CreateUniqueTempDir());
ImageLoaderConfig config(key, temp_dir_.value().c_str(),
- scoped_mount_dir.path().value().c_str(),
- std::move(mount_mock));
+ scoped_mount_dir.path().value().c_str());
ImageLoaderImpl loader(std::move(config));
// We previously tested RegisterComponent, so assume this works if it reports
@@ -131,26 +130,28 @@
const std::string expected_path =
scoped_mount_dir.path().value() + "/PepperFlashPlayer/22.0.0.158";
- EXPECT_EQ(expected_path, loader.LoadComponent(kTestComponentName));
+ EXPECT_EQ(expected_path,
+ loader.LoadComponent(kTestComponentName, helper_mock.get()));
// Let's also test mounting the component at a fixed point.
const std::string expected_path2 =
scoped_mount_dir.path().value() + "/FixedMountPoint";
- EXPECT_TRUE(loader.LoadComponent(kTestComponentName, expected_path2));
+ EXPECT_TRUE(loader.LoadComponent(kTestComponentName, expected_path2,
+ helper_mock.get()));
}
TEST_F(ImageLoaderTest, MountInvalidImage) {
std::vector<uint8_t> key(std::begin(kDevPublicKey), std::end(kDevPublicKey));
- auto mount_mock = base::MakeUnique<MockVerityMounter>();
- ON_CALL(*mount_mock, Mount(_, _, _)).WillByDefault(testing::Return(true));
- EXPECT_CALL(*mount_mock, Mount(_, _, _)).Times(0);
+ auto helper_mock = base::MakeUnique<MockHelperProcess>();
+ EXPECT_CALL(*helper_mock, SendMountCommand(_, _, _)).Times(0);
+ ON_CALL(*helper_mock, SendMountCommand(_, _, _))
+ .WillByDefault(testing::Return(true));
base::ScopedTempDir scoped_mount_dir;
ASSERT_TRUE(scoped_mount_dir.CreateUniqueTempDir());
ImageLoaderConfig config(key, temp_dir_.value().c_str(),
- scoped_mount_dir.path().value().c_str(),
- std::move(mount_mock));
+ scoped_mount_dir.path().value().c_str());
ImageLoaderImpl loader(std::move(config));
// We previously tested RegisterComponent, so assume this works if it reports
@@ -164,7 +165,7 @@
std::string contents = "corrupt";
ASSERT_EQ(static_cast<int>(contents.size()),
base::WriteFile(table, contents.data(), contents.size()));
- ASSERT_EQ("", loader.LoadComponent(kTestComponentName));
+ ASSERT_EQ("", loader.LoadComponent(kTestComponentName, helper_mock.get()));
}
TEST_F(ImageLoaderTest, SetupTable) {
diff --git a/ipc.proto b/ipc.proto
new file mode 100644
index 0000000..c3f6e96
--- /dev/null
+++ b/ipc.proto
@@ -0,0 +1,21 @@
+// Copyright 2016 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+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 MountImage {
+ required string mount_path = 1;
+ required string table = 2;
+}
+
+message MountResponse {
+ required bool success = 1;
+}
diff --git a/mock_helper_process.h b/mock_helper_process.h
new file mode 100644
index 0000000..4116b9e
--- /dev/null
+++ b/mock_helper_process.h
@@ -0,0 +1,30 @@
+// Copyright 2016 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IMAGELOADER_MOCK_HELPER_PROCESS_H_
+#define IMAGELOADER_MOCK_HELPER_PROCESS_H_
+
+#include "gmock/gmock.h"
+#include "helper_process.h"
+
+namespace imageloader {
+
+// Mock helper process used for unit testing.
+class MockHelperProcess : public HelperProcess {
+ public:
+ MockHelperProcess() {}
+ ~MockHelperProcess() {}
+
+ // Sends a message telling the helper process to mount the file backed by |fd|
+ // at the |path|.
+ MOCK_METHOD3(SendMountCommand,
+ bool(int, const std::string&, const std::string&));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockHelperProcess);
+};
+
+} // namespace imageloader
+
+#endif // IMAGELOADER_MOCK_HELPER_PROCESS_H_
diff --git a/mount_helper.cc b/mount_helper.cc
new file mode 100644
index 0000000..4f94800
--- /dev/null
+++ b/mount_helper.cc
@@ -0,0 +1,121 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mount_helper.h"
+
+#include <fcntl.h>
+#include <libminijail.h>
+#include <sys/capability.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <base/files/file_path.h>
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/time/time.h>
+
+#include "imageloader.h"
+#include "ipc.pb.h"
+#include "verity_mounter.h"
+
+namespace imageloader {
+
+namespace {
+constexpr char kSeccompFilterPath[] =
+ "/opt/google/imageloader/imageloader-helper-seccomp.policy";
+} // namespace
+
+MountHelper::MountHelper(base::ScopedFD control_fd)
+ : control_fd_(std::move(control_fd)),
+ control_watcher_(),
+ pending_fd_(-1),
+ mounter_() {}
+
+int MountHelper::OnInit() {
+ // Prevent the main process from sending us any signals.
+ // errno can be EPERM if the process is already the group leader.
+ if (setsid() < 0 && errno != EPERM) PLOG(FATAL) << "setsid failed";
+
+ // Run with minimal privileges.
+ struct minijail* jail = minijail_new();
+ minijail_no_new_privs(jail);
+ minijail_use_seccomp_filter(jail);
+ minijail_parse_seccomp_filters(jail, kSeccompFilterPath);
+ minijail_reset_signal_mask(jail);
+ minijail_namespace_net(jail);
+ minijail_skip_remount_private(jail);
+ minijail_enter(jail);
+
+ MessageLoopForIO::current()->WatchFileDescriptor(control_fd_.get(), true,
+ MessageLoopForIO::WATCH_READ,
+ &control_watcher_, this);
+ return Daemon::OnInit();
+}
+
+void MountHelper::OnFileCanReadWithoutBlocking(int fd) {
+ CHECK_EQ(fd, control_fd_.get());
+ char buffer[4096 * 4];
+ memset(buffer, '\0', sizeof(buffer));
+
+ struct msghdr msg = {0};
+ struct iovec iov[1];
+
+ iov[0].iov_base = buffer;
+ iov[0].iov_len = sizeof(buffer);
+
+ msg.msg_iov = iov;
+ msg.msg_iovlen = sizeof(iov) / sizeof(iov[0]);
+
+ char c_buffer[256];
+ msg.msg_control = c_buffer;
+ msg.msg_controllen = sizeof(c_buffer);
+
+ ssize_t bytes = recvmsg(fd, &msg, 0);
+ if (bytes < 0) PLOG(FATAL) << "recvmsg failed";
+ // Per recvmsg(2), the return value will be 0 when the peer has performed an
+ // orderly shutdown.
+ if (bytes == 0) _exit(0);
+
+ 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)))
+ LOG(FATAL) << "error parsing protobuf";
+
+ // Handle the command to mount the image.
+ MountResponse response = HandleCommand(command);
+ // Reply to the parent process with the success or failure.
+ SendResponse(response);
+}
+
+MountResponse MountHelper::HandleCommand(MountImage& command) {
+ bool status = mounter_.Mount(base::ScopedFD(pending_fd_),
+ base::FilePath(command.mount_path()),
+ command.table());
+ if (!status) LOG(ERROR) << "mount failed";
+
+ MountResponse response;
+ response.set_success(status);
+ return response;
+}
+
+void MountHelper::SendResponse(MountResponse& response) {
+ std::string response_str;
+ if (!response.SerializeToString(&response_str))
+ LOG(FATAL) << "failed to serialize protobuf";
+
+ if (write(control_fd_.get(), response_str.data(), response_str.size()) !=
+ static_cast<ssize_t>(response_str.size())) {
+ LOG(FATAL) << "short write on protobuf";
+ }
+}
+
+} // namespace imageloader
diff --git a/mount_helper.h b/mount_helper.h
new file mode 100644
index 0000000..499a5b3
--- /dev/null
+++ b/mount_helper.h
@@ -0,0 +1,48 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IMAGELOADER_MOUNT_HELPER_H_
+#define IMAGELOADER_MOUNT_HELPER_H_
+
+#include <base/files/scoped_file.h>
+#include <base/message_loop/message_loop.h>
+#include <brillo/daemons/daemon.h>
+
+#include "ipc.pb.h"
+#include "verity_mounter.h"
+
+using MessageLoopForIO = base::MessageLoopForIO;
+
+namespace imageloader {
+
+// Main loop for the Mount helper process.
+// This object is used in the subprocess.
+class MountHelper : public brillo::Daemon,
+ public base::MessageLoopForIO::Watcher {
+ public:
+ explicit MountHelper(base::ScopedFD control_fd);
+
+ protected:
+ // OVerrides Daemon init callback.
+ int OnInit() override;
+
+ // Overrides MessageLoopForIO callbacks for new data on |control_fd_|.
+ void OnFileCanReadWithoutBlocking(int fd) override;
+ void OnFileCanWriteWithoutBlocking(int fd) override {}
+
+ private:
+ MountResponse HandleCommand(MountImage& command);
+ void SendResponse(MountResponse& response);
+
+ base::ScopedFD control_fd_;
+ MessageLoopForIO::FileDescriptorWatcher control_watcher_;
+ int pending_fd_;
+ VerityMounter mounter_;
+
+ DISALLOW_COPY_AND_ASSIGN(MountHelper);
+};
+
+} // namespace imageloader
+
+#endif // IMAGELOADER_MOUNT_HELPER_H_
diff --git a/seccomp/imageloader-helper-seccomp-amd64.policy b/seccomp/imageloader-helper-seccomp-amd64.policy
new file mode 100644
index 0000000..4142276
--- /dev/null
+++ b/seccomp/imageloader-helper-seccomp-amd64.policy
@@ -0,0 +1,75 @@
+# Copyright 2017 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+access:1
+arch_prctl: 1
+brk: 1
+capget: 1
+capset: 1
+chown: 1
+clock_getres: 1
+clone: 1
+close: 1
+connect: 1
+dup2: 1
+epoll_ctl: 1
+epoll_wait: 1
+execve: 1
+exit: 1
+exit_group: 1
+fcntl: 1
+fdatasync: 1
+fstat: 1
+futex: 1
+getdents: 1
+getdents64: 1
+geteuid: 1
+getresgid: 1
+getresuid: 1
+getrlimit: 1
+getsockname: 1
+gettimeofday: 1
+gettid: 1
+ioctl: 1
+lseek: 1
+lstat: 1
+mkdir: 1
+mmap: 1
+mount: 1
+mprotect: 1
+munmap: 1
+nanosleep: 1
+open: 1
+openat: 1
+pipe: 1
+poll: 1
+prctl: 1
+pwrite64: 1
+read: 1
+recvmsg: 1
+rename: 1
+restart_syscall: 1
+rmdir: 1
+rt_sigaction: 1
+rt_sigreturn: 1
+rt_sigprocmask: 1
+semctl: 1
+semget: 1
+semop: 1
+sendmsg: 1
+sendto: 1
+set_robust_list: 1
+set_tid_address: 1
+setgroups: 1
+setresgid: 1
+setresuid: 1
+signalfd4: 1
+socket: 1
+stat: 1
+statfs: 1
+sysinfo: 1
+tgkill: 1
+uname: 1
+unlink: 1
+wait4: 1
+write: 1
diff --git a/seccomp/imageloader-helper-seccomp-arm.policy b/seccomp/imageloader-helper-seccomp-arm.policy
new file mode 100644
index 0000000..53f7da2
--- /dev/null
+++ b/seccomp/imageloader-helper-seccomp-arm.policy
@@ -0,0 +1,76 @@
+# Copyright 2017 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+_llseek: 1
+ARM_set_tls: 1
+access:1
+brk: 1
+capget: 1
+capset: 1
+chown: 1
+clock_getres: 1
+clock_gettime: 1
+clone: 1
+close: 1
+connect: 1
+dup2: 1
+execve: 1
+epoll_ctl: 1
+epoll_wait: 1
+exit: 1
+exit_group: 1
+fcntl64: 1
+fdatasync: 1
+fstat64: 1
+futex: 1
+getdents64: 1
+geteuid32: 1
+getresgid32: 1
+getresuid32: 1
+getsockname: 1
+gettimeofday: 1
+gettid: 1
+ioctl: 1
+lstat64: 1
+mkdir: 1
+mmap2: 1
+mount: 1
+mprotect: 1
+munmap: 1
+nanosleep: 1
+open: 1
+openat: 1
+pipe: 1
+poll: 1
+prctl: 1
+prlimit64: 1
+pwrite64: 1
+read: 1
+recvmsg: 1
+rename: 1
+restart_syscall: 1
+rmdir: 1
+rt_sigaction: 1
+rt_sigprocmask: 1
+semctl: 1
+semget: 1
+semop: 1
+send: 1
+sendmsg: 1
+sendto: 1
+set_robust_list: 1
+set_tid_address: 1
+setgroups32: 1
+setresgid32: 1
+setresuid32: 1
+sigreturn: 1
+signalfd4: 1
+socket: 1
+stat64: 1
+statfs64: 1
+sysinfo: 1
+tgkill: 1
+ugetrlimit: 1
+uname: 1
+wait4: 1
+write: 1
diff --git a/seccomp/imageloader-helper-seccomp-x86.policy b/seccomp/imageloader-helper-seccomp-x86.policy
new file mode 100644
index 0000000..cfcba52
--- /dev/null
+++ b/seccomp/imageloader-helper-seccomp-x86.policy
@@ -0,0 +1,79 @@
+# Copyright 2016 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+_llseek: 1
+access:1
+brk: 1
+capget: 1
+capset: 1
+chown: 1
+clock_getres: 1
+clock_gettime: 1
+clone: 1
+close: 1
+dup2: 1
+epoll_ctl: 1
+epoll_wait: 1
+execve: 1
+exit: 1
+exit_group: 1
+fcntl64: 1
+fdatasync: 1
+fstat64: 1
+futex: 1
+getdents: 1
+getdents64: 1
+geteuid32: 1
+getresgid32: 1
+getresuid32: 1
+getrlimit: 1
+gettimeofday: 1
+gettid: 1
+ioctl: 1
+ipc: 1
+lseek: 1
+lstat64: 1
+mkdir: 1
+mmap2: 1
+mount: 1
+mprotect: 1
+munmap: 1
+nanosleep: 1
+open: 1
+openat: 1
+pipe: 1
+poll: 1
+prctl: 1
+prlimit64: 1
+pwrite64: 1
+read: 1
+rename: 1
+restart_syscall: 1
+rmdir: 1
+rt_sigaction: 1
+rt_sigreturn: 1
+rt_sigprocmask: 1
+set_robust_list: 1
+set_thread_area: 1
+set_tid_address: 1
+setgroups: 1
+setresgid: 1
+setresuid: 1
+signalfd4: 1
+sigreturn: 1
+# On 32-bit x86, the networking operatons such as connect() and
+# sendmsg() are multiplexed through the socketcall() system call. arg0
+# is the operation, such as SYS_SOCKETPAIR (8). See <linux/net.h> for the
+# symbolic names.
+socketcall: arg0 == 1 || arg0 == 3 || arg0 == 6 || arg0 == 8 || arg0 == 9 || arg0 == 16 || arg0 == 17
+stat64: 1
+statfs64: 1
+sysinfo: 1
+tgkill: 1
+time: 1
+ugetrlimit: 1
+uname: 1
+unlink: 1
+wait4: 1
+waitpid: 1
+write: 1
diff --git a/seccomp/imageloader-seccomp-amd64.policy b/seccomp/imageloader-seccomp-amd64.policy
index 2f600d9..6dac10c 100644
--- a/seccomp/imageloader-seccomp-amd64.policy
+++ b/seccomp/imageloader-seccomp-amd64.policy
@@ -55,4 +55,5 @@
tgkill: 1
uname: 1
unlink: 1
+waitid: 1
write: 1
diff --git a/verity_mounter.cc b/verity_mounter.cc
index 79659b8..220ed50 100644
--- a/verity_mounter.cc
+++ b/verity_mounter.cc
@@ -7,21 +7,28 @@
#include <algorithm>
#include <string>
+#include </usr/include/linux/magic.h>
#include <fcntl.h>
#include <linux/loop.h>
#include <sys/ioctl.h>
-#include <sys/stat.h>
#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/vfs.h>
#include <base/command_line.h>
+#include <base/containers/adapters.h>
+#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/process/launch.h>
#include <base/rand_util.h>
#include <base/strings/string_number_conversions.h>
-#include <base/strings/stringprintf.h>
#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
#include <base/time/time.h>
+#include "component.h"
+
namespace imageloader {
namespace {
@@ -81,8 +88,96 @@
void ClearLoopDevice(const std::string& device_path) {
base::ScopedFD loop_device_fd(
open(device_path.c_str(), O_RDONLY | O_CLOEXEC));
- if (loop_device_fd.is_valid())
- ioctl(loop_device_fd.get(), LOOP_CLR_FD, 0);
+ if (loop_device_fd.is_valid()) ioctl(loop_device_fd.get(), LOOP_CLR_FD, 0);
+}
+
+bool SetupDeviceMapper(const std::string& device_path, const std::string& table,
+ std::string* dev_name) {
+ // Now setup the dmsetup table.
+ std::string final_table = table;
+ if (!VerityMounter::SetupTable(&final_table, device_path)) return false;
+
+ // Generate a name with a random string of 32 characters: we consider this to
+ // have sufficiently low chance of collision to assume the name isn't taken.
+ std::vector<uint8_t> rand_bytes(32);
+ base::RandBytes(rand_bytes.data(), rand_bytes.size());
+ std::string name = base::HexEncode(rand_bytes.data(), rand_bytes.size());
+
+ if (!LaunchDMCreate(name, final_table)) {
+ LOG(ERROR) << "Failed to run dmsetup.";
+ return false;
+ }
+
+ dev_name->assign("/dev/mapper/" + name);
+ return true;
+}
+
+bool CreateDirectoryWithMode(const base::FilePath& full_path, int mode) {
+ std::vector<base::FilePath> subpaths;
+
+ // Collect a list of all parent directories.
+ base::FilePath last_path = full_path;
+ subpaths.push_back(full_path);
+ for (base::FilePath path = full_path.DirName();
+ path.value() != last_path.value(); path = path.DirName()) {
+ subpaths.push_back(path);
+ last_path = path;
+ }
+
+ // Iterate through the parents and create the missing ones.
+ for (const auto& subpath : base::Reversed(subpaths)) {
+ if (base::DirectoryExists(subpath)) continue;
+ if (mkdir(subpath.value().c_str(), mode) == 0) continue;
+ // Mkdir failed, but it might have failed with EEXIST, or some other error
+ // due to the the directory appearing out of thin air. This can occur if
+ // two processes are trying to create the same file system tree at the same
+ // time. Check to see if it exists and make sure it is a directory.
+ if (!base::DirectoryExists(subpath)) {
+ PLOG(ERROR) << "Failed to create directory: " << subpath.value();
+ return false;
+ }
+ }
+ return true;
+}
+
+bool CreateMountPointIfNeeded(const base::FilePath& mount_point,
+ bool* already_mounted) {
+ LOG(INFO) << "In CreateMountPoint if needed";
+ *already_mounted = false;
+ // Is this mount point somehow already taken?
+ struct stat st;
+ if (lstat(mount_point.value().c_str(), &st) == 0) {
+ if (!S_ISDIR(st.st_mode)) {
+ LOG(ERROR) << "Mount point exists but is not a directory.";
+ return false;
+ }
+
+ base::FilePath mount_parent = mount_point.DirName();
+ struct stat st2;
+ if (stat(mount_parent.value().c_str(), &st2) != 0) {
+ PLOG(ERROR) << "Could not stat the mount point parent";
+ return false;
+ }
+ if (st.st_dev != st2.st_dev) {
+ struct statfs st_fs;
+ if (statfs(mount_point.value().c_str(), &st_fs) != 0) {
+ PLOG(ERROR) << "statfs";
+ return false;
+ }
+ if (st_fs.f_type != SQUASHFS_MAGIC || !(st_fs.f_flags & ST_NODEV) ||
+ !(st_fs.f_flags & ST_NOSUID) || !(st_fs.f_flags & ST_RDONLY)) {
+ LOG(ERROR) << "File system is not the expected type.";
+ return false;
+ }
+ LOG(INFO) << "The mount point already exists: " << mount_point.value();
+ *already_mounted = true;
+ return true;
+ }
+ } else if (!CreateDirectoryWithMode(mount_point, kComponentDirPerms)) {
+ LOG(ERROR) << "Failed to create mount point: " << mount_point.value();
+ return false;
+ }
+ return true;
}
// Reserves a loop device and associates it with |image_fd|. The path to the
@@ -123,29 +218,7 @@
return MountStatus::SUCCESS;
}
-bool SetupDeviceMapper(const std::string& device_path, const std::string& table,
- std::string* dev_name) {
- // Now setup the dmsetup table.
- std::string final_table = table;
- if (!VerityMounter::SetupTable(&final_table, device_path))
- return false;
-
- // Generate a name with a random string of 32 characters: we consider this to
- // have sufficiently low chance of collision to assume the name isn't taken.
- std::vector<uint8_t> rand_bytes(32);
- base::RandBytes(rand_bytes.data(), rand_bytes.size());
- std::string name = base::HexEncode(rand_bytes.data(), rand_bytes.size());
-
- if (!LaunchDMCreate(name, final_table)) {
- LOG(ERROR) << "Failed to run dmsetup.";
- return false;
- }
-
- dev_name->assign("/dev/mapper/" + name);
- return true;
-}
-
-} // namespace
+} // namespace
// static
bool VerityMounter::SetupTable(std::string* table,
@@ -171,6 +244,11 @@
bool VerityMounter::Mount(const base::ScopedFD& image_fd,
const base::FilePath& mount_point,
const std::string& table) {
+ // First check if the component is already mounted and avoid unnecessary work.
+ bool already_mounted = false;
+ if (!CreateMountPointIfNeeded(mount_point, &already_mounted)) return false;
+ if (already_mounted) return true;
+
std::string loop_device_path;
// We need to retry because another program could grap the loop device,
// resulting in an EBUSY error. If that happens, run again and grab a new