| /* |
| * 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<F>( |
| odrefresh_path: &Path, |
| args: &OdrefreshArgs, |
| authfs_service: Strong<dyn IAuthFsService>, |
| success_fn: F, |
| ) -> Result<ExitCode> |
| 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<String> { |
| 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 <var name> <value>" |
| 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<Minijail> { |
| // 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<String, String>); |
| |
| 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<String> { |
| // 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() |
| } |
| } |