blob: 6d711b965407b1f9bd01e4edf3583cf3bfbdf961 [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 29
#include <string>
#include <type_traits>
#include <base/at_exit.h>
#include <base/bind.h>
#include <base/bind_helpers.h>
#include <base/command_line.h>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/memory/ptr_util.h>
#include <base/strings/stringprintf.h>
#include <brillo/syslog_logging.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/bus.h>
#include <fuse.h>
#include <fuse/cuse_lowlevel.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include "container_utils/device_jail/permission_broker_client.h"
namespace dbus {
class ObjectProxy;
}
namespace device_jail {
class DeviceJail {
public:
DeviceJail(std::string device_path, dev_t device_number,
PermissionBrokerClientInterface* broker_client)
: device_path_(std::move(device_path)),
broker_client_(broker_client) {
jailed_device_name_ = base::StringPrintf("jailed-%d-%d",
major(device_number),
minor(device_number));
}
static DeviceJail* Get(fuse_req_t req) {
return static_cast<DeviceJail*>(fuse_req_userdata(req));
}
void OpenWithBroker(const base::Callback<void(int)>& callback) {
broker_client_->Open(device_path_, callback);
}
const std::string& jailed_device_name() {
return jailed_device_name_;
}
private:
const std::string device_path_;
std::string jailed_device_name_;
PermissionBrokerClientInterface* broker_client_; // weak
};
static void jail_open_helper(fuse_req_t req, struct fuse_file_info fi, int fd) {
if (fd < 0) {
fuse_reply_err(req, -fd);
} else {
fi.fh = fd;
fuse_reply_open(req, &fi);
}
}
void jail_open(fuse_req_t req, struct fuse_file_info* fi) {
DLOG(INFO) << "open";
// Copy |fi| because we're doing this asynchronously and it's on the FUSE
// message loop stack.
DeviceJail::Get(req)->OpenWithBroker(base::Bind(&jail_open_helper, req, *fi));
}
void jail_read(fuse_req_t req, size_t size, off_t off,
struct fuse_file_info* fi) {
DLOG(INFO) << "read(" << size << ")";
std::vector<char> buf(size);
// Ignore |off| because character devices are not seekable and
// CUSE always passes in 0.
int ret = read(fi->fh, buf.data(), size);
if (ret < 0)
fuse_reply_err(req, errno);
else
fuse_reply_buf(req, buf.data(), ret);
}
void jail_write(fuse_req_t req, const char* buf, size_t size, off_t off,
struct fuse_file_info* fi) {
DLOG(INFO) << "write(" << size << ")";
// Ignore |off| (see comment in jail_read).
int ret = write(fi->fh, buf, size);
if (ret < 0)
fuse_reply_err(req, errno);
else
fuse_reply_write(req, ret);
}
void jail_release(fuse_req_t req, struct fuse_file_info* fi) {
DLOG(INFO) << "close";
fuse_reply_err(req, close(fi->fh) < 0 ? errno : 0);
}
void jail_ioctl(fuse_req_t req, int cmd, void* arg,
struct fuse_file_info* fi, unsigned int flags,
const void *in_buf, size_t in_bufsz, size_t out_bufsz) {
DLOG(INFO) << "ioctl(" << cmd << ")";
if (flags & FUSE_IOCTL_COMPAT) {
fuse_reply_err(req, ENOSYS);
return;
}
// We are using restricted ioctls because there's no way to get
// enough information to reliably pass through unrestricted ioctls.
// This means all the direction and size information is encoded in
// the ioctl number, and the kernel has already set up the necessary
// storage in *arg.
int ret = ioctl(fi->fh, cmd, arg);
if (ret < 0) {
fuse_reply_err(req, errno);
} else {
if (_IOC_DIR(cmd) & _IOC_WRITE)
fuse_reply_ioctl(req, ret, arg, _IOC_SIZE(cmd));
else
fuse_reply_ioctl(req, ret, nullptr, 0);
}
}
struct cuse_lowlevel_ops cops = {
.init = nullptr,
.init_done = nullptr,
.destroy = nullptr,
.open = jail_open,
.read = jail_read,
.write = jail_write,
.flush = nullptr,
.release = jail_release,
.fsync = nullptr,
.ioctl = jail_ioctl,
.poll = nullptr,
};
} // namespace device_jail
int main(int argc, char** argv) {
brillo::OpenLog("device_jail", true);
brillo::InitLog(brillo::kLogToSyslog);
base::CommandLine command_line(argc, argv);
if (command_line.GetArgs().empty())
LOG(FATAL) << "Need device to jail";
std::string device_path = command_line.GetArgs()[0];
base::AtExitManager at_exit_manager;
base::MessageLoopForIO message_loop;
struct stat dev_stat;
if (stat(device_path.c_str(), &dev_stat) < 0)
PLOG(FATAL) << "stat(" << device_path << ")";
if (!S_ISCHR(dev_stat.st_mode))
LOG(FATAL) << device_path << " does not describe character device";
dev_t device_number = dev_stat.st_rdev;
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
scoped_refptr<dbus::Bus> bus = new dbus::Bus(options);
if (!bus->Connect())
LOG(FATAL) << "D-Bus unavailable";
dbus::ObjectProxy* broker_proxy = bus->GetObjectProxy(
permission_broker::kPermissionBrokerServiceName,
dbus::ObjectPath(permission_broker::kPermissionBrokerServicePath));
auto broker_client = base::MakeUnique<device_jail::PermissionBrokerClient>(
broker_proxy, &message_loop);
device_jail::DeviceJail jail(device_path, device_number, broker_client.get());
std::string cuse_devname_arg = "DEVNAME=" + jail.jailed_device_name();
const char* dev_info_argv[] = { cuse_devname_arg.c_str() };
struct cuse_info ci = {
.dev_major = major(device_number),
.dev_minor = ~minor(device_number) & 0xFFFF,
.dev_info_argc = std::extent<decltype(dev_info_argv)>::value,
.dev_info_argv = dev_info_argv,
.flags = 0,
};
// Keep CUSE in the foreground to avoid forking and reparenting the
// daemon.
char foreground_opt[] = "-f";
char* fuse_argv[] = { argv[0], foreground_opt };
int fuse_argc = std::extent<decltype(fuse_argv)>::value;
base::Thread cuse_thread("cuse_lowlevel_main");
DLOG(INFO) << "Starting cuse_lowlevel_main thread";
cuse_thread.Start();
cuse_thread.task_runner()->PostTask(
FROM_HERE,
base::Bind(base::IgnoreResult(&cuse_lowlevel_main),
fuse_argc, fuse_argv, &ci, &device_jail::cops, &jail));
message_loop.Run();
return 0;
}