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
is_fuse(path: &str) -> Result<bool>42 fn is_fuse(path: &str) -> Result<bool> {
43 Ok(statfs(path)?.filesystem_type() == FUSE_SUPER_MAGIC)
44 }
45
spawn_authfs(config: &Config) -> Result<Minijail>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
wait_until_authfs_ready(authfs_root: &str) -> Result<()>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
open_authfs_file(authfs_root: &str, basename: PseudoRawFd, writable: bool) -> io::Result<File>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
open_authfs_files_for_mapping(config: &Config) -> io::Result<Vec<(File, PseudoRawFd)>>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
spawn_jailed_task(config: &Config, fd_mapping: Vec<(File, PseudoRawFd)>) -> Result<Minijail>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
parse_args() -> Result<Config>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
main() -> Result<()>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