/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ use anyhow::{anyhow, bail, Context, Result}; use log::{debug, info, warn}; use minijail::{self, Minijail}; use regex::Regex; use rustutils::system_properties; use std::collections::HashMap; use std::env; use std::ffi::OsString; use std::path::{self, Path, PathBuf}; use std::process::Command; use authfs_aidl_interface::aidl::com::android::virt::fs::{ AuthFsConfig::{ AuthFsConfig, InputDirFdAnnotation::InputDirFdAnnotation, OutputDirFdAnnotation::OutputDirFdAnnotation, }, IAuthFsService::IAuthFsService, }; use binder::Strong; use compos_aidl_interface::aidl::com::android::compos::ICompOsService::{ CompilationMode::CompilationMode, OdrefreshArgs::OdrefreshArgs, }; use compos_common::odrefresh::ExitCode; const FD_SERVER_PORT: i32 = 3264; // TODO: support dynamic port fn validate_args(args: &OdrefreshArgs) -> Result<()> { if args.compilationMode != CompilationMode::NORMAL_COMPILE { // Conservatively check debuggability. let debuggable = system_properties::read_bool("ro.boot.microdroid.debuggable", false).unwrap_or(false); if !debuggable { bail!("Requested compilation mode only available in debuggable VMs"); } } if args.systemDirFd < 0 || args.outputDirFd < 0 || args.stagingDirFd < 0 { bail!("The remote FDs are expected to be non-negative"); } if !matches!(&args.zygoteArch[..], "zygote64" | "zygote64_32") { bail!("Invalid zygote arch"); } // Disallow any sort of path traversal if args.targetDirName.contains(path::MAIN_SEPARATOR) { bail!("Invalid target directory {}", args.targetDirName); } // We're not validating/allowlisting the compiler filter, and just assume the compiler will // reject an invalid string. We need to accept "verify" filter anyway, and potential // performance degration by the attacker is not currently in scope. This also allows ART to // specify new compiler filter and configure through system property without change to // CompOS. Ok(()) } pub fn odrefresh( odrefresh_path: &Path, args: &OdrefreshArgs, authfs_service: Strong, success_fn: F, ) -> Result where F: FnOnce(PathBuf) -> Result<()>, { validate_args(args)?; // Mount authfs (via authfs_service). The authfs instance unmounts once the `authfs` variable // is out of scope. let mut input_dir_fd_annotations = vec![InputDirFdAnnotation { fd: args.systemDirFd, // Use the 0th APK of the extra_apks in compos/apk/assets/vm_config*.json manifestPath: "/mnt/extra-apk/0/assets/build_manifest.pb".to_string(), prefix: "system/".to_string(), }]; if args.systemExtDirFd >= 0 { input_dir_fd_annotations.push(InputDirFdAnnotation { fd: args.systemExtDirFd, // Use the 1st APK of the extra_apks in compos/apk/assets/vm_config_system_ext_*.json manifestPath: "/mnt/extra-apk/1/assets/build_manifest.pb".to_string(), prefix: "system_ext/".to_string(), }); } let authfs_config = AuthFsConfig { port: FD_SERVER_PORT, inputDirFdAnnotations: input_dir_fd_annotations, outputDirFdAnnotations: vec![ OutputDirFdAnnotation { fd: args.outputDirFd }, OutputDirFdAnnotation { fd: args.stagingDirFd }, ], ..Default::default() }; let authfs = authfs_service.mount(&authfs_config)?; let mountpoint = PathBuf::from(authfs.getMountPoint()?); // Make a copy of our environment as the basis of the one we will give odrefresh let mut odrefresh_vars = EnvMap::from_current_env(); let mut android_root = mountpoint.clone(); android_root.push(args.systemDirFd.to_string()); android_root.push("system"); odrefresh_vars.set("ANDROID_ROOT", path_to_str(&android_root)?); debug!("ANDROID_ROOT={:?}", &android_root); if args.systemExtDirFd >= 0 { let mut system_ext_root = mountpoint.clone(); system_ext_root.push(args.systemExtDirFd.to_string()); system_ext_root.push("system_ext"); odrefresh_vars.set("SYSTEM_EXT_ROOT", path_to_str(&system_ext_root)?); debug!("SYSTEM_EXT_ROOT={:?}", &system_ext_root); } let art_apex_data = mountpoint.join(args.outputDirFd.to_string()); odrefresh_vars.set("ART_APEX_DATA", path_to_str(&art_apex_data)?); debug!("ART_APEX_DATA={:?}", &art_apex_data); let staging_dir = mountpoint.join(args.stagingDirFd.to_string()); set_classpaths(&mut odrefresh_vars, &android_root)?; let mut command_line_args = vec![ "odrefresh".to_string(), "--compilation-os-mode".to_string(), format!("--zygote-arch={}", args.zygoteArch), format!("--dalvik-cache={}", args.targetDirName), format!("--staging-dir={}", staging_dir.display()), "--no-refresh".to_string(), ]; if !args.systemServerCompilerFilter.is_empty() { command_line_args .push(format!("--system-server-compiler-filter={}", args.systemServerCompilerFilter)); } let compile_flag = match args.compilationMode { CompilationMode::NORMAL_COMPILE => "--compile", CompilationMode::TEST_COMPILE => "--force-compile", other => bail!("Unknown compilation mode {:?}", other), }; command_line_args.push(compile_flag.to_string()); debug!("Running odrefresh with args: {:?}", &command_line_args); let jail = spawn_jailed_task(odrefresh_path, &command_line_args, &odrefresh_vars.into_env()) .context("Spawn odrefresh")?; let exit_code = match jail.wait() { Ok(_) => 0, Err(minijail::Error::ReturnCode(exit_code)) => exit_code, Err(e) => bail!("Unexpected minijail error: {}", e), }; let exit_code = ExitCode::from_i32(exit_code.into())?; info!("odrefresh exited with {:?}", exit_code); if exit_code == ExitCode::CompilationSuccess { let target_dir = art_apex_data.join(&args.targetDirName); success_fn(target_dir)?; } Ok(exit_code) } fn path_to_str(path: &Path) -> Result<&str> { path.to_str().ok_or_else(|| anyhow!("Bad path {:?}", path)) } fn set_classpaths(odrefresh_vars: &mut EnvMap, android_root: &Path) -> Result<()> { let export_lines = run_derive_classpath(android_root)?; load_classpath_vars(odrefresh_vars, &export_lines) } fn run_derive_classpath(android_root: &Path) -> Result { let classpaths_root = android_root.join("etc/classpaths"); let mut bootclasspath_arg = OsString::new(); bootclasspath_arg.push("--bootclasspath-fragment="); bootclasspath_arg.push(classpaths_root.join("bootclasspath.pb")); let mut systemserverclasspath_arg = OsString::new(); systemserverclasspath_arg.push("--systemserverclasspath-fragment="); systemserverclasspath_arg.push(classpaths_root.join("systemserverclasspath.pb")); let result = Command::new("/apex/com.android.sdkext/bin/derive_classpath") .arg(bootclasspath_arg) .arg(systemserverclasspath_arg) .arg("/proc/self/fd/1") .output() .context("Failed to run derive_classpath")?; if !result.status.success() { bail!("derive_classpath returned {}", result.status); } String::from_utf8(result.stdout).context("Converting derive_classpath output") } fn load_classpath_vars(odrefresh_vars: &mut EnvMap, export_lines: &str) -> Result<()> { // Each line should be in the format "export " let pattern = Regex::new(r"^export ([^ ]+) ([^ ]+)$").context("Failed to construct Regex")?; for line in export_lines.lines() { if let Some(captures) = pattern.captures(line) { let name = &captures[1]; let value = &captures[2]; odrefresh_vars.set(name, value); } else { warn!("Malformed line from derive_classpath: {}", line); } } Ok(()) } fn spawn_jailed_task(executable: &Path, args: &[String], env_vars: &[String]) -> Result { // TODO(b/185175567): Run in a more restricted sandbox. let jail = Minijail::new()?; let keep_fds = []; let command = minijail::Command::new_for_path(executable, &keep_fds, args, Some(env_vars))?; let _pid = jail.run_command(command)?; Ok(jail) } struct EnvMap(HashMap); impl EnvMap { fn from_current_env() -> Self { Self(env::vars().collect()) } fn set(&mut self, key: &str, value: &str) { self.0.insert(key.to_owned(), value.to_owned()); } fn into_env(self) -> Vec { // execve() expects an array of "k=v" strings, rather than a list of (k, v) pairs. self.0.into_iter().map(|(k, v)| k + "=" + &v).collect() } }