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