Merge "Enable adb and adb root with debug level or debug policy"
diff --git a/compos/common/Android.bp b/compos/common/Android.bp
index 35947d7..05bc093 100644
--- a/compos/common/Android.bp
+++ b/compos/common/Android.bp
@@ -12,6 +12,7 @@
"compos_aidl_interface-rust",
"libanyhow",
"libbinder_rs",
+ "libglob",
"liblazy_static",
"liblog_rust",
"libnested_virt",
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 92c9a3c..96c8147 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -27,12 +27,13 @@
VirtualMachineAppConfig::{DebugLevel::DebugLevel, Payload::Payload, VirtualMachineAppConfig},
VirtualMachineConfig::VirtualMachineConfig,
};
-use anyhow::{bail, Context, Result};
+use anyhow::{anyhow, bail, Context, Result};
use binder::{ParcelFileDescriptor, Strong};
use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
+use glob::glob;
use log::{info, warn};
use rustutils::system_properties;
-use std::fs::{self, File};
+use std::fs::File;
use std::path::{Path, PathBuf};
use vmclient::{DeathReason, ErrorCode, VmInstance, VmWaitError};
@@ -194,15 +195,19 @@
// Our config APK will be in a directory under app, but the name of the directory is at the
// discretion of the build system. So just look in each sub-directory until we find it.
// (In practice there will be exactly one directory, so this shouldn't take long.)
- let app_dir = apex_dir.join("app");
- for dir in fs::read_dir(app_dir).context("Reading app dir")? {
- let apk_file = dir?.path().join("CompOSPayloadApp.apk");
- if apk_file.is_file() {
- return Ok(apk_file);
- }
+ let app_glob = apex_dir.join("app").join("**").join("CompOSPayloadApp*.apk");
+ let mut entries: Vec<PathBuf> =
+ glob(app_glob.to_str().ok_or_else(|| anyhow!("Invalid path: {}", app_glob.display()))?)
+ .context("failed to glob")?
+ .filter_map(|e| e.ok())
+ .collect();
+ if entries.len() > 1 {
+ bail!("Found more than one apk matching {}", app_glob.display());
}
-
- bail!("Failed to locate CompOSPayloadApp.apk")
+ match entries.pop() {
+ Some(path) => Ok(path),
+ None => Err(anyhow!("No apks match {}", app_glob.display())),
+ }
}
fn prepare_idsig(
diff --git a/docs/debug/tracing.md b/docs/debug/tracing.md
new file mode 100644
index 0000000..7d7ea0c
--- /dev/null
+++ b/docs/debug/tracing.md
@@ -0,0 +1,75 @@
+# Hypervisor & guest tracing
+
+## Hypervisor tracing
+
+Starting with android14-5.15 kernel it is possible to get traces from the hypervisor.
+
+### User space interface
+
+The user space hypervisor tracing interface is located either at /sys/kernel/tracing/hyp or at
+/sys/kernel/debug/tracing/hyp. On the Android phones it will usually be /sys/kernel/tracing/hyp,
+while on QEMU it will be /sys/kernel/debug/tracing/hyp.
+
+The user space interface is very similar to the ftrace user space interface, however there are some
+differences, e.g.:
+
+* Only boot clock is supported, and there is no way for user space to change the tracing_clock.
+* Hypervisor tracing periodically polls the data from the hypervisor, this is different from the
+ regular ftrace instance which pushes the events into the ring buffer.
+
+Note: the list above is not exhaustive.
+
+TODO(b/271412868): add more documentation on the user space interface.
+
+### Perfetto integration
+
+[Perfetto](https://perfetto.dev/docs/) is an open-source stack for performance instrumentation and
+trace analysis widely used in Android. Perfetto supports capturing and visualizing hypervisor
+traces.
+
+#### Capturing hypervisor traces on Android
+
+Consider first familiarizing yourself with Perfetto documentation for recording traces on Android:
+https://perfetto.dev/docs/quickstart/android-tracing.
+
+So far it is only possible to capture hypervisor trace events by providing the full trace config
+file to Perfetto. Here is the minimal
+
+```shell
+cat<<EOF>config.pbtx
+duration_ms: 10000
+
+buffers: {
+ size_kb: 8960
+ fill_policy: DISCARD
+}
+
+data_sources: {
+ config {
+ name: "linux.ftrace"
+ ftrace_config {
+ instance_name: "hyp"
+ ftrace_events: "hyp/hyp_enter"
+ ftrace_events: "hyp/hyp_exit"
+ }
+ }
+}
+EOF
+
+./record_android_trace -c config.pbtx -o trace_file.perfetto-trace
+```
+
+If you have an Android tree checked out, then record_android_trace helper script can be located at
+${REPO_ROOT}/external/perfetto/tools/record_android_traces. Otherwise, you can download the script
+by following steps outlined in the [Perfetto docs](
+https://perfetto.dev/docs/quickstart/android-tracing#recording-a-trace-through-the-cmdline)
+
+#### Capturing hypervisor traces on QEMU
+
+TODO(b/271412868): fill in this section
+
+TODO(b/271412868): Stay tuned, more docs coming soon!
+
+## Microdroid VM tracing
+
+TODO(b/271412868): Stay tuned, more docs are coming soon!
diff --git a/javalib/jni/android_system_virtualmachine_VirtualizationService.cpp b/javalib/jni/android_system_virtualmachine_VirtualizationService.cpp
index bd80880..fbd1fd5 100644
--- a/javalib/jni/android_system_virtualmachine_VirtualizationService.cpp
+++ b/javalib/jni/android_system_virtualmachine_VirtualizationService.cpp
@@ -29,7 +29,7 @@
using namespace android::base;
static constexpr const char VIRTMGR_PATH[] = "/apex/com.android.virt/bin/virtmgr";
-static constexpr size_t VIRTMGR_THREADS = 16;
+static constexpr size_t VIRTMGR_THREADS = 2;
extern "C" JNIEXPORT jint JNICALL
Java_android_system_virtualmachine_VirtualizationService_nativeSpawn(
@@ -83,7 +83,6 @@
ARpcSession_setFileDescriptorTransportMode(session.get(),
ARpcSession_FileDescriptorTransportMode::Unix);
ARpcSession_setMaxIncomingThreads(session.get(), VIRTMGR_THREADS);
- ARpcSession_setMaxOutgoingThreads(session.get(), VIRTMGR_THREADS);
// SAFETY - ARpcSession_setupUnixDomainBootstrapClient does not take ownership of clientFd.
auto client = ARpcSession_setupUnixDomainBootstrapClient(session.get(), clientFd);
return AIBinder_toJavaBinder(env, client);
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 668d7dc..93e65db 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -31,11 +31,13 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.sysprop.HypervisorProperties;
import android.system.virtualizationservice.VirtualMachineAppConfig;
import android.system.virtualizationservice.VirtualMachinePayloadConfig;
+import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
@@ -47,6 +49,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
+import java.util.zip.ZipFile;
/**
* Represents a configuration of a virtual machine. A configuration consists of hardware
@@ -57,6 +60,7 @@
*/
@SystemApi
public final class VirtualMachineConfig {
+ private static final String TAG = "VirtualMachineConfig";
private static final String[] EMPTY_STRING_ARRAY = {};
// These define the schema of the config file persisted on disk.
@@ -296,8 +300,7 @@
/**
* Returns the absolute path of the APK which should contain the binary payload that will
- * execute within the VM. Returns null if no specific path has been set, so the primary APK will
- * be used.
+ * execute within the VM. Returns null if no specific path has been set.
*
* @hide
*/
@@ -445,18 +448,7 @@
throws VirtualMachineException {
VirtualMachineAppConfig vsConfig = new VirtualMachineAppConfig();
- String apkPath = mApkPath;
- if (apkPath == null) {
- try {
- ApplicationInfo appInfo =
- packageManager.getApplicationInfo(
- mPackageName, PackageManager.ApplicationInfoFlags.of(0));
- // This really is the path to the APK, not a directory.
- apkPath = appInfo.sourceDir;
- } catch (PackageManager.NameNotFoundException e) {
- throw new VirtualMachineException("Package not found", e);
- }
- }
+ String apkPath = (mApkPath != null) ? mApkPath : findPayloadApk(packageManager);
try {
vsConfig.apk = ParcelFileDescriptor.open(new File(apkPath), MODE_READ_ONLY);
@@ -495,6 +487,45 @@
return vsConfig;
}
+ private String findPayloadApk(PackageManager packageManager) throws VirtualMachineException {
+ ApplicationInfo appInfo;
+ try {
+ appInfo =
+ packageManager.getApplicationInfo(
+ mPackageName, PackageManager.ApplicationInfoFlags.of(0));
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new VirtualMachineException("Package not found", e);
+ }
+
+ String[] splitApkPaths = appInfo.splitSourceDirs;
+ String[] abis = Build.SUPPORTED_64_BIT_ABIS;
+
+ // If there are split APKs, and we know the payload binary name, see if we can find a
+ // split APK containing the binary.
+ if (mPayloadBinaryName != null && splitApkPaths != null && abis.length != 0) {
+ String[] libraryNames = new String[abis.length];
+ for (int i = 0; i < abis.length; i++) {
+ libraryNames[i] = "lib/" + abis[i] + "/" + mPayloadBinaryName;
+ }
+
+ for (String path : splitApkPaths) {
+ try (ZipFile zip = new ZipFile(path)) {
+ for (String name : libraryNames) {
+ if (zip.getEntry(name) != null) {
+ Log.i(TAG, "Found payload in " + path);
+ return path;
+ }
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to scan split APK: " + path, e);
+ }
+ }
+ }
+
+ // This really is the path to the APK, not a directory.
+ return appInfo.sourceDir;
+ }
+
private int bytesToMebiBytes(long mMemoryBytes) {
long oneMebi = 1024 * 1024;
// We can't express requests for more than 2 exabytes, but then they're not going to succeed
@@ -596,7 +627,8 @@
/**
* Sets the absolute path of the APK containing the binary payload that will execute within
- * the VM. If not set explicitly, defaults to the primary APK of the context.
+ * the VM. If not set explicitly, defaults to the split APK containing the payload, if there
+ * is one, and otherwise the primary APK of the context.
*
* @hide
*/
diff --git a/libs/apkverify/src/algorithms.rs b/libs/apkverify/src/algorithms.rs
index 442b47c..c05ab38 100644
--- a/libs/apkverify/src/algorithms.rs
+++ b/libs/apkverify/src/algorithms.rs
@@ -204,9 +204,10 @@
}
/// Hash algorithms.
-#[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive, Default)]
#[repr(u32)]
pub enum HashAlgorithm {
+ #[default]
/// SHA-256
SHA256 = 1,
}
@@ -217,9 +218,3 @@
Self::from_u32(val).context(format!("Unsupported hash algorithm: {}", val))
}
}
-
-impl Default for HashAlgorithm {
- fn default() -> Self {
- HashAlgorithm::SHA256
- }
-}
diff --git a/libs/apkverify/src/v4.rs b/libs/apkverify/src/v4.rs
index 94abf99..e77ad77 100644
--- a/libs/apkverify/src/v4.rs
+++ b/libs/apkverify/src/v4.rs
@@ -104,9 +104,10 @@
}
/// Version of the idsig file format
-#[derive(Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
+#[derive(Debug, PartialEq, Eq, FromPrimitive, ToPrimitive, Default)]
#[repr(u32)]
pub enum Version {
+ #[default]
/// Version 2, the only supported version.
V2 = 2,
}
@@ -117,12 +118,6 @@
}
}
-impl Default for Version {
- fn default() -> Self {
- Version::V2
- }
-}
-
impl V4Signature<fs::File> {
/// Creates a `V4Signature` struct from the given idsig path.
pub fn from_idsig_path<P: AsRef<Path>>(idsig_path: P) -> Result<Self> {
diff --git a/microdroid/payload/config/src/lib.rs b/microdroid/payload/config/src/lib.rs
index 925a543..cdef3e4 100644
--- a/microdroid/payload/config/src/lib.rs
+++ b/microdroid/payload/config/src/lib.rs
@@ -64,10 +64,11 @@
/// Payload's task can be one of plain executable
/// or an .so library which can be started via /system/bin/microdroid_launcher
-#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)]
pub enum TaskType {
/// Task's command indicates the path to the executable binary.
#[serde(rename = "executable")]
+ #[default]
Executable,
/// Task's command indicates the .so library in /mnt/apk/lib/{arch}
#[serde(rename = "microdroid_launcher")]
@@ -87,12 +88,6 @@
pub command: String,
}
-impl Default for TaskType {
- fn default() -> TaskType {
- TaskType::Executable
- }
-}
-
/// APEX config
/// For now, we only pass the name of APEX.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
diff --git a/pvmfw/README.md b/pvmfw/README.md
index 1e4b605..c652f01 100644
--- a/pvmfw/README.md
+++ b/pvmfw/README.md
@@ -61,13 +61,27 @@
Starting in Android T, the `PRODUCT_BUILD_PVMFW_IMAGE` build variable controls
the generation of `pvmfw.img`, a new [ABL partition][ABL-part] containing the
-pvmfw binary and following the internal format of the [`boot`][boot-img]
-partition, intended to be verified and loaded by ABL on AVF-compatible devices.
+pvmfw binary (sometimes called "`pvmfw.bin`") and following the internal format
+of the [`boot`][boot-img] partition, intended to be verified and loaded by ABL
+on AVF-compatible devices.
+
+Once ABL has verified the `pvmfw.img` chained static partition, the contained
+[`boot.img` header][boot-img] may be used to obtain the size of the `pvmfw.bin`
+image (recorded in the `kernel_size` field), as it already does for the kernel
+itself. In accordance with the header format, the `kernel_size` bytes of the
+partition following the header will be the `pvmfw.bin` image.
+
+Note that when it gets executed in the context of a pVM, `pvmfw` expects to have
+been loaded at 4KiB-aligned intermediate physical address (IPA) so if ABL loads
+the `pvmfw.bin` image without respecting this alignment, it is the
+responsibility of the hypervisor to either reject the image or copy it into
+guest address space with the right alignment.
To support pKVM, ABL is expected to describe the region using a reserved memory
device tree node where both address and size have been properly aligned to the
-page size used by the hypervisor. For example, the following node describes a
-region of size `0x40000` at address `0x80000000`:
+page size used by the hypervisor. This single region must include both the pvmfw
+binary image and its configuration data (see below). For example, the following
+node describes a region of size `0x40000` at address `0x80000000`:
```
reserved-memory {
...
diff --git a/pvmfw/src/rand.rs b/pvmfw/src/rand.rs
index a53cac6..bf0edd5 100644
--- a/pvmfw/src/rand.rs
+++ b/pvmfw/src/rand.rs
@@ -52,12 +52,11 @@
fn fill_with_entropy(s: &mut [u8]) -> Result<()> {
const MAX_BYTES_PER_CALL: usize = size_of::<hvc::TrngRng64Entropy>();
- let bits = usize::try_from(u8::BITS).unwrap();
let (aligned, remainder) = s.split_at_mut(s.len() - s.len() % MAX_BYTES_PER_CALL);
for chunk in aligned.chunks_exact_mut(MAX_BYTES_PER_CALL) {
- let (r, s, t) = hvc::trng_rnd64((chunk.len() * bits).try_into().unwrap())?;
+ let (r, s, t) = repeat_trng_rnd(chunk.len())?;
let mut words = chunk.chunks_exact_mut(size_of::<u64>());
words.next().unwrap().clone_from_slice(&t.to_ne_bytes());
@@ -67,7 +66,7 @@
if !remainder.is_empty() {
let mut entropy = [0; MAX_BYTES_PER_CALL];
- let (r, s, t) = hvc::trng_rnd64((remainder.len() * bits).try_into().unwrap())?;
+ let (r, s, t) = repeat_trng_rnd(remainder.len())?;
let mut words = entropy.chunks_exact_mut(size_of::<u64>());
words.next().unwrap().clone_from_slice(&t.to_ne_bytes());
@@ -80,6 +79,17 @@
Ok(())
}
+fn repeat_trng_rnd(n_bytes: usize) -> hvc::trng::Result<hvc::TrngRng64Entropy> {
+ let bits = usize::try_from(u8::BITS).unwrap();
+ let n_bits = (n_bytes * bits).try_into().unwrap();
+ loop {
+ match hvc::trng_rnd64(n_bits) {
+ Err(hvc::trng::Error::NoEntropy) => continue,
+ res => return res,
+ }
+ }
+}
+
pub fn random_array<const N: usize>() -> Result<[u8; N]> {
let mut arr = [0; N];
fill_with_entropy(&mut arr)?;
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index f84be8b..6e4694c 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -1899,6 +1899,7 @@
private static final int MS_NOEXEC = 8;
@Test
+ @CddTest(requirements = {"9.17/C-1-5"})
public void dataIsMountedWithNoExec() throws Exception {
assumeSupportedDevice();
@@ -1923,6 +1924,33 @@
.isEqualTo(MS_NOEXEC);
}
+ @Test
+ @CddTest(requirements = {"9.17/C-1-5"})
+ public void encryptedStoreIsMountedWithNoExec() throws Exception {
+ assumeSupportedDevice();
+
+ VirtualMachineConfig vmConfig =
+ newVmConfigBuilder()
+ .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setEncryptedStorageBytes(4_000_000)
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_encstore_no_exec", vmConfig);
+
+ TestResults testResults =
+ runVmTestService(
+ TAG,
+ vm,
+ (ts, tr) -> {
+ tr.mMountFlags = ts.getMountFlags("/mnt/encryptedstore");
+ });
+
+ assertThat(testResults.mException).isNull();
+ assertWithMessage("/mnt/encryptedstore should be mounted with MS_NOEXEC")
+ .that(testResults.mMountFlags & MS_NOEXEC)
+ .isEqualTo(MS_NOEXEC);
+ }
+
private static class VmShareServiceConnection implements ServiceConnection {
private final CountDownLatch mLatch = new CountDownLatch(1);
diff --git a/virtualizationmanager/src/debug_config.rs b/virtualizationmanager/src/debug_config.rs
index bca8f75..666c98d 100644
--- a/virtualizationmanager/src/debug_config.rs
+++ b/virtualizationmanager/src/debug_config.rs
@@ -47,19 +47,13 @@
pub fn is_ramdump_needed(config: &VirtualMachineConfig) -> bool {
let enabled_in_dp =
get_debug_policy_bool("/proc/device-tree/avf/guest/common/ramdump").unwrap_or_default();
- let (protected, debuggable) = match config {
- VirtualMachineConfig::RawConfig(config) => {
+ let debuggable = match config {
+ VirtualMachineConfig::RawConfig(_) => {
// custom VMs are considered debuggable for flexibility
- (config.protectedVm, true)
+ true
}
- VirtualMachineConfig::AppConfig(config) => {
- (config.protectedVm, config.debugLevel == DebugLevel::FULL)
- }
+ VirtualMachineConfig::AppConfig(config) => config.debugLevel == DebugLevel::FULL,
};
- if protected {
- enabled_in_dp
- } else {
- enabled_in_dp || debuggable
- }
+ enabled_in_dp || debuggable
}
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 5d785de..36edc64 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -152,7 +152,7 @@
}
fn find_empty_payload_apk_path() -> Result<PathBuf, Error> {
- const GLOB_PATTERN: &str = "/apex/com.android.virt/app/**/EmptyPayloadApp.apk";
+ const GLOB_PATTERN: &str = "/apex/com.android.virt/app/**/EmptyPayloadApp*.apk";
let mut entries: Vec<PathBuf> =
glob(GLOB_PATTERN).context("failed to glob")?.filter_map(|e| e.ok()).collect();
if entries.len() > 1 {
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index 0e3d140..d67d87e 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -57,7 +57,7 @@
"android.system.virtualizationservice";
const VIRTMGR_PATH: &str = "/apex/com.android.virt/bin/virtmgr";
-const VIRTMGR_THREADS: usize = 16;
+const VIRTMGR_THREADS: usize = 2;
fn posix_pipe() -> Result<(OwnedFd, OwnedFd), io::Error> {
use nix::fcntl::OFlag;
@@ -122,7 +122,6 @@
let session = RpcSession::new();
session.set_file_descriptor_transport_mode(FileDescriptorTransportMode::Unix);
session.set_max_incoming_threads(VIRTMGR_THREADS);
- session.set_max_outgoing_threads(VIRTMGR_THREADS);
session
.setup_unix_domain_bootstrap_client(self.client_fd.as_fd())
.map_err(|_| io::Error::from(io::ErrorKind::ConnectionRefused))