blob: 79659b85a6a3872b1447897df857239fdd17c4a0 [file] [log] [blame]
// 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.
#include "verity_mounter.h"
#include <algorithm>
#include <string>
#include <fcntl.h>
#include <linux/loop.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <base/command_line.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/time/time.h>
namespace imageloader {
namespace {
enum class MountStatus { FAIL, RETRY, SUCCESS };
constexpr int GET_LOOP_DEVICE_MAX_RETRY = 5;
constexpr int DMSETUP_TIMEOUT_SECONDS = 3;
// |argv| should include all the commands and table to dmsetup, but not
// include the path to the binary.
bool RunDMSetup(const std::vector<std::string>& argv) {
base::LaunchOptions options;
options.clear_environ = true;
std::vector<std::string> full_argv(argv);
full_argv.insert(full_argv.begin(), "/sbin/dmsetup");
base::Process process = base::LaunchProcess(full_argv, options);
if (!process.IsValid()) {
LOG(ERROR) << "Failed to launch dmsetup process";
return false;
}
int exit_code;
if (!process.WaitForExitWithTimeout(
base::TimeDelta::FromSeconds(DMSETUP_TIMEOUT_SECONDS), &exit_code)) {
LOG(ERROR) << "Failed to wait for dmsetup process.";
return false;
}
return exit_code == 0;
}
bool LaunchDMCreate(const std::string& name, const std::string& table) {
const std::vector<std::string> argv = {"create", name, "--table",
table.c_str(), "--readonly"};
return RunDMSetup(argv);
}
// Clear the /dev/mapper/<foo> verity device.
void ClearVerityDevice(const std::string& name) {
// Per the man page, wipe_table:
// Wait for any I/O in-flight through the device to complete, then replace the
// table with a new table that fails any new I/O sent to the device. If
// successful, this should release any devices held open by the device's
// table(s).
const std::vector<std::string> wipe_argv = {"wipe_table", name};
RunDMSetup(wipe_argv);
// Now remove the actual device.
const std::vector<std::string> remove_argv = {"remove", name};
RunDMSetup(remove_argv);
}
// Clear the file descriptor behind a loop device.
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);
}
// Reserves a loop device and associates it with |image_fd|. The path to the
// loop device is returned in |device_path_out|. When the loop device is no
// longer being used, free the resource with ClearLoopDevice().
MountStatus GetLoopDevice(const base::ScopedFD& image_fd,
std::string* device_path_out) {
base::ScopedFD loopctl_fd(open("/dev/loop-control", O_RDONLY | O_CLOEXEC));
if (!loopctl_fd.is_valid()) {
PLOG(ERROR) << "loopctl_fd";
return MountStatus::FAIL;
}
int device_free_number = ioctl(loopctl_fd.get(), LOOP_CTL_GET_FREE);
if (device_free_number < 0) {
PLOG(ERROR) << "ioctl : LOOP_CTL_GET_FREE";
return MountStatus::FAIL;
}
std::string device_path =
base::StringPrintf("/dev/loop%d", device_free_number);
base::ScopedFD loop_device_fd(
open(device_path.c_str(), O_RDONLY | O_CLOEXEC));
if (!loop_device_fd.is_valid()) {
PLOG(ERROR) << "Failed to open loop device: " << device_path;
return MountStatus::FAIL;
}
if (ioctl(loop_device_fd.get(), LOOP_SET_FD, image_fd.get()) == -1) {
if (errno != EBUSY) {
PLOG(ERROR) << "ioctl: LOOP_SET_FD";
ioctl(loop_device_fd.get(), LOOP_CLR_FD, 0);
return MountStatus::FAIL;
}
return MountStatus::RETRY;
}
device_path_out->assign(device_path);
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
// static
bool VerityMounter::SetupTable(std::string* table,
const std::string& device_path) {
// Make sure there is only one entry in the device mapper table.
if (std::count(table->begin(), table->end(), '\n') > 1) return false;
// Remove all newlines from the table. This is to workaround the server
// incorrectly inserting a newline when writing out the table.
table->erase(std::remove(table->begin(), table->end(), '\n'), table->end());
// Replace in the actual loop device name.
base::ReplaceSubstringsAfterOffset(table, 0, "ROOT_DEV", device_path);
base::ReplaceSubstringsAfterOffset(table, 0, "HASH_DEV", device_path);
// If the table does not specify an error condition, use the default (eio).
// This is critical because the default behavior is to panic the device and
// force a system recovery. Do not do this for component corruption.
if (table->find("error_behavior") == std::string::npos)
table->append(" error_behavior=eio");
return true;
}
bool VerityMounter::Mount(const base::ScopedFD& image_fd,
const base::FilePath& mount_point,
const std::string& table) {
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
// device.
int retries = GET_LOOP_DEVICE_MAX_RETRY;
while (true) {
MountStatus status = GetLoopDevice(image_fd, &loop_device_path);
if (status == MountStatus::FAIL ||
(status == MountStatus::RETRY && retries == 0)) {
LOG(ERROR) << "GetLoopDevice failed, mount_point: "
<< mount_point.value();
return false;
} else if (status == MountStatus::SUCCESS) {
break;
}
--retries;
}
std::string dev_name;
if (!SetupDeviceMapper(loop_device_path, table, &dev_name)) {
LOG(ERROR) << "mount_point: " << mount_point.value();
ClearLoopDevice(loop_device_path);
return false;
}
if (mount(dev_name.c_str(), mount_point.value().c_str(), "squashfs",
MS_RDONLY | MS_NOSUID | MS_NODEV, "") < 0) {
PLOG(ERROR) << "mount, mount_point " << mount_point.value();
ClearVerityDevice(dev_name);
ClearLoopDevice(loop_device_path);
return false;
}
return true;
}
} // namespace imageloader