1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 //! This executable works as a child/worker for the main compsvc service. This worker is mainly 18 //! responsible for setting up the execution environment, e.g. to create file descriptors for 19 //! remote file access via an authfs mount. 20 21 use anyhow::{bail, Result}; 22 use log::warn; 23 use minijail::Minijail; 24 use nix::sys::statfs::{statfs, FsType}; 25 use std::fs::{File, OpenOptions}; 26 use std::io; 27 use std::os::unix::io::AsRawFd; 28 use std::path::Path; 29 use std::process::exit; 30 use std::thread::sleep; 31 use std::time::{Duration, Instant}; 32 33 const AUTHFS_BIN: &str = "/apex/com.android.virt/bin/authfs"; 34 const AUTHFS_SETUP_POLL_INTERVAL_MS: Duration = Duration::from_millis(50); 35 const AUTHFS_SETUP_TIMEOUT_SEC: Duration = Duration::from_secs(10); 36 const FUSE_SUPER_MAGIC: FsType = FsType(0x65735546); 37 38 /// The number that hints the future file descriptor. These are not really file descriptor, but 39 /// represents the file descriptor number to pass to the task. 40 type PseudoRawFd = i32; 41 42 fn is_fuse(path: &str) -> Result<bool> { 43 Ok(statfs(path)?.filesystem_type() == FUSE_SUPER_MAGIC) 44 } 45 46 fn spawn_authfs(config: &Config) -> Result<Minijail> { 47 // TODO(b/185175567): Run in a more restricted sandbox. 48 let jail = Minijail::new()?; 49 50 let mut args = vec![AUTHFS_BIN.to_string(), config.authfs_root.clone()]; 51 for conf in &config.in_fds { 52 // TODO(b/185178698): Many input files need to be signed and verified. 53 // or can we use debug cert for now, which is better than nothing? 54 args.push("--remote-ro-file-unverified".to_string()); 55 args.push(format!("{}:{}:{}", conf.fd, conf.fd, conf.file_size)); 56 } 57 for conf in &config.out_fds { 58 args.push("--remote-new-rw-file".to_string()); 59 args.push(format!("{}:{}", conf.fd, conf.fd)); 60 } 61 62 let preserve_fds = if config.debuggable { 63 vec![1, 2] // inherit/redirect stdout/stderr for debugging 64 } else { 65 vec![] 66 }; 67 68 let _pid = jail.run(Path::new(AUTHFS_BIN), &preserve_fds, &args)?; 69 Ok(jail) 70 } 71 72 fn wait_until_authfs_ready(authfs_root: &str) -> Result<()> { 73 let start_time = Instant::now(); 74 loop { 75 if is_fuse(authfs_root)? { 76 break; 77 } 78 if start_time.elapsed() > AUTHFS_SETUP_TIMEOUT_SEC { 79 bail!("Time out mounting authfs"); 80 } 81 sleep(AUTHFS_SETUP_POLL_INTERVAL_MS); 82 } 83 Ok(()) 84 } 85 86 fn open_authfs_file(authfs_root: &str, basename: PseudoRawFd, writable: bool) -> io::Result<File> { 87 OpenOptions::new().read(true).write(writable).open(format!("{}/{}", authfs_root, basename)) 88 } 89 90 fn open_authfs_files_for_mapping(config: &Config) -> io::Result<Vec<(File, PseudoRawFd)>> { 91 let mut fd_mapping = Vec::with_capacity(config.in_fds.len() + config.out_fds.len()); 92 93 let results: io::Result<Vec<_>> = config 94 .in_fds 95 .iter() 96 .map(|conf| Ok((open_authfs_file(&config.authfs_root, conf.fd, false)?, conf.fd))) 97 .collect(); 98 fd_mapping.append(&mut results?); 99 100 let results: io::Result<Vec<_>> = config 101 .out_fds 102 .iter() 103 .map(|conf| Ok((open_authfs_file(&config.authfs_root, conf.fd, true)?, conf.fd))) 104 .collect(); 105 fd_mapping.append(&mut results?); 106 107 Ok(fd_mapping) 108 } 109 110 fn spawn_jailed_task(config: &Config, fd_mapping: Vec<(File, PseudoRawFd)>) -> Result<Minijail> { 111 // TODO(b/185175567): Run in a more restricted sandbox. 112 let jail = Minijail::new()?; 113 let mut preserve_fds: Vec<_> = fd_mapping.iter().map(|(f, id)| (f.as_raw_fd(), *id)).collect(); 114 if config.debuggable { 115 // inherit/redirect stdout/stderr for debugging 116 preserve_fds.push((1, 1)); 117 preserve_fds.push((2, 2)); 118 } 119 let _pid = 120 jail.run_remap(&Path::new(&config.args[0]), preserve_fds.as_slice(), &config.args)?; 121 Ok(jail) 122 } 123 124 struct InFdAnnotation { 125 fd: PseudoRawFd, 126 file_size: u64, 127 } 128 129 struct OutFdAnnotation { 130 fd: PseudoRawFd, 131 } 132 133 struct Config { 134 authfs_root: String, 135 in_fds: Vec<InFdAnnotation>, 136 out_fds: Vec<OutFdAnnotation>, 137 args: Vec<String>, 138 debuggable: bool, 139 } 140 141 fn parse_args() -> Result<Config> { 142 #[rustfmt::skip] 143 let matches = clap::App::new("compsvc_worker") 144 .arg(clap::Arg::with_name("authfs-root") 145 .long("authfs-root") 146 .value_name("DIR") 147 .required(true) 148 .takes_value(true)) 149 .arg(clap::Arg::with_name("in-fd") 150 .long("in-fd") 151 .multiple(true) 152 .takes_value(true) 153 .requires("authfs-root")) 154 .arg(clap::Arg::with_name("out-fd") 155 .long("out-fd") 156 .multiple(true) 157 .takes_value(true) 158 .requires("authfs-root")) 159 .arg(clap::Arg::with_name("debug") 160 .long("debug")) 161 .arg(clap::Arg::with_name("args") 162 .last(true) 163 .required(true) 164 .multiple(true)) 165 .get_matches(); 166 167 // Safe to unwrap since the arg is required by the clap rule 168 let authfs_root = matches.value_of("authfs-root").unwrap().to_string(); 169 170 let results: Result<Vec<_>> = matches 171 .values_of("in-fd") 172 .unwrap_or_default() 173 .into_iter() 174 .map(|arg| { 175 if let Some(index) = arg.find(':') { 176 let (fd, size) = arg.split_at(index); 177 Ok(InFdAnnotation { fd: fd.parse()?, file_size: size[1..].parse()? }) 178 } else { 179 bail!("Invalid argument: {}", arg); 180 } 181 }) 182 .collect(); 183 let in_fds = results?; 184 185 let results: Result<Vec<_>> = matches 186 .values_of("out-fd") 187 .unwrap_or_default() 188 .into_iter() 189 .map(|arg| Ok(OutFdAnnotation { fd: arg.parse()? })) 190 .collect(); 191 let out_fds = results?; 192 193 let args: Vec<_> = matches.values_of("args").unwrap().map(|s| s.to_string()).collect(); 194 let debuggable = matches.is_present("debug"); 195 196 Ok(Config { authfs_root, in_fds, out_fds, args, debuggable }) 197 } 198 199 fn main() -> Result<()> { 200 let log_level = 201 if env!("TARGET_BUILD_VARIANT") == "eng" { log::Level::Trace } else { log::Level::Info }; 202 android_logger::init_once( 203 android_logger::Config::default().with_tag("compsvc_worker").with_min_level(log_level), 204 ); 205 206 let config = parse_args()?; 207 208 let authfs_jail = spawn_authfs(&config)?; 209 let authfs_lifetime = scopeguard::guard(authfs_jail, |authfs_jail| { 210 if let Err(e) = authfs_jail.kill() { 211 if !matches!(e, minijail::Error::Killed(_)) { 212 warn!("Failed to kill authfs: {}", e); 213 } 214 } 215 }); 216 217 wait_until_authfs_ready(&config.authfs_root)?; 218 let fd_mapping = open_authfs_files_for_mapping(&config)?; 219 220 let jail = spawn_jailed_task(&config, fd_mapping)?; 221 let jail_result = jail.wait(); 222 223 // Be explicit about the lifetime, which should last at least until the task is finished. 224 drop(authfs_lifetime); 225 226 match jail_result { 227 Ok(_) => Ok(()), 228 Err(minijail::Error::ReturnCode(exit_code)) => { 229 exit(exit_code as i32); 230 } 231 Err(e) => { 232 bail!("Unexpected minijail error: {}", e); 233 } 234 } 235 } 236