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 use anyhow::{anyhow, bail, Context, Result};
18 use log::{debug, info, warn};
19 use minijail::{self, Minijail};
20 use regex::Regex;
21 use rustutils::system_properties;
22 use std::collections::HashMap;
23 use std::env;
24 use std::ffi::OsString;
25 use std::path::{self, Path, PathBuf};
26 use std::process::Command;
27 
28 use authfs_aidl_interface::aidl::com::android::virt::fs::{
29     AuthFsConfig::{
30         AuthFsConfig, InputDirFdAnnotation::InputDirFdAnnotation,
31         OutputDirFdAnnotation::OutputDirFdAnnotation,
32     },
33     IAuthFsService::IAuthFsService,
34 };
35 use binder::Strong;
36 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::{
37     CompilationMode::CompilationMode, OdrefreshArgs::OdrefreshArgs,
38 };
39 use compos_common::odrefresh::ExitCode;
40 
41 const FD_SERVER_PORT: i32 = 3264; // TODO: support dynamic port
42 
validate_args(args: &OdrefreshArgs) -> Result<()>43 fn validate_args(args: &OdrefreshArgs) -> Result<()> {
44     if args.compilationMode != CompilationMode::NORMAL_COMPILE {
45         // Conservatively check debuggability.
46         let debuggable =
47             system_properties::read_bool("ro.boot.microdroid.debuggable", false).unwrap_or(false);
48         if !debuggable {
49             bail!("Requested compilation mode only available in debuggable VMs");
50         }
51     }
52 
53     if args.systemDirFd < 0 || args.outputDirFd < 0 || args.stagingDirFd < 0 {
54         bail!("The remote FDs are expected to be non-negative");
55     }
56     if !matches!(&args.zygoteArch[..], "zygote64" | "zygote64_32") {
57         bail!("Invalid zygote arch");
58     }
59     // Disallow any sort of path traversal
60     if args.targetDirName.contains(path::MAIN_SEPARATOR) {
61         bail!("Invalid target directory {}", args.targetDirName);
62     }
63 
64     // We're not validating/allowlisting the compiler filter, and just assume the compiler will
65     // reject an invalid string. We need to accept "verify" filter anyway, and potential
66     // performance degration by the attacker is not currently in scope. This also allows ART to
67     // specify new compiler filter and configure through system property without change to
68     // CompOS.
69     Ok(())
70 }
71 
odrefresh<F>( odrefresh_path: &Path, args: &OdrefreshArgs, authfs_service: Strong<dyn IAuthFsService>, success_fn: F, ) -> Result<ExitCode> where F: FnOnce(PathBuf) -> Result<()>,72 pub fn odrefresh<F>(
73     odrefresh_path: &Path,
74     args: &OdrefreshArgs,
75     authfs_service: Strong<dyn IAuthFsService>,
76     success_fn: F,
77 ) -> Result<ExitCode>
78 where
79     F: FnOnce(PathBuf) -> Result<()>,
80 {
81     validate_args(args)?;
82 
83     // Mount authfs (via authfs_service). The authfs instance unmounts once the `authfs` variable
84     // is out of scope.
85 
86     let mut input_dir_fd_annotations = vec![InputDirFdAnnotation {
87         fd: args.systemDirFd,
88         // Use the 0th APK of the extra_apks in compos/apk/assets/vm_config*.json
89         manifestPath: "/mnt/extra-apk/0/assets/build_manifest.pb".to_string(),
90         prefix: "system/".to_string(),
91     }];
92     if args.systemExtDirFd >= 0 {
93         input_dir_fd_annotations.push(InputDirFdAnnotation {
94             fd: args.systemExtDirFd,
95             // Use the 1st APK of the extra_apks in compos/apk/assets/vm_config_system_ext_*.json
96             manifestPath: "/mnt/extra-apk/1/assets/build_manifest.pb".to_string(),
97             prefix: "system_ext/".to_string(),
98         });
99     }
100 
101     let authfs_config = AuthFsConfig {
102         port: FD_SERVER_PORT,
103         inputDirFdAnnotations: input_dir_fd_annotations,
104         outputDirFdAnnotations: vec![
105             OutputDirFdAnnotation { fd: args.outputDirFd },
106             OutputDirFdAnnotation { fd: args.stagingDirFd },
107         ],
108         ..Default::default()
109     };
110     let authfs = authfs_service.mount(&authfs_config)?;
111     let mountpoint = PathBuf::from(authfs.getMountPoint()?);
112 
113     // Make a copy of our environment as the basis of the one we will give odrefresh
114     let mut odrefresh_vars = EnvMap::from_current_env();
115 
116     let mut android_root = mountpoint.clone();
117     android_root.push(args.systemDirFd.to_string());
118     android_root.push("system");
119     odrefresh_vars.set("ANDROID_ROOT", path_to_str(&android_root)?);
120     debug!("ANDROID_ROOT={:?}", &android_root);
121 
122     if args.systemExtDirFd >= 0 {
123         let mut system_ext_root = mountpoint.clone();
124         system_ext_root.push(args.systemExtDirFd.to_string());
125         system_ext_root.push("system_ext");
126         odrefresh_vars.set("SYSTEM_EXT_ROOT", path_to_str(&system_ext_root)?);
127         debug!("SYSTEM_EXT_ROOT={:?}", &system_ext_root);
128     }
129 
130     let art_apex_data = mountpoint.join(args.outputDirFd.to_string());
131     odrefresh_vars.set("ART_APEX_DATA", path_to_str(&art_apex_data)?);
132     debug!("ART_APEX_DATA={:?}", &art_apex_data);
133 
134     let staging_dir = mountpoint.join(args.stagingDirFd.to_string());
135 
136     set_classpaths(&mut odrefresh_vars, &android_root)?;
137 
138     let mut command_line_args = vec![
139         "odrefresh".to_string(),
140         "--compilation-os-mode".to_string(),
141         format!("--zygote-arch={}", args.zygoteArch),
142         format!("--dalvik-cache={}", args.targetDirName),
143         format!("--staging-dir={}", staging_dir.display()),
144         "--no-refresh".to_string(),
145     ];
146 
147     if !args.systemServerCompilerFilter.is_empty() {
148         command_line_args
149             .push(format!("--system-server-compiler-filter={}", args.systemServerCompilerFilter));
150     }
151 
152     let compile_flag = match args.compilationMode {
153         CompilationMode::NORMAL_COMPILE => "--compile",
154         CompilationMode::TEST_COMPILE => "--force-compile",
155         other => bail!("Unknown compilation mode {:?}", other),
156     };
157     command_line_args.push(compile_flag.to_string());
158 
159     debug!("Running odrefresh with args: {:?}", &command_line_args);
160     let jail = spawn_jailed_task(odrefresh_path, &command_line_args, &odrefresh_vars.into_env())
161         .context("Spawn odrefresh")?;
162     let exit_code = match jail.wait() {
163         Ok(_) => 0,
164         Err(minijail::Error::ReturnCode(exit_code)) => exit_code,
165         Err(e) => bail!("Unexpected minijail error: {}", e),
166     };
167 
168     let exit_code = ExitCode::from_i32(exit_code.into())?;
169     info!("odrefresh exited with {:?}", exit_code);
170 
171     if exit_code == ExitCode::CompilationSuccess {
172         let target_dir = art_apex_data.join(&args.targetDirName);
173         success_fn(target_dir)?;
174     }
175 
176     Ok(exit_code)
177 }
178 
path_to_str(path: &Path) -> Result<&str>179 fn path_to_str(path: &Path) -> Result<&str> {
180     path.to_str().ok_or_else(|| anyhow!("Bad path {:?}", path))
181 }
182 
set_classpaths(odrefresh_vars: &mut EnvMap, android_root: &Path) -> Result<()>183 fn set_classpaths(odrefresh_vars: &mut EnvMap, android_root: &Path) -> Result<()> {
184     let export_lines = run_derive_classpath(android_root)?;
185     load_classpath_vars(odrefresh_vars, &export_lines)
186 }
187 
run_derive_classpath(android_root: &Path) -> Result<String>188 fn run_derive_classpath(android_root: &Path) -> Result<String> {
189     let classpaths_root = android_root.join("etc/classpaths");
190 
191     let mut bootclasspath_arg = OsString::new();
192     bootclasspath_arg.push("--bootclasspath-fragment=");
193     bootclasspath_arg.push(classpaths_root.join("bootclasspath.pb"));
194 
195     let mut systemserverclasspath_arg = OsString::new();
196     systemserverclasspath_arg.push("--systemserverclasspath-fragment=");
197     systemserverclasspath_arg.push(classpaths_root.join("systemserverclasspath.pb"));
198 
199     let result = Command::new("/apex/com.android.sdkext/bin/derive_classpath")
200         .arg(bootclasspath_arg)
201         .arg(systemserverclasspath_arg)
202         .arg("/proc/self/fd/1")
203         .output()
204         .context("Failed to run derive_classpath")?;
205 
206     if !result.status.success() {
207         bail!("derive_classpath returned {}", result.status);
208     }
209 
210     String::from_utf8(result.stdout).context("Converting derive_classpath output")
211 }
212 
load_classpath_vars(odrefresh_vars: &mut EnvMap, export_lines: &str) -> Result<()>213 fn load_classpath_vars(odrefresh_vars: &mut EnvMap, export_lines: &str) -> Result<()> {
214     // Each line should be in the format "export <var name> <value>"
215     let pattern = Regex::new(r"^export ([^ ]+) ([^ ]+)$").context("Failed to construct Regex")?;
216     for line in export_lines.lines() {
217         if let Some(captures) = pattern.captures(line) {
218             let name = &captures[1];
219             let value = &captures[2];
220             odrefresh_vars.set(name, value);
221         } else {
222             warn!("Malformed line from derive_classpath: {}", line);
223         }
224     }
225 
226     Ok(())
227 }
228 
spawn_jailed_task(executable: &Path, args: &[String], env_vars: &[String]) -> Result<Minijail>229 fn spawn_jailed_task(executable: &Path, args: &[String], env_vars: &[String]) -> Result<Minijail> {
230     // TODO(b/185175567): Run in a more restricted sandbox.
231     let jail = Minijail::new()?;
232     let keep_fds = [];
233     let command = minijail::Command::new_for_path(executable, &keep_fds, args, Some(env_vars))?;
234     let _pid = jail.run_command(command)?;
235     Ok(jail)
236 }
237 
238 struct EnvMap(HashMap<String, String>);
239 
240 impl EnvMap {
from_current_env() -> Self241     fn from_current_env() -> Self {
242         Self(env::vars().collect())
243     }
244 
set(&mut self, key: &str, value: &str)245     fn set(&mut self, key: &str, value: &str) {
246         self.0.insert(key.to_owned(), value.to_owned());
247     }
248 
into_env(self) -> Vec<String>249     fn into_env(self) -> Vec<String> {
250         // execve() expects an array of "k=v" strings, rather than a list of (k, v) pairs.
251         self.0.into_iter().map(|(k, v)| k + "=" + &v).collect()
252     }
253 }
254