Merge changes from topic "mount-vendor-in-microdroid" into main
* changes:
Propagate to Microdroid whether it should mount vendor partition
Add test api for setting vendor disk image
Add a way to add a vendor disk image when launching Microdroid VM
diff --git a/javalib/api/test-current.txt b/javalib/api/test-current.txt
index 1298000..cf95770 100644
--- a/javalib/api/test-current.txt
+++ b/javalib/api/test-current.txt
@@ -13,6 +13,7 @@
public static final class VirtualMachineConfig.Builder {
method @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadConfigPath(@NonNull String);
+ method @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setVendorDiskImage(@NonNull java.io.File);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setVmConsoleInputSupported(boolean);
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index b400eeb..4cad2e3 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -76,6 +76,7 @@
private static final String KEY_ENCRYPTED_STORAGE_BYTES = "encryptedStorageBytes";
private static final String KEY_VM_OUTPUT_CAPTURED = "vmOutputCaptured";
private static final String KEY_VM_CONSOLE_INPUT_SUPPORTED = "vmConsoleInputSupported";
+ private static final String KEY_VENDOR_DISK_IMAGE_PATH = "vendorDiskImagePath";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -167,6 +168,8 @@
/** Whether the app can write console input to the VM */
private final boolean mVmConsoleInputSupported;
+ @Nullable private final File mVendorDiskImage;
+
private VirtualMachineConfig(
@Nullable String packageName,
@Nullable String apkPath,
@@ -178,7 +181,8 @@
@CpuTopology int cpuTopology,
long encryptedStorageBytes,
boolean vmOutputCaptured,
- boolean vmConsoleInputSupported) {
+ boolean vmConsoleInputSupported,
+ @Nullable File vendorDiskImage) {
// This is only called from Builder.build(); the builder handles parameter validation.
mPackageName = packageName;
mApkPath = apkPath;
@@ -191,6 +195,7 @@
mEncryptedStorageBytes = encryptedStorageBytes;
mVmOutputCaptured = vmOutputCaptured;
mVmConsoleInputSupported = vmConsoleInputSupported;
+ mVendorDiskImage = vendorDiskImage;
}
/** Loads a config from a file. */
@@ -267,6 +272,11 @@
builder.setVmOutputCaptured(b.getBoolean(KEY_VM_OUTPUT_CAPTURED));
builder.setVmConsoleInputSupported(b.getBoolean(KEY_VM_CONSOLE_INPUT_SUPPORTED));
+ String vendorDiskImagePath = b.getString(KEY_VENDOR_DISK_IMAGE_PATH);
+ if (vendorDiskImagePath != null) {
+ builder.setVendorDiskImage(new File(vendorDiskImagePath));
+ }
+
return builder.build();
}
@@ -302,6 +312,9 @@
}
b.putBoolean(KEY_VM_OUTPUT_CAPTURED, mVmOutputCaptured);
b.putBoolean(KEY_VM_CONSOLE_INPUT_SUPPORTED, mVmConsoleInputSupported);
+ if (mVendorDiskImage != null) {
+ b.putString(KEY_VENDOR_DISK_IMAGE_PATH, mVendorDiskImage.getAbsolutePath());
+ }
b.writeToStream(output);
}
@@ -501,6 +514,20 @@
vsConfig.cpuTopology = android.system.virtualizationservice.CpuTopology.ONE_CPU;
break;
}
+ if (mVendorDiskImage != null) {
+ VirtualMachineAppConfig.CustomConfig customConfig =
+ new VirtualMachineAppConfig.CustomConfig();
+ customConfig.taskProfiles = new String[0];
+ try {
+ customConfig.vendorImage =
+ ParcelFileDescriptor.open(mVendorDiskImage, MODE_READ_ONLY);
+ } catch (FileNotFoundException e) {
+ throw new VirtualMachineException(
+ "Failed to open vendor disk image " + mVendorDiskImage.getAbsolutePath(),
+ e);
+ }
+ vsConfig.customConfig = customConfig;
+ }
return vsConfig;
}
@@ -572,6 +599,7 @@
private long mEncryptedStorageBytes;
private boolean mVmOutputCaptured = false;
private boolean mVmConsoleInputSupported = false;
+ @Nullable private File mVendorDiskImage;
/**
* Creates a builder for the given context.
@@ -645,7 +673,8 @@
mCpuTopology,
mEncryptedStorageBytes,
mVmOutputCaptured,
- mVmConsoleInputSupported);
+ mVmConsoleInputSupported,
+ mVendorDiskImage);
}
/**
@@ -863,5 +892,18 @@
mVmConsoleInputSupported = supported;
return this;
}
+
+ /**
+ * Sets the path to the disk image with vendor-specific modules.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION)
+ @NonNull
+ public Builder setVendorDiskImage(@NonNull File vendorDiskImage) {
+ mVendorDiskImage = vendorDiskImage;
+ return this;
+ }
}
}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 5440695..2d3f084 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -23,6 +23,10 @@
"apex",
"linkerconfig",
"second_stage_resources",
+
+ // Ideally we should only create the /vendor for Microdroid VMs that will mount /vendor, but
+ // for the time being we will just create it unconditionally.
+ "vendor",
]
microdroid_symlinks = [
diff --git a/microdroid/bootconfig.x86_64 b/microdroid/bootconfig.x86_64
index 6076889..eed9212 100644
--- a/microdroid/bootconfig.x86_64
+++ b/microdroid/bootconfig.x86_64
@@ -1 +1 @@
-androidboot.boot_devices = pci0000:00/0000:00:04.0,pci0000:00/0000:00:05.0,pci0000:00/0000:00:06.0
+androidboot.boot_devices = pci0000:00/0000:00:04.0,pci0000:00/0000:00:05.0,pci0000:00/0000:00:06.0,pci0000:00/0000:00:07.0
diff --git a/microdroid/fstab.microdroid b/microdroid/fstab.microdroid
index 9478c7c..da000b9 100644
--- a/microdroid/fstab.microdroid
+++ b/microdroid/fstab.microdroid
@@ -1 +1,7 @@
system /system ext4 noatime,ro,errors=panic wait,slotselect,avb=vbmeta,first_stage_mount,logical
+# This is a temporary solution to unblock other devs that depend on /vendor partition in Microdroid
+# The /vendor partition will only be mounted if the kernel cmdline contains
+# androidboot.microdroid.mount_vendor=1.
+# TODO(b/285855430): this should probably be defined in the DT
+# TODO(b/285855436): should be mounted on top of dm-verity device
+/dev/block/by-name/microdroid-vendor /vendor ext4 noatime,ro,errors=panic wait,first_stage_mount
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index fe8f5c9..8a31c21 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -43,7 +43,10 @@
],
min_sdk_version: "33",
// Defined in ../vmshareapp/Android.bp
- data: [":MicrodroidVmShareApp"],
+ data: [
+ ":MicrodroidVmShareApp",
+ ":test_microdroid_vendor_image",
+ ],
}
// Defaults shared between MicrodroidTestNativeLib and MicrodroidPayloadInOtherAppNativeLib shared
diff --git a/tests/testapk/AndroidManifest.xml b/tests/testapk/AndroidManifest.xml
index 2ea3f6c..d6e6004 100644
--- a/tests/testapk/AndroidManifest.xml
+++ b/tests/testapk/AndroidManifest.xml
@@ -22,8 +22,7 @@
<queries>
<package android:name="com.android.microdroid.vmshare_app" />
</queries>
- <application>
- </application>
+ <application />
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.microdroid.test"
android:label="Microdroid Test" />
diff --git a/tests/testapk/AndroidTest.xml b/tests/testapk/AndroidTest.xml
index 929dd31..e72a2e3 100644
--- a/tests/testapk/AndroidTest.xml
+++ b/tests/testapk/AndroidTest.xml
@@ -23,6 +23,14 @@
<option name="test-file-name" value="MicrodroidTestApp.apk" />
<option name="test-file-name" value="MicrodroidVmShareApp.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts/microdroid" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts/microdroid" />
+ </target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="test_microdroid_vendor_image.img->/data/local/tmp/cts/microdroid/test_microdroid_vendor_image.img" />
+ </target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.microdroid.test" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
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 5ec4ca8..15cb450 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -1975,8 +1975,10 @@
.isEqualTo(OsConstants.S_IRUSR | OsConstants.S_IXUSR);
}
- // Taken from bionic/libs/kernel/uapi/linux/mounth.h.
+ // Taken from bionic/libc/kernel/uapi/linux/mount.h
+ private static final int MS_RDONLY = 1;
private static final int MS_NOEXEC = 8;
+ private static final int MS_NOATIME = 1024;
@Test
@CddTest(requirements = {"9.17/C-1-5"})
@@ -2050,6 +2052,60 @@
}
}
+ @Test
+ public void configuringVendorDiskImageRequiresCustomPermission() throws Exception {
+ assumeSupportedDevice();
+
+ File vendorDiskImage =
+ new File("/data/local/tmp/cts/microdroid/test_microdroid_vendor_image.img");
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+ .setVendorDiskImage(vendorDiskImage)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+
+ VirtualMachine vm =
+ forceCreateNewVirtualMachine("test_vendor_image_req_custom_permission", config);
+
+ SecurityException e =
+ assertThrows(
+ SecurityException.class, () -> runVmTestService(TAG, vm, (ts, tr) -> {}));
+ assertThat(e)
+ .hasMessageThat()
+ .contains("android.permission.USE_CUSTOM_VIRTUAL_MACHINE permission");
+ }
+
+ @Test
+ public void bootsWithVendorPartition() throws Exception {
+ assumeSupportedDevice();
+
+ grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
+
+ File vendorDiskImage =
+ new File("/data/local/tmp/cts/microdroid/test_microdroid_vendor_image.img");
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+ .setVendorDiskImage(vendorDiskImage)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+
+ VirtualMachine vm = forceCreateNewVirtualMachine("test_boot_with_vendor", config);
+
+ TestResults testResults =
+ runVmTestService(
+ TAG,
+ vm,
+ (ts, tr) -> {
+ tr.mMountFlags = ts.getMountFlags("/vendor");
+ });
+
+ assertThat(testResults.mException).isNull();
+ int expectedFlags = MS_NOATIME | MS_RDONLY;
+ assertThat(testResults.mMountFlags & expectedFlags).isEqualTo(expectedFlags);
+ }
+
private static class VmShareServiceConnection implements ServiceConnection {
private final CountDownLatch mLatch = new CountDownLatch(1);
diff --git a/tests/vendor_images/Android.bp b/tests/vendor_images/Android.bp
new file mode 100644
index 0000000..09c657c
--- /dev/null
+++ b/tests/vendor_images/Android.bp
@@ -0,0 +1,9 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_filesystem {
+ name: "test_microdroid_vendor_image",
+ type: "ext4",
+ file_contexts: ":microdroid_vendor_file_contexts.gen",
+}
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index dd74d55..d0a8e85 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -20,7 +20,7 @@
use crate::composite::make_composite_image;
use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmContext, VmInstance, VmState};
use crate::debug_config::DebugConfig;
-use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images};
+use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images, add_microdroid_vendor_image};
use crate::selinux::{getfilecon, SeContext};
use android_os_permissions_aidl::aidl::android::os::IPermissionController;
use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::{
@@ -579,6 +579,15 @@
Ok(DiskFile { image, writable: disk.writable })
}
+fn append_kernel_param(param: &str, vm_config: &mut VirtualMachineRawConfig) {
+ if let Some(ref mut params) = vm_config.params {
+ params.push(' ');
+ params.push_str(param)
+ } else {
+ vm_config.params = Some(param.to_owned())
+ }
+}
+
fn load_app_config(
config: &VirtualMachineAppConfig,
debug_config: &DebugConfig,
@@ -620,6 +629,11 @@
}
vm_config.taskProfiles = custom_config.taskProfiles.clone();
vm_config.gdbPort = custom_config.gdbPort;
+
+ if let Some(file) = custom_config.vendorImage.as_ref() {
+ add_microdroid_vendor_image(clone_file(file)?, &mut vm_config);
+ append_kernel_param("androidboot.microdroid.mount_vendor=1", &mut vm_config)
+ }
}
if config.memoryMib > 0 {
@@ -1349,4 +1363,19 @@
assert!(modified_orig == modified_new, "idsig file was updated unnecessarily");
Ok(())
}
+
+ #[test]
+ fn test_append_kernel_param_first_param() {
+ let mut vm_config = VirtualMachineRawConfig { ..Default::default() };
+ append_kernel_param("foo=1", &mut vm_config);
+ assert_eq!(vm_config.params, Some("foo=1".to_owned()))
+ }
+
+ #[test]
+ fn test_append_kernel_param() {
+ let mut vm_config =
+ VirtualMachineRawConfig { params: Some("foo=5".to_owned()), ..Default::default() };
+ append_kernel_param("bar=42", &mut vm_config);
+ assert_eq!(vm_config.params, Some("foo=5 bar=42".to_owned()))
+ }
}
diff --git a/virtualizationmanager/src/payload.rs b/virtualizationmanager/src/payload.rs
index 733add6..ab6f31c 100644
--- a/virtualizationmanager/src/payload.rs
+++ b/virtualizationmanager/src/payload.rs
@@ -398,6 +398,18 @@
.collect()
}
+pub fn add_microdroid_vendor_image(vendor_image: File, vm_config: &mut VirtualMachineRawConfig) {
+ vm_config.disks.push(DiskImage {
+ image: None,
+ writable: false,
+ partitions: vec![Partition {
+ label: "microdroid-vendor".to_owned(),
+ image: Some(ParcelFileDescriptor::new(vendor_image)),
+ writable: false,
+ }],
+ })
+}
+
pub fn add_microdroid_system_images(
config: &VirtualMachineAppConfig,
instance_file: File,
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
index 6a0bf7c..2b762c4 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
@@ -105,6 +105,9 @@
* List of task profile names to apply for the VM
*/
String[] taskProfiles;
+
+ /** A disk image containing vendor specific modules. */
+ @nullable ParcelFileDescriptor vendorImage;
}
/** Configuration parameters guarded by android.permission.USE_CUSTOM_VIRTUAL_MACHINE */
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 0800f57..d7c2c4d 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -115,6 +115,10 @@
/// Path to custom kernel image to use when booting Microdroid.
#[clap(long)]
kernel: Option<PathBuf>,
+
+ /// Path to disk image containing vendor-specific modules.
+ #[clap(long)]
+ vendor: Option<PathBuf>,
},
/// Run a virtual machine with Microdroid inside
RunMicrodroid {
@@ -179,6 +183,10 @@
/// Path to custom kernel image to use when booting Microdroid.
#[clap(long)]
kernel: Option<PathBuf>,
+
+ /// Path to disk image containing vendor-specific modules.
+ #[clap(long)]
+ vendor: Option<PathBuf>,
},
/// Run a virtual machine
Run {
@@ -299,6 +307,7 @@
extra_idsigs,
gdb,
kernel,
+ vendor,
} => command_run_app(
name,
get_service()?.as_ref(),
@@ -320,6 +329,7 @@
&extra_idsigs,
gdb,
kernel.as_deref(),
+ vendor.as_deref(),
),
Opt::RunMicrodroid {
name,
@@ -336,6 +346,7 @@
task_profiles,
gdb,
kernel,
+ vendor,
} => command_run_microdroid(
name,
get_service()?.as_ref(),
@@ -352,6 +363,7 @@
task_profiles,
gdb,
kernel.as_deref(),
+ vendor.as_deref(),
),
Opt::Run { name, config, cpu_topology, task_profiles, console, console_in, log, gdb } => {
command_run(
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 84072ca..64da2d9 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -65,6 +65,7 @@
extra_idsigs: &[PathBuf],
gdb: Option<NonZeroU16>,
kernel: Option<&Path>,
+ vendor: Option<&Path>,
) -> Result<(), Error> {
let apk_file = File::open(apk).context("Failed to open APK file")?;
@@ -122,6 +123,8 @@
let kernel = kernel.map(|p| open_parcel_file(p, false)).transpose()?;
+ let vendor = vendor.map(|p| open_parcel_file(p, false)).transpose()?;
+
let extra_idsig_files: Result<Vec<File>, _> = extra_idsigs.iter().map(File::open).collect();
let extra_idsig_fds = extra_idsig_files?.into_iter().map(ParcelFileDescriptor::new).collect();
@@ -144,6 +147,7 @@
customKernelImage: kernel,
gdbPort: gdb.map(u16::from).unwrap_or(0) as i32, // 0 means no gdb
taskProfiles: task_profiles,
+ vendorImage: vendor,
};
let config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
@@ -203,6 +207,7 @@
task_profiles: Vec<String>,
gdb: Option<NonZeroU16>,
kernel: Option<&Path>,
+ vendor: Option<&Path>,
) -> Result<(), Error> {
let apk = find_empty_payload_apk_path()?;
println!("found path {}", apk.display());
@@ -236,6 +241,7 @@
&extra_sig,
gdb,
kernel,
+ vendor,
)
}