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