blob: 0456d58870cdc3718bdc411ee10a9df7a18497cb [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* 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.
*/
//! This executable works as a child/worker for the main compsvc service. This worker is mainly
//! responsible for setting up the execution environment, e.g. to create file descriptors for
//! remote file access via an authfs mount.
use anyhow::{bail, Result};
use log::warn;
use minijail::Minijail;
use nix::sys::statfs::{statfs, FsType};
use std::fs::{File, OpenOptions};
use std::io;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::process::exit;
use std::thread::sleep;
use std::time::{Duration, Instant};
const AUTHFS_BIN: &str = "/system/bin/authfs";
const AUTHFS_SETUP_POLL_INTERVAL_MS: Duration = Duration::from_millis(50);
const AUTHFS_SETUP_TIMEOUT_SEC: Duration = Duration::from_secs(10);
const FUSE_SUPER_MAGIC: FsType = FsType(0x65735546);
/// The number that hints the future file descriptor. These are not really file descriptor, but
/// represents the file descriptor number to pass to the task.
type PseudoRawFd = i32;
fn is_fuse(path: &str) -> Result<bool> {
Ok(statfs(path)?.filesystem_type() == FUSE_SUPER_MAGIC)
}
fn spawn_authfs(config: &Config) -> Result<Minijail> {
// TODO(b/185175567): Run in a more restricted sandbox.
let jail = Minijail::new()?;
let mut args = vec![
AUTHFS_BIN.to_string(),
config.authfs_root.clone(),
"--cid=2".to_string(), // Always use host unless we need to support other cases
];
for conf in &config.in_fds {
// TODO(b/185178698): Many input files need to be signed and verified.
// or can we use debug cert for now, which is better than nothing?
args.push("--remote-ro-file-unverified".to_string());
args.push(format!("{}:{}:{}", conf.fd, conf.fd, conf.file_size));
}
for conf in &config.out_fds {
args.push("--remote-new-rw-file".to_string());
args.push(format!("{}:{}", conf.fd, conf.fd));
}
let preserve_fds = if config.debuggable {
vec![1, 2] // inherit/redirect stdout/stderr for debugging
} else {
vec![]
};
let _pid = jail.run(Path::new(AUTHFS_BIN), &preserve_fds, &args)?;
Ok(jail)
}
fn wait_until_authfs_ready(authfs_root: &str) -> Result<()> {
let start_time = Instant::now();
loop {
if is_fuse(authfs_root)? {
break;
}
if start_time.elapsed() > AUTHFS_SETUP_TIMEOUT_SEC {
bail!("Time out mounting authfs");
}
sleep(AUTHFS_SETUP_POLL_INTERVAL_MS);
}
Ok(())
}
fn open_authfs_file(authfs_root: &str, basename: PseudoRawFd, writable: bool) -> io::Result<File> {
OpenOptions::new().read(true).write(writable).open(format!("{}/{}", authfs_root, basename))
}
fn open_authfs_files_for_mapping(config: &Config) -> io::Result<Vec<(File, PseudoRawFd)>> {
let mut fd_mapping = Vec::with_capacity(config.in_fds.len() + config.out_fds.len());
let results: io::Result<Vec<_>> = config
.in_fds
.iter()
.map(|conf| Ok((open_authfs_file(&config.authfs_root, conf.fd, false)?, conf.fd)))
.collect();
fd_mapping.append(&mut results?);
let results: io::Result<Vec<_>> = config
.out_fds
.iter()
.map(|conf| Ok((open_authfs_file(&config.authfs_root, conf.fd, true)?, conf.fd)))
.collect();
fd_mapping.append(&mut results?);
Ok(fd_mapping)
}
fn spawn_jailed_task(config: &Config, fd_mapping: Vec<(File, PseudoRawFd)>) -> Result<Minijail> {
// TODO(b/185175567): Run in a more restricted sandbox.
let jail = Minijail::new()?;
let mut preserve_fds: Vec<_> = fd_mapping.iter().map(|(f, id)| (f.as_raw_fd(), *id)).collect();
if config.debuggable {
// inherit/redirect stdout/stderr for debugging
preserve_fds.push((1, 1));
preserve_fds.push((2, 2));
}
let _pid =
jail.run_remap(&Path::new(&config.args[0]), preserve_fds.as_slice(), &config.args)?;
Ok(jail)
}
struct InFdAnnotation {
fd: PseudoRawFd,
file_size: u64,
}
struct OutFdAnnotation {
fd: PseudoRawFd,
}
struct Config {
authfs_root: String,
in_fds: Vec<InFdAnnotation>,
out_fds: Vec<OutFdAnnotation>,
args: Vec<String>,
debuggable: bool,
}
fn parse_args() -> Result<Config> {
#[rustfmt::skip]
let matches = clap::App::new("compsvc_worker")
.arg(clap::Arg::with_name("authfs-root")
.long("authfs-root")
.value_name("DIR")
.required(true)
.takes_value(true))
.arg(clap::Arg::with_name("in-fd")
.long("in-fd")
.multiple(true)
.takes_value(true)
.requires("authfs-root"))
.arg(clap::Arg::with_name("out-fd")
.long("out-fd")
.multiple(true)
.takes_value(true)
.requires("authfs-root"))
.arg(clap::Arg::with_name("debug")
.long("debug"))
.arg(clap::Arg::with_name("args")
.last(true)
.required(true)
.multiple(true))
.get_matches();
// Safe to unwrap since the arg is required by the clap rule
let authfs_root = matches.value_of("authfs-root").unwrap().to_string();
let results: Result<Vec<_>> = matches
.values_of("in-fd")
.unwrap_or_default()
.into_iter()
.map(|arg| {
if let Some(index) = arg.find(':') {
let (fd, size) = arg.split_at(index);
Ok(InFdAnnotation { fd: fd.parse()?, file_size: size[1..].parse()? })
} else {
bail!("Invalid argument: {}", arg);
}
})
.collect();
let in_fds = results?;
let results: Result<Vec<_>> = matches
.values_of("out-fd")
.unwrap_or_default()
.into_iter()
.map(|arg| Ok(OutFdAnnotation { fd: arg.parse()? }))
.collect();
let out_fds = results?;
let args: Vec<_> = matches.values_of("args").unwrap().map(|s| s.to_string()).collect();
let debuggable = matches.is_present("debug");
Ok(Config { authfs_root, in_fds, out_fds, args, debuggable })
}
fn main() -> Result<()> {
let log_level =
if env!("TARGET_BUILD_VARIANT") == "eng" { log::Level::Trace } else { log::Level::Info };
android_logger::init_once(
android_logger::Config::default().with_tag("compsvc_worker").with_min_level(log_level),
);
let config = parse_args()?;
let authfs_jail = spawn_authfs(&config)?;
let authfs_lifetime = scopeguard::guard(authfs_jail, |authfs_jail| {
if let Err(e) = authfs_jail.kill() {
if !matches!(e, minijail::Error::Killed(_)) {
warn!("Failed to kill authfs: {}", e);
}
}
});
wait_until_authfs_ready(&config.authfs_root)?;
let fd_mapping = open_authfs_files_for_mapping(&config)?;
let jail = spawn_jailed_task(&config, fd_mapping)?;
let jail_result = jail.wait();
// Be explicit about the lifetime, which should last at least until the task is finished.
drop(authfs_lifetime);
match jail_result {
Ok(_) => Ok(()),
Err(minijail::Error::ReturnCode(exit_code)) => {
exit(exit_code as i32);
}
Err(e) => {
bail!("Unexpected minijail error: {}", e);
}
}
}