mirror of
https://github.com/google/nsjail.git
synced 2026-08-30 18:41:30 -07:00
Implement support for the new Linux mount API and refactor mount logic into dedicated files. Added the --experimental_mnt flag to allow forcing legacy or new mount behaviors.
This commit is contained in:
9
Makefile
9
Makefile
@@ -46,7 +46,7 @@ endif
|
||||
BIN = nsjail
|
||||
LIBS = kafel/libkafel.a
|
||||
|
||||
SRCS_CXX = caps.cc cgroup.cc cgroup2.cc cmdline.cc config.cc contain.cc cpu.cc logs.cc mnt.cc net.cc nsjail.cc pid.cc sandbox.cc subproc.cc uts.cc user.cc util.cc
|
||||
SRCS_CXX = caps.cc cgroup.cc cgroup2.cc cmdline.cc config.cc contain.cc cpu.cc logs.cc mnt.cc mnt_legacy.cc mnt_newapi.cc net.cc nsjail.cc pid.cc sandbox.cc subproc.cc uts.cc user.cc util.cc
|
||||
SRCS_PROTO = config.proto
|
||||
|
||||
SRCS_PB_CXX = $(SRCS_PROTO:.proto=.pb.cc)
|
||||
@@ -112,14 +112,17 @@ caps.o: caps.h nsjail.h config.pb.h logs.h macros.h util.h
|
||||
cgroup.o: cgroup.h nsjail.h config.pb.h logs.h util.h
|
||||
cgroup2.o: cgroup2.h nsjail.h config.pb.h logs.h util.h
|
||||
cmdline.o: cmdline.h nsjail.h config.pb.h caps.h config.h logs.h macros.h
|
||||
cmdline.o: mnt.h user.h util.h
|
||||
cmdline.o: mnt.h mnt_newapi.h user.h util.h
|
||||
config.o: config.h nsjail.h config.pb.h caps.h cmdline.h logs.h macros.h
|
||||
config.o: mnt.h user.h util.h
|
||||
contain.o: contain.h nsjail.h config.pb.h caps.h cgroup.h cgroup2.h config.h
|
||||
contain.o: cpu.h logs.h macros.h mnt.h net.h pid.h user.h util.h uts.h
|
||||
cpu.o: cpu.h nsjail.h config.pb.h logs.h util.h
|
||||
logs.o: logs.h macros.h util.h nsjail.h config.pb.h
|
||||
mnt.o: mnt.h nsjail.h config.pb.h logs.h macros.h subproc.h util.h
|
||||
mnt.o: mnt.h nsjail.h config.pb.h logs.h macros.h mnt_legacy.h mnt_newapi.h
|
||||
mnt.o: subproc.h util.h
|
||||
mnt_legacy.o: mnt_legacy.h mnt.h nsjail.h config.pb.h logs.h macros.h util.h
|
||||
mnt_newapi.o: mnt_newapi.h mnt.h nsjail.h config.pb.h logs.h util.h
|
||||
net.o: net.h nsjail.h config.pb.h logs.h util.h
|
||||
nsjail.o: nsjail.h config.pb.h cgroup2.h cmdline.h logs.h macros.h net.h
|
||||
nsjail.o: sandbox.h subproc.h util.h
|
||||
|
||||
25
cmdline.cc
25
cmdline.cc
@@ -53,6 +53,7 @@
|
||||
#include "logs.h"
|
||||
#include "macros.h"
|
||||
#include "mnt.h"
|
||||
#include "mnt_newapi.h"
|
||||
#include "user.h"
|
||||
#include "util.h"
|
||||
|
||||
@@ -139,6 +140,7 @@ static const struct custom_option custom_opts[] = {
|
||||
{ { "disable_proc", no_argument, nullptr, 0x0603 }, "Disable mounting procfs in the jail" },
|
||||
{ { "proc_path", required_argument, nullptr, 0x0605 }, "Path used to mount procfs (default: '/proc')" },
|
||||
{ { "proc_rw", no_argument, nullptr, 0x0606 }, "Is procfs mounted as R/W (default: R/O)" },
|
||||
{ { "experimental_mnt", required_argument, nullptr, 0x0609 }, "Mount API to use: 'new' (fsopen/fsmount), 'old' (mount syscall), or 'default' (auto-detect based on kernel version)" },
|
||||
{ { "seccomp_policy", required_argument, nullptr, 'P' }, "Path to file containing seccomp-bpf policy (see kafel/)" },
|
||||
{ { "seccomp_string", required_argument, nullptr, 0x0901 }, "String with kafel seccomp-bpf policy (see kafel/)" },
|
||||
{ { "seccomp_log", no_argument, nullptr, 0x0902 }, "Use SECCOMP_FILTER_FLAG_LOG. Log all actions except SECCOMP_RET_ALLOW). Supported since kernel version 4.14" },
|
||||
@@ -408,22 +410,12 @@ static bool setupMounts(nsj_t* nsj) {
|
||||
for (int i = nsj->njc.mount_size() - 1; i > 0; i--) {
|
||||
nsj->njc.mutable_mount()->SwapElements(i, i - 1);
|
||||
}
|
||||
} else {
|
||||
nsjail::MountPt* p = nsj->njc.add_mount();
|
||||
p->set_dst("/");
|
||||
p->set_fstype("tmpfs");
|
||||
p->set_rw(nsj->is_root_rw);
|
||||
p->set_is_dir(true);
|
||||
/* Insert at the beginning */
|
||||
for (int i = nsj->njc.mount_size() - 1; i > 0; i--) {
|
||||
nsj->njc.mutable_mount()->SwapElements(i, i - 1);
|
||||
}
|
||||
}
|
||||
if (!nsj->proc_path.empty()) {
|
||||
nsjail::MountPt* p = nsj->njc.add_mount();
|
||||
p->set_dst(nsj->proc_path);
|
||||
p->set_fstype("proc");
|
||||
p->set_rw(nsj->njc.mount_proc());
|
||||
p->set_rw(nsj->is_proc_rw);
|
||||
p->set_is_dir(true);
|
||||
}
|
||||
|
||||
@@ -470,6 +462,7 @@ std::unique_ptr<nsj_t> parseArgs(int argc, char* argv[]) {
|
||||
nsj->orig_euid = geteuid();
|
||||
nsj->seccomp_fprog.filter = NULL;
|
||||
nsj->seccomp_fprog.len = 0;
|
||||
nsj->mnt_newapi = mnt::newapi::isAvailable();
|
||||
|
||||
nsj->openfds.push_back(STDIN_FILENO);
|
||||
nsj->openfds.push_back(STDOUT_FILENO);
|
||||
@@ -685,6 +678,16 @@ std::unique_ptr<nsj_t> parseArgs(int argc, char* argv[]) {
|
||||
case 0x0606:
|
||||
nsj->is_proc_rw = true;
|
||||
break;
|
||||
case 0x0609:
|
||||
if (strcasecmp(optarg, "new") == 0) {
|
||||
nsj->mnt_newapi = true;
|
||||
} else if (strcasecmp(optarg, "old") == 0) {
|
||||
nsj->mnt_newapi = false;
|
||||
} else if (strcasecmp(optarg, "default") != 0) {
|
||||
LOG_E("--experimental_mnt must be 'new', 'old', or 'default'");
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
case 0x0607:
|
||||
nsj->njc.mutable_exec_bin()->set_exec_fd(true);
|
||||
break;
|
||||
|
||||
23
macros.h
23
macros.h
@@ -24,6 +24,8 @@
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#if !defined(TEMP_FAILURE_RETRY)
|
||||
#define TEMP_FAILURE_RETRY(expression) \
|
||||
(__extension__({ \
|
||||
@@ -40,4 +42,25 @@
|
||||
|
||||
#define NS_VALSTR_STRUCT(x) {(uint64_t)x, #x}
|
||||
|
||||
/* go-style defer */
|
||||
template <typename F>
|
||||
struct Defer {
|
||||
F f;
|
||||
Defer(F f) : f(std::move(f)) {
|
||||
}
|
||||
~Defer() noexcept {
|
||||
f();
|
||||
}
|
||||
|
||||
Defer(const Defer&) = delete;
|
||||
Defer& operator=(const Defer&) = delete;
|
||||
Defer(Defer&&) = default;
|
||||
Defer& operator=(Defer&&) = default;
|
||||
};
|
||||
|
||||
#define _DEFER_1(x, y) x##y
|
||||
#define _DEFER_2(x, y) _DEFER_1(x, y)
|
||||
#define _DEFER_3(x) _DEFER_2(x, __COUNTER__)
|
||||
#define defer [[maybe_unused]] Defer _DEFER_3(_defer_) = [&]()
|
||||
|
||||
#endif /* NS_COMMON_H */
|
||||
|
||||
442
mnt.cc
442
mnt.cc
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "mnt.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
@@ -41,27 +42,17 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "logs.h"
|
||||
#include "macros.h"
|
||||
#include "mnt_legacy.h"
|
||||
#include "mnt_newapi.h"
|
||||
#include "subproc.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace mnt {
|
||||
|
||||
struct mount_t {
|
||||
std::string src;
|
||||
std::string src_content;
|
||||
std::string dst;
|
||||
std::string fs_type;
|
||||
std::string options;
|
||||
uintptr_t flags;
|
||||
bool is_dir;
|
||||
bool is_symlink;
|
||||
bool is_mandatory;
|
||||
bool mounted;
|
||||
};
|
||||
|
||||
#if !defined(MS_NOSYMFOLLOW)
|
||||
#define MS_NOSYMFOLLOW 256
|
||||
#endif /* if !defined(MS_NOSYMFOLLOW) */
|
||||
@@ -75,11 +66,7 @@ struct mount_t {
|
||||
#define MS_NOUSER (1 << 31)
|
||||
#endif /* if !defined(MS_NOUSER) */
|
||||
|
||||
#if !defined(ST_NOSYMFOLLOW)
|
||||
#define ST_NOSYMFOLLOW 8192
|
||||
#endif /* if !defined(ST_NOSYMFOLLOW) */
|
||||
|
||||
static const std::string flagsToStr(unsigned long flags) {
|
||||
const std::string flagsToStr(unsigned long flags) {
|
||||
std::string res;
|
||||
|
||||
struct {
|
||||
@@ -161,339 +148,6 @@ const std::string describeMountPt(const nsjail::MountPt& mpt) {
|
||||
return descr;
|
||||
}
|
||||
|
||||
/* Helper for internal use with mount_t */
|
||||
static const std::string describeMountPt(const mount_t& mpt) {
|
||||
std::string descr;
|
||||
|
||||
descr.append(mpt.src.empty() ? "" : QC(mpt.src))
|
||||
.append(mpt.src.empty() ? "" : " -> ")
|
||||
.append(QC(mpt.dst))
|
||||
.append(" flags:")
|
||||
.append(flagsToStr(mpt.flags))
|
||||
.append(" type:")
|
||||
.append(QC(mpt.fs_type))
|
||||
.append(" options:")
|
||||
.append(QC(mpt.options));
|
||||
|
||||
if (mpt.is_dir) {
|
||||
descr.append(" dir:true");
|
||||
} else {
|
||||
descr.append(" dir:false");
|
||||
}
|
||||
if (!mpt.is_mandatory) {
|
||||
descr.append(" mandatory:false");
|
||||
}
|
||||
if (!mpt.src_content.empty()) {
|
||||
descr.append(" src_content_len:").append(std::to_string(mpt.src_content.length()));
|
||||
}
|
||||
if (mpt.is_symlink) {
|
||||
descr.append(" symlink:true");
|
||||
}
|
||||
|
||||
return descr;
|
||||
}
|
||||
|
||||
static bool isDir(const char* path) {
|
||||
/*
|
||||
* If the source dir is NULL, we assume it's a dir (for /proc and tmpfs)
|
||||
*/
|
||||
if (path == nullptr) {
|
||||
return true;
|
||||
}
|
||||
struct stat st;
|
||||
if (stat(path, &st) == -1) {
|
||||
PLOG_D("stat(%s)", QC(path));
|
||||
return false;
|
||||
}
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int mountRWIfPossible(mount_t* mpt, const char* src, const char* dst) {
|
||||
int res =
|
||||
mount(src, dst, mpt->fs_type.c_str(), mpt->flags & ~(MS_RDONLY), mpt->options.c_str());
|
||||
if ((mpt->flags & MS_RDONLY) && res == -1 && errno == EPERM) {
|
||||
LOG_W("mount(%s) src:%s dstpath:%s could not mount read-write, falling "
|
||||
"back to mounting read-only directly",
|
||||
describeMountPt(*mpt).c_str(), QC(src), QC(dst));
|
||||
res = mount(src, dst, mpt->fs_type.c_str(), mpt->flags, mpt->options.c_str());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool mountPt(mount_t* mpt, const char* newroot, const char* tmpdir) {
|
||||
LOG_D("Mounting %s", describeMountPt(*mpt).c_str());
|
||||
|
||||
char dstpath[PATH_MAX];
|
||||
snprintf(dstpath, sizeof(dstpath), "%s/%s", newroot, mpt->dst.c_str());
|
||||
|
||||
char srcpath[PATH_MAX];
|
||||
if (!mpt->src.empty()) {
|
||||
snprintf(srcpath, sizeof(srcpath), "%s", mpt->src.c_str());
|
||||
} else {
|
||||
snprintf(srcpath, sizeof(srcpath), "none");
|
||||
}
|
||||
|
||||
if (!util::createDirRecursively(dstpath)) {
|
||||
LOG_W("Couldn't create upper directories for '%s'", dstpath);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mpt->is_symlink) {
|
||||
LOG_D("symlink(%s, %s)", util::StrQuote(srcpath).c_str(),
|
||||
util::StrQuote(dstpath).c_str());
|
||||
if (symlink(srcpath, dstpath) == -1) {
|
||||
if (mpt->is_mandatory) {
|
||||
PLOG_E("symlink('%s', '%s')", util::StrQuote(srcpath).c_str(),
|
||||
util::StrQuote(dstpath).c_str());
|
||||
return false;
|
||||
} else {
|
||||
PLOG_W("symlink('%s', '%s'), but it's not mandatory, continuing",
|
||||
util::StrQuote(srcpath).c_str(),
|
||||
util::StrQuote(dstpath).c_str());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mpt->is_dir) {
|
||||
if (mkdir(dstpath, 0711) == -1 && errno != EEXIST) {
|
||||
PLOG_W("mkdir(%s)", QC(dstpath));
|
||||
}
|
||||
} else {
|
||||
int fd = TEMP_FAILURE_RETRY(open(dstpath, O_CREAT | O_RDONLY | O_CLOEXEC, 0644));
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
} else {
|
||||
PLOG_W("open(%s, O_CREAT|O_RDONLY|O_CLOEXEC, 0644)", QC(dstpath));
|
||||
}
|
||||
}
|
||||
|
||||
if (!mpt->src_content.empty()) {
|
||||
static uint64_t df_counter = 0;
|
||||
snprintf(
|
||||
srcpath, sizeof(srcpath), "%s/dynamic_file.%" PRIu64, tmpdir, ++df_counter);
|
||||
int fd = TEMP_FAILURE_RETRY(
|
||||
open(srcpath, O_CREAT | O_EXCL | O_CLOEXEC | O_WRONLY, 0644));
|
||||
if (fd < 0) {
|
||||
PLOG_W("open(srcpath, O_CREAT|O_EXCL|O_CLOEXEC|O_WRONLY, 0644) failed");
|
||||
return false;
|
||||
}
|
||||
if (!util::writeToFd(fd, mpt->src_content.data(), mpt->src_content.length())) {
|
||||
LOG_W(
|
||||
"Writing %zu bytes to '%s' failed", mpt->src_content.length(), srcpath);
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
close(fd);
|
||||
mpt->flags |= (MS_BIND | MS_REC | MS_PRIVATE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initially mount it as RW, it will be remounted later on if needed
|
||||
*/
|
||||
if (mountRWIfPossible(mpt, srcpath, dstpath) == -1) {
|
||||
if (errno == EACCES) {
|
||||
PLOG_W("mount('%s') src:'%s' dstpath:'%s' failed. "
|
||||
"Try fixing this problem by applying 'chmod o+x' to the '%s' "
|
||||
"directory and its ancestors",
|
||||
describeMountPt(*mpt).c_str(), srcpath, dstpath, srcpath);
|
||||
} else {
|
||||
PLOG_W("mount('%s') src:'%s' dstpath:'%s' failed",
|
||||
describeMountPt(*mpt).c_str(), srcpath, dstpath);
|
||||
if (mpt->fs_type.compare("proc") == 0) {
|
||||
PLOG_W("procfs can only be mounted if the original /proc doesn't "
|
||||
"have any other file-systems mounted on top of it (e.g. "
|
||||
"/dev/null on top of /proc/kcore)");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
mpt->mounted = true;
|
||||
}
|
||||
|
||||
if (!mpt->src_content.empty() && unlink(srcpath) == -1) {
|
||||
PLOG_W("unlink('%s')", srcpath);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool remountPt(const mount_t& mpt) {
|
||||
if (!mpt.mounted) {
|
||||
return true;
|
||||
}
|
||||
if (mpt.is_symlink) {
|
||||
return true;
|
||||
}
|
||||
|
||||
struct statvfs vfs;
|
||||
if (TEMP_FAILURE_RETRY(statvfs(mpt.dst.c_str(), &vfs)) == -1) {
|
||||
PLOG_W("statvfs('%s')", mpt.dst.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
struct {
|
||||
const unsigned long mount_flag;
|
||||
const unsigned long vfs_flag;
|
||||
} static const mountPairs[] = {
|
||||
{MS_NOSUID, ST_NOSUID},
|
||||
{MS_NODEV, ST_NODEV},
|
||||
{MS_NOEXEC, ST_NOEXEC},
|
||||
{MS_SYNCHRONOUS, ST_SYNCHRONOUS},
|
||||
{MS_MANDLOCK, ST_MANDLOCK},
|
||||
{MS_NOATIME, ST_NOATIME},
|
||||
{MS_NODIRATIME, ST_NODIRATIME},
|
||||
{MS_RELATIME, ST_RELATIME},
|
||||
{MS_NOSYMFOLLOW, ST_NOSYMFOLLOW},
|
||||
};
|
||||
|
||||
const unsigned long per_mountpoint_flags =
|
||||
MS_LAZYTIME | MS_MANDLOCK | MS_NOATIME | MS_NODEV | MS_NODIRATIME | MS_NOEXEC |
|
||||
MS_NOSUID | MS_RELATIME | MS_RDONLY | MS_SYNCHRONOUS | MS_NOSYMFOLLOW;
|
||||
unsigned long new_flags = MS_REMOUNT | MS_BIND | (mpt.flags & per_mountpoint_flags);
|
||||
for (const auto& i : mountPairs) {
|
||||
if (vfs.f_flag & i.vfs_flag) {
|
||||
new_flags |= i.mount_flag;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_D("Re-mounting '%s' (flags:%s)", mpt.dst.c_str(), flagsToStr(new_flags).c_str());
|
||||
if (mount(mpt.dst.c_str(), mpt.dst.c_str(), NULL, new_flags, 0) == -1) {
|
||||
PLOG_W("mount('%s', flags:%s)", mpt.dst.c_str(), flagsToStr(new_flags).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mkdirAndTest(const std::string& dir) {
|
||||
if (mkdir(dir.c_str(), 0755) == -1 && errno != EEXIST) {
|
||||
PLOG_D("Couldn't create '%s' directory", dir.c_str());
|
||||
return false;
|
||||
}
|
||||
if (access(dir.c_str(), R_OK) == -1) {
|
||||
PLOG_W("access('%s', R_OK)", dir.c_str());
|
||||
return false;
|
||||
}
|
||||
LOG_D("Created accessible directory in '%s'", dir.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::unique_ptr<std::string> getDir(nsj_t* nsj, const char* name) {
|
||||
std::unique_ptr<std::string> dir(new std::string);
|
||||
|
||||
dir->assign("/run/user/").append(std::to_string(nsj->orig_uid)).append("/nsjail");
|
||||
if (mkdirAndTest(*dir)) {
|
||||
dir->append("/").append(name);
|
||||
if (mkdirAndTest(*dir)) {
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
dir->assign("/run/user/")
|
||||
.append("/nsjail.")
|
||||
.append(std::to_string(nsj->orig_uid))
|
||||
.append(".")
|
||||
.append(name);
|
||||
if (mkdirAndTest(*dir)) {
|
||||
return dir;
|
||||
}
|
||||
dir->assign("/tmp/nsjail.").append(std::to_string(nsj->orig_uid)).append(".").append(name);
|
||||
if (mkdirAndTest(*dir)) {
|
||||
return dir;
|
||||
}
|
||||
const char* tmp = getenv("TMPDIR");
|
||||
if (tmp) {
|
||||
dir->assign(tmp)
|
||||
.append("/")
|
||||
.append("nsjail.")
|
||||
.append(std::to_string(nsj->orig_uid))
|
||||
.append(".")
|
||||
.append(name);
|
||||
if (mkdirAndTest(*dir)) {
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
dir->assign("/dev/shm/nsjail.")
|
||||
.append(std::to_string(nsj->orig_uid))
|
||||
.append(".")
|
||||
.append(name);
|
||||
if (mkdirAndTest(*dir)) {
|
||||
return dir;
|
||||
}
|
||||
dir->assign("/tmp/nsjail.")
|
||||
.append(std::to_string(nsj->orig_uid))
|
||||
.append(".")
|
||||
.append(name)
|
||||
.append(".")
|
||||
.append(std::to_string(util::rnd64()));
|
||||
if (mkdirAndTest(*dir)) {
|
||||
return dir;
|
||||
}
|
||||
|
||||
LOG_E("Couldn't create tmp directory of type '%s'", QC(name));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool addMountPt(mount_t* mnt, const std::string& src, const std::string& dst,
|
||||
const std::string& fstype, const std::string& options, uintptr_t flags, isDir_t is_dir,
|
||||
bool is_mandatory, const std::string& src_env, const std::string& dst_env,
|
||||
const std::string& src_content, bool is_symlink) {
|
||||
if (!src_env.empty()) {
|
||||
const char* e = getenv(src_env.c_str());
|
||||
if (e == nullptr) {
|
||||
LOG_W("No such envar:%s", QC(src_env));
|
||||
return false;
|
||||
}
|
||||
mnt->src = e;
|
||||
}
|
||||
mnt->src.append(src);
|
||||
|
||||
if (!dst_env.empty()) {
|
||||
const char* e = getenv(dst_env.c_str());
|
||||
if (e == nullptr) {
|
||||
LOG_W("No such envar:%s", QC(dst_env));
|
||||
return false;
|
||||
}
|
||||
mnt->dst = e;
|
||||
}
|
||||
mnt->dst.append(dst);
|
||||
|
||||
mnt->fs_type = fstype;
|
||||
mnt->options = options;
|
||||
mnt->flags = flags;
|
||||
mnt->is_symlink = is_symlink;
|
||||
mnt->is_mandatory = is_mandatory;
|
||||
mnt->mounted = false;
|
||||
mnt->src_content = src_content;
|
||||
|
||||
switch (is_dir) {
|
||||
case NS_DIR_YES:
|
||||
mnt->is_dir = true;
|
||||
break;
|
||||
case NS_DIR_NO:
|
||||
mnt->is_dir = false;
|
||||
break;
|
||||
case NS_DIR_MAYBE: {
|
||||
if (!src_content.empty()) {
|
||||
mnt->is_dir = false;
|
||||
} else if (mnt->src.empty()) {
|
||||
mnt->is_dir = true;
|
||||
} else if (mnt->flags & MS_BIND) {
|
||||
mnt->is_dir = mnt::isDir(mnt->src.c_str());
|
||||
} else {
|
||||
mnt->is_dir = true;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
LOG_E("Unknown is_dir value: %d", is_dir);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool initNoCloneNs(nsj_t* nsj) {
|
||||
/*
|
||||
* If CLONE_NEWNS is not used, we would be changing the global mount namespace, so simply
|
||||
@@ -514,71 +168,26 @@ static bool initNoCloneNs(nsj_t* nsj) {
|
||||
}
|
||||
|
||||
static bool initCloneNs(nsj_t* nsj) {
|
||||
if (chdir("/") == -1) {
|
||||
PLOG_E("chdir('/')");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::string> destdir = getDir(nsj, "root");
|
||||
if (!destdir) {
|
||||
LOG_E("Couldn't obtain root mount directories");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Make changes to / (recursively) private, to avoid changing the global mount ns */
|
||||
if (mount("/", "/", NULL, MS_REC | MS_PRIVATE, NULL) == -1) {
|
||||
PLOG_E("mount('/', '/', NULL, MS_REC|MS_PRIVATE, NULL)");
|
||||
return false;
|
||||
}
|
||||
if (mount(NULL, destdir->c_str(), "tmpfs", 0, "size=16777216") == -1) {
|
||||
PLOG_E("mount(%s, 'tmpfs')", QC(*destdir));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::string> tmpdir = getDir(nsj, "tmp");
|
||||
if (!tmpdir) {
|
||||
LOG_E("Couldn't obtain temporary mount directories");
|
||||
return false;
|
||||
}
|
||||
if (mount(NULL, tmpdir->c_str(), "tmpfs", 0, "size=16777216") == -1) {
|
||||
PLOG_E("mount(%s, 'tmpfs')", QC(*tmpdir));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<mount_t> mounted_mpts;
|
||||
for (const auto& p : nsj->njc.mount()) {
|
||||
uintptr_t flags = (p.rw() ? 0 : (uintptr_t)MS_RDONLY);
|
||||
if (p.is_bind()) {
|
||||
flags |= (MS_BIND | MS_REC | MS_PRIVATE);
|
||||
defer {
|
||||
for (auto& p : mounted_mpts) {
|
||||
if (p.fd >= 0) {
|
||||
close(p.fd);
|
||||
p.fd = -1;
|
||||
}
|
||||
if (p.nosuid()) {
|
||||
flags |= MS_NOSUID;
|
||||
}
|
||||
if (p.nodev()) {
|
||||
flags |= MS_NODEV;
|
||||
}
|
||||
if (p.noexec()) {
|
||||
flags |= MS_NOEXEC;
|
||||
};
|
||||
|
||||
std::unique_ptr<std::string> destdir;
|
||||
|
||||
if (nsj->mnt_newapi) {
|
||||
destdir = newapi::buildMountTree(nsj, &mounted_mpts);
|
||||
} else {
|
||||
destdir = legacy::buildMountTree(nsj, &mounted_mpts);
|
||||
}
|
||||
|
||||
mount_t mpt;
|
||||
if (!addMountPt(&mpt, p.src(), p.dst(), p.fstype(), p.options(), flags,
|
||||
p.has_is_dir() ? (p.is_dir() ? NS_DIR_YES : NS_DIR_NO) : NS_DIR_MAYBE,
|
||||
p.mandatory(), p.prefix_src_env(), p.prefix_dst_env(), p.src_content(),
|
||||
p.is_symlink())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mountPt(&mpt, destdir->c_str(), tmpdir->c_str()) && mpt.is_mandatory) {
|
||||
LOG_E("Couldn't mount %s", QC(mpt.dst));
|
||||
return false;
|
||||
}
|
||||
|
||||
mounted_mpts.push_back(mpt);
|
||||
}
|
||||
|
||||
if (umount2(tmpdir->c_str(), MNT_DETACH) == -1) {
|
||||
PLOG_E("umount2(%s, MNT_DETACH)", QC(*tmpdir));
|
||||
if (!destdir) {
|
||||
LOG_E("Failed to build mount tree");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -601,6 +210,7 @@ static bool initCloneNs(nsj_t* nsj) {
|
||||
PLOG_E("umount2('/', MNT_DETACH)");
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
/*
|
||||
* pivot_root would normally un-mount the old root, however in certain cases this
|
||||
@@ -641,8 +251,14 @@ static bool initCloneNs(nsj_t* nsj) {
|
||||
}
|
||||
|
||||
/* Remounting R/O, if needed. Only for mount points that were actually mounted */
|
||||
for (const auto& mpt : mounted_mpts) {
|
||||
if (!remountPt(mpt) && mpt.is_mandatory) {
|
||||
for (auto& mpt : mounted_mpts) {
|
||||
bool success;
|
||||
if (nsj->mnt_newapi) {
|
||||
success = newapi::remountPt(mpt);
|
||||
} else {
|
||||
success = legacy::remountPt(mpt);
|
||||
}
|
||||
if (!success && mpt.mpt->mandatory()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
12
mnt.h
12
mnt.h
@@ -37,8 +37,20 @@ typedef enum {
|
||||
NS_DIR_MAYBE,
|
||||
} isDir_t;
|
||||
|
||||
/* Shared mount point structure used by both legacy and new API */
|
||||
struct mount_t {
|
||||
const nsjail::MountPt* mpt;
|
||||
std::string src;
|
||||
std::string dst;
|
||||
uintptr_t flags;
|
||||
bool is_dir;
|
||||
bool mounted;
|
||||
int fd; /* Used by new API for deferred remount */
|
||||
};
|
||||
|
||||
bool initNs(nsj_t* nsj);
|
||||
const std::string describeMountPt(const nsjail::MountPt& mpt);
|
||||
const std::string flagsToStr(unsigned long flags);
|
||||
|
||||
} // namespace mnt
|
||||
|
||||
|
||||
432
mnt_legacy.cc
Normal file
432
mnt_legacy.cc
Normal file
@@ -0,0 +1,432 @@
|
||||
/*
|
||||
|
||||
nsjail - mount namespace routines using the legacy mount(2) API
|
||||
-----------------------------------------
|
||||
|
||||
Copyright 2014 Google Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
#include "mnt_legacy.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "logs.h"
|
||||
#include "macros.h"
|
||||
#include "mnt.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace mnt {
|
||||
namespace legacy {
|
||||
#if !defined(MS_LAZYTIME)
|
||||
#define MS_LAZYTIME (1 << 25)
|
||||
#endif
|
||||
#if !defined(ST_NOSYMFOLLOW)
|
||||
#define ST_NOSYMFOLLOW 8192
|
||||
#endif
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static bool tryCreateDir(const std::string& path, bool log_errors = true) {
|
||||
if (mkdir(path.c_str(), 0755) == -1 && errno != EEXIST) {
|
||||
if (log_errors) {
|
||||
PLOG_D("mkdir('%s')", path.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (access(path.c_str(), R_OK) == -1) {
|
||||
if (log_errors) {
|
||||
PLOG_W("access('%s', R_OK)", path.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
LOG_D("Created directory '%s'", path.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string findWritableDirUnderRoot() {
|
||||
std::error_code ec;
|
||||
for (const auto& entry : fs::directory_iterator("/", ec)) {
|
||||
auto name = entry.path().filename().string();
|
||||
if (name == "." || name == "..") {
|
||||
continue;
|
||||
}
|
||||
if (!entry.is_directory(ec)) {
|
||||
continue;
|
||||
}
|
||||
if (access(entry.path().c_str(), W_OK | X_OK) == 0) {
|
||||
return entry.path().string();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static std::unique_ptr<std::string> findWorkDir(nsj_t* nsj, const char* purpose) {
|
||||
const std::string uid = std::to_string(nsj->orig_uid);
|
||||
const std::string suffix = "nsjail." + uid + "." + purpose;
|
||||
|
||||
/* Try standard locations */
|
||||
std::vector<std::string> candidates = {
|
||||
"/run/user/" + uid + "/nsjail/" + purpose,
|
||||
"/run/user/" + suffix,
|
||||
"/tmp/" + suffix,
|
||||
"/dev/shm/" + suffix,
|
||||
};
|
||||
|
||||
if (const char* tmpdir = getenv("TMPDIR")) {
|
||||
candidates.insert(candidates.begin() + 3, std::string(tmpdir) + "/" + suffix);
|
||||
}
|
||||
|
||||
for (const auto& path : candidates) {
|
||||
size_t last_slash = path.rfind('/');
|
||||
if (last_slash != std::string::npos && last_slash > 0) {
|
||||
tryCreateDir(path.substr(0, last_slash), false);
|
||||
}
|
||||
if (tryCreateDir(path, true)) {
|
||||
return std::make_unique<std::string>(path);
|
||||
}
|
||||
}
|
||||
|
||||
std::string root_dir = findWritableDirUnderRoot();
|
||||
if (!root_dir.empty()) {
|
||||
std::string candidate = root_dir + "/" + suffix;
|
||||
if (tryCreateDir(candidate, false)) {
|
||||
return std::make_unique<std::string>(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
std::string fallback = "/tmp/" + suffix + "." + std::to_string(util::rnd64());
|
||||
if (tryCreateDir(fallback, true)) {
|
||||
return std::make_unique<std::string>(fallback);
|
||||
}
|
||||
|
||||
LOG_E("Failed to create work directory for '%s'", purpose);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool isDirectory(const char* path) {
|
||||
if (!path) {
|
||||
return true;
|
||||
}
|
||||
struct stat st;
|
||||
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
|
||||
}
|
||||
|
||||
static mount_t prepareMountPoint(const nsjail::MountPt& proto) {
|
||||
mount_t mpt = {
|
||||
.mpt = &proto,
|
||||
.src = "",
|
||||
.dst = "",
|
||||
.flags = 0,
|
||||
.is_dir = true,
|
||||
.mounted = false,
|
||||
.fd = -1,
|
||||
};
|
||||
|
||||
if (!proto.prefix_src_env().empty()) {
|
||||
if (const char* env = getenv(proto.prefix_src_env().c_str())) {
|
||||
mpt.src = env;
|
||||
} else {
|
||||
LOG_W("Environment variable not set: %s", QC(proto.prefix_src_env()));
|
||||
return mpt;
|
||||
}
|
||||
}
|
||||
mpt.src += proto.src();
|
||||
|
||||
if (!proto.prefix_dst_env().empty()) {
|
||||
if (const char* env = getenv(proto.prefix_dst_env().c_str())) {
|
||||
mpt.dst = env;
|
||||
} else {
|
||||
LOG_W("Environment variable not set: %s", QC(proto.prefix_dst_env()));
|
||||
return mpt;
|
||||
}
|
||||
}
|
||||
mpt.dst += proto.dst();
|
||||
|
||||
mpt.flags = proto.rw() ? 0 : (uintptr_t)MS_RDONLY;
|
||||
if (proto.is_bind()) {
|
||||
mpt.flags |= MS_BIND | MS_REC | MS_PRIVATE;
|
||||
}
|
||||
if (proto.nosuid()) {
|
||||
mpt.flags |= MS_NOSUID;
|
||||
}
|
||||
if (proto.nodev()) {
|
||||
mpt.flags |= MS_NODEV;
|
||||
}
|
||||
if (proto.noexec()) {
|
||||
mpt.flags |= MS_NOEXEC;
|
||||
}
|
||||
|
||||
if (proto.has_is_dir()) {
|
||||
mpt.is_dir = proto.is_dir();
|
||||
} else if (!proto.src_content().empty()) {
|
||||
mpt.is_dir = false;
|
||||
} else if (mpt.src.empty()) {
|
||||
mpt.is_dir = true;
|
||||
} else if (mpt.flags & MS_BIND) {
|
||||
mpt.is_dir = isDirectory(mpt.src.c_str());
|
||||
} else {
|
||||
mpt.is_dir = true;
|
||||
}
|
||||
|
||||
return mpt;
|
||||
}
|
||||
|
||||
static int tryMountRW(mount_t* mpt, const char* src, const char* dst) {
|
||||
int res = mount(src, dst, mpt->mpt->fstype().c_str(), mpt->flags & ~MS_RDONLY,
|
||||
mpt->mpt->options().c_str());
|
||||
|
||||
if (res == -1 && errno == EPERM && (mpt->flags & MS_RDONLY)) {
|
||||
LOG_W("mount('%s' -> '%s'): RW failed, falling back to RO", src, dst);
|
||||
res = mount(
|
||||
src, dst, mpt->mpt->fstype().c_str(), mpt->flags, mpt->mpt->options().c_str());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool createMountTarget(const std::string& path, bool is_dir) {
|
||||
if (is_dir) {
|
||||
if (mkdir(path.c_str(), 0711) == -1 && errno != EEXIST) {
|
||||
PLOG_W("mkdir('%s')", path.c_str());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
int fd =
|
||||
TEMP_FAILURE_RETRY(open(path.c_str(), O_CREAT | O_RDONLY | O_CLOEXEC, 0644));
|
||||
if (fd == -1) {
|
||||
PLOG_W("open('%s', O_CREAT)", path.c_str());
|
||||
return false;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mountSymlink(mount_t* mpt, const std::string& dstpath) {
|
||||
LOG_D("Creating symlink: %s -> %s", mpt->src.c_str(), dstpath.c_str());
|
||||
if (symlink(mpt->src.c_str(), dstpath.c_str()) == -1) {
|
||||
if (mpt->mpt->mandatory()) {
|
||||
PLOG_E("symlink('%s' -> '%s')", mpt->src.c_str(), dstpath.c_str());
|
||||
return false;
|
||||
}
|
||||
PLOG_W("symlink('%s' -> '%s') failed (non-mandatory)", mpt->src.c_str(),
|
||||
dstpath.c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mountWithDynamicContent(
|
||||
mount_t* mpt, const std::string& dstpath, const std::string& tmpdir) {
|
||||
static uint64_t counter = 0;
|
||||
std::string srcpath = tmpdir + "/dynamic." + std::to_string(++counter);
|
||||
defer {
|
||||
unlink(srcpath.c_str());
|
||||
};
|
||||
|
||||
int fd = TEMP_FAILURE_RETRY(
|
||||
open(srcpath.c_str(), O_CREAT | O_EXCL | O_CLOEXEC | O_WRONLY, 0644));
|
||||
if (fd == -1) {
|
||||
PLOG_W("open('%s', O_CREAT)", srcpath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& content = mpt->mpt->src_content();
|
||||
bool write_ok = util::writeToFd(fd, content.data(), content.length());
|
||||
close(fd);
|
||||
|
||||
if (!write_ok) {
|
||||
LOG_W("Failed to write %zu bytes to '%s'", content.length(), srcpath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
mpt->flags |= MS_BIND | MS_REC | MS_PRIVATE;
|
||||
if (tryMountRW(mpt, srcpath.c_str(), dstpath.c_str()) == -1) {
|
||||
PLOG_W("mount('%s' -> '%s')", srcpath.c_str(), dstpath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
mpt->mounted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mountSinglePoint(mount_t* mpt, const char* newroot, const char* tmpdir) {
|
||||
LOG_D("Mounting (legacy): %s", mnt::describeMountPt(*mpt->mpt).c_str());
|
||||
|
||||
const std::string dstpath = std::string(newroot) + "/" + mpt->dst;
|
||||
std::string srcpath = mpt->src.empty() ? "none" : mpt->src;
|
||||
|
||||
if (!util::createDirRecursively(dstpath.c_str())) {
|
||||
LOG_W("Failed to create parent directories for '%s'", dstpath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mpt->mpt->is_symlink()) {
|
||||
return mountSymlink(mpt, dstpath);
|
||||
}
|
||||
|
||||
if (!createMountTarget(dstpath, mpt->is_dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mpt->mpt->src_content().empty()) {
|
||||
return mountWithDynamicContent(mpt, dstpath, tmpdir);
|
||||
}
|
||||
|
||||
if (tryMountRW(mpt, srcpath.c_str(), dstpath.c_str()) == -1) {
|
||||
if (errno == EACCES) {
|
||||
PLOG_W("mount('%s' -> '%s'): try 'chmod o+x' on source path",
|
||||
srcpath.c_str(), dstpath.c_str());
|
||||
} else if (mpt->mpt->fstype() == "proc") {
|
||||
PLOG_W("mount('%s' -> '%s'): procfs mount may fail if /proc has "
|
||||
"overmounts (e.g., /dev/null on /proc/kcore)",
|
||||
srcpath.c_str(), dstpath.c_str());
|
||||
} else {
|
||||
PLOG_W("mount('%s' -> '%s')", srcpath.c_str(), dstpath.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
mpt->mounted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static unsigned long computeRemountFlags(const mount_t& mpt, const struct statvfs& vfs) {
|
||||
struct {
|
||||
const unsigned long mount_flag;
|
||||
const unsigned long vfs_flag;
|
||||
} static const mountPairs[] = {
|
||||
{MS_NOSUID, ST_NOSUID},
|
||||
{MS_NODEV, ST_NODEV},
|
||||
{MS_NOEXEC, ST_NOEXEC},
|
||||
{MS_SYNCHRONOUS, ST_SYNCHRONOUS},
|
||||
{MS_MANDLOCK, ST_MANDLOCK},
|
||||
{MS_NOATIME, ST_NOATIME},
|
||||
{MS_NODIRATIME, ST_NODIRATIME},
|
||||
{MS_RELATIME, ST_RELATIME},
|
||||
{MS_NOSYMFOLLOW, ST_NOSYMFOLLOW},
|
||||
};
|
||||
|
||||
const unsigned long per_mountpoint_flags =
|
||||
MS_LAZYTIME | MS_MANDLOCK | MS_NOATIME | MS_NODEV | MS_NODIRATIME | MS_NOEXEC |
|
||||
MS_NOSUID | MS_RELATIME | MS_RDONLY | MS_SYNCHRONOUS | MS_NOSYMFOLLOW;
|
||||
|
||||
unsigned long flags = MS_REMOUNT | MS_BIND | (mpt.flags & per_mountpoint_flags);
|
||||
for (const auto& i : mountPairs) {
|
||||
if (vfs.f_flag & i.vfs_flag) {
|
||||
flags |= i.mount_flag;
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
bool remountPt(mnt::mount_t& mpt) {
|
||||
if (!mpt.mounted || mpt.mpt->is_symlink()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
struct statvfs vfs;
|
||||
if (TEMP_FAILURE_RETRY(statvfs(mpt.dst.c_str(), &vfs)) == -1) {
|
||||
PLOG_W("statvfs('%s')", mpt.dst.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned long flags = computeRemountFlags(mpt, vfs);
|
||||
LOG_D("Remounting '%s' with flags: %s", mpt.dst.c_str(), mnt::flagsToStr(flags).c_str());
|
||||
|
||||
if (mount(mpt.dst.c_str(), mpt.dst.c_str(), nullptr, flags, nullptr) == -1) {
|
||||
PLOG_W("mount('%s', flags=%s)", mpt.dst.c_str(), mnt::flagsToStr(flags).c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::string> buildMountTree(nsj_t* nsj, std::vector<mnt::mount_t>* mounted_mpts) {
|
||||
if (chdir("/") == -1) {
|
||||
PLOG_E("chdir('/')");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const size_t tmpfsSize = 16 * 1024 * 1024;
|
||||
|
||||
auto destdir = findWorkDir(nsj, "root");
|
||||
if (!destdir) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mount("/", "/", nullptr, MS_REC | MS_PRIVATE, nullptr) == -1) {
|
||||
PLOG_E("mount('/', MS_REC|MS_PRIVATE)");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mount(nullptr, destdir->c_str(), "tmpfs", 0,
|
||||
("size=" + std::to_string(tmpfsSize)).c_str()) == -1) {
|
||||
PLOG_E("mount('%s', tmpfs)", destdir->c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto tmpdir = findWorkDir(nsj, "tmp");
|
||||
if (!tmpdir) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mount(nullptr, tmpdir->c_str(), "tmpfs", 0,
|
||||
("size=" + std::to_string(tmpfsSize)).c_str()) == -1) {
|
||||
PLOG_E("mount('%s', tmpfs)", tmpdir->c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const auto& proto : nsj->njc.mount()) {
|
||||
mount_t mpt = prepareMountPoint(proto);
|
||||
|
||||
if (!mountSinglePoint(&mpt, destdir->c_str(), tmpdir->c_str())) {
|
||||
if (mpt.mpt->mandatory()) {
|
||||
LOG_E("Failed to mount mandatory point: %s", QC(mpt.dst));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
mounted_mpts->push_back(mpt);
|
||||
}
|
||||
|
||||
if (!nsj->is_root_rw) {
|
||||
if (mount(destdir->c_str(), destdir->c_str(), nullptr, MS_REMOUNT | MS_RDONLY,
|
||||
nullptr) == -1) {
|
||||
PLOG_E("mount('%s', MS_REMOUNT|MS_RDONLY)", destdir->c_str());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (umount2(tmpdir->c_str(), MNT_DETACH) == -1) {
|
||||
PLOG_E("umount2('%s', MNT_DETACH)", tmpdir->c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return destdir;
|
||||
}
|
||||
|
||||
} // namespace legacy
|
||||
} // namespace mnt
|
||||
20
mnt_legacy.h
Normal file
20
mnt_legacy.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef NS_MNT_LEGACY_H
|
||||
#define NS_MNT_LEGACY_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "mnt.h"
|
||||
#include "nsjail.h"
|
||||
|
||||
namespace mnt {
|
||||
namespace legacy {
|
||||
|
||||
std::unique_ptr<std::string> buildMountTree(nsj_t* nsj, std::vector<mnt::mount_t>* mounted_mpts);
|
||||
bool remountPt(mnt::mount_t& mpt);
|
||||
|
||||
} // namespace legacy
|
||||
} // namespace mnt
|
||||
|
||||
#endif /* NS_MNT_LEGACY_H */
|
||||
555
mnt_newapi.cc
Normal file
555
mnt_newapi.cc
Normal file
@@ -0,0 +1,555 @@
|
||||
/*
|
||||
|
|
||||
| nsjail - mount namespace routines using the new mount API (fsopen/fsmount/move_mount)
|
||||
| -----------------------------------------
|
||||
|
|
||||
| Copyright 2025 Google Inc. All Rights Reserved.
|
||||
|
|
||||
| Licensed under the Apache License, Version 2.0 (the "License");
|
||||
| you may not use this file except in compliance with the License.
|
||||
| You may obtain a copy of the License at
|
||||
|
|
||||
| http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
|
||||
| Unless required by applicable law or agreed to in writing, software
|
||||
| distributed under the License is distributed on an "AS IS" BASIS,
|
||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
| See the License for the specific language governing permissions and
|
||||
| limitations under the License.
|
||||
|
|
||||
*/
|
||||
|
||||
#include "mnt_newapi.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <linux/mount.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#include "logs.h"
|
||||
#include "util.h"
|
||||
|
||||
/*
|
||||
* Compile-time feature detection for the new mount API.
|
||||
* Requires kernel headers with fsopen/fsconfig/fsmount/move_mount support.
|
||||
*/
|
||||
#if defined(__NR_fsopen) && defined(__NR_fsconfig) && defined(__NR_fsmount) && \
|
||||
defined(__NR_move_mount) && defined(__NR_open_tree) && defined(__NR_mount_setattr) && \
|
||||
defined(FSOPEN_CLOEXEC) && defined(FSMOUNT_CLOEXEC) && defined(MOVE_MOUNT_F_EMPTY_PATH) && \
|
||||
defined(MOUNT_ATTR_RDONLY) && defined(MOUNT_ATTR_NOSUID) && defined(MOUNT_ATTR_NODEV) && \
|
||||
defined(MOUNT_ATTR_NOEXEC) && defined(AT_EMPTY_PATH) && defined(AT_RECURSIVE)
|
||||
#define MNT_NEWAPI_SUPPORTED 1
|
||||
#endif
|
||||
|
||||
#if !defined(MNT_NEWAPI_SUPPORTED)
|
||||
|
||||
namespace mnt {
|
||||
namespace newapi {
|
||||
|
||||
bool isAvailable() {
|
||||
LOG_W("New mount API unavailable: missing compile-time support");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool remountPt(mnt::mount_t&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::string> buildMountTree(nsj_t*, std::vector<mnt::mount_t>*) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace newapi
|
||||
} // namespace mnt
|
||||
|
||||
#else /* MNT_NEWAPI_SUPPORTED */
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "macros.h"
|
||||
#include "mnt.h"
|
||||
|
||||
namespace mnt {
|
||||
namespace newapi {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static bool applyMountFlags(int fd, uintptr_t flags) {
|
||||
struct mount_attr attr = {};
|
||||
|
||||
if (flags & MS_RDONLY) {
|
||||
attr.attr_set |= MOUNT_ATTR_RDONLY;
|
||||
}
|
||||
if (flags & MS_NOSUID) {
|
||||
attr.attr_set |= MOUNT_ATTR_NOSUID;
|
||||
}
|
||||
if (flags & MS_NODEV) {
|
||||
attr.attr_set |= MOUNT_ATTR_NODEV;
|
||||
}
|
||||
if (flags & MS_NOEXEC) {
|
||||
attr.attr_set |= MOUNT_ATTR_NOEXEC;
|
||||
}
|
||||
|
||||
if (util::syscall(__NR_mount_setattr, (uintptr_t)fd, (uintptr_t)"",
|
||||
(uintptr_t)AT_EMPTY_PATH, (uintptr_t)&attr, sizeof(attr)) < 0) {
|
||||
PLOG_W("mount_setattr(fd=%d, flags=0x%" PRIx64 ")", fd, (uint64_t)attr.attr_set);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string findFirstDirUnderRoot() {
|
||||
std::error_code ec;
|
||||
for (const auto& entry : fs::directory_iterator("/", ec)) {
|
||||
auto name = entry.path().filename().string();
|
||||
if (name == "." || name == "..") {
|
||||
continue;
|
||||
}
|
||||
if (entry.is_directory(ec) && !entry.is_symlink(ec)) {
|
||||
return entry.path().string();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static bool createDirAt(int dir_fd, const char* path, mode_t mode) {
|
||||
path = util::stripLeadingSlashes(path);
|
||||
if (!path[0]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string cumulative;
|
||||
for (const auto& component : util::strSplit(path, '/')) {
|
||||
if (component.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cumulative.empty()) {
|
||||
cumulative += '/';
|
||||
}
|
||||
cumulative += component;
|
||||
|
||||
if (mkdirat(dir_fd, cumulative.c_str(), mode) == -1 && errno != EEXIST) {
|
||||
PLOG_W("mkdirat(%d, '%s')", dir_fd, cumulative.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int createDetachedTmpfs(size_t size) {
|
||||
int fs_fd = util::syscall(__NR_fsopen, (uintptr_t)"tmpfs", (uintptr_t)FSOPEN_CLOEXEC);
|
||||
if (fs_fd < 0) {
|
||||
PLOG_W("fsopen('tmpfs')");
|
||||
return -1;
|
||||
}
|
||||
defer {
|
||||
close(fs_fd);
|
||||
};
|
||||
|
||||
const std::string size_str = std::to_string(size);
|
||||
if (util::syscall(__NR_fsconfig, (uintptr_t)fs_fd, (uintptr_t)FSCONFIG_SET_STRING,
|
||||
(uintptr_t)"size", (uintptr_t)size_str.c_str(), (uintptr_t)0) < 0) {
|
||||
PLOG_W("fsconfig(size=%s)", size_str.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (util::syscall(__NR_fsconfig, (uintptr_t)fs_fd, (uintptr_t)FSCONFIG_CMD_CREATE,
|
||||
(uintptr_t)nullptr, (uintptr_t)nullptr, (uintptr_t)0) < 0) {
|
||||
PLOG_W("fsconfig(CMD_CREATE)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int mnt_fd =
|
||||
util::syscall(__NR_fsmount, (uintptr_t)fs_fd, (uintptr_t)FSMOUNT_CLOEXEC, (uintptr_t)0);
|
||||
if (mnt_fd < 0) {
|
||||
PLOG_W("fsmount('tmpfs')");
|
||||
}
|
||||
return mnt_fd;
|
||||
}
|
||||
|
||||
static int createFilesystemMount(const mount_t& mpt) {
|
||||
int fs_fd = util::syscall(
|
||||
__NR_fsopen, (uintptr_t)mpt.mpt->fstype().c_str(), (uintptr_t)FSOPEN_CLOEXEC);
|
||||
if (fs_fd < 0) {
|
||||
PLOG_W("fsopen('%s')", mpt.mpt->fstype().c_str());
|
||||
return -1;
|
||||
}
|
||||
defer {
|
||||
close(fs_fd);
|
||||
};
|
||||
|
||||
if (!mpt.src.empty() && mpt.src != "none") {
|
||||
if (util::syscall(__NR_fsconfig, (uintptr_t)fs_fd, (uintptr_t)FSCONFIG_SET_STRING,
|
||||
(uintptr_t)"source", (uintptr_t)mpt.src.c_str(), (uintptr_t)0) < 0) {
|
||||
PLOG_W("fsconfig(source='%s')", mpt.src.c_str());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (mpt.mpt->has_options()) {
|
||||
for (const auto& opt : util::strSplit(mpt.mpt->options(), ',')) {
|
||||
if (opt.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (util::syscall(__NR_fsconfig, (uintptr_t)fs_fd,
|
||||
(uintptr_t)FSCONFIG_SET_FLAG, (uintptr_t)opt.c_str(),
|
||||
(uintptr_t)nullptr, (uintptr_t)0) < 0) {
|
||||
PLOG_W("fsconfig(flag='%s')", opt.c_str());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (util::syscall(__NR_fsconfig, (uintptr_t)fs_fd, (uintptr_t)FSCONFIG_CMD_CREATE,
|
||||
(uintptr_t)nullptr, (uintptr_t)nullptr, (uintptr_t)0) < 0) {
|
||||
PLOG_W("fsconfig(CMD_CREATE)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int mnt_fd =
|
||||
util::syscall(__NR_fsmount, (uintptr_t)fs_fd, (uintptr_t)FSMOUNT_CLOEXEC, (uintptr_t)0);
|
||||
if (mnt_fd < 0) {
|
||||
PLOG_W("fsmount('%s')", mpt.mpt->fstype().c_str());
|
||||
}
|
||||
return mnt_fd;
|
||||
}
|
||||
|
||||
static bool mountSymlinkAt(mount_t* mpt, int root_fd, const char* rel_dst) {
|
||||
LOG_D("Creating symlink: %s -> %s (fd-relative)", mpt->src.c_str(), rel_dst);
|
||||
if (symlinkat(mpt->src.c_str(), root_fd, rel_dst) == -1) {
|
||||
if (mpt->mpt->mandatory()) {
|
||||
PLOG_E("symlinkat('%s' -> '%s')", mpt->src.c_str(), rel_dst);
|
||||
return false;
|
||||
}
|
||||
PLOG_W("symlinkat('%s' -> '%s') failed (non-mandatory)", mpt->src.c_str(), rel_dst);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mountDynamicContentAt(mount_t* mpt, int root_fd, const char* rel_dst) {
|
||||
static uint64_t counter = 0;
|
||||
std::string src_rel = ".dyn." + std::to_string(++counter);
|
||||
|
||||
int src_fd =
|
||||
openat(root_fd, src_rel.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0644);
|
||||
if (src_fd < 0) {
|
||||
PLOG_W("openat(root_fd, '%s', O_CREAT)", src_rel.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& content = mpt->mpt->src_content();
|
||||
bool ok = util::writeToFd(src_fd, content.data(), content.length());
|
||||
close(src_fd);
|
||||
if (!ok) {
|
||||
LOG_W("Failed to write %zu bytes for dynamic content '%s'", content.length(),
|
||||
rel_dst);
|
||||
unlinkat(root_fd, src_rel.c_str(), 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
int mnt_fd =
|
||||
syscall(__NR_open_tree, root_fd, src_rel.c_str(), OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
|
||||
if (mnt_fd < 0) {
|
||||
PLOG_W("open_tree('%s')", src_rel.c_str());
|
||||
unlinkat(root_fd, src_rel.c_str(), 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!applyMountFlags(mnt_fd, mpt->flags & ~MS_RDONLY)) {
|
||||
LOG_W("Failed to apply mount flags to '%s'", rel_dst);
|
||||
}
|
||||
|
||||
if (util::syscall(__NR_move_mount, (uintptr_t)mnt_fd, (uintptr_t)"", (uintptr_t)root_fd,
|
||||
(uintptr_t)rel_dst, (uintptr_t)MOVE_MOUNT_F_EMPTY_PATH) < 0) {
|
||||
PLOG_W("move_mount('%s' -> '%s')", src_rel.c_str(), rel_dst);
|
||||
close(mnt_fd);
|
||||
unlinkat(root_fd, src_rel.c_str(), 0);
|
||||
return false;
|
||||
}
|
||||
close(mnt_fd);
|
||||
|
||||
if (unlinkat(root_fd, src_rel.c_str(), 0) == -1) {
|
||||
PLOG_W("unlinkat(root_fd, '%s')", src_rel.c_str());
|
||||
}
|
||||
|
||||
mpt->fd = syscall(__NR_open_tree, root_fd, rel_dst, (unsigned int)OPEN_TREE_CLOEXEC);
|
||||
if (mpt->fd < 0) {
|
||||
PLOG_W("open_tree(root_fd, '%s')", rel_dst);
|
||||
return false;
|
||||
}
|
||||
mpt->mounted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool doBindMountAt(mount_t* mpt, int root_fd, const char* rel_dst) {
|
||||
unsigned int flags = OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC;
|
||||
if (mpt->flags & MS_REC) {
|
||||
flags |= AT_RECURSIVE;
|
||||
}
|
||||
|
||||
LOG_D("open_tree('%s', flags=0x%x)", mpt->src.c_str(), flags);
|
||||
int mnt_fd = syscall(__NR_open_tree, AT_FDCWD, mpt->src.c_str(), flags);
|
||||
if (mnt_fd < 0) {
|
||||
PLOG_W("open_tree('%s')", mpt->src.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Apply non-RO flags now; RO applied later via remount */
|
||||
if (!applyMountFlags(mnt_fd, mpt->flags & ~MS_RDONLY)) {
|
||||
LOG_W("Failed to apply mount flags to '%s'", rel_dst);
|
||||
}
|
||||
|
||||
if (util::syscall(__NR_move_mount, (uintptr_t)mnt_fd, (uintptr_t)"", (uintptr_t)root_fd,
|
||||
(uintptr_t)rel_dst, (uintptr_t)MOVE_MOUNT_F_EMPTY_PATH) < 0) {
|
||||
PLOG_W("move_mount('%s' -> '%s')", mpt->src.c_str(), rel_dst);
|
||||
close(mnt_fd);
|
||||
return false;
|
||||
}
|
||||
close(mnt_fd);
|
||||
|
||||
/* Re-acquire fd for later remount (kernel 6.13+ requires this) */
|
||||
mpt->fd = syscall(__NR_open_tree, root_fd, rel_dst, (unsigned int)OPEN_TREE_CLOEXEC);
|
||||
mpt->mounted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mountSinglePointAt(mount_t* mpt, int root_fd) {
|
||||
LOG_D("Mounting (new API): %s", mnt::describeMountPt(*mpt->mpt).c_str());
|
||||
|
||||
const char* rel_dst = util::stripLeadingSlashes(mpt->dst.c_str());
|
||||
if (!rel_dst[0]) {
|
||||
rel_dst = ".";
|
||||
}
|
||||
|
||||
const char* last_slash = strrchr(rel_dst, '/');
|
||||
if (last_slash && last_slash != rel_dst) {
|
||||
std::string parent(rel_dst, last_slash - rel_dst);
|
||||
if (!createDirAt(root_fd, parent.c_str(), 0755)) {
|
||||
LOG_W("Failed to create parent directories for '%s'", rel_dst);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mpt->mpt->is_symlink()) {
|
||||
return mountSymlinkAt(mpt, root_fd, rel_dst);
|
||||
}
|
||||
|
||||
if (mpt->is_dir) {
|
||||
if (strcmp(rel_dst, ".") != 0 && mkdirat(root_fd, rel_dst, 0711) == -1 &&
|
||||
errno != EEXIST) {
|
||||
PLOG_W("mkdirat(root_fd, '%s')", rel_dst);
|
||||
}
|
||||
} else {
|
||||
int fd = openat(root_fd, rel_dst, O_CREAT | O_RDONLY | O_CLOEXEC, 0644);
|
||||
if (fd < 0) {
|
||||
PLOG_W("openat(root_fd, '%s', O_CREAT)", rel_dst);
|
||||
} else {
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
if (!mpt->mpt->src_content().empty()) {
|
||||
return mountDynamicContentAt(mpt, root_fd, rel_dst);
|
||||
}
|
||||
|
||||
if (mpt->flags & MS_BIND) {
|
||||
return doBindMountAt(mpt, root_fd, rel_dst);
|
||||
}
|
||||
|
||||
int mnt_fd = createFilesystemMount(*mpt);
|
||||
if (mnt_fd < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!applyMountFlags(mnt_fd, mpt->flags & ~MS_RDONLY)) {
|
||||
LOG_W("Failed to apply mount flags to '%s'", rel_dst);
|
||||
}
|
||||
|
||||
if (util::syscall(__NR_move_mount, (uintptr_t)mnt_fd, (uintptr_t)"", (uintptr_t)root_fd,
|
||||
(uintptr_t)rel_dst, (uintptr_t)MOVE_MOUNT_F_EMPTY_PATH) < 0) {
|
||||
PLOG_W("move_mount() for '%s'", rel_dst);
|
||||
close(mnt_fd);
|
||||
return false;
|
||||
}
|
||||
close(mnt_fd);
|
||||
|
||||
mpt->fd = syscall(__NR_open_tree, root_fd, rel_dst, (unsigned int)OPEN_TREE_CLOEXEC);
|
||||
mpt->mounted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static mount_t prepareMountPoint(const nsjail::MountPt& proto) {
|
||||
mount_t mpt = {
|
||||
.mpt = &proto,
|
||||
.src = "",
|
||||
.dst = "",
|
||||
.flags = 0,
|
||||
.is_dir = true,
|
||||
.mounted = false,
|
||||
.fd = -1,
|
||||
};
|
||||
|
||||
if (!proto.prefix_src_env().empty()) {
|
||||
if (const char* env = getenv(proto.prefix_src_env().c_str())) {
|
||||
mpt.src = env;
|
||||
} else {
|
||||
LOG_W("Environment variable not set: %s", QC(proto.prefix_src_env()));
|
||||
return mpt;
|
||||
}
|
||||
}
|
||||
mpt.src += proto.src();
|
||||
|
||||
if (!proto.prefix_dst_env().empty()) {
|
||||
if (const char* env = getenv(proto.prefix_dst_env().c_str())) {
|
||||
mpt.dst = env;
|
||||
} else {
|
||||
LOG_W("Environment variable not set: %s", QC(proto.prefix_dst_env()));
|
||||
return mpt;
|
||||
}
|
||||
}
|
||||
mpt.dst += proto.dst();
|
||||
|
||||
mpt.flags = proto.rw() ? 0 : (uintptr_t)MS_RDONLY;
|
||||
if (proto.is_bind()) {
|
||||
mpt.flags |= MS_BIND | MS_REC | MS_PRIVATE;
|
||||
}
|
||||
if (proto.nosuid()) {
|
||||
mpt.flags |= MS_NOSUID;
|
||||
}
|
||||
if (proto.nodev()) {
|
||||
mpt.flags |= MS_NODEV;
|
||||
}
|
||||
if (proto.noexec()) {
|
||||
mpt.flags |= MS_NOEXEC;
|
||||
}
|
||||
|
||||
if (proto.has_is_dir()) {
|
||||
mpt.is_dir = proto.is_dir();
|
||||
} else if (!proto.src_content().empty()) {
|
||||
mpt.is_dir = false;
|
||||
} else if (mpt.src.empty()) {
|
||||
mpt.is_dir = true;
|
||||
} else if (mpt.flags & MS_BIND) {
|
||||
struct stat st;
|
||||
mpt.is_dir = (stat(mpt.src.c_str(), &st) == 0 && S_ISDIR(st.st_mode));
|
||||
} else {
|
||||
mpt.is_dir = true;
|
||||
}
|
||||
|
||||
return mpt;
|
||||
}
|
||||
|
||||
bool isAvailable() {
|
||||
if (util::kernelVersionAtLeast(6, 3, 0)) {
|
||||
LOG_D("New mount API available (kernel >= 6.3)");
|
||||
return true;
|
||||
}
|
||||
LOG_W("New mount API unavailable (kernel < 6.3)");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool remountPt(mnt::mount_t& mpt) {
|
||||
if (!mpt.mounted || mpt.mpt->is_symlink() || mpt.fd < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!applyMountFlags(mpt.fd, mpt.flags)) {
|
||||
LOG_W("Failed to apply final flags to '%s'", mpt.dst.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
close(mpt.fd);
|
||||
mpt.fd = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::string> buildMountTree(nsj_t* nsj, std::vector<mnt::mount_t>* mounted_mpts) {
|
||||
if (chdir("/") == -1) {
|
||||
PLOG_E("chdir('/')");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Make root mount private recursively */
|
||||
if (mount("/", "/", nullptr, MS_REC | MS_PRIVATE, nullptr) == -1) {
|
||||
PLOG_E("mount('/', MS_REC|MS_PRIVATE)");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int root_mfd = createDetachedTmpfs(16 * 1024 * 1024);
|
||||
if (root_mfd < 0) {
|
||||
LOG_E("Failed to create root tmpfs");
|
||||
return nullptr;
|
||||
}
|
||||
LOG_D("Created detached root tmpfs (fd=%d)", root_mfd);
|
||||
|
||||
if (!applyMountFlags(root_mfd, 0)) {
|
||||
LOG_W("mount_setattr(root_mfd, 0) failed");
|
||||
}
|
||||
|
||||
std::string attachdir = findFirstDirUnderRoot();
|
||||
if (attachdir.empty()) {
|
||||
LOG_E("No directory found under / to mount new root");
|
||||
close(root_mfd);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (util::syscall(__NR_move_mount, (uintptr_t)root_mfd, (uintptr_t)"", (uintptr_t)AT_FDCWD,
|
||||
(uintptr_t)attachdir.c_str(), (uintptr_t)MOVE_MOUNT_F_EMPTY_PATH) < 0) {
|
||||
PLOG_E("move_mount(root_fd -> '%s')", attachdir.c_str());
|
||||
close(root_mfd);
|
||||
return nullptr;
|
||||
}
|
||||
close(root_mfd);
|
||||
|
||||
int root_fd =
|
||||
openat(AT_FDCWD, attachdir.c_str(), O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY);
|
||||
if (root_fd < 0) {
|
||||
PLOG_E("openat('%s')", attachdir.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
defer {
|
||||
close(root_fd);
|
||||
};
|
||||
|
||||
/* Build entire mount tree using fd-relative operations */
|
||||
for (const auto& proto : nsj->njc.mount()) {
|
||||
mount_t mpt = prepareMountPoint(proto);
|
||||
|
||||
if (!mountSinglePointAt(&mpt, root_fd)) {
|
||||
if (mpt.mpt->mandatory()) {
|
||||
LOG_E("Failed to mount mandatory point: %s", QC(mpt.dst));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
mounted_mpts->push_back(mpt);
|
||||
}
|
||||
|
||||
/* Apply RO to root if needed */
|
||||
if (!nsj->is_root_rw) {
|
||||
struct mount_attr ro_attr = {};
|
||||
ro_attr.attr_set = MOUNT_ATTR_RDONLY;
|
||||
if (util::syscall(__NR_mount_setattr, (uintptr_t)root_fd, (uintptr_t)"",
|
||||
(uintptr_t)AT_EMPTY_PATH, (uintptr_t)&ro_attr, sizeof(ro_attr)) < 0) {
|
||||
PLOG_W("mount_setattr(root_fd, MOUNT_ATTR_RDONLY)");
|
||||
LOG_W("Root filesystem may still be writable");
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_unique<std::string>(attachdir);
|
||||
}
|
||||
|
||||
} // namespace newapi
|
||||
|
||||
} // namespace mnt
|
||||
|
||||
#endif /* MNT_NEWAPI_SUPPORTED */
|
||||
21
mnt_newapi.h
Normal file
21
mnt_newapi.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef NS_MNT_NEWAPI_H
|
||||
#define NS_MNT_NEWAPI_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "mnt.h"
|
||||
#include "nsjail.h"
|
||||
|
||||
namespace mnt {
|
||||
namespace newapi {
|
||||
|
||||
bool isAvailable();
|
||||
std::unique_ptr<std::string> buildMountTree(nsj_t* nsj, std::vector<mnt::mount_t>* mounted_mpts);
|
||||
bool remountPt(mnt::mount_t& mpt);
|
||||
|
||||
} // namespace newapi
|
||||
} // namespace mnt
|
||||
|
||||
#endif /* NS_MNT_NEWAPI_H */
|
||||
1
nsjail.h
1
nsjail.h
@@ -98,6 +98,7 @@ struct nsj_t {
|
||||
std::string chroot;
|
||||
std::string proc_path;
|
||||
bool is_root_rw;
|
||||
bool mnt_newapi;
|
||||
bool is_proc_rw;
|
||||
struct sock_fprog seccomp_fprog;
|
||||
};
|
||||
|
||||
30
util.cc
30
util.cc
@@ -40,6 +40,7 @@
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -433,4 +434,33 @@ bool makeRangeCOE(unsigned int first [[maybe_unused]], unsigned int last [[maybe
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* stripLeadingSlashes(const char* path) {
|
||||
while (*path == '/') {
|
||||
path++;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
bool kernelVersionAtLeast(int major, int minor, int patch) {
|
||||
struct utsname uts;
|
||||
if (uname(&uts) == -1) {
|
||||
PLOG_W("uname()");
|
||||
return false;
|
||||
}
|
||||
|
||||
int kmajor = 0, kminor = 0, kpatch = 0;
|
||||
if (sscanf(uts.release, "%d.%d.%d", &kmajor, &kminor, &kpatch) < 2) {
|
||||
LOG_W("Couldn't parse kernel version from '%s'", uts.release);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (kmajor != major) {
|
||||
return kmajor > major;
|
||||
}
|
||||
if (kminor != minor) {
|
||||
return kminor > minor;
|
||||
}
|
||||
return kpatch >= patch;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
|
||||
2
util.h
2
util.h
@@ -75,6 +75,8 @@ long syscall(long sysno, uintptr_t a0 = 0, uintptr_t a1 = 0, uintptr_t a2 = 0, u
|
||||
long setrlimit(int res, const struct rlimit64& newlim);
|
||||
long getrlimit(int res, struct rlimit64* curlim);
|
||||
bool makeRangeCOE(unsigned int first, unsigned int last);
|
||||
const char* stripLeadingSlashes(const char* path);
|
||||
bool kernelVersionAtLeast(int major, int minor, int patch);
|
||||
|
||||
} // namespace util
|
||||
|
||||
|
||||
Reference in New Issue
Block a user