blob: 12d9cdf8e4478c6d5986dcdb0141daa26d024e7b [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.
#define FUSE_USE_VERSION 26
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <fuse.h>
#include <libminijail.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sysexits.h>
#include <unistd.h>
#include <algorithm>
#include <set>
#include <string>
#include <vector>
#include <base/at_exit.h>
#include <base/bind.h>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/macros.h>
#include <base/memory/free_deleter.h>
#include <brillo/flag_helper.h>
#include <brillo/syslog_logging.h>
#include "container_utils/fs_data.h"
namespace {
const char kDevfsPath[] = "/dev";
// Some wrappers around syscalls.
struct linux_dirent64 {
uint64_t d_ino;
int64_t d_off;
unsigned short d_reclen; // NOLINT(runtime/int)
unsigned char d_type;
char d_name[];
};
static int getdents(int fd, struct linux_dirent64* dirp, unsigned int count) {
return syscall(SYS_getdents64, fd, dirp, count);
}
} // namespace
namespace device_jail {
static FsData* GetFsData() {
return static_cast<FsData*>(fuse_get_context()->private_data);
}
// Removes symlinks that are broken in the new filesystem and devices
// that aren't marked either passthrough or jailed. For jailed devices,
// spin up a jail if necessary, and stat that instead.
static int jail_stat(const char* path, struct stat* file_stat) {
DVLOG(1) << "jail_stat(" << path << ")";
int ret = fstatat(GetFsData()->root_fd(), path + 1, file_stat,
AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
if (ret < 0)
return ret;
std::string real_path(kDevfsPath);
real_path.append(path);
// If it's a symlink and the path is relative, check that it's not
// broken in the new filesystem.
if (S_ISLNK(file_stat->st_mode)) {
std::unique_ptr<char, base::FreeDeleter> resolved_path(
realpath(real_path.c_str(), nullptr));
if (!resolved_path)
return -1;
// If it doesn't end up somewhere in devfs, it doesn't matter to
// us. Let it through.
if (strncmp(resolved_path.get(), kDevfsPath, strlen(kDevfsPath)) != 0)
return 0;
// If this link ends up somewhere in devfs, check to make sure
// it points at something that exists in the new filesystem.
struct stat link_stat;
// The path relative from our root is going to be after "/dev", so
// move the pointer up accordingly.
return jail_stat(resolved_path.get() + strlen(kDevfsPath), &link_stat);
}
// Allow all other files that aren't device files through.
if (!(S_ISCHR(file_stat->st_mode) || S_ISBLK(file_stat->st_mode)))
return ret;
static const char* const kPassthroughDevices[] = {
"/full",
"/null",
"/urandom",
"/zero",
};
for (size_t i = 0; i < arraysize(kPassthroughDevices); i++) {
if (strcmp(path, kPassthroughDevices[i]) == 0)
return ret;
}
static const char* const kJailDevices[] = {
"/bus/usb",
};
for (size_t i = 0; i < arraysize(kJailDevices); i++) {
if (strncmp(path, kJailDevices[i], strlen(kJailDevices[i])) == 0)
return GetFsData()->GetStatForJail(real_path, file_stat);
}
errno = ENOENT;
return -1;
}
static int djfs_getattr(const char* path, struct stat* file_stat) {
DVLOG(1) << "stat(" << path << ")";
int ret = jail_stat(path, file_stat);
if (ret < 0)
return -errno;
return ret;
}
static int djfs_readlink(const char* path, char* buf, size_t size) {
DVLOG(1) << "readlink(" << path << ")";
ssize_t ret = readlinkat(GetFsData()->root_fd(), path + 1, buf, size - 1);
if (ret < 0)
return -errno;
buf[ret] = '\0';
return 0;
}
static int djfs_opendir(const char* path, struct fuse_file_info* fi) {
DVLOG(1) << "opendir(" << path << ", " << fi->flags << ")";
int ret;
if (strcmp("/", path) == 0) {
ret = dup(GetFsData()->root_fd());
} else {
ret = openat(GetFsData()->root_fd(), path + 1,
fi->flags | O_DIRECTORY | O_CLOEXEC);
}
if (ret < 0)
return -errno;
fi->fh = ret;
return 0;
}
static int djfs_release(const char* path, struct fuse_file_info* fi) {
DVLOG(1) << "close(" << path << ")";
return (close(fi->fh) < 0) ? -errno : 0;
}
static int djfs_readdir(const char* path, void* buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info* fi) {
DVLOG(1) << "readdir(" << path << ", " << buf << ", " << offset << ")";
const int kBufSize = 1024;
char getdents_buf[kBufSize];
ssize_t bytes_read;
off_t dir_off;
dir_off = lseek(fi->fh, 0, SEEK_SET);
if (dir_off < 0) {
DPLOG(ERROR) << "could not reset offset for directory " << path;
return -errno;
}
for (;;) {
bytes_read = getdents(
fi->fh, reinterpret_cast<struct linux_dirent64*>(getdents_buf),
kBufSize);
if (bytes_read < 0)
return -errno;
if (bytes_read == 0)
break;
for (ssize_t i = 0; i < bytes_read;) {
auto d = reinterpret_cast<struct linux_dirent64*>(getdents_buf + i);
base::FilePath full_path = base::FilePath(path).Append(d->d_name);
struct stat file_stat;
if (jail_stat(full_path.value().c_str(), &file_stat) >= 0)
filler(buf, d->d_name, &file_stat, 0);
i += d->d_reclen;
}
}
return 0;
}
static void* djfs_init(struct fuse_conn_info* conn) {
// Drop root.
struct minijail* j = minijail_new();
minijail_change_user(j, "devicejail");
minijail_change_group(j, "devicejail");
minijail_inherit_usergroups(j);
minijail_enter(j);
minijail_destroy(j);
// Whatever you return from the init function becomes the user_data,
// even if there was already stuff in there.
return GetFsData();
}
static struct fuse_operations fops = {
.getattr = djfs_getattr,
.readlink = djfs_readlink,
.getdir = nullptr,
.mknod = nullptr,
.mkdir = nullptr,
.unlink = nullptr,
.rmdir = nullptr,
.symlink = nullptr,
.rename = nullptr,
.link = nullptr,
.chmod = nullptr,
.chown = nullptr,
.truncate = nullptr,
.utime = nullptr,
.open = nullptr,
.read = nullptr,
.write = nullptr,
.statfs = nullptr,
.flush = nullptr,
.release = djfs_release,
.fsync = nullptr,
.setxattr = nullptr,
.getxattr = nullptr,
.listxattr = nullptr,
.removexattr = nullptr,
.opendir = djfs_opendir,
.readdir = djfs_readdir,
.releasedir = djfs_release,
.fsyncdir = nullptr,
.init = djfs_init,
.destroy = nullptr,
.access = nullptr,
.create = nullptr,
.ftruncate = nullptr,
.fgetattr = nullptr,
.lock = nullptr,
.utimens = nullptr,
.bmap = nullptr,
.flag_nullpath_ok = 0,
.flag_reserved = 0, // placeholder
.ioctl = nullptr,
.poll = nullptr,
};
} // namespace device_jail
int main(int argc, char** argv) {
brillo::OpenLog("device_jail_fs", true);
brillo::InitLog(brillo::kLogToSyslog);
base::CommandLine command_line(argc, argv);
if (argc != 2) {
LOG(ERROR) << "Usage: device_jail_fs <mount point>";
return EX_USAGE;
}
std::string mount_point = command_line.GetArgs()[0];
base::AtExitManager at_exit_manager;
if (getuid() != 0) {
LOG(ERROR) << "need root to mount with devices";
return EX_USAGE;
}
DLOG(INFO) << "device_jail_fs mounting " << kDevfsPath << " onto "
<< mount_point;
std::unique_ptr<device_jail::FsData> fs_data =
device_jail::FsData::Create(kDevfsPath, mount_point);
if (!fs_data) {
LOG(ERROR) << "could not initialize filesystem";
return EX_SOFTWARE;
}
struct fuse_args args = FUSE_ARGS_INIT(0, nullptr);
fuse_opt_add_arg(&args, argv[0]);
fuse_opt_add_arg(&args, "-f");
fuse_opt_add_arg(&args, "-odev,allow_other,default_permissions");
fuse_opt_add_arg(&args, mount_point.c_str());
int ret = fuse_main(args.argc, args.argv, &device_jail::fops, fs_data.get());
fuse_opt_free_args(&args);
DVLOG(1) << "device_jail_fs exiting";
return ret;
}