blob: 6878687f6acf64dfa50bf3cf3ea976738fb4c09b [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 <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <syscall.h>
#include <unistd.h>
#include <algorithm>
#include <map>
#include <memory>
#include <ostream>
#include <set>
#include <sstream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/bind_helpers.h>
#include <base/callback_helpers.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/logging.h>
#include <base/macros.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <libminijail.h>
#include <scoped_minijail.h>
#include "libcontainer/cgroup.h"
#include "libcontainer/config.h"
#include "libcontainer/libcontainer.h"
#include "libcontainer/libcontainer_util.h"
#define QUOTE(s) ('"' + std::string(s) + '"')
// Not available in sys/prctl.h yet, but supported on some kernels.
#ifndef PR_SET_CORE_SCHED
#define PR_SET_CORE_SCHED 0x200
#endif
namespace {
using libcontainer::DeviceMapperDetach;
using libcontainer::DeviceMapperSetup;
using libcontainer::GetUsernsOutsideId;
using libcontainer::Loopdev;
using libcontainer::LoopdevDetach;
using libcontainer::LoopdevSetup;
using libcontainer::MakeDir;
using libcontainer::MountExternal;
using libcontainer::TouchFile;
constexpr size_t kMaxRlimits = 32; // Linux defines 15 at the time of writing.
struct Mount {
std::string name;
base::FilePath source;
base::FilePath destination;
std::string type;
std::string data;
std::string verity;
int flags;
int uid;
int gid;
int mode;
// True if mount should happen in new vfs ns.
bool mount_in_ns;
// True if target should be created if it doesn't exist.
bool create;
// True if target should be mounted via loopback.
bool loopback;
};
struct Device {
// 'c' or 'b' for char or block
char type;
base::FilePath path;
int fs_permissions;
int major;
int minor;
// Copy the major from existing node, ignores |major|.
bool copy_major;
// Copy the minor from existing node, ignores |minor|.
bool copy_minor;
int uid;
int gid;
};
struct CgroupDevice {
bool allow;
char type;
// -1 for either major or minor means all.
int major;
int minor;
bool read;
bool write;
bool modify;
};
struct CpuCgroup {
int shares;
int quota;
int period;
int rt_runtime;
int rt_period;
};
struct Rlimit {
int type;
rlim_t cur;
rlim_t max;
};
} // namespace
// Structure that configures how the container is run.
struct container_config {
// Path to the root of the container itself.
base::FilePath config_root;
// Path to the root of the container's filesystem.
base::FilePath rootfs;
// Flags that will be passed to mount() for the rootfs.
unsigned long rootfs_mount_flags = 0x0;
// Path to where the container will be run.
base::FilePath premounted_runfs;
// Path to the file where the pid should be written.
base::FilePath pid_file_path;
// The program to run and args, e.g. "/sbin/init".
std::vector<std::string> program_argv;
// The uid the container will run as.
uid_t uid = 0;
// Mapping of UIDs in the container, e.g. "0 100000 1024"
std::string uid_map;
// The gid the container will run as.
gid_t gid = 0;
// Mapping of GIDs in the container, e.g. "0 100000 1024"
std::string gid_map;
// The supplementary gids that attached to the container.
std::vector<gid_t> additional_gids;
// Syscall table to use or nullptr if none.
std::string alt_syscall_table;
// Filesystems to mount in the new namespace.
std::vector<Mount> mounts;
// Namespaces that should be used for the container.
std::set<std::string> namespaces;
// Device nodes to create.
std::vector<Device> devices;
// Device node cgroup permissions.
std::vector<CgroupDevice> cgroup_devices;
// CPU cgroup params.
CpuCgroup cpu_cgparams;
// Parent dir for cgroup creation
base::FilePath cgroup_parent;
// uid to own the created cgroups
uid_t cgroup_owner = 0;
// gid to own the created cgroups
gid_t cgroup_group = 0;
// Allow the child process to keep open FDs (for stdin/out/err).
bool keep_fds_open = false;
// Array of rlimits for the contained process.
Rlimit rlimits[kMaxRlimits];
// The number of elements in `rlimits`.
int num_rlimits = 0;
bool use_capmask = false;
bool use_capmask_ambient = false;
uint64_t capmask = 0x0;
// The mask of securebits to skip when restricting caps.
uint64_t securebits_skip_mask = 0x0;
// Core Scheduling policy
bool core_sched = false;
// Whether the container needs an extra process to be run as init.
bool do_init = false;
// The SELinux context name the container will run under.
std::string selinux_context;
// A function pointer to be called prior to calling execve(2).
minijail_hook_t pre_start_hook = nullptr;
// Parameter that will be passed to pre_start_hook().
void* pre_start_hook_payload = nullptr;
// A list of file descriptors to inherit.
std::vector<int> inherited_fds;
// A list of hooks that will be called upon minijail reaching various states
// of execution.
std::map<minijail_hook_event_t, std::vector<libcontainer::HookCallback>>
hooks;
};
// Container manipulation
struct container {
std::unique_ptr<libcontainer::Cgroup> cgroup;
ScopedMinijail jail;
pid_t init_pid = -1;
base::FilePath config_root;
base::FilePath runfs;
base::FilePath rundir;
base::FilePath runfsroot;
base::FilePath pid_file_path;
// Mounts made outside of the minijail.
std::vector<base::FilePath> ext_mounts;
std::vector<Loopdev> loopdevs;
std::vector<std::string> device_mappers;
std::string name;
std::vector<std::pair<libcontainer::HookState,
std::vector<libcontainer::HookCallback>>>
hook_states;
};
namespace {
std::string GetMountFlagsAsString(int flags) {
#define CHECK_MOUNT_FLAG(flag) \
do { \
if (flags & flag) \
result.push_back(#flag); \
} while (false)
std::vector<std::string> result;
CHECK_MOUNT_FLAG(MS_RDONLY);
CHECK_MOUNT_FLAG(MS_NOSUID);
CHECK_MOUNT_FLAG(MS_NODEV);
CHECK_MOUNT_FLAG(MS_NOEXEC);
CHECK_MOUNT_FLAG(MS_SYNCHRONOUS);
CHECK_MOUNT_FLAG(MS_REMOUNT);
CHECK_MOUNT_FLAG(MS_MANDLOCK);
CHECK_MOUNT_FLAG(MS_DIRSYNC);
CHECK_MOUNT_FLAG(MS_NOATIME);
CHECK_MOUNT_FLAG(MS_NODIRATIME);
CHECK_MOUNT_FLAG(MS_BIND);
CHECK_MOUNT_FLAG(MS_MOVE);
CHECK_MOUNT_FLAG(MS_REC);
CHECK_MOUNT_FLAG(MS_SILENT);
CHECK_MOUNT_FLAG(MS_POSIXACL);
CHECK_MOUNT_FLAG(MS_UNBINDABLE);
CHECK_MOUNT_FLAG(MS_PRIVATE);
CHECK_MOUNT_FLAG(MS_SLAVE);
CHECK_MOUNT_FLAG(MS_SHARED);
return result.empty() ? "no flags" : base::JoinString(result, " | ");
#undef CHECK_MOUNT_FLAG
}
std::ostream& operator<<(std::ostream& stream, const Mount& mount) {
stream << "mount:" << std::endl
<< " name: " << QUOTE(mount.name) << std::endl
<< " source: " << QUOTE(mount.source.value()) << std::endl
<< " destination: " << QUOTE(mount.destination.value()) << std::endl
<< " type: " << QUOTE(mount.type) << std::endl
<< " data: " << QUOTE(mount.data) << std::endl
<< " verity: " << QUOTE(mount.verity) << std::endl
<< " flags: 0x" << std::hex << mount.flags << std::dec << " ("
<< GetMountFlagsAsString(mount.flags) << ")" << std::endl
<< " uid: " << mount.uid << std::endl
<< " gid: " << mount.gid << std::endl
<< " mode: 0" << std::oct << mount.mode << std::dec << std::endl
<< " mount_in_ns: " << mount.mount_in_ns << std::endl
<< " create: " << mount.create << std::endl
<< " loopback: " << mount.loopback << std::endl;
return stream;
}
std::ostream& operator<<(std::ostream& stream, const Device& device) {
stream << "device:" << std::endl
<< " type: " << device.type << std::endl
<< " path: " << QUOTE(device.path.value()) << std::endl
<< " fs_permissions: 0" << std::oct << device.fs_permissions
<< std::dec << std::endl
<< " major: " << device.major << std::endl
<< " minor: " << device.minor << std::endl
<< " copy_minor: " << device.copy_minor << std::endl
<< " uid: " << device.uid << std::endl
<< " gid: " << device.gid << std::endl;
return stream;
}
std::ostream& operator<<(std::ostream& stream,
const CgroupDevice& cgroup_device) {
stream << "cgroup_device:" << std::endl
<< " allow: " << cgroup_device.allow << std::endl
<< " type: " << cgroup_device.type << std::endl
<< " major: " << cgroup_device.major << std::endl
<< " minor: " << cgroup_device.minor << std::endl
<< " read: " << cgroup_device.read << std::endl
<< " write: " << cgroup_device.write << std::endl
<< " modify: " << cgroup_device.modify << std::endl;
return stream;
}
std::ostream& operator<<(std::ostream& stream, const CpuCgroup& cpu_cgroup) {
stream << "cpu_cgroup:" << std::endl
<< " shares: " << cpu_cgroup.shares << std::endl
<< " quota: " << cpu_cgroup.quota << std::endl
<< " period: " << cpu_cgroup.period << std::endl
<< " rt_runtime: " << cpu_cgroup.rt_runtime << std::endl
<< " rt_period: " << cpu_cgroup.rt_period << std::endl;
return stream;
}
std::ostream& operator<<(std::ostream& stream, const Rlimit& rlimit) {
stream << "rlimit:" << std::endl
<< " type: " << rlimit.type << std::endl
<< " cur: " << rlimit.cur << std::endl
<< " max: " << rlimit.max << std::endl;
return stream;
}
void DumpConfig(std::ostream* stream,
const container_config* c,
bool sort_vectors) {
*stream << "config_root: " << QUOTE(c->config_root.value()) << std::endl
<< "rootfs: " << QUOTE(c->rootfs.value()) << std::endl
<< "rootfs_mount_flags: 0x" << std::hex << c->rootfs_mount_flags
<< std::dec << " (" << GetMountFlagsAsString(c->rootfs_mount_flags)
<< ")" << std::endl
<< "premounted_runfs: " << QUOTE(c->premounted_runfs.value())
<< std::endl
<< "pid_file_path: " << QUOTE(c->pid_file_path.value()) << std::endl
<< "program_argv: size=" << c->program_argv.size() << std::endl;
for (const std::string& argv : c->program_argv)
*stream << " " << QUOTE(argv) << std::endl;
*stream << "uid: " << c->uid << std::endl
<< "uid_map: " << QUOTE(c->uid_map) << std::endl
<< "gid: " << c->gid << std::endl
<< "gid_map: " << QUOTE(c->gid_map) << std::endl
<< "alt_syscall_table: " << QUOTE(c->alt_syscall_table) << std::endl
<< "core_sched:" << (c->core_sched ? "enable" : "disable")
<< std::endl;
auto mount_sorted = c->mounts;
if (sort_vectors) {
std::stable_sort(mount_sorted.begin(), mount_sorted.end(),
[](const Mount& lhs, const Mount& rhs) {
return std::make_tuple(lhs.destination.value(),
lhs.source.value(), lhs.flags) <
std::make_tuple(rhs.destination.value(),
rhs.source.value(), rhs.flags);
});
}
for (const auto& mount : mount_sorted)
*stream << mount;
*stream << "namespaces: size=" << c->namespaces.size() << std::endl;
for (const std::string& ns : c->namespaces)
*stream << " " << QUOTE(ns) << std::endl;
auto devices_sorted = c->devices;
if (sort_vectors) {
std::stable_sort(devices_sorted.begin(), devices_sorted.end(),
[](const Device& lhs, const Device& rhs) {
return lhs.path.value() < rhs.path.value();
});
}
for (const auto& device : devices_sorted)
*stream << device;
auto cgroup_devices_sorted = c->cgroup_devices;
if (sort_vectors) {
std::stable_sort(cgroup_devices_sorted.begin(), cgroup_devices_sorted.end(),
[](const CgroupDevice& lhs, const CgroupDevice& rhs) {
return std::make_tuple(lhs.type, lhs.major, lhs.minor) <
std::make_tuple(rhs.type, rhs.major, rhs.minor);
});
}
for (const auto& cgroup_device : cgroup_devices_sorted)
*stream << cgroup_device;
*stream << c->cpu_cgparams
<< "cgroup_parent: " << QUOTE(c->cgroup_parent.value()) << std::endl
<< "cgroup_owner: " << c->cgroup_owner << std::endl
<< "cgroup_group: " << c->cgroup_group << std::endl
<< "keep_fds_open: " << c->keep_fds_open << std::endl;
*stream << "num_rlimits: " << c->num_rlimits << std::endl;
for (size_t i = 0; i < c->num_rlimits; ++i)
*stream << c->rlimits[i];
*stream << "use_capmask: " << c->use_capmask << std::endl
<< "use_capmask_ambient: " << c->use_capmask_ambient << std::endl
<< "capmask: 0x" << std::hex << c->capmask << std::dec << std::endl
<< "securebits_skip_mask: 0x" << std::hex << c->securebits_skip_mask
<< std::dec << std::endl
<< "do_init: " << c->do_init << std::endl
<< "selinux_context: " << QUOTE(c->selinux_context) << std::endl
<< "pre_start_hook: " << reinterpret_cast<void*>(c->pre_start_hook)
<< std::endl
<< "pre_start_hook_payload: " << c->pre_start_hook_payload
<< std::endl
<< "inherited_fds: size=" << c->inherited_fds.size() << std::endl;
for (int fd : c->inherited_fds)
*stream << " " << fd << std::endl;
*stream << "hooks: size=" << c->hooks.size() << std::endl;
}
// Returns the path for |path_in_container| in the outer namespace.
base::FilePath GetPathInOuterNamespace(
const base::FilePath& root, const base::FilePath& path_in_container) {
if (path_in_container.IsAbsolute())
return base::FilePath(root.value() + path_in_container.value());
return root.Append(path_in_container);
}
// Make sure the mount target exists in the new rootfs. Create if needed and
// possible.
bool SetupMountDestination(const struct container_config* config,
const Mount& mount,
const base::FilePath& source,
const base::FilePath& dest) {
struct stat st_buf;
if (stat(dest.value().c_str(), &st_buf) == 0) {
// destination exists.
return true;
}
// Try to create the destination. Either make directory or touch a file
// depending on the source type.
int uid_userns;
if (!GetUsernsOutsideId(config->uid_map, mount.uid, &uid_userns))
return false;
int gid_userns;
if (!GetUsernsOutsideId(config->gid_map, mount.gid, &gid_userns))
return false;
if (stat(source.value().c_str(), &st_buf) != 0 || S_ISDIR(st_buf.st_mode) ||
S_ISBLK(st_buf.st_mode)) {
return MakeDir(dest, uid_userns, gid_userns, mount.mode);
}
return TouchFile(dest, uid_userns, gid_userns, mount.mode);
}
// Unmounts anything we mounted in this mount namespace in the opposite order
// that they were mounted.
bool UnmountExternalMounts(struct container* c) {
bool ret = true;
for (auto it = c->ext_mounts.rbegin(); it != c->ext_mounts.rend(); ++it) {
if (umount(it->value().c_str()) != 0) {
PLOG(ERROR) << "Failed to unmount " << it->value();
ret = false;
}
}
c->ext_mounts.clear();
for (auto it = c->loopdevs.rbegin(); it != c->loopdevs.rend(); ++it) {
if (!LoopdevDetach(&(*it)))
ret = false;
}
c->loopdevs.clear();
for (auto it = c->device_mappers.rbegin(); it != c->device_mappers.rend();
++it) {
if (!DeviceMapperDetach(*it))
ret = false;
}
c->device_mappers.clear();
return ret;
}
bool DoContainerMount(struct container* c,
const struct container_config* config,
const Mount& mount) {
base::FilePath dest =
GetPathInOuterNamespace(c->runfsroot, mount.destination);
// If it's a bind mount relative to rootfs, append source to
// rootfs path, otherwise source path is absolute.
base::FilePath source;
if ((mount.flags & MS_BIND) && !mount.source.IsAbsolute()) {
source = GetPathInOuterNamespace(c->runfsroot, mount.source);
} else if (mount.loopback && !mount.source.IsAbsolute() &&
!c->config_root.empty()) {
source = GetPathInOuterNamespace(c->config_root, mount.source);
} else {
source = mount.source;
}
// Only create the destinations for external mounts, minijail will take
// care of those mounted in the new namespace.
if (mount.create && !mount.mount_in_ns) {
if (!SetupMountDestination(config, mount, source, dest))
return false;
}
if (mount.loopback) {
Loopdev loopdev;
if (!LoopdevSetup(source, &loopdev))
return false;
// Replace the mount source with the loopback device path.
source = loopdev.path;
// Save this to cleanup when shutting down.
c->loopdevs.emplace_back(std::move(loopdev));
}
if (!mount.verity.empty()) {
// Set this device up via dm-verity.
std::string dm_name;
base::FilePath dm_source = source;
if (!DeviceMapperSetup(dm_source, mount.verity, &source, &dm_name))
return false;
// Save this to cleanup when shutting down.
c->device_mappers.push_back(dm_name);
}
if (mount.mount_in_ns) {
// We can mount this with minijail.
if (minijail_mount_with_data(
c->jail.get(), source.value().c_str(),
mount.destination.value().c_str(), mount.type.c_str(), mount.flags,
mount.data.empty() ? nullptr : mount.data.c_str()) != 0) {
return false;
}
} else {
// Mount this externally and unmount it on exit.
if (!MountExternal(source.value(), dest.value(), mount.type, mount.flags,
mount.data)) {
return false;
}
// Save this to unmount when shutting down.
c->ext_mounts.push_back(dest);
}
return true;
}
bool DoContainerMounts(struct container* c,
const struct container_config* config) {
UnmountExternalMounts(c);
// This will run in all the error cases.
base::ScopedClosureRunner teardown(base::Bind(
base::IgnoreResult(&UnmountExternalMounts), base::Unretained(c)));
for (const auto& mount : config->mounts) {
if (!DoContainerMount(c, config, mount))
return false;
}
// The mounts have been done successfully, no need to tear them down anymore.
ignore_result(teardown.Release());
return true;
}
bool ContainerCreateDevice(const struct container* c,
const struct container_config* config,
const Device& dev,
int major,
int minor) {
mode_t mode = dev.fs_permissions;
switch (dev.type) {
case 'b':
mode |= S_IFBLK;
break;
case 'c':
mode |= S_IFCHR;
break;
default:
return false;
}
int uid_userns;
if (!GetUsernsOutsideId(config->uid_map, dev.uid, &uid_userns))
return false;
int gid_userns;
if (!GetUsernsOutsideId(config->gid_map, dev.gid, &gid_userns))
return false;
base::FilePath path = GetPathInOuterNamespace(c->runfsroot, dev.path);
if (!libcontainer::CreateDirectoryOwnedBy(path.DirName(), 0755, uid_userns,
gid_userns)) {
PLOG(ERROR) << "Failed to create parent directory for " << path.value();
return false;
}
if (mknod(path.value().c_str(), mode, makedev(major, minor)) != 0 &&
errno != EEXIST) {
PLOG(ERROR) << "Failed to mknod " << path.value();
return false;
}
if (chown(path.value().c_str(), uid_userns, gid_userns) != 0) {
PLOG(ERROR) << "Failed to chown " << path.value();
return false;
}
if (chmod(path.value().c_str(), dev.fs_permissions) != 0) {
PLOG(ERROR) << "Failed to chmod " << path.value();
return false;
}
return true;
}
bool MountRunfs(struct container* c, const struct container_config* config) {
{
std::string runfs_template = base::StringPrintf(
"%s/%s_XXXXXX", c->rundir.value().c_str(), c->name.c_str());
// TODO(lhchavez): Replace this with base::CreateTemporaryDirInDir().
char* runfs_path = mkdtemp(const_cast<char*>(runfs_template.c_str()));
if (!runfs_path) {
PLOG(ERROR) << "Failed to mkdtemp in " << c->rundir.value();
return false;
}
c->runfs = base::FilePath(runfs_path);
}
int uid_userns;
if (!GetUsernsOutsideId(config->uid_map, config->uid, &uid_userns))
return false;
int gid_userns;
if (!GetUsernsOutsideId(config->gid_map, config->gid, &gid_userns))
return false;
// Make sure the container uid can access the rootfs.
if (chmod(c->runfs.value().c_str(), 0700) != 0) {
PLOG(ERROR) << "Failed to chmod " << c->runfs.value();
return false;
}
if (chown(c->runfs.value().c_str(), uid_userns, gid_userns) != 0) {
PLOG(ERROR) << "Failed to chown " << c->runfs.value();
return false;
}
c->runfsroot = c->runfs.Append("root");
constexpr mode_t kRootDirMode = 0660;
if (mkdir(c->runfsroot.value().c_str(), kRootDirMode) != 0) {
PLOG(ERROR) << "Failed to mkdir " << c->runfsroot.value();
return false;
}
if (chmod(c->runfsroot.value().c_str(), kRootDirMode) != 0) {
PLOG(ERROR) << "Failed to chmod " << c->runfsroot.value();
return false;
}
if (mount(config->rootfs.value().c_str(), c->runfsroot.value().c_str(), "",
MS_BIND | (config->rootfs_mount_flags & MS_REC), nullptr) != 0) {
PLOG(ERROR) << "Failed to bind-mount " << config->rootfs.value();
return false;
}
// MS_BIND ignores any flags passed to it (except MS_REC). We need a
// second call to mount() to actually set them.
if (config->rootfs_mount_flags &&
mount(config->rootfs.value().c_str(), c->runfsroot.value().c_str(), "",
(config->rootfs_mount_flags & ~MS_REC), nullptr) != 0) {
PLOG(ERROR) << "Failed to remount " << c->runfsroot.value();
return false;
}
return true;
}
bool CreateDeviceNodes(struct container* c,
const struct container_config* config,
pid_t container_pid) {
for (const auto& dev : config->devices) {
int major = dev.major;
int minor = dev.minor;
if (dev.copy_major || dev.copy_minor) {
struct stat st_buff;
if (stat(dev.path.value().c_str(), &st_buff) != 0)
continue;
if (dev.copy_major)
major = major(st_buff.st_rdev);
if (dev.copy_minor)
minor = minor(st_buff.st_rdev);
}
if (major < 0 || minor < 0)
continue;
if (!ContainerCreateDevice(c, config, dev, major, minor))
return false;
}
return true;
}
bool DeviceSetup(struct container* c, const struct container_config* config) {
c->cgroup->DenyAllDevices();
for (const auto& dev : config->cgroup_devices) {
if (!c->cgroup->AddDevice(dev.allow, dev.major, dev.minor, dev.read,
dev.write, dev.modify, dev.type)) {
return false;
}
}
for (const auto& loopdev : c->loopdevs) {
if (!c->cgroup->AddDevice(1, major(loopdev.info.lo_rdevice),
minor(loopdev.info.lo_rdevice), 1, 0, 0, 'b')) {
return false;
}
}
return true;
}
int SetCoreSched(void* payload) {
int ret = prctl(PR_SET_CORE_SCHED, 1);
if (ret != 0 && errno != EINVAL) {
// Bubble error, minijail will abort child process.
return -errno;
}
// Success or unsupported on this kernel, continue.
return 0;
}
int Setexeccon(void* payload) {
char* init_domain = reinterpret_cast<char*>(payload);
pid_t tid = syscall(SYS_gettid);
if (tid < 0) {
PLOG(ERROR) << "Failed to gettid";
return -errno;
}
std::string exec_path =
base::StringPrintf("/proc/self/task/%d/attr/exec", tid);
base::ScopedFD fd(open(exec_path.c_str(), O_WRONLY | O_CLOEXEC));
if (!fd.is_valid()) {
PLOG(ERROR) << "Failed to open " << exec_path;
return -errno;
}
if (!base::WriteFileDescriptor(fd.get(), init_domain, strlen(init_domain))) {
PLOG(ERROR) << "Failed to write the SELinux label to " << exec_path;
return -errno;
}
return 0;
}
bool ContainerTeardown(struct container* c) {
UnmountExternalMounts(c);
if (!c->runfsroot.empty() && !c->runfs.empty()) {
/* |c->runfsroot| may have been mounted recursively. Thus use
* MNT_DETACH to "immediately disconnect the filesystem and all
* filesystems mounted below it from each other and from the
* mount table". Otherwise one would need to unmount every
* single dependent mount before unmounting |c->runfsroot|
* itself.
*/
if (umount2(c->runfsroot.value().c_str(), MNT_DETACH) != 0) {
PLOG(ERROR) << "Failed to detach " << c->runfsroot.value();
return false;
}
if (rmdir(c->runfsroot.value().c_str()) != 0) {
PLOG(ERROR) << "Failed to rmdir " << c->runfsroot.value();
return false;
}
c->runfsroot = base::FilePath();
}
if (!c->pid_file_path.empty()) {
if (unlink(c->pid_file_path.value().c_str()) != 0) {
PLOG(ERROR) << "Failed to unlink " << c->pid_file_path.value();
return false;
}
c->pid_file_path = base::FilePath();
}
if (!c->runfs.empty()) {
if (rmdir(c->runfs.value().c_str()) != 0) {
PLOG(ERROR) << "Failed to rmdir " << c->runfs.value();
return false;
}
c->runfs = base::FilePath();
}
return true;
}
void CancelContainerStart(struct container* c) {
if (c->init_pid != -1)
container_kill(c);
ContainerTeardown(c);
}
} // namespace
struct container_config* container_config_create() {
return new (std::nothrow) struct container_config();
}
void container_config_destroy(struct container_config* c) {
if (c == nullptr)
return;
delete c;
}
int container_config_config_root(struct container_config* c,
const char* config_root) {
c->config_root = base::FilePath(config_root);
return 0;
}
const char* container_config_get_config_root(const struct container_config* c) {
return c->config_root.value().c_str();
}
int container_config_rootfs(struct container_config* c, const char* rootfs) {
c->rootfs = base::FilePath(rootfs);
return 0;
}
const char* container_config_get_rootfs(const struct container_config* c) {
return c->rootfs.value().c_str();
}
void container_config_rootfs_mount_flags(struct container_config* c,
unsigned long rootfs_mount_flags) {
/* Since we are going to add MS_REMOUNT anyways, add it here so we can
* simply check against zero later. MS_BIND is also added to avoid
* re-mounting the original filesystem, since the rootfs is always
* bind-mounted.
*/
c->rootfs_mount_flags = MS_REMOUNT | MS_BIND | rootfs_mount_flags;
}
unsigned long container_config_get_rootfs_mount_flags(
const struct container_config* c) {
return c->rootfs_mount_flags;
}
int container_config_premounted_runfs(struct container_config* c,
const char* runfs) {
c->premounted_runfs = base::FilePath(runfs);
return 0;
}
const char* container_config_get_premounted_runfs(
const struct container_config* c) {
return c->premounted_runfs.value().c_str();
}
int container_config_pid_file(struct container_config* c, const char* path) {
c->pid_file_path = base::FilePath(path);
return 0;
}
const char* container_config_get_pid_file(const struct container_config* c) {
return c->pid_file_path.value().c_str();
}
int container_config_program_argv(struct container_config* c,
const char** argv,
size_t num_args) {
if (num_args < 1) {
errno = EINVAL;
return -1;
}
c->program_argv.clear();
c->program_argv.reserve(num_args);
for (size_t i = 0; i < num_args; ++i)
c->program_argv.emplace_back(argv[i]);
return 0;
}
size_t container_config_get_num_program_args(const struct container_config* c) {
return c->program_argv.size();
}
const char* container_config_get_program_arg(const struct container_config* c,
size_t index) {
if (index >= c->program_argv.size())
return nullptr;
return c->program_argv[index].c_str();
}
void container_config_uid(struct container_config* c, uid_t uid) {
c->uid = uid;
}
uid_t container_config_get_uid(const struct container_config* c) {
return c->uid;
}
int container_config_uid_map(struct container_config* c, const char* uid_map) {
c->uid_map = uid_map;
return 0;
}
void container_config_gid(struct container_config* c, gid_t gid) {
c->gid = gid;
}
gid_t container_config_get_gid(const struct container_config* c) {
return c->gid;
}
int container_config_gid_map(struct container_config* c, const char* gid_map) {
c->gid_map = gid_map;
return 0;
}
void container_config_additional_gids(struct container_config* c,
const gid_t* gids,
size_t num_gids) {
c->additional_gids.assign(gids, gids + num_gids);
}
int container_config_alt_syscall_table(struct container_config* c,
const char* alt_syscall_table) {
c->alt_syscall_table = alt_syscall_table;
return 0;
}
int container_config_add_rlimit(struct container_config* c,
int type,
rlim_t cur,
rlim_t max) {
if (c->num_rlimits >= kMaxRlimits) {
errno = ENOMEM;
return -1;
}
c->rlimits[c->num_rlimits].type = type;
c->rlimits[c->num_rlimits].cur = cur;
c->rlimits[c->num_rlimits].max = max;
c->num_rlimits++;
return 0;
}
int container_config_add_mount(struct container_config* c,
const char* name,
const char* source,
const char* destination,
const char* type,
const char* data,
const char* verity,
int flags,
int uid,
int gid,
int mode,
int mount_in_ns,
int create,
int loopback) {
if (name == nullptr || source == nullptr || destination == nullptr ||
type == nullptr) {
errno = EINVAL;
return -1;
}
c->mounts.emplace_back(
Mount{name, base::FilePath(source), base::FilePath(destination), type,
data ? data : "", verity ? verity : "", flags, uid, gid, mode,
mount_in_ns != 0, create != 0, loopback != 0});
return 0;
}
int container_config_add_cgroup_device(struct container_config* c,
int allow,
char type,
int major,
int minor,
int read,
int write,
int modify) {
c->cgroup_devices.emplace_back(CgroupDevice{
allow != 0, type, major, minor, read != 0, write != 0, modify != 0});
return 0;
}
int container_config_add_device(struct container_config* c,
char type,
const char* path,
int fs_permissions,
int major,
int minor,
int copy_major,
int copy_minor,
int uid,
int gid,
int read_allowed,
int write_allowed,
int modify_allowed) {
if (path == nullptr) {
errno = EINVAL;
return -1;
}
/* If using a dynamic major/minor number, ensure that major/minor is -1. */
if ((copy_major && (major != -1)) || (copy_minor && (minor != -1))) {
errno = EINVAL;
return -1;
}
if (read_allowed || write_allowed || modify_allowed) {
if (container_config_add_cgroup_device(c, 1, type, major, minor,
read_allowed, write_allowed,
modify_allowed) != 0) {
errno = ENOMEM;
return -1;
}
}
c->devices.emplace_back(Device{
type,
base::FilePath(path),
fs_permissions,
major,
minor,
copy_major != 0,
copy_minor != 0,
uid,
gid,
});
return 0;
}
int container_config_set_core_sched(struct container_config* c, int enable) {
c->core_sched = enable;
return 0;
}
int container_config_set_cpu_shares(struct container_config* c, int shares) {
/* CPU shares must be 2 or higher. */
if (shares < 2) {
errno = EINVAL;
return -1;
}
c->cpu_cgparams.shares = shares;
return 0;
}
int container_config_set_cpu_cfs_params(struct container_config* c,
int quota,
int period) {
/*
* quota could be set higher than period to utilize more than one CPU.
* quota could also be set as -1 to indicate the cgroup does not adhere
* to any CPU time restrictions.
*/
if (quota <= 0 && quota != -1) {
errno = EINVAL;
return -1;
}
if (period <= 0) {
errno = EINVAL;
return -1;
}
c->cpu_cgparams.quota = quota;
c->cpu_cgparams.period = period;
return 0;
}
int container_config_set_cpu_rt_params(struct container_config* c,
int rt_runtime,
int rt_period) {
/*
* rt_runtime could be set as 0 to prevent the cgroup from using
* realtime CPU.
*/
if (rt_runtime < 0 || rt_runtime >= rt_period) {
errno = EINVAL;
return -1;
}
c->cpu_cgparams.rt_runtime = rt_runtime;
c->cpu_cgparams.rt_period = rt_period;
return 0;
}
int container_config_get_cpu_shares(struct container_config* c) {
return c->cpu_cgparams.shares;
}
int container_config_get_cpu_quota(struct container_config* c) {
return c->cpu_cgparams.quota;
}
int container_config_get_cpu_period(struct container_config* c) {
return c->cpu_cgparams.period;
}
int container_config_get_cpu_rt_runtime(struct container_config* c) {
return c->cpu_cgparams.rt_runtime;
}
int container_config_get_cpu_rt_period(struct container_config* c) {
return c->cpu_cgparams.rt_period;
}
int container_config_set_cgroup_parent(struct container_config* c,
const char* parent,
uid_t cgroup_owner,
gid_t cgroup_group) {
c->cgroup_owner = cgroup_owner;
c->cgroup_group = cgroup_group;
c->cgroup_parent = base::FilePath(parent);
return 0;
}
const char* container_config_get_cgroup_parent(struct container_config* c) {
return c->cgroup_parent.value().c_str();
}
int container_config_namespaces(struct container_config* c,
const char** namespaces,
size_t num_ns) {
if (num_ns < 1)
return -EINVAL;
c->namespaces.clear();
for (size_t i = 0; i < num_ns; ++i)
c->namespaces.emplace(namespaces[i]);
return 0;
}
size_t container_config_get_num_namespaces(const struct container_config* c) {
return c->namespaces.size();
}
bool container_config_has_namespace(const struct container_config* c,
const char* ns) {
return c->namespaces.find(ns) != c->namespaces.end();
}
void container_config_keep_fds_open(struct container_config* c) {
c->keep_fds_open = true;
}
void container_config_set_capmask(struct container_config* c,
uint64_t capmask,
int ambient) {
c->use_capmask = true;
c->capmask = capmask;
c->use_capmask_ambient = ambient;
}
void container_config_set_securebits_skip_mask(struct container_config* c,
uint64_t securebits_skip_mask) {
c->securebits_skip_mask = securebits_skip_mask;
}
void container_config_set_run_as_init(struct container_config* c,
int run_as_init) {
c->do_init = !run_as_init;
}
int container_config_set_selinux_context(struct container_config* c,
const char* context) {
if (!context) {
errno = EINVAL;
return -1;
}
c->selinux_context = context;
return 0;
}
void container_config_set_pre_execve_hook(struct container_config* c,
int (*hook)(void*),
void* payload) {
c->pre_start_hook = hook;
c->pre_start_hook_payload = payload;
}
void container_config_add_hook(struct container_config* c,
minijail_hook_event_t event,
libcontainer::HookCallback callback) {
auto it = c->hooks.insert(
std::make_pair(event, std::vector<libcontainer::HookCallback>()));
it.first->second.emplace_back(std::move(callback));
}
int container_config_add_hook(struct container_config* c,
minijail_hook_event_t event,
const char* filename,
const char** argv,
size_t num_args,
int* pstdin_fd,
int* pstdout_fd,
int* pstderr_fd) {
std::vector<std::string> args;
args.reserve(num_args);
for (size_t i = 0; i < num_args; ++i)
args.emplace_back(argv[i]);
// First element of the array belongs to the parent and the second one belongs
// to the child.
base::ScopedFD stdin_fds[2], stdout_fds[2], stderr_fds[2];
if (pstdin_fd) {
if (!libcontainer::Pipe2(&stdin_fds[1], &stdin_fds[0], 0))
return -1;
}
if (pstdout_fd) {
if (!libcontainer::Pipe2(&stdout_fds[0], &stdout_fds[0], 0))
return -1;
}
if (pstderr_fd) {
if (!libcontainer::Pipe2(&stderr_fds[0], &stderr_fds[0], 0))
return -1;
}
// After this point the call has been successful, so we can now commit to
// whatever pipes we have opened.
if (pstdin_fd) {
*pstdin_fd = stdin_fds[0].release();
c->inherited_fds.emplace_back(stdin_fds[1].get());
}
if (pstdout_fd) {
*pstdout_fd = stdout_fds[0].release();
c->inherited_fds.emplace_back(stdout_fds[1].get());
}
if (pstderr_fd) {
*pstderr_fd = stderr_fds[0].release();
c->inherited_fds.emplace_back(stderr_fds[1].get());
}
container_config_add_hook(
c, event,
libcontainer::CreateExecveCallback(
base::FilePath(filename), args, std::move(stdin_fds[1]),
std::move(stdout_fds[1]), std::move(stderr_fds[1])));
return 0;
}
int container_config_inherit_fds(struct container_config* c,
const int* inherited_fds,
size_t inherited_fd_count) {
if (!c->inherited_fds.empty()) {
errno = EINVAL;
return -1;
}
for (size_t i = 0; i < inherited_fd_count; ++i)
c->inherited_fds.emplace_back(inherited_fds[i]);
return 0;
}
struct container* container_new(const char* name, const char* rundir) {
struct container* c = new (std::nothrow) container();
if (!c)
return nullptr;
c->rundir = base::FilePath(rundir);
c->name = name;
return c;
}
void container_destroy(struct container* c) {
delete c;
}
int container_start(struct container* c,
const struct container_config* config) {
if (!c) {
errno = EINVAL;
return -1;
}
if (!config) {
errno = EINVAL;
return -1;
}
if (config->program_argv.empty()) {
errno = EINVAL;
return -1;
}
// This will run in all the error cases.
base::ScopedClosureRunner teardown(
base::Bind(&CancelContainerStart, base::Unretained(c)));
if (!config->config_root.empty())
c->config_root = config->config_root;
if (!config->premounted_runfs.empty()) {
c->runfs.clear();
c->runfsroot = config->premounted_runfs;
} else {
if (!MountRunfs(c, config))
return -1;
}
c->jail.reset(minijail_new());
if (!c->jail) {
errno = ENOMEM;
return -1;
}
if (!DoContainerMounts(c, config))
return -1;
int cgroup_uid;
if (!GetUsernsOutsideId(config->uid_map, config->cgroup_owner, &cgroup_uid))
return -1;
int cgroup_gid;
if (!GetUsernsOutsideId(config->gid_map, config->cgroup_group, &cgroup_gid))
return -1;
c->cgroup = libcontainer::Cgroup::Create(
c->name, base::FilePath("/sys/fs/cgroup"), config->cgroup_parent,
cgroup_uid, cgroup_gid);
if (!c->cgroup)
return -1;
// Must be root to modify device cgroup or mknod.
std::map<minijail_hook_event_t, std::vector<libcontainer::HookCallback>>
hook_callbacks;
if (getuid() == 0) {
if (!config->devices.empty()) {
// Create the devices in the mount namespace.
auto it = hook_callbacks.insert(
std::make_pair(MINIJAIL_HOOK_EVENT_PRE_CHROOT,
std::vector<libcontainer::HookCallback>()));
it.first->second.emplace_back(
libcontainer::AdaptCallbackToRunInNamespaces(
base::Bind(&CreateDeviceNodes, base::Unretained(c),
base::Unretained(config)),
{CLONE_NEWNS}));
}
if (!DeviceSetup(c, config))
return -1;
}
/* Setup CPU cgroup params. */
if (config->cpu_cgparams.shares) {
if (!c->cgroup->SetCpuShares(config->cpu_cgparams.shares))
return -1;
}
if (config->cpu_cgparams.period) {
if (!c->cgroup->SetCpuQuota(config->cpu_cgparams.quota))
return -1;
if (!c->cgroup->SetCpuPeriod(config->cpu_cgparams.period))
return -1;
}
if (config->cpu_cgparams.rt_period) {
if (!c->cgroup->SetCpuRtRuntime(config->cpu_cgparams.rt_runtime))
return -1;
if (!c->cgroup->SetCpuRtPeriod(config->cpu_cgparams.rt_period))
return -1;
}
/* Setup and start the container with libminijail. */
if (!config->pid_file_path.empty())
c->pid_file_path = config->pid_file_path;
else if (!c->runfs.empty())
c->pid_file_path = c->runfs.Append("container.pid");
if (!c->pid_file_path.empty())
minijail_write_pid_file(c->jail.get(), c->pid_file_path.value().c_str());
minijail_forward_signals(c->jail.get());
minijail_reset_signal_mask(c->jail.get());
minijail_reset_signal_handlers(c->jail.get());
/* Setup container namespaces. */
if (container_config_has_namespace(config, "ipc"))
minijail_namespace_ipc(c->jail.get());
if (container_config_has_namespace(config, "mount"))
minijail_namespace_vfs(c->jail.get());
if (container_config_has_namespace(config, "network"))
minijail_namespace_net(c->jail.get());
if (container_config_has_namespace(config, "pid"))
minijail_namespace_pids(c->jail.get());
if (container_config_has_namespace(config, "user")) {
minijail_namespace_user(c->jail.get());
if (minijail_uidmap(c->jail.get(), config->uid_map.c_str()) != 0)
return -1;
if (minijail_gidmap(c->jail.get(), config->gid_map.c_str()) != 0)
return -1;
}
if (container_config_has_namespace(config, "cgroup"))
minijail_namespace_cgroups(c->jail.get());
if (getuid() != 0)
minijail_namespace_user_disable_setgroups(c->jail.get());
// Set the UID/GID inside the container if not 0.
if (!GetUsernsOutsideId(config->uid_map, config->uid, nullptr))
return -1;
else if (config->uid > 0)
minijail_change_uid(c->jail.get(), config->uid);
if (!GetUsernsOutsideId(config->gid_map, config->gid, nullptr))
return -1;
else if (config->gid > 0)
minijail_change_gid(c->jail.get(), config->gid);
// Set the supplementary GIDs inside the container, if specified.
if (!config->additional_gids.empty()) {
for (const gid_t additional_gid : config->additional_gids) {
if (!GetUsernsOutsideId(config->gid_map, additional_gid, nullptr))
return -1;
}
minijail_set_supplementary_gids(c->jail.get(),
config->additional_gids.size(),
config->additional_gids.data());
}
if (minijail_enter_pivot_root(c->jail.get(), c->runfsroot.value().c_str()) !=
0) {
return -1;
}
// Add the cgroups configured above.
for (int32_t i = 0; i < libcontainer::Cgroup::Type::NUM_TYPES; i++) {
if (c->cgroup->has_tasks_path(i)) {
if (minijail_add_to_cgroup(
c->jail.get(), c->cgroup->tasks_path(i).value().c_str()) != 0) {
return -1;
}
}
}
if (!config->alt_syscall_table.empty())
minijail_use_alt_syscall(c->jail.get(), config->alt_syscall_table.c_str());
if (config->core_sched) {
if (minijail_add_hook(c->jail.get(), &SetCoreSched, nullptr,
MINIJAIL_HOOK_EVENT_PRE_DROP_CAPS) != 0) {
return -1;
}
}
for (int i = 0; i < config->num_rlimits; i++) {
const Rlimit& lim = config->rlimits[i];
if (minijail_rlimit(c->jail.get(), lim.type, lim.cur, lim.max) != 0)
return -1;
}
if (!config->selinux_context.empty()) {
if (minijail_add_hook(c->jail.get(), &Setexeccon,
const_cast<char*>(config->selinux_context.c_str()),
MINIJAIL_HOOK_EVENT_PRE_EXECVE) != 0) {
return -1;
}
}
if (config->pre_start_hook) {
if (minijail_add_hook(c->jail.get(), config->pre_start_hook,
config->pre_start_hook_payload,
MINIJAIL_HOOK_EVENT_PRE_EXECVE) != 0) {
return -1;
}
}
// Now that all pre-requisite hooks are installed, copy the ones in the
// container_config object in the correct order.
for (const auto& config_hook : config->hooks) {
auto it = hook_callbacks.insert(std::make_pair(
config_hook.first, std::vector<libcontainer::HookCallback>()));
it.first->second.insert(it.first->second.end(), config_hook.second.begin(),
config_hook.second.end());
}
c->hook_states.clear();
// Reserve enough memory to hold all the hooks, so that their addresses do not
// get invalidated by reallocation.
c->hook_states.reserve(MINIJAIL_HOOK_EVENT_MAX);
for (minijail_hook_event_t event :
{MINIJAIL_HOOK_EVENT_PRE_CHROOT, MINIJAIL_HOOK_EVENT_PRE_DROP_CAPS,
MINIJAIL_HOOK_EVENT_PRE_EXECVE}) {
const auto& it = hook_callbacks.find(event);
if (it == hook_callbacks.end())
continue;
c->hook_states.emplace_back(
std::make_pair(libcontainer::HookState(), it->second));
if (!c->hook_states.back().first.InstallHook(c->jail.get(), event))
return -1;
}
for (int fd : config->inherited_fds) {
if (minijail_preserve_fd(c->jail.get(), fd, fd) != 0)
return -1;
}
/* TODO(dgreid) - remove this once shared mounts are cleaned up. */
minijail_skip_remount_private(c->jail.get());
if (!config->keep_fds_open)
minijail_close_open_fds(c->jail.get());
if (config->use_capmask) {
minijail_use_caps(c->jail.get(), config->capmask);
if (config->use_capmask_ambient)
minijail_set_ambient_caps(c->jail.get());
if (config->securebits_skip_mask) {
minijail_skip_setting_securebits(c->jail.get(),
config->securebits_skip_mask);
}
}
if (!config->do_init)
minijail_run_as_init(c->jail.get());
std::vector<char*> argv_cstr;
argv_cstr.reserve(config->program_argv.size() + 1);
for (const auto& arg : config->program_argv)
argv_cstr.emplace_back(const_cast<char*>(arg.c_str()));
argv_cstr.emplace_back(nullptr);
if (minijail_run_pid_pipes_no_preload(c->jail.get(), argv_cstr[0],
argv_cstr.data(), &c->init_pid, nullptr,
nullptr, nullptr) != 0) {
return -1;
}
// |hook_states| is already sorted in the correct order.
for (auto& hook_state : c->hook_states) {
if (!hook_state.first.WaitForHookAndRun(hook_state.second, c->init_pid))
return -1;
}
// The container has started successfully, no need to tear it down anymore.
ignore_result(teardown.Release());
return 0;
}
const char* container_root(struct container* c) {
return c->runfs.value().c_str();
}
int container_pid(struct container* c) {
return c->init_pid;
}
int container_wait(struct container* c) {
int rc;
do {
rc = minijail_wait(c->jail.get());
} while (rc == -EINTR);
// If the process had already been reaped, still perform teardown.
if (rc == -ECHILD || rc >= 0) {
if (!ContainerTeardown(c))
rc = -errno;
}
return rc;
}
int container_kill(struct container* c) {
if (kill(c->init_pid, SIGKILL) != 0 && errno != ESRCH) {
PLOG(ERROR) << "Failed to kill " << c->init_pid;
return -errno;
}
return container_wait(c);
}
char* container_config_dump(struct container_config* c, int sort_vectors) {
std::stringstream out;
DumpConfig(&out, c, sort_vectors);
return strdup(out.str().c_str());
}