Merge "Entry point for Rust pvmfw."
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 80d0807..f40da7e 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -21,7 +21,7 @@
],
"postsubmit": [
{
- "name": "odsign_e2e_tests"
+ "name": "odsign_e2e_tests_full"
}
],
"imports": [
diff --git a/apkdmverity/TEST_MAPPING b/apkdmverity/TEST_MAPPING
index 1bbec76..997b3f9 100644
--- a/apkdmverity/TEST_MAPPING
+++ b/apkdmverity/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "presubmit" : [
+ "postsubmit" : [
{
"name" : "apkdmverity.test"
}
diff --git a/authfs/src/file/remote_file.rs b/authfs/src/file/remote_file.rs
index 039285f..4c112bd 100644
--- a/authfs/src/file/remote_file.rs
+++ b/authfs/src/file/remote_file.rs
@@ -40,7 +40,6 @@
}
pub struct RemoteFileReader {
- // This needs to have Sync trait to be used in fuse::worker::start_message_loop.
service: VirtFdService,
file_fd: i32,
}
@@ -81,7 +80,6 @@
}
pub struct RemoteMerkleTreeReader {
- // This needs to be a Sync to be used in fuse::worker::start_message_loop.
service: VirtFdService,
file_fd: i32,
}
@@ -108,7 +106,6 @@
}
pub struct RemoteFileEditor {
- // This needs to have Sync trait to be used in fuse::worker::start_message_loop.
service: VirtFdService,
file_fd: i32,
}
diff --git a/authfs/src/fusefs.rs b/authfs/src/fusefs.rs
index 511db68..beb6b30 100644
--- a/authfs/src/fusefs.rs
+++ b/authfs/src/fusefs.rs
@@ -184,10 +184,9 @@
type DirHandleTable = BTreeMap<Handle, Arc<DirEntriesSnapshot>>;
-// AuthFS needs to be `Sync` to be accepted by fuse::worker::start_message_loop as a `FileSystem`.
+// AuthFS needs to be `Sync` to be used with the `fuse` crate.
pub struct AuthFs {
- /// Table for `Inode` to `InodeState` lookup. This needs to be `Sync` to be used in
- /// `fuse::worker::start_message_loop`.
+ /// Table for `Inode` to `InodeState` lookup.
inode_table: RwLock<BTreeMap<Inode, InodeState>>,
/// The next available inode number.
diff --git a/authfs/src/fusefs/mount.rs b/authfs/src/fusefs/mount.rs
index 294c6b1..7f8bac1 100644
--- a/authfs/src/fusefs/mount.rs
+++ b/authfs/src/fusefs/mount.rs
@@ -16,16 +16,18 @@
use fuse::mount::MountOption;
use std::fs::OpenOptions;
+use std::num::NonZeroU8;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use super::AuthFs;
-/// Maximum bytes in the write transaction to the FUSE device. This limits the maximum buffer
-/// size in a read request (including FUSE protocol overhead) that the filesystem writes to.
+/// Maximum bytes (excluding the FUSE header) `AuthFs` will receive from the kernel for write
+/// operations by another process.
pub const MAX_WRITE_BYTES: u32 = 65536;
-/// Maximum bytes in a read operation.
+/// Maximum bytes (excluding the FUSE header) `AuthFs` will receive from the kernel for read
+/// operations by another process.
/// TODO(victorhsieh): This option is deprecated by FUSE. Figure out if we can remove this.
const MAX_READ_BYTES: u32 = 65536;
@@ -34,6 +36,7 @@
authfs: AuthFs,
mountpoint: &Path,
extra_options: &Option<String>,
+ threads: Option<NonZeroU8>,
) -> Result<(), fuse::Error> {
let dev_fuse = OpenOptions::new()
.read(true)
@@ -61,5 +64,10 @@
)
.expect("Failed to mount fuse");
- fuse::worker::start_message_loop(dev_fuse, MAX_WRITE_BYTES, MAX_READ_BYTES, authfs)
+ let mut config = fuse::FuseConfig::new();
+ config.dev_fuse(dev_fuse).max_write(MAX_WRITE_BYTES).max_read(MAX_READ_BYTES);
+ if let Some(num) = threads {
+ config.num_threads(u8::from(num).into());
+ }
+ config.enter_message_loop(authfs)
}
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index bdca5b4..60318e8 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -37,6 +37,7 @@
use protobuf::Message;
use std::convert::TryInto;
use std::fs::File;
+use std::num::NonZeroU8;
use std::path::{Path, PathBuf};
use structopt::StructOpt;
@@ -67,6 +68,10 @@
#[structopt(short = "o")]
extra_options: Option<String>,
+ /// Number of threads to serve FUSE requests.
+ #[structopt(short = "j")]
+ thread_number: Option<NonZeroU8>,
+
/// A read-only remote file with integrity check. Can be multiple.
///
/// For example, `--remote-ro-file 5:sha256-1234abcd` tells the filesystem to associate the
@@ -312,7 +317,12 @@
let mut authfs = AuthFs::new(RemoteFsStatsReader::new(service.clone()));
prepare_root_dir_entries(service, &mut authfs, &args)?;
- fusefs::mount_and_enter_message_loop(authfs, &args.mount_point, &args.extra_options)?;
+ fusefs::mount_and_enter_message_loop(
+ authfs,
+ &args.mount_point,
+ &args.extra_options,
+ args.thread_number,
+ )?;
bail!("Unexpected exit after the handler loop")
}
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 9787434..16dc2cf 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -38,12 +38,12 @@
use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
use log::{info, warn};
use rustutils::system_properties;
-use std::fs::File;
+use std::fs::{self, File};
use std::io::{BufRead, BufReader};
use std::num::NonZeroU32;
use std::os::raw;
use std::os::unix::io::IntoRawFd;
-use std::path::Path;
+use std::path::{Path, PathBuf};
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
@@ -95,8 +95,8 @@
let apex_dir = Path::new(COMPOS_APEX_ROOT);
let data_dir = Path::new(COMPOS_DATA_ROOT);
- let apk_fd = File::open(apex_dir.join("app/CompOSPayloadApp/CompOSPayloadApp.apk"))
- .context("Failed to open config APK file")?;
+ let config_apk = Self::locate_config_apk(apex_dir)?;
+ let apk_fd = File::open(config_apk).context("Failed to open config APK file")?;
let apk_fd = ParcelFileDescriptor::new(apk_fd);
let idsig_fd = prepare_idsig(service, &apk_fd, idsig)?;
@@ -166,6 +166,21 @@
Ok(VmInstance { vm, cid })
}
+ fn locate_config_apk(apex_dir: &Path) -> Result<PathBuf> {
+ // 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);
+ }
+ }
+
+ bail!("Failed to locate CompOSPayloadApp.apk")
+ }
+
/// Create and return an RPC Binder connection to the Comp OS service in the VM.
pub fn get_service(&self) -> Result<Strong<dyn ICompOsService>> {
let mut vsock_factory = VsockFactory::new(&*self.vm);
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 6773eb7..eec9e39 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -182,15 +182,8 @@
private void killVmAndReconnectAdb() throws Exception {
CommandRunner android = new CommandRunner(getDevice());
- // When a VM exits, we tend to see adb disconnecting. So we attempt to reconnect
- // when we kill it to avoid problems. Of course VirtualizationService may exit anyway
- // (it's an on-demand service and all its clients have gone), taking the VM with it,
- // which makes this a bit unpredictable.
- reconnectHostAdb(getDevice());
android.tryRun("killall", "crosvm");
- reconnectHostAdb(getDevice());
android.tryRun("stop", "virtualizationservice");
- reconnectHostAdb(getDevice());
// Delete stale data
android.tryRun("rm", "-rf", "/data/misc/virtualizationservice/*");
diff --git a/libs/avb_bindgen/Android.bp b/libs/avb_bindgen/Android.bp
index 1035498..1e62864 100644
--- a/libs/avb_bindgen/Android.bp
+++ b/libs/avb_bindgen/Android.bp
@@ -4,6 +4,7 @@
rust_bindgen {
name: "libavb_bindgen",
+ host_supported: true,
wrapper_src: "bindgen/avb.h",
crate_name: "avb_bindgen",
source_stem: "bindings",
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index b265d5c..60f8fd4 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -77,6 +77,7 @@
"linkerconfig",
"servicemanager.microdroid",
"tombstoned",
+ "tombstone_transmit.microdroid",
"cgroups.json",
"public.libraries.android.txt",
@@ -88,11 +89,9 @@
"microdroid_property_contexts",
"microdroid_service_contexts",
- // TODO(b/195425111) these four should be added automatically
- "android.hardware.security.secureclock-V1-ndk",
- "android.hardware.security.sharedsecret-V1-ndk",
- "libcrypto",
- "liblzma",
+ // TODO(b/195425111) these should be added automatically
+ "libcrypto", // used by many (init_second_stage, microdroid_manager, toybox, etc)
+ "liblzma", // used by init_second_stage
] + microdroid_shell_and_utilities,
multilib: {
common: {
@@ -111,9 +110,6 @@
"authfs_service",
"microdroid_manager",
"zipfuse",
-
- // TODO(b/184872979): Needed by authfs. Remove once the Rust API is created.
- "libbinder_rpc_unstable",
],
},
},
@@ -532,7 +528,7 @@
genrule {
name: "microdroid_uboot_env_gen",
tools: [
- "mkenvimage_host",
+ "mkenvimage_slim",
"avbtool",
],
srcs: [
@@ -540,7 +536,7 @@
":microdroid_sign_key",
],
out: ["output.img"],
- cmd: "$(location mkenvimage_host) -s 4096 -o $(out) $(location uboot-env.txt) && " +
+ cmd: "$(location mkenvimage_slim) -output_path $(out) -input_path $(location uboot-env.txt) && " +
"$(location avbtool) add_hash_footer " +
"--algorithm SHA256_RSA4096 " +
"--partition_name uboot_env " +
@@ -612,4 +608,4 @@
src: "microdroid_event-log-tags",
filename: "event-log-tags",
installable: false,
-}
\ No newline at end of file
+}
diff --git a/microdroid/bootconfig.common b/microdroid/bootconfig.common
index eda95a2..c202ba0 100644
--- a/microdroid/bootconfig.common
+++ b/microdroid/bootconfig.common
@@ -1,2 +1,6 @@
androidboot.first_stage_console = 1
androidboot.hardware = microdroid
+
+# tombstone_transmit is enabled. Tombstones will be transmitted to the host
+# TODO(b/227443903) : Make this configurable by VM owners
+androidboot.tombstone_transmit.enabled=1
diff --git a/microdroid/init.rc b/microdroid/init.rc
index f6d5092..dbe5ac7 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -173,6 +173,13 @@
mkdir /data/local 0751 root root
mkdir /data/local/tmp 0771 shell shell
+on init && property:ro.boot.tombstone_transmit.enabled=1
+ start tombstone_transmit
+
+service tombstone_transmit /system/bin/tombstone_transmit.microdroid -cid 2 -port 2000
+ user root
+ group system
+
service apexd-vm /system/bin/apexd --vm
user root
group system
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index d94334c..fb1373d 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -1,3 +1,7 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
rust_ffi_static {
name: "libpvmfw",
crate_name: "pvmfw",
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index 40be248..440ae18 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -82,28 +82,12 @@
// disconnect from microdroid
tryRunOnHost("adb", "disconnect", MICRODROID_SERIAL);
- reconnectHostAdb(androidDevice);
-
// kill stale VMs and directories
android.tryRun("killall", "crosvm");
android.tryRun("stop", "virtualizationservice");
android.tryRun("rm", "-rf", "/data/misc/virtualizationservice/*");
}
- public static void reconnectHostAdb(ITestDevice androidDevice)
- throws DeviceNotAvailableException {
- CommandRunner android = new CommandRunner(androidDevice);
-
- // Make sure we're connected to the host adb; this connection seems to get dropped when a VM
- // exits.
- for (int retry = 0; retry < 10; ++retry) {
- if (android.tryRun("true") != null) {
- break;
- }
- androidDevice.waitForDeviceOnline(1000);
- }
- }
-
public static void testIfDeviceIsCapable(ITestDevice androidDevice) throws Exception {
assumeTrue("Need an actual TestDevice", androidDevice instanceof TestDevice);
TestDevice testDevice = (TestDevice) androidDevice;
@@ -121,7 +105,7 @@
}
// Run an arbitrary command in the host side and returns the result
- private static String runOnHost(String... cmd) {
+ public static String runOnHost(String... cmd) {
return runOnHostWithTimeout(10000, cmd);
}
@@ -149,6 +133,19 @@
return result.getStdout().trim();
}
+ // Same as runOnMicrodroid, but keeps retrying on error till timeout
+ private static String runOnMicrodroidRetryingOnFailure(String... cmd) {
+ final long timeoutMs = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
+ int attempts = (int) MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES * 60 * 1000 / 500;
+ CommandResult result = RunUtil.getDefault()
+ .runTimedCmdRetry(timeoutMs, 500, attempts,
+ "adb", "-s", MICRODROID_SERIAL, "shell", join(cmd));
+ if (result.getStatus() != CommandStatus.SUCCESS) {
+ fail(join(cmd) + " has failed: " + result);
+ }
+ return result.getStdout().trim();
+ }
+
// Same as runOnMicrodroid, but returns null on error.
public static String tryRunOnMicrodroid(String... cmd) {
CommandResult result = runOnMicrodroidForResult(cmd);
@@ -344,34 +341,20 @@
// Shutdown the VM
android.run(VIRT_APEX + "bin/vm", "stop", cid);
-
- // TODO(192660485): Figure out why shutting down the VM disconnects adb on cuttlefish
- // temporarily. Without this wait, the rest of `runOnAndroid/skipIfFail` fails due to the
- // connection loss, and results in assumption error exception for the rest of the tests.
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
}
public static void rootMicrodroid() {
runOnHost("adb", "-s", MICRODROID_SERIAL, "root");
-
- // TODO(192660959): Figure out the root cause and remove the sleep. For unknown reason,
- // even though `adb root` actually wait-for-disconnect then wait-for-device, the next
- // `adb -s $MICRODROID_SERIAL shell ...` often fails with "adb: device offline".
- try {
- Thread.sleep(1000);
- runOnHostWithTimeout(
- MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES * 60 * 1000,
- "adb",
- "-s",
- MICRODROID_SERIAL,
- "wait-for-device");
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
+ runOnHostWithTimeout(
+ MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES * 60 * 1000,
+ "adb",
+ "-s",
+ MICRODROID_SERIAL,
+ "wait-for-device");
+ // There have been tests when adb wait-for-device succeeded but the following command
+ // fails with error: closed. Hence, we run adb shell true in microdroid with retries
+ // before returning.
+ runOnMicrodroidRetryingOnFailure("true");
}
// Establish an adb connection to microdroid by letting Android forward the connection to
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 5b71eba..7944245 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -22,6 +22,7 @@
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -100,6 +101,13 @@
false);
}
+ // Wait until logd-init starts. The service is one of the last services that are started in
+ // the microdroid boot procedure. Therefore, waiting for the service means that we wait for
+ // the boot to complete. TODO: we need a better marker eventually.
+ private void waitForLogdInit() {
+ tryRunOnMicrodroid("watch -e \"getprop init.svc.logd-reinit | grep '^$'\"");
+ }
+
@Test
public void testCreateVmRequiresPermission() throws Exception {
// Revoke the MANAGE_VIRTUAL_MACHINE permission for the test app
@@ -350,6 +358,41 @@
}
@Test
+ public void testTombstonesAreBeingForwarded() throws Exception {
+ // Note this test relies on logcat values being printed by tombstone_transmit on
+ // and the reeceiver on host (virtualization_service)
+ final String configPath = "assets/vm_config.json"; // path inside the APK
+ final String cid =
+ startMicrodroid(
+ getDevice(),
+ getBuild(),
+ APK_NAME,
+ PACKAGE_NAME,
+ configPath,
+ /* debug */ true,
+ minMemorySize(),
+ Optional.of(NUM_VCPUS),
+ Optional.of(CPU_AFFINITY));
+ adbConnectToMicrodroid(getDevice(), cid);
+ waitForLogdInit();
+ runOnMicrodroid("logcat -c");
+ // We need root permission to write to /data/tombstones/
+ rootMicrodroid();
+ // Write a test tombstone file in /data/tombstones
+ runOnMicrodroid("echo -n \'Test tombstone in VM with 34 bytes\'"
+ + "> /data/tombstones/transmit.txt");
+ // check if the tombstone have been tranferred from VM
+ assertNotEquals(runOnMicrodroid("timeout 15s logcat | grep -m 1 "
+ + "'tombstone_transmit.microdroid:.*data/tombstones/transmit.txt'"),
+ "");
+ // Confirm that tombstone is received (from host logcat)
+ assertNotEquals(runOnHost("adb", "-s", getDevice().getSerialNumber(),
+ "logcat", "-d", "-e",
+ "Received 34 bytes from guest & wrote to tombstone file.*"),
+ "");
+ }
+
+ @Test
public void testMicrodroidBoots() throws Exception {
final String configPath = "assets/vm_config.json"; // path inside the APK
final String cid =
@@ -364,12 +407,7 @@
Optional.of(NUM_VCPUS),
Optional.of(CPU_AFFINITY));
adbConnectToMicrodroid(getDevice(), cid);
-
- // Wait until logd-init starts. The service is one of the last services that are started in
- // the microdroid boot procedure. Therefore, waiting for the service means that we wait for
- // the boot to complete. TODO: we need a better marker eventually.
- tryRunOnMicrodroid("watch -e \"getprop init.svc.logd-reinit | grep '^$'\"");
-
+ waitForLogdInit();
// Test writing to /data partition
runOnMicrodroid("echo MicrodroidTest > /data/local/tmp/test.txt");
assertThat(runOnMicrodroid("cat /data/local/tmp/test.txt"), is("MicrodroidTest"));
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 36bea72..f7261d3 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -262,11 +262,6 @@
public void changingDebugLevelInvalidatesVmIdentity()
throws VirtualMachineException, InterruptedException, IOException {
assume()
- .withMessage("Skip on Cuttlefish. b/195765441")
- .that(android.os.Build.DEVICE)
- .isNotEqualTo("vsoc_x86_64");
-
- assume()
.withMessage("SKip on 5.4 kernel. b/218303240")
.that(KERNEL_VERSION)
.isNotEqualTo("5.4");
@@ -344,11 +339,6 @@
public void instancesOfSameVmHaveDifferentCdis()
throws VirtualMachineException, InterruptedException {
assume()
- .withMessage("Skip on Cuttlefish. b/195765441")
- .that(android.os.Build.DEVICE)
- .isNotEqualTo("vsoc_x86_64");
-
- assume()
.withMessage("SKip on 5.4 kernel. b/218303240")
.that(KERNEL_VERSION)
.isNotEqualTo("5.4");
@@ -368,11 +358,6 @@
public void sameInstanceKeepsSameCdis()
throws VirtualMachineException, InterruptedException {
assume()
- .withMessage("Skip on Cuttlefish. b/195765441")
- .that(android.os.Build.DEVICE)
- .isNotEqualTo("vsoc_x86_64");
-
- assume()
.withMessage("SKip on 5.4 kernel. b/218303240")
.that(KERNEL_VERSION)
.isNotEqualTo("5.4");
@@ -389,11 +374,6 @@
public void bccIsSuperficiallyWellFormed()
throws VirtualMachineException, InterruptedException, CborException {
assume()
- .withMessage("Skip on Cuttlefish. b/195765441")
- .that(android.os.Build.DEVICE)
- .isNotEqualTo("vsoc_x86_64");
-
- assume()
.withMessage("SKip on 5.4 kernel. b/218303240")
.that(KERNEL_VERSION)
.isNotEqualTo("5.4");
@@ -529,20 +509,12 @@
@Test
public void bootFailsWhenMicrodroidDataIsCompromised()
throws VirtualMachineException, InterruptedException, IOException {
- assume().withMessage("Skip on Cuttlefish. b/195765441")
- .that(android.os.Build.DEVICE)
- .isNotEqualTo("vsoc_x86_64");
-
assertThatBootFailsAfterCompromisingPartition(MICRODROID_PARTITION_UUID);
}
@Test
public void bootFailsWhenUBootAvbDataIsCompromised()
throws VirtualMachineException, InterruptedException, IOException {
- assume().withMessage("Skip on Cuttlefish. b/195765441")
- .that(android.os.Build.DEVICE)
- .isNotEqualTo("vsoc_x86_64");
-
if (mProtectedVm) {
assertThatBootFailsAfterCompromisingPartition(U_BOOT_AVB_PARTITION_UUID);
} else {
@@ -554,10 +526,6 @@
@Test
public void bootFailsWhenUBootEnvDataIsCompromised()
throws VirtualMachineException, InterruptedException, IOException {
- assume().withMessage("Skip on Cuttlefish. b/195765441")
- .that(android.os.Build.DEVICE)
- .isNotEqualTo("vsoc_x86_64");
-
if (mProtectedVm) {
assertThatBootFailsAfterCompromisingPartition(U_BOOT_ENV_PARTITION_UUID);
} else {
@@ -569,10 +537,6 @@
@Test
public void bootFailsWhenPvmFwDataIsCompromised()
throws VirtualMachineException, InterruptedException, IOException {
- assume().withMessage("Skip on Cuttlefish. b/195765441")
- .that(android.os.Build.DEVICE)
- .isNotEqualTo("vsoc_x86_64");
-
if (mProtectedVm) {
assertThatBootFailsAfterCompromisingPartition(PVM_FW_PARTITION_UUID);
} else {
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 9b2b740..7a8da96 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -45,6 +45,7 @@
"libserde_xml_rs",
"libshared_child",
"libstatslog_virtualization_rust",
+ "libtombstoned_client_rust",
"libvmconfig",
"libzip",
"libvsock",
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
index 1a16f2a..dff5d46 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -30,6 +30,12 @@
const int VM_BINDER_SERVICE_PORT = 5000;
/**
+ * Port number that VirtualMachineService listens on connections from the guest VMs for the
+ * tombtones
+ */
+ const int VM_TOMBSTONES_SERVICE_PORT = 2000;
+
+ /**
* Notifies that the payload has started.
*/
void notifyPayloadStarted();
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index d9825dc..a2e856c 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -42,7 +42,7 @@
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::{
IVirtualMachineService::{
BnVirtualMachineService, IVirtualMachineService, VM_BINDER_SERVICE_PORT,
- VM_STREAM_SERVICE_PORT,
+ VM_STREAM_SERVICE_PORT, VM_TOMBSTONES_SERVICE_PORT,
},
};
use anyhow::{anyhow, bail, Context, Result};
@@ -57,13 +57,14 @@
use std::convert::TryInto;
use std::ffi::CStr;
use std::fs::{create_dir, File, OpenOptions};
-use std::io::{Error, ErrorKind, Write};
+use std::io::{Error, ErrorKind, Write, Read};
use std::num::NonZeroU32;
use std::os::raw;
use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::path::{Path, PathBuf};
use std::ptr::null_mut;
use std::sync::{Arc, Mutex, Weak};
+use tombstoned_client::{TombstonedConnection, DebuggerdDumpType};
use vmconfig::VmConfig;
use vsock::{SockAddr, VsockListener, VsockStream};
use zip::ZipArchive;
@@ -86,6 +87,8 @@
/// Version of the instance image format
const ANDROID_VM_INSTANCE_VERSION: u16 = 1;
+const CHUNK_RECV_MAX_LEN: usize = 1024;
+
/// Implementation of `IVirtualizationService`, the entry point of the AIDL service.
#[derive(Debug, Default)]
pub struct VirtualizationService {
@@ -129,139 +132,26 @@
console_fd: Option<&ParcelFileDescriptor>,
log_fd: Option<&ParcelFileDescriptor>,
) -> binder::Result<Strong<dyn IVirtualMachine>> {
- check_manage_access()?;
- let state = &mut *self.state.lock().unwrap();
- let console_fd = console_fd.map(clone_file).transpose()?;
- let log_fd = log_fd.map(clone_file).transpose()?;
- let requester_uid = ThreadState::get_calling_uid();
- let requester_sid = get_calling_sid()?;
- let requester_debug_pid = ThreadState::get_calling_pid();
- let cid = next_cid().or(Err(ExceptionCode::ILLEGAL_STATE))?;
-
- // Counter to generate unique IDs for temporary image files.
- let mut next_temporary_image_id = 0;
- // Files which are referred to from composite images. These must be mapped to the crosvm
- // child process, and not closed before it is started.
- let mut indirect_files = vec![];
-
- // Make directory for temporary files.
- let temporary_directory: PathBuf = format!("{}/{}", TEMPORARY_DIRECTORY, cid).into();
- create_dir(&temporary_directory).map_err(|e| {
- // At this point, we do not know the protected status of Vm
- // setting it to false, though this may not be correct.
- write_vm_creation_stats(false, false);
- error!(
- "Failed to create temporary directory {:?} for VM files: {}",
- temporary_directory, e
- );
- new_binder_exception(
- ExceptionCode::SERVICE_SPECIFIC,
- format!(
- "Failed to create temporary directory {:?} for VM files: {}",
- temporary_directory, e
- ),
- )
- })?;
-
- let is_app_config = matches!(config, VirtualMachineConfig::AppConfig(_));
-
- let config = match config {
- VirtualMachineConfig::AppConfig(config) => BorrowedOrOwned::Owned(
- load_app_config(config, &temporary_directory).map_err(|e| {
- error!("Failed to load app config from {}: {}", &config.configPath, e);
- write_vm_creation_stats(config.protectedVm, false);
- new_binder_exception(
- ExceptionCode::SERVICE_SPECIFIC,
- format!("Failed to load app config from {}: {}", &config.configPath, e),
- )
- })?,
- ),
- VirtualMachineConfig::RawConfig(config) => BorrowedOrOwned::Borrowed(config),
- };
- let config = config.as_ref();
- let protected = config.protectedVm;
-
- // Check if partition images are labeled incorrectly. This is to prevent random images
- // which are not protected by the Android Verified Boot (e.g. bits downloaded by apps) from
- // being loaded in a pVM. Specifically, for images in the raw config, nothing is allowed
- // to be labeled as app_data_file. For images in the app config, nothing but the instance
- // partition is allowed to be labeled as such.
- config
- .disks
- .iter()
- .flat_map(|disk| disk.partitions.iter())
- .filter(|partition| {
- if is_app_config {
- partition.label != "vm-instance"
- } else {
- true // all partitions are checked
- }
- })
- .try_for_each(check_label_for_partition)
- .map_err(|e| new_binder_exception(ExceptionCode::SERVICE_SPECIFIC, e.to_string()))?;
-
- let zero_filler_path = temporary_directory.join("zero.img");
- write_zero_filler(&zero_filler_path).map_err(|e| {
- error!("Failed to make composite image: {}", e);
- write_vm_creation_stats(protected, false);
- new_binder_exception(
- ExceptionCode::SERVICE_SPECIFIC,
- format!("Failed to make composite image: {}", e),
- )
- })?;
-
- // Assemble disk images if needed.
- let disks = config
- .disks
- .iter()
- .map(|disk| {
- assemble_disk_image(
- disk,
- &zero_filler_path,
- &temporary_directory,
- &mut next_temporary_image_id,
- &mut indirect_files,
- )
- })
- .collect::<Result<Vec<DiskFile>, _>>()?;
-
- // Actually start the VM.
- let crosvm_config = CrosvmConfig {
- cid,
- bootloader: maybe_clone_file(&config.bootloader)?,
- kernel: maybe_clone_file(&config.kernel)?,
- initrd: maybe_clone_file(&config.initrd)?,
- disks,
- params: config.params.to_owned(),
- protected,
- memory_mib: config.memoryMib.try_into().ok().and_then(NonZeroU32::new),
- cpus: config.numCpus.try_into().ok().and_then(NonZeroU32::new),
- cpu_affinity: config.cpuAffinity.clone(),
- console_fd,
- log_fd,
- indirect_files,
- platform_version: parse_platform_version_req(&config.platformVersion)?,
- };
- let instance = Arc::new(
- VmInstance::new(
- crosvm_config,
- temporary_directory,
- requester_uid,
- requester_sid,
- requester_debug_pid,
- )
- .map_err(|e| {
- error!("Failed to create VM with config {:?}: {}", config, e);
- write_vm_creation_stats(protected, false);
- new_binder_exception(
- ExceptionCode::SERVICE_SPECIFIC,
- format!("Failed to create VM: {}", e),
- )
- })?,
- );
- state.add_vm(Arc::downgrade(&instance));
- write_vm_creation_stats(protected, true);
- Ok(VirtualMachine::create(instance))
+ let mut is_protected = false;
+ let ret = self.create_vm_internal(config, console_fd, log_fd, &mut is_protected);
+ match ret {
+ Ok(_) => {
+ let ok_status = Status::ok();
+ write_vm_creation_stats(
+ is_protected,
+ /*creation_succeeded*/ true,
+ ok_status.exception_code() as i32,
+ );
+ }
+ Err(ref e) => {
+ write_vm_creation_stats(
+ is_protected,
+ /*creation_succeeded*/ false,
+ e.exception_code() as i32,
+ );
+ }
+ }
+ ret
}
/// Initialise an empty partition image of the given size to be used as a writable partition.
@@ -371,6 +261,55 @@
}
}
+fn handle_stream_connection_tombstoned() -> Result<()> {
+ let listener =
+ VsockListener::bind_with_cid_port(VMADDR_CID_HOST, VM_TOMBSTONES_SERVICE_PORT as u32)?;
+ info!("Listening to tombstones from guests ...");
+ for incoming_stream in listener.incoming() {
+ let mut incoming_stream = match incoming_stream {
+ Err(e) => {
+ warn!("invalid incoming connection: {}", e);
+ continue;
+ }
+ Ok(s) => s,
+ };
+ std::thread::spawn(move || {
+ if let Err(e) = handle_tombstone(&mut incoming_stream) {
+ error!("Failed to write tombstone- {:?}", e);
+ }
+ });
+ }
+ Ok(())
+}
+
+fn handle_tombstone(stream: &mut VsockStream) -> Result<()> {
+ if let Ok(SockAddr::Vsock(addr)) = stream.peer_addr() {
+ info!("Vsock Stream connected to cid={} for tombstones", addr.cid());
+ }
+ let tb_connection =
+ TombstonedConnection::connect(std::process::id() as i32, DebuggerdDumpType::Tombstone)
+ .context("Failed to connect to tombstoned")?;
+ let mut text_output = tb_connection
+ .text_output
+ .as_ref()
+ .ok_or_else(|| anyhow!("Could not get file to write the tombstones on"))?;
+ let mut num_bytes_read = 0;
+ loop {
+ let mut chunk_recv = [0; CHUNK_RECV_MAX_LEN];
+ let n = stream
+ .read(&mut chunk_recv)
+ .context("Failed to read tombstone data from Vsock stream")?;
+ if n == 0 {
+ break;
+ }
+ num_bytes_read += n;
+ text_output.write_all(&chunk_recv[0..n]).context("Failed to write guests tombstones")?;
+ }
+ info!("Received {} bytes from guest & wrote to tombstone file", num_bytes_read);
+ tb_connection.notify_completion()?;
+ Ok(())
+}
+
impl VirtualizationService {
pub fn init() -> VirtualizationService {
let service = VirtualizationService::default();
@@ -381,8 +320,15 @@
handle_stream_connection_from_vm(state).unwrap();
});
+ std::thread::spawn(|| {
+ if let Err(e) = handle_stream_connection_tombstoned() {
+ warn!("Error receiving tombstone from guest or writing them. Error: {}", e);
+ }
+ });
+
// binder server for vm
- let mut state = service.state.clone(); // reference to state (not the state itself) is copied
+ // reference to state (not the state itself) is copied
+ let mut state = service.state.clone();
std::thread::spawn(move || {
let state_ptr = &mut state as *mut _ as *mut raw::c_void;
@@ -407,11 +353,149 @@
});
service
}
+
+ fn create_vm_internal(
+ &self,
+ config: &VirtualMachineConfig,
+ console_fd: Option<&ParcelFileDescriptor>,
+ log_fd: Option<&ParcelFileDescriptor>,
+ is_protected: &mut bool,
+ ) -> binder::Result<Strong<dyn IVirtualMachine>> {
+ check_manage_access()?;
+ let state = &mut *self.state.lock().unwrap();
+ let console_fd = console_fd.map(clone_file).transpose()?;
+ let log_fd = log_fd.map(clone_file).transpose()?;
+ let requester_uid = ThreadState::get_calling_uid();
+ let requester_sid = get_calling_sid()?;
+ let requester_debug_pid = ThreadState::get_calling_pid();
+ let cid = next_cid().or(Err(ExceptionCode::ILLEGAL_STATE))?;
+
+ // Counter to generate unique IDs for temporary image files.
+ let mut next_temporary_image_id = 0;
+ // Files which are referred to from composite images. These must be mapped to the crosvm
+ // child process, and not closed before it is started.
+ let mut indirect_files = vec![];
+
+ // Make directory for temporary files.
+ let temporary_directory: PathBuf = format!("{}/{}", TEMPORARY_DIRECTORY, cid).into();
+ create_dir(&temporary_directory).map_err(|e| {
+ // At this point, we do not know the protected status of Vm
+ // setting it to false, though this may not be correct.
+ error!(
+ "Failed to create temporary directory {:?} for VM files: {}",
+ temporary_directory, e
+ );
+ new_binder_exception(
+ ExceptionCode::SERVICE_SPECIFIC,
+ format!(
+ "Failed to create temporary directory {:?} for VM files: {}",
+ temporary_directory, e
+ ),
+ )
+ })?;
+
+ let is_app_config = matches!(config, VirtualMachineConfig::AppConfig(_));
+
+ let config = match config {
+ VirtualMachineConfig::AppConfig(config) => BorrowedOrOwned::Owned(
+ load_app_config(config, &temporary_directory).map_err(|e| {
+ error!("Failed to load app config from {}: {}", &config.configPath, e);
+ *is_protected = config.protectedVm;
+ new_binder_exception(
+ ExceptionCode::SERVICE_SPECIFIC,
+ format!("Failed to load app config from {}: {}", &config.configPath, e),
+ )
+ })?,
+ ),
+ VirtualMachineConfig::RawConfig(config) => BorrowedOrOwned::Borrowed(config),
+ };
+ let config = config.as_ref();
+ *is_protected = config.protectedVm;
+
+ // Check if partition images are labeled incorrectly. This is to prevent random images
+ // which are not protected by the Android Verified Boot (e.g. bits downloaded by apps) from
+ // being loaded in a pVM. Specifically, for images in the raw config, nothing is allowed
+ // to be labeled as app_data_file. For images in the app config, nothing but the instance
+ // partition is allowed to be labeled as such.
+ config
+ .disks
+ .iter()
+ .flat_map(|disk| disk.partitions.iter())
+ .filter(|partition| {
+ if is_app_config {
+ partition.label != "vm-instance"
+ } else {
+ true // all partitions are checked
+ }
+ })
+ .try_for_each(check_label_for_partition)
+ .map_err(|e| new_binder_exception(ExceptionCode::SERVICE_SPECIFIC, e.to_string()))?;
+
+ let zero_filler_path = temporary_directory.join("zero.img");
+ write_zero_filler(&zero_filler_path).map_err(|e| {
+ error!("Failed to make composite image: {}", e);
+ new_binder_exception(
+ ExceptionCode::SERVICE_SPECIFIC,
+ format!("Failed to make composite image: {}", e),
+ )
+ })?;
+
+ // Assemble disk images if needed.
+ let disks = config
+ .disks
+ .iter()
+ .map(|disk| {
+ assemble_disk_image(
+ disk,
+ &zero_filler_path,
+ &temporary_directory,
+ &mut next_temporary_image_id,
+ &mut indirect_files,
+ )
+ })
+ .collect::<Result<Vec<DiskFile>, _>>()?;
+
+ // Actually start the VM.
+ let crosvm_config = CrosvmConfig {
+ cid,
+ bootloader: maybe_clone_file(&config.bootloader)?,
+ kernel: maybe_clone_file(&config.kernel)?,
+ initrd: maybe_clone_file(&config.initrd)?,
+ disks,
+ params: config.params.to_owned(),
+ protected: *is_protected,
+ memory_mib: config.memoryMib.try_into().ok().and_then(NonZeroU32::new),
+ cpus: config.numCpus.try_into().ok().and_then(NonZeroU32::new),
+ cpu_affinity: config.cpuAffinity.clone(),
+ console_fd,
+ log_fd,
+ indirect_files,
+ platform_version: parse_platform_version_req(&config.platformVersion)?,
+ };
+ let instance = Arc::new(
+ VmInstance::new(
+ crosvm_config,
+ temporary_directory,
+ requester_uid,
+ requester_sid,
+ requester_debug_pid,
+ )
+ .map_err(|e| {
+ error!("Failed to create VM with config {:?}: {}", config, e);
+ new_binder_exception(
+ ExceptionCode::SERVICE_SPECIFIC,
+ format!("Failed to create VM: {}", e),
+ )
+ })?,
+ );
+ state.add_vm(Arc::downgrade(&instance));
+ Ok(VirtualMachine::create(instance))
+ }
}
/// Write the stats of VMCreation to statsd
-fn write_vm_creation_stats(protected: bool, success: bool) {
- match stats_write(Hypervisor::Pkvm, protected, success) {
+fn write_vm_creation_stats(is_protected: bool, creation_succeeded: bool, exception_code: i32) {
+ match stats_write(Hypervisor::Pkvm, is_protected, creation_succeeded, exception_code) {
Err(e) => {
warn!("statslog_rust failed with error: {}", e);
}
diff --git a/zipfuse/src/main.rs b/zipfuse/src/main.rs
index a91642c..c3fae69 100644
--- a/zipfuse/src/main.rs
+++ b/zipfuse/src/main.rs
@@ -82,7 +82,9 @@
libc::MS_NOSUID | libc::MS_NODEV | libc::MS_RDONLY,
&mount_options,
)?;
- Ok(fuse::worker::start_message_loop(dev_fuse, MAX_READ, MAX_WRITE, ZipFuse::new(zip_file)?)?)
+ let mut config = fuse::FuseConfig::new();
+ config.dev_fuse(dev_fuse).max_write(MAX_WRITE).max_read(MAX_READ);
+ Ok(config.enter_message_loop(ZipFuse::new(zip_file)?)?)
}
struct ZipFuse {