Merge "Revert "Revert "Move ramdisk to the new init_boot partition"""
diff --git a/apex/product_packages.mk b/apex/product_packages.mk
index 322b73e..ec295f5 100644
--- a/apex/product_packages.mk
+++ b/apex/product_packages.mk
@@ -31,3 +31,5 @@
PRODUCT_APEX_SYSTEM_SERVER_JARS := com.android.compos:service-compos
PRODUCT_SYSTEM_EXT_PROPERTIES := ro.config.isolated_compilation_enabled=true
+
+PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA := true
diff --git a/authfs/src/fsverity/metadata/Android.bp b/authfs/src/fsverity/metadata/Android.bp
index 39ce515..b155224 100644
--- a/authfs/src/fsverity/metadata/Android.bp
+++ b/authfs/src/fsverity/metadata/Android.bp
@@ -1,3 +1,7 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
rust_bindgen {
name: "libauthfs_fsverity_metadata_bindgen",
wrapper_src: "metadata.hpp",
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index 858b099..18b7b51 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -269,9 +269,12 @@
Path::new("/system/framework/framework.jar"),
Path::new("/system/framework/ims-common.jar"),
Path::new("/system/framework/services.jar"),
+ Path::new("/system/framework/services.jar.prof"),
Path::new("/system/framework/telephony-common.jar"),
Path::new("/system/framework/voip-common.jar"),
Path::new("/system/etc/boot-image.prof"),
+ Path::new("/system/etc/classpaths/bootclasspath.pb"),
+ Path::new("/system/etc/classpaths/systemserverclasspath.pb"),
Path::new("/system/etc/dirty-image-objects"),
];
diff --git a/compos/Android.bp b/compos/Android.bp
index 783ba22..ab55efb 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -3,33 +3,6 @@
}
rust_binary {
- name: "pvm_exec",
- srcs: ["src/pvm_exec.rs"],
- rustlibs: [
- "android.system.composd.internal-rust",
- "compos_aidl_interface-rust",
- "libandroid_logger",
- "libanyhow",
- "libbinder_rpc_unstable_bindgen",
- "libbinder_rs",
- "libclap",
- "libcompos_common",
- "liblibc",
- "liblog_rust",
- "libminijail_rust",
- "libnix",
- "libscopeguard",
- ],
- prefer_rlib: true,
- shared_libs: [
- "libbinder_rpc_unstable",
- ],
- apex_available: [
- "com.android.compos",
- ],
-}
-
-rust_binary {
name: "compsvc",
srcs: ["src/compsvc_main.rs"],
rustlibs: [
@@ -52,6 +25,7 @@
"libnix",
"libodsign_proto_rust",
"libprotobuf",
+ "libregex",
"libring",
"libscopeguard",
],
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index 395a09b..e3e0317 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -33,15 +33,6 @@
void initializeSigningKey(in byte[] keyBlob);
/**
- * Initializes the classpaths necessary for preparing and running compilation.
- *
- * TODO(198211396): Implement properly. We can't simply accepting the classpaths from Android
- * since they are not derived from staged APEX (besides security reasons).
- */
- void initializeClasspaths(
- String bootClasspath, String dex2oatBootClasspath, String systemServerClassPath);
-
- /**
* Run odrefresh in the VM context.
*
* The execution is based on the VM's APEX mounts, files on Android's /system (by accessing
@@ -54,10 +45,11 @@
* @param targetDirName The sub-directory of the output directory to which artifacts are to be
* written (e.g. dalvik-cache)
* @param zygoteArch The zygote architecture (ro.zygote)
+ * @param systemServerCompilerFilter The compiler filter used to compile system server
* @return odrefresh exit code
*/
byte odrefresh(int systemDirFd, int outputDirFd, int stagingDirFd, String targetDirName,
- String zygoteArch);
+ String zygoteArch, String systemServerCompilerFilter);
/**
* Run dex2oat command with provided args, in a context that may be specified in FdAnnotation,
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
index 564a380..ea72018 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -46,7 +46,6 @@
"compos_verify_key",
"composd",
"composd_cmd",
- "pvm_exec", // deprecated
// Used in VM
"compsvc",
diff --git a/compos/apk/assets/vm_config.json b/compos/apk/assets/vm_config.json
index 3a6eff4..260332e 100644
--- a/compos/apk/assets/vm_config.json
+++ b/compos/apk/assets/vm_config.json
@@ -10,6 +10,11 @@
"--log_to_stderr"
]
},
+ "extra_apks": [
+ {
+ "path": "/system/etc/security/fsverity/BuildManifest.apk"
+ }
+ ],
"apexes": [
{
"name": "com.android.art"
@@ -18,13 +23,10 @@
"name": "com.android.compos"
},
{
- "name": "{DEX2OATBOOTCLASSPATH}"
+ "name": "com.android.sdkext"
},
{
- "name": "{BOOTCLASSPATH}"
- },
- {
- "name": "{SYSTEMSERVERCLASSPATH}"
+ "name": "{CLASSPATH}"
}
]
-}
\ No newline at end of file
+}
diff --git a/compos/apk/assets/vm_config_staged.json b/compos/apk/assets/vm_config_staged.json
index 9c81e4e..1affa79 100644
--- a/compos/apk/assets/vm_config_staged.json
+++ b/compos/apk/assets/vm_config_staged.json
@@ -11,6 +11,11 @@
]
},
"prefer_staged": true,
+ "extra_apks": [
+ {
+ "path": "/system/etc/security/fsverity/BuildManifest.apk"
+ }
+ ],
"apexes": [
{
"name": "com.android.art"
@@ -19,13 +24,10 @@
"name": "com.android.compos"
},
{
- "name": "{DEX2OATBOOTCLASSPATH}"
+ "name": "com.android.sdkext"
},
{
- "name": "{BOOTCLASSPATH}"
- },
- {
- "name": "{SYSTEMSERVERCLASSPATH}"
+ "name": "{CLASSPATH}"
}
]
-}
\ No newline at end of file
+}
diff --git a/compos/apk/assets/vm_test_config.json b/compos/apk/assets/vm_test_config.json
index 22f8293..16d1037 100644
--- a/compos/apk/assets/vm_test_config.json
+++ b/compos/apk/assets/vm_test_config.json
@@ -1,27 +1,24 @@
{
- "version": 1,
- "os": {
- "name": "microdroid"
+ "version": 1,
+ "os": {
+ "name": "microdroid"
+ },
+ "task": {
+ "type": "executable",
+ "command": "/apex/com.android.compos/bin/compsvc"
+ },
+ "apexes": [
+ {
+ "name": "com.android.art"
},
- "task": {
- "type": "executable",
- "command": "/apex/com.android.compos/bin/compsvc"
+ {
+ "name": "com.android.compos"
},
- "apexes": [
- {
- "name": "com.android.art"
- },
- {
- "name": "com.android.compos"
- },
- {
- "name": "{DEX2OATBOOTCLASSPATH}"
- },
- {
- "name": "{BOOTCLASSPATH}"
- },
- {
- "name": "{SYSTEMSERVERCLASSPATH}"
- }
- ]
-}
\ No newline at end of file
+ {
+ "name": "com.android.sdkext"
+ },
+ {
+ "name": "{CLASSPATH}"
+ }
+ ]
+}
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 3bb066f..4216e1a 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -73,6 +73,7 @@
service: &dyn IVirtualizationService,
instance_image: File,
idsig: &Path,
+ idsig_manifest_apk: &Path,
parameters: &VmParameters,
) -> Result<VmInstance> {
let instance_fd = ParcelFileDescriptor::new(instance_image);
@@ -83,19 +84,12 @@
let apk_fd = File::open(apex_dir.join("app/CompOSPayloadApp/CompOSPayloadApp.apk"))
.context("Failed to open config APK file")?;
let apk_fd = ParcelFileDescriptor::new(apk_fd);
+ let idsig_fd = prepare_idsig(service, &apk_fd, idsig)?;
- if !idsig.exists() {
- // Prepare idsig file via VirtualizationService
- let idsig_file = File::create(idsig).context("Failed to create idsig file")?;
- let idsig_fd = ParcelFileDescriptor::new(idsig_file);
- service
- .createOrUpdateIdsigFile(&apk_fd, &idsig_fd)
- .context("Failed to update idsig file")?;
- }
-
- // Open idsig as read-only
- let idsig_file = File::open(idsig).context("Failed to open idsig file")?;
- let idsig_fd = ParcelFileDescriptor::new(idsig_file);
+ let manifest_apk_fd = File::open("/system/etc/security/fsverity/BuildManifest.apk")
+ .context("Failed to open build manifest APK file")?;
+ let manifest_apk_fd = ParcelFileDescriptor::new(manifest_apk_fd);
+ let idsig_manifest_apk_fd = prepare_idsig(service, &manifest_apk_fd, idsig_manifest_apk)?;
let (console_fd, log_fd, debug_level) = if parameters.debug_mode {
// Console output and the system log output from the VM are redirected to file.
@@ -117,6 +111,7 @@
instanceImage: Some(instance_fd),
configPath: config_path.to_owned(),
debugLevel: debug_level,
+ extraIdsigs: vec![idsig_manifest_apk_fd],
..Default::default()
});
@@ -165,6 +160,26 @@
}
}
+fn prepare_idsig(
+ service: &dyn IVirtualizationService,
+ apk_fd: &ParcelFileDescriptor,
+ idsig_path: &Path,
+) -> Result<ParcelFileDescriptor> {
+ if !idsig_path.exists() {
+ // Prepare idsig file via VirtualizationService
+ let idsig_file = File::create(idsig_path).context("Failed to create idsig file")?;
+ let idsig_fd = ParcelFileDescriptor::new(idsig_file);
+ service
+ .createOrUpdateIdsigFile(apk_fd, &idsig_fd)
+ .context("Failed to update idsig file")?;
+ }
+
+ // Open idsig as read-only
+ let idsig_file = File::open(idsig_path).context("Failed to open idsig file")?;
+ let idsig_fd = ParcelFileDescriptor::new(idsig_file);
+ Ok(idsig_fd)
+}
+
struct VsockFactory<'a> {
vm: &'a dyn IVirtualMachine,
}
diff --git a/compos/common/lib.rs b/compos/common/lib.rs
index 9a4d0e3..66ce8cb 100644
--- a/compos/common/lib.rs
+++ b/compos/common/lib.rs
@@ -58,6 +58,10 @@
/// The file that holds the idsig for the CompOS Payload APK.
pub const IDSIG_FILE: &str = "idsig";
+/// The file that holds the idsig for the build manifest APK (that makes enumerated files from
+/// /system available in CompOS).
+pub const IDSIG_MANIFEST_APK_FILE: &str = "idsig_manifest_apk";
+
/// The path within our config APK of our default VM configuration file, used at boot time.
pub const DEFAULT_VM_CONFIG_PATH: &str = "assets/vm_config.json";
diff --git a/compos/composd/src/instance_starter.rs b/compos/composd/src/instance_starter.rs
index 91a0e61..4fed98a 100644
--- a/compos/composd/src/instance_starter.rs
+++ b/compos/composd/src/instance_starter.rs
@@ -26,10 +26,10 @@
use compos_aidl_interface::binder::{ParcelFileDescriptor, Strong};
use compos_common::compos_client::{VmInstance, VmParameters};
use compos_common::{
- COMPOS_DATA_ROOT, IDSIG_FILE, INSTANCE_IMAGE_FILE, PRIVATE_KEY_BLOB_FILE, PUBLIC_KEY_FILE,
+ COMPOS_DATA_ROOT, IDSIG_FILE, IDSIG_MANIFEST_APK_FILE, INSTANCE_IMAGE_FILE,
+ PRIVATE_KEY_BLOB_FILE, PUBLIC_KEY_FILE,
};
use log::{info, warn};
-use std::env;
use std::fs;
use std::path::{Path, PathBuf};
@@ -52,6 +52,7 @@
instance_root: PathBuf,
instance_image: PathBuf,
idsig: PathBuf,
+ idsig_manifest_apk: PathBuf,
key_blob: PathBuf,
public_key: PathBuf,
vm_parameters: VmParameters,
@@ -63,6 +64,7 @@
let instance_root_path = instance_root.as_path();
let instance_image = instance_root_path.join(INSTANCE_IMAGE_FILE);
let idsig = instance_root_path.join(IDSIG_FILE);
+ let idsig_manifest_apk = instance_root_path.join(IDSIG_MANIFEST_APK_FILE);
let key_blob = instance_root_path.join(PRIVATE_KEY_BLOB_FILE);
let public_key = instance_root_path.join(PUBLIC_KEY_FILE);
Self {
@@ -70,6 +72,7 @@
instance_root,
instance_image,
idsig,
+ idsig_manifest_apk,
key_blob,
public_key,
vm_parameters,
@@ -111,8 +114,7 @@
// If we get this far then the instance image is valid in the current context (e.g. the
// current set of APEXes) and the key blob can be successfully decrypted by the VM. So the
// files have not been tampered with and we're good to go.
-
- Self::initialize_service(service, &key_blob)?;
+ service.initializeSigningKey(&key_blob).context("Loading signing key")?;
Ok(compos_instance)
}
@@ -127,8 +129,9 @@
let _ = fs::create_dir(&self.instance_root);
self.create_instance_image(virtualization_service)?;
- // Delete existing idsig file. Ignore error in case idsig doesn't exist.
+ // Delete existing idsig files. Ignore error in case idsig doesn't exist.
let _ = fs::remove_file(&self.idsig);
+ let _ = fs::remove_file(&self.idsig_manifest_apk);
let compos_instance = self.start_vm(virtualization_service)?;
let service = &compos_instance.service;
@@ -145,27 +148,11 @@
// Unlike when starting an existing instance, we don't need to verify the key, since we
// just generated it and have it in memory.
-
- Self::initialize_service(service, &key_data.keyBlob)?;
+ service.initializeSigningKey(&key_data.keyBlob).context("Loading signing key")?;
Ok(compos_instance)
}
- fn initialize_service(service: &Strong<dyn ICompOsService>, key_blob: &[u8]) -> Result<()> {
- // Key blob is assumed to be verified/trusted.
- service.initializeSigningKey(key_blob).context("Loading signing key")?;
-
- // TODO(198211396): Implement correctly.
- service
- .initializeClasspaths(
- &env::var("BOOTCLASSPATH")?,
- &env::var("DEX2OATBOOTCLASSPATH")?,
- &env::var("SYSTEMSERVERCLASSPATH")?,
- )
- .context("Initializing *CLASSPATH")?;
- Ok(())
- }
-
fn start_vm(
&self,
virtualization_service: &dyn IVirtualizationService,
@@ -179,6 +166,7 @@
virtualization_service,
instance_image,
&self.idsig,
+ &self.idsig_manifest_apk,
&self.vm_parameters,
)
.context("Starting VM")?;
diff --git a/compos/composd/src/odrefresh_task.rs b/compos/composd/src/odrefresh_task.rs
index 56b697e..330f0ab 100644
--- a/compos/composd/src/odrefresh_task.rs
+++ b/compos/composd/src/odrefresh_task.rs
@@ -131,12 +131,15 @@
let fd_server_raii = fd_server_config.into_fd_server()?;
let zygote_arch = system_properties::read("ro.zygote")?;
+ let system_server_compiler_filter =
+ system_properties::read("dalvik.vm.systemservercompilerfilter").unwrap_or_default();
let exit_code = service.odrefresh(
system_dir.as_raw_fd(),
output_dir.as_raw_fd(),
staging_dir.as_raw_fd(),
target_dir_name,
&zygote_arch,
+ &system_server_compiler_filter,
)?;
drop(fd_server_raii);
diff --git a/compos/service/java/com/android/server/compos/IsolatedCompilationService.java b/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
index 6918572..6ecccd2 100644
--- a/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
+++ b/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
@@ -132,9 +132,7 @@
for (String moduleName : moduleNames) {
try {
StagedApexInfo apexInfo = mPackageNative.getStagedApexInfo(moduleName);
- if (apexInfo != null && (apexInfo.hasBootClassPathJars
- || apexInfo.hasDex2OatBootClassPathJars
- || apexInfo.hasSystemServerClassPathJars)) {
+ if (apexInfo != null && apexInfo.hasClassPathJars) {
Log.i(TAG, "Classpath affecting module updated: " + moduleName);
needCompilation = true;
break;
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index 2ca4dd4..50d79c1 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -15,12 +15,15 @@
*/
use anyhow::{anyhow, bail, Context, Result};
-use log::{debug, error, info};
+use log::{debug, error, info, warn};
use minijail::{self, Minijail};
+use regex::Regex;
use std::env;
+use std::ffi::OsString;
use std::fs::{read_dir, File};
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::{self, Path, PathBuf};
+use std::process::Command;
use crate::artifact_signer::ArtifactSigner;
use crate::compos_key_service::Signer;
@@ -67,6 +70,7 @@
staging_dir_fd: i32,
target_dir_name: &'a str,
zygote_arch: &'a str,
+ system_server_compiler_filter: &'a str,
}
impl<'a> OdrefreshContext<'a> {
@@ -76,11 +80,12 @@
staging_dir_fd: i32,
target_dir_name: &'a str,
zygote_arch: &'a str,
+ system_server_compiler_filter: &'a str,
) -> Result<Self> {
if system_dir_fd < 0 || output_dir_fd < 0 || staging_dir_fd < 0 {
bail!("The remote FDs are expected to be non-negative");
}
- if zygote_arch != "zygote64" && zygote_arch != "zygote64_32" {
+ if !matches!(zygote_arch, "zygote64" | "zygote64_32") {
bail!("Invalid zygote arch");
}
// Disallow any sort of path traversal
@@ -88,7 +93,20 @@
bail!("Invalid target directory {}", target_dir_name);
}
- Ok(Self { system_dir_fd, output_dir_fd, staging_dir_fd, target_dir_name, zygote_arch })
+ // 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(Self {
+ system_dir_fd,
+ output_dir_fd,
+ staging_dir_fd,
+ target_dir_name,
+ zygote_arch,
+ system_server_compiler_filter,
+ })
}
}
@@ -129,14 +147,24 @@
let staging_dir = mountpoint.join(context.staging_dir_fd.to_string());
- let args = vec![
+ set_classpaths(&android_root)?;
+
+ let mut args = vec![
"odrefresh".to_string(),
format!("--zygote-arch={}", context.zygote_arch),
format!("--dalvik-cache={}", context.target_dir_name),
- "--no-refresh".to_string(),
format!("--staging-dir={}", staging_dir.display()),
- "--force-compile".to_string(),
+ "--no-refresh".to_string(),
];
+
+ if !context.system_server_compiler_filter.is_empty() {
+ args.push(format!(
+ "--system-server-compiler-filter={}",
+ context.system_server_compiler_filter
+ ));
+ }
+ args.push("--force-compile".to_string());
+
debug!("Running odrefresh with args: {:?}", &args);
let jail = spawn_jailed_task(odrefresh_path, &args, Vec::new() /* fd_mapping */)
.context("Spawn odrefresh")?;
@@ -164,6 +192,53 @@
Ok(exit_code)
}
+fn set_classpaths(android_root: &Path) -> Result<()> {
+ let export_lines = run_derive_classpath(android_root)?;
+ load_classpath_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(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];
+ // TODO(b/213416778) Don't modify our env, construct a fresh one for odrefresh
+ env::set_var(name, value);
+ } else {
+ warn!("Malformed line from derive_classpath: {}", line);
+ }
+ }
+
+ Ok(())
+}
+
fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
for entry in
read_dir(&target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 8f1e205..5a2c3ca 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -23,7 +23,6 @@
use compos_common::binder::to_binder_result;
use log::warn;
use std::default::Default;
-use std::env;
use std::path::PathBuf;
use std::sync::RwLock;
@@ -95,19 +94,6 @@
}
}
- fn initializeClasspaths(
- &self,
- boot_classpath: &str,
- dex2oat_boot_classpath: &str,
- system_server_classpath: &str,
- ) -> BinderResult<()> {
- // TODO(198211396): Implement correctly.
- env::set_var("BOOTCLASSPATH", boot_classpath);
- env::set_var("DEX2OATBOOTCLASSPATH", dex2oat_boot_classpath);
- env::set_var("SYSTEMSERVERCLASSPATH", system_server_classpath);
- Ok(())
- }
-
fn odrefresh(
&self,
system_dir_fd: i32,
@@ -115,6 +101,7 @@
staging_dir_fd: i32,
target_dir_name: &str,
zygote_arch: &str,
+ system_server_compiler_filter: &str,
) -> BinderResult<i8> {
let context = to_binder_result(OdrefreshContext::new(
system_dir_fd,
@@ -122,6 +109,7 @@
staging_dir_fd,
target_dir_name,
zygote_arch,
+ system_server_compiler_filter,
))?;
let authfs_service = get_authfs_service()?;
diff --git a/compos/src/pvm_exec.rs b/compos/src/pvm_exec.rs
deleted file mode 100644
index 3068c66..0000000
--- a/compos/src/pvm_exec.rs
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * 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.
- */
-
-//! pvm_exec is a proxy/wrapper command to run compilation task remotely. The most important task
-//! for this program is to run a `fd_server` that serves remote file read/write requests.
-//!
-//! It currently works as a command line wrapper to make it easy to schedule an existing dex2oat
-//! task to run in the VM.
-//!
-//! Example:
-//! $ adb shell exec 3</input/dex 4<>/output/oat ... pvm_exec --in-fd 3 --out-fd 4 -- dex2oat64 ...
-//!
-//! Note the immediate argument "dex2oat64" right after "--" is not really used. It is only for
-//! ergonomics.
-
-use anyhow::{bail, Context, Result};
-use binder::unstable_api::{new_spibinder, AIBinder};
-use binder::FromIBinder;
-use clap::{value_t, App, Arg};
-use log::{debug, error, warn};
-use minijail::Minijail;
-use nix::fcntl::{fcntl, FcntlArg::F_GETFD, OFlag};
-use nix::unistd::pipe2;
-use std::fs::File;
-use std::io::Read;
-use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
-use std::path::Path;
-use std::process::exit;
-
-use android_system_composd_internal::{
- aidl::android::system::composd::internal::ICompilationInternal::ICompilationInternal,
- binder::wait_for_interface,
-};
-use compos_aidl_interface::aidl::com::android::compos::{
- FdAnnotation::FdAnnotation, ICompOsService::ICompOsService,
-};
-use compos_aidl_interface::binder::Strong;
-use compos_common::{COMPOS_VSOCK_PORT, VMADDR_CID_ANY};
-
-const FD_SERVER_BIN: &str = "/apex/com.android.virt/bin/fd_server";
-
-fn get_composd() -> Result<Strong<dyn ICompilationInternal>> {
- wait_for_interface::<dyn ICompilationInternal>("android.system.composd.internal")
- .context("Failed to find ICompilationInternal")
-}
-
-fn get_rpc_binder(cid: u32) -> Result<Strong<dyn ICompOsService>> {
- // SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can be
- // safely taken by new_spibinder.
- let ibinder = unsafe {
- new_spibinder(
- binder_rpc_unstable_bindgen::RpcClient(cid, COMPOS_VSOCK_PORT) as *mut AIBinder
- )
- };
- if let Some(ibinder) = ibinder {
- <dyn ICompOsService>::try_from(ibinder).context("Cannot connect to RPC service")
- } else {
- bail!("Invalid raw AIBinder")
- }
-}
-
-fn spawn_fd_server(
- fd_annotation: &FdAnnotation,
- ready_file: File,
- debuggable: bool,
-) -> Result<Minijail> {
- let mut inheritable_fds = if debuggable {
- vec![1, 2] // inherit/redirect stdout/stderr for debugging
- } else {
- vec![]
- };
-
- let mut args = vec![FD_SERVER_BIN.to_string()];
- for fd in &fd_annotation.input_fds {
- args.push("--ro-fds".to_string());
- args.push(fd.to_string());
- inheritable_fds.push(*fd);
- }
- for fd in &fd_annotation.output_fds {
- args.push("--rw-fds".to_string());
- args.push(fd.to_string());
- inheritable_fds.push(*fd);
- }
- let ready_fd = ready_file.as_raw_fd();
- args.push("--ready-fd".to_string());
- args.push(ready_fd.to_string());
- inheritable_fds.push(ready_fd);
-
- let jail = Minijail::new()?;
- let _pid = jail.run(Path::new(FD_SERVER_BIN), &inheritable_fds, &args)?;
- Ok(jail)
-}
-
-fn is_fd_valid(fd: RawFd) -> Result<bool> {
- let retval = fcntl(fd, F_GETFD)?;
- Ok(retval >= 0)
-}
-
-fn parse_arg_fd(arg: &str) -> Result<RawFd> {
- let fd = arg.parse::<RawFd>()?;
- if !is_fd_valid(fd)? {
- bail!("Bad FD: {}", fd);
- }
- Ok(fd)
-}
-
-struct Config {
- args: Vec<String>,
- fd_annotation: FdAnnotation,
- cid: u32,
- debuggable: bool,
-}
-
-fn parse_args() -> Result<Config> {
- #[rustfmt::skip]
- let matches = App::new("pvm_exec")
- .arg(Arg::with_name("in-fd")
- .long("in-fd")
- .takes_value(true)
- .multiple(true)
- .use_delimiter(true))
- .arg(Arg::with_name("out-fd")
- .long("out-fd")
- .takes_value(true)
- .multiple(true)
- .use_delimiter(true))
- .arg(Arg::with_name("cid")
- .takes_value(true)
- .required(true)
- .long("cid"))
- .arg(Arg::with_name("debug")
- .long("debug"))
- .arg(Arg::with_name("args")
- .last(true)
- .required(true)
- .multiple(true))
- .get_matches();
-
- let results: Result<Vec<_>> =
- matches.values_of("in-fd").unwrap_or_default().map(parse_arg_fd).collect();
- let input_fds = results?;
-
- let results: Result<Vec<_>> =
- matches.values_of("out-fd").unwrap_or_default().map(parse_arg_fd).collect();
- let output_fds = results?;
-
- let args: Vec<_> = matches.values_of("args").unwrap().map(|s| s.to_string()).collect();
- let cid = value_t!(matches, "cid", i32)? as u32;
- let debuggable = matches.is_present("debug");
-
- Ok(Config { args, fd_annotation: FdAnnotation { input_fds, output_fds }, cid, debuggable })
-}
-
-fn create_pipe() -> Result<(File, File)> {
- let (raw_read, raw_write) = pipe2(OFlag::O_CLOEXEC)?;
- // SAFETY: We are the sole owners of these fds as they were just created.
- let read_fd = unsafe { File::from_raw_fd(raw_read) };
- let write_fd = unsafe { File::from_raw_fd(raw_write) };
- Ok((read_fd, write_fd))
-}
-
-fn wait_for_fd_server_ready(mut ready_fd: File) -> Result<()> {
- let mut buffer = [0];
- // When fd_server is ready it closes its end of the pipe. And if it exits, the pipe is also
- // closed. Either way this read will return 0 bytes at that point, and there's no point waiting
- // any longer.
- let _ = ready_fd.read(&mut buffer).context("Waiting for fd_server to be ready")?;
- debug!("fd_server is ready");
- Ok(())
-}
-
-fn try_main() -> Result<()> {
- // 1. Parse the command line arguments for collect execution data.
- let Config { args, fd_annotation, cid, debuggable } = parse_args()?;
-
- // 2. Spawn and configure a fd_server to serve remote read/write requests.
- let (ready_read_fd, ready_write_fd) = create_pipe()?;
- let fd_server_jail = spawn_fd_server(&fd_annotation, ready_write_fd, debuggable)?;
- let fd_server_lifetime = scopeguard::guard(fd_server_jail, |fd_server_jail| {
- if let Err(e) = fd_server_jail.kill() {
- if !matches!(e, minijail::Error::Killed(_)) {
- warn!("Failed to kill fd_server: {}", e);
- }
- }
- });
-
- // 3. Send the command line args to the remote to execute.
- let result = if cid == VMADDR_CID_ANY {
- // Sentinel value that indicates we should use composd
- let composd = get_composd()?;
- wait_for_fd_server_ready(ready_read_fd)?;
- composd.compile_cmd(&args, &fd_annotation)
- } else {
- // Call directly into the VM
- let compos_vm = get_rpc_binder(cid)?;
- wait_for_fd_server_ready(ready_read_fd)?;
- compos_vm.compile_cmd(&args, &fd_annotation)
- };
- let result = result.context("Binder call failed")?;
-
- // TODO: store/use the signature
- debug!(
- "Signature length: oat {}, vdex {}, image {}",
- result.oatSignature.len(),
- result.vdexSignature.len(),
- result.imageSignature.len()
- );
-
- // Be explicit about the lifetime, which should last at least until the task is finished.
- drop(fd_server_lifetime);
-
- if result.exitCode != 0 {
- error!("remote execution failed with exit code {}", result.exitCode);
- exit(result.exitCode as i32);
- }
- Ok(())
-}
-
-fn main() {
- let debuggable = env!("TARGET_BUILD_VARIANT") != "user";
- let log_level = if debuggable { log::Level::Trace } else { log::Level::Info };
- android_logger::init_once(
- android_logger::Config::default().with_tag("pvm_exec").with_min_level(log_level),
- );
-
- // Make sure we log and indicate failure if we were unable to run the command and get its exit
- // code.
- if let Err(e) = try_main() {
- error!("{:?}", e);
- std::process::exit(-1)
- }
-}
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 8906361..1e439af 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -55,9 +55,20 @@
// Files that define the "test" instance of CompOS
private static final String COMPOS_TEST_ROOT = "/data/misc/apexdata/com.android.compos/test/";
+ private static final String SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME =
+ "dalvik.vm.systemservercompilerfilter";
+ private String mBackupSystemServerCompilerFilter;
+
@Before
public void setUp() throws Exception {
testIfDeviceIsCapable(getDevice());
+
+ String value = getDevice().getProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME);
+ if (value == null) {
+ mBackupSystemServerCompilerFilter = "";
+ } else {
+ mBackupSystemServerCompilerFilter = value;
+ }
}
@After
@@ -71,10 +82,28 @@
// And any artifacts generated by odrefresh
android.tryRun("rm", "-rf", ODREFRESH_OUTPUT_DIR);
+
+ if (mBackupSystemServerCompilerFilter != null) {
+ CLog.d("Restore dalvik.vm.systemservercompilerfilter to "
+ + mBackupSystemServerCompilerFilter);
+ getDevice().setProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME,
+ mBackupSystemServerCompilerFilter);
+ }
}
@Test
- public void testOdrefresh() throws Exception {
+ public void testOdrefreshSpeed() throws Exception {
+ getDevice().setProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME, "speed");
+ testOdrefresh();
+ }
+
+ @Test
+ public void testOdrefreshSpeedProfile() throws Exception {
+ getDevice().setProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME, "speed-profile");
+ testOdrefresh();
+ }
+
+ private void testOdrefresh() throws Exception {
CommandRunner android = new CommandRunner(getDevice());
// Prepare the groundtruth. The compilation on Android should finish successfully.
@@ -126,8 +155,6 @@
private CommandResult runOdrefresh(CommandRunner android, String command) throws Exception {
return android.runForResultWithTimeout(
ODREFRESH_TIMEOUT_MS,
- // TODO(b/210472252): Remove this when the VM handles STANDALONE_SYSTEMSERVER_JARS
- "STANDALONE_SYSTEMSERVER_JARS=",
ODREFRESH_BIN,
"--dalvik-cache=" + TEST_ARTIFACTS_DIR,
command);
diff --git a/compos/verify_key/verify_key.rs b/compos/verify_key/verify_key.rs
index a028264..13d3c8b 100644
--- a/compos/verify_key/verify_key.rs
+++ b/compos/verify_key/verify_key.rs
@@ -21,8 +21,9 @@
use compos_aidl_interface::binder::ProcessState;
use compos_common::compos_client::{VmInstance, VmParameters};
use compos_common::{
- COMPOS_DATA_ROOT, CURRENT_INSTANCE_DIR, IDSIG_FILE, INSTANCE_IMAGE_FILE, PENDING_INSTANCE_DIR,
- PRIVATE_KEY_BLOB_FILE, PUBLIC_KEY_FILE, TEST_INSTANCE_DIR,
+ COMPOS_DATA_ROOT, CURRENT_INSTANCE_DIR, IDSIG_FILE, IDSIG_MANIFEST_APK_FILE,
+ INSTANCE_IMAGE_FILE, PENDING_INSTANCE_DIR, PRIVATE_KEY_BLOB_FILE, PUBLIC_KEY_FILE,
+ TEST_INSTANCE_DIR,
};
use std::fs::{self, File};
use std::io::Read;
@@ -100,6 +101,7 @@
let public_key = instance_dir.join(PUBLIC_KEY_FILE);
let instance_image = instance_dir.join(INSTANCE_IMAGE_FILE);
let idsig = instance_dir.join(IDSIG_FILE);
+ let idsig_manifest_apk = instance_dir.join(IDSIG_MANIFEST_APK_FILE);
let blob = read_small_file(blob).context("Failed to read key blob")?;
let public_key = read_small_file(public_key).context("Failed to read public key")?;
@@ -110,6 +112,7 @@
&*virtualization_service,
instance_image,
&idsig,
+ &idsig_manifest_apk,
&VmParameters { debug_mode, ..Default::default() },
)?;
let service = vm_instance.get_service()?;
diff --git a/demo/README.md b/demo/README.md
index 37198ad..43d2ebc 100644
--- a/demo/README.md
+++ b/demo/README.md
@@ -9,10 +9,13 @@
## Installing
```
-adb install out/dist/MicrodroidDemoApp.apk
+adb install -t out/dist/MicrodroidDemoApp.apk
adb shell pm grant com.android.microdroid.demo android.permission.MANAGE_VIRTUAL_MACHINE
```
+Don't run the app before granting the permission. Or you will have to uninstall
+the app, and then re-install it.
+
## Running
Run the app by touching the icon on the launcher. Press the `run` button to
diff --git a/microdroid/build.prop b/microdroid/build.prop
index 4ae50c0..8cbabff 100644
--- a/microdroid/build.prop
+++ b/microdroid/build.prop
@@ -4,8 +4,10 @@
service.adb.listen_addrs=vsock:5555
# TODO(b/189164487): support build related properties
-ro.build.version.release=11
-ro.build.version.security_patch=2021-07-05
+ro.build.version.codename=Tiramisu
+ro.build.version.release=12
+ro.build.version.sdk=32
+ro.build.version.security_patch=2021-12-05
# Payload metadata partition
apexd.payload_metadata.path=/dev/block/by-name/payload-metadata
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 277432b..c3ab39a 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -36,6 +36,7 @@
"libmicrodroid_metadata",
"libmicrodroid_payload_config",
"libonce_cell",
+ "libregex",
"librustutils",
"libselinux_bindgen",
"libserde",
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index 8f7a69a..a8c22cd 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -22,16 +22,18 @@
use android_system_virtualizationservice::binder::ParcelFileDescriptor;
use anyhow::{anyhow, bail, Context, Result};
use binder::wait_for_interface;
-use log::{error, info};
+use log::{info, warn};
use microdroid_metadata::{ApexPayload, ApkPayload, Metadata};
use microdroid_payload_config::{ApexConfig, VmPayloadConfig};
use once_cell::sync::OnceCell;
use packagemanager_aidl::aidl::android::content::pm::IPackageManagerNative::IPackageManagerNative;
+use regex::Regex;
use serde::Deserialize;
use serde_xml_rs::from_reader;
-use std::env;
+use std::collections::HashSet;
use std::fs::{File, OpenOptions};
use std::path::{Path, PathBuf};
+use std::process::Command;
use vmconfig::open_parcel_file;
/// The list of APEXes which microdroid requires.
@@ -58,11 +60,7 @@
path: PathBuf,
#[serde(default)]
- boot_classpath: bool,
- #[serde(default)]
- systemserver_classpath: bool,
- #[serde(default)]
- dex2oatboot_classpath: bool,
+ has_classpath_jar: bool,
}
impl ApexInfoList {
@@ -75,19 +73,16 @@
let mut apex_info_list: ApexInfoList = from_reader(apex_info_list)
.context(format!("Failed to parse {}", APEX_INFO_LIST_PATH))?;
- // For active APEXes, we refer env variables to see if it contributes to classpath
- let boot_classpath_apexes = find_apex_names_in_classpath_env("BOOTCLASSPATH");
- let systemserver_classpath_apexes =
- find_apex_names_in_classpath_env("SYSTEMSERVERCLASSPATH");
- let dex2oatboot_classpath_apexes =
- find_apex_names_in_classpath_env("DEX2OATBOOTCLASSPATH");
+ // For active APEXes, we run derive_classpath and parse its output to see if it
+ // contributes to the classpath(s). (This allows us to handle any new classpath env
+ // vars seamlessly.)
+ let classpath_vars = run_derive_classpath()?;
+ let classpath_apexes = find_apex_names_in_classpath(&classpath_vars)?;
+
for apex_info in apex_info_list.list.iter_mut() {
- apex_info.boot_classpath = boot_classpath_apexes.contains(&apex_info.name);
- apex_info.systemserver_classpath =
- systemserver_classpath_apexes.contains(&apex_info.name);
- apex_info.dex2oatboot_classpath =
- dex2oatboot_classpath_apexes.contains(&apex_info.name);
+ apex_info.has_classpath_jar = classpath_apexes.contains(&apex_info.name);
}
+
Ok(apex_info_list)
})
}
@@ -133,11 +128,7 @@
let staged_apex_info = pm.getStagedApexInfo(&apex_info.name)?;
if let Some(staged_apex_info) = staged_apex_info {
apex_info.path = PathBuf::from(staged_apex_info.diskImagePath);
- apex_info.boot_classpath = staged_apex_info.hasBootClassPathJars;
- apex_info.systemserver_classpath =
- staged_apex_info.hasSystemServerClassPathJars;
- apex_info.dex2oatboot_classpath =
- staged_apex_info.hasDex2OatBootClassPathJars;
+ apex_info.has_classpath_jar = staged_apex_info.hasClassPathJars;
}
}
}
@@ -269,22 +260,41 @@
Ok(DiskImage { image: None, partitions, writable: false })
}
-fn find_apex_names_in_classpath_env(classpath_env_var: &str) -> Vec<String> {
- let val = env::var(classpath_env_var).unwrap_or_else(|e| {
- error!("Reading {} failed: {}", classpath_env_var, e);
- String::from("")
- });
- val.split(':')
- .filter_map(|path| {
- Path::new(path)
- .strip_prefix("/apex/")
- .map(|stripped| {
- let first = stripped.iter().next().unwrap();
- first.to_str().unwrap().to_string()
- })
- .ok()
- })
- .collect()
+fn run_derive_classpath() -> Result<String> {
+ let result = Command::new("/apex/com.android.sdkext/bin/derive_classpath")
+ .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 find_apex_names_in_classpath(classpath_vars: &str) -> Result<HashSet<String>> {
+ // Each line should be in the format "export <var name> <paths>", where <paths> is a
+ // colon-separated list of paths to JARs. We don't care about the var names, and we're only
+ // interested in paths that look like "/apex/<apex name>/<anything>" so we know which APEXes
+ // contribute to at least one var.
+ let mut apexes = HashSet::new();
+
+ let pattern = Regex::new(r"^export [^ ]+ ([^ ]+)$").context("Failed to construct Regex")?;
+ for line in classpath_vars.lines() {
+ if let Some(captures) = pattern.captures(line) {
+ if let Some(paths) = captures.get(1) {
+ apexes.extend(paths.as_str().split(':').filter_map(|path| {
+ let path = path.strip_prefix("/apex/")?;
+ Some(path[..path.find('/')?].to_owned())
+ }));
+ continue;
+ }
+ }
+ warn!("Malformed line from derive_classpath: {}", line);
+ }
+
+ Ok(apexes)
}
// Collect APEX names from config
@@ -293,17 +303,13 @@
apexes: &[ApexConfig],
debug_level: DebugLevel,
) -> Vec<String> {
- // Process pseudo names like "{BOOTCLASSPATH}".
+ // Process pseudo names like "{CLASSPATH}".
// For now we have following pseudo APEX names:
- // - {BOOTCLASSPATH}: represents APEXes contributing "BOOTCLASSPATH" environment variable
- // - {DEX2OATBOOTCLASSPATH}: represents APEXes contributing "DEX2OATBOOTCLASSPATH" environment variable
- // - {SYSTEMSERVERCLASSPATH}: represents APEXes contributing "SYSTEMSERVERCLASSPATH" environment variable
+ // - {CLASSPATH}: represents APEXes contributing to any derive_classpath environment variable
let mut apex_names: Vec<String> = apexes
.iter()
.flat_map(|apex| match apex.name.as_str() {
- "{BOOTCLASSPATH}" => apex_list.get_matching(|apex| apex.boot_classpath),
- "{DEX2OATBOOTCLASSPATH}" => apex_list.get_matching(|apex| apex.dex2oatboot_classpath),
- "{SYSTEMSERVERCLASSPATH}" => apex_list.get_matching(|apex| apex.systemserver_classpath),
+ "{CLASSPATH}" => apex_list.get_matching(|apex| apex.has_classpath_jar),
_ => vec![apex.name.clone()],
})
.collect();
@@ -369,13 +375,16 @@
mod tests {
use super::*;
#[test]
- fn test_find_apex_names_in_classpath_env() {
- let key = "TEST_BOOTCLASSPATH";
- let classpath = "/apex/com.android.foo/javalib/foo.jar:/system/framework/framework.jar:/apex/com.android.bar/javalib/bar.jar";
- env::set_var(key, classpath);
- assert_eq!(
- find_apex_names_in_classpath_env(key),
- vec!["com.android.foo".to_owned(), "com.android.bar".to_owned()]
- );
+ fn test_find_apex_names_in_classpath() {
+ let vars = r#"
+export FOO /apex/unterminated
+export BAR /apex/valid.apex/something
+wrong
+export EMPTY
+export OTHER /foo/bar:/baz:/apex/second.valid.apex/:gibberish:"#;
+ let expected = vec!["valid.apex", "second.valid.apex"];
+ let expected: HashSet<_> = expected.into_iter().map(ToString::to_string).collect();
+
+ assert_eq!(find_apex_names_in_classpath(vars).unwrap(), expected);
}
}