Merge "Move VM DT overlay logic into maybe_create_device_tree_overlay" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index db0b43a..6d585a6 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -18,6 +18,9 @@
"name": "MicrodroidTestApp"
},
{
+ "name": "MicrodroidTestAppNoInternetPerm"
+ },
+ {
"name": "MicrodroidTestAppNoPerm"
},
{
diff --git a/apex/Android.bp b/apex/Android.bp
index 43819dc..17b1f9e 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -157,6 +157,7 @@
"microdroid.json",
"microdroid_kernel",
"com.android.virt.init.rc",
+ "android_bootloader_crosvm_aarch64",
],
host_required: [
"vm_shell",
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index bd011fa..b31f4f3 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -24,11 +24,13 @@
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import android.platform.test.annotations.RootPermissionTest;
import com.android.microdroid.test.host.CommandRunner;
import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
+import com.android.tradefed.device.TestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.LogDataType;
@@ -85,6 +87,8 @@
assumeDeviceIsCapable(getDevice());
// Test takes too long to run on Cuttlefish (b/292824951).
assumeFalse("Skipping test on Cuttlefish", isCuttlefish());
+ // CompOS requires a protected VM
+ assumeTrue(((TestDevice) getDevice()).supportsMicrodroid(/*protectedVm*/ true));
String value = getDevice().getProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME);
if (value == null) {
diff --git a/docs/custom_vm.md b/docs/custom_vm.md
index 5511758..d52aa95 100644
--- a/docs/custom_vm.md
+++ b/docs/custom_vm.md
@@ -194,7 +194,8 @@
"protected": false,
"cpu_topology": "match_host",
"platform_version": "~1.0",
- "memory_mib" : 8096
+ "memory_mib" : 8096,
+ "console_input_device": "ttyS0"
}
```
@@ -296,4 +297,16 @@
* DNS: 8.8.8.8 (or any DNS server you know)
These settings are persistent; stored in chromiumos_test_image.bin. So you
-don’t have to repeat this next time.`
+don’t have to repeat this next time.
+
+### Debugging
+
+To see console log, check
+`/data/data/com.android.virtualization.vmlauncher/files/console.log`
+
+For ChromiumOS, you can ssh-in. Use following commands after network setup.
+
+```shell
+$ adb kill-server ; adb start-server; adb forward tcp:9222 tcp:9222
+$ ssh -oProxyCommand=none -o UserKnownHostsFile=/dev/null root@localhost -p 9222
+```
diff --git a/docs/vm_remote_attestation.md b/docs/vm_remote_attestation.md
index 3483351..195804f 100644
--- a/docs/vm_remote_attestation.md
+++ b/docs/vm_remote_attestation.md
@@ -117,7 +117,16 @@
- The DICE chain should have RKP VM markers that help identify RKP VM as required
by the [remote provisioning HAL][rkp-hal-markers].
-The feature is enabled by default. To disable it, you can set
-`PRODUCT_AVF_REMOTE_ATTESTATION_DISABLED` to true in your Makefile.
+The feature is enabled by default. To disable it, you have two options:
+
+1. Set `PRODUCT_AVF_REMOTE_ATTESTATION_DISABLED` to `true` in your Makefile to
+ disable the feature at build time.
+
+2. Set the system property `avf.remote_attestation.enabled` to `0` to disable
+ the feature at boot time by including the following line in vendor init:
+ `setprop avf.remote_attestation.enabled 0`.
+
+If you don't set any of these variables, VM remote attestation will be enabled
+by default.
[rkp-hal-markers]: https://android.googlesource.com/platform/hardware/interfaces/+/main/security/rkp/README.md#hal
diff --git a/java/framework/api/test-current.txt b/java/framework/api/test-current.txt
index d20d543..30dd7d9 100644
--- a/java/framework/api/test-current.txt
+++ b/java/framework/api/test-current.txt
@@ -11,12 +11,14 @@
method @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") @NonNull public java.util.List<java.lang.String> getExtraApks();
method @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") @NonNull public String getOs();
method @Nullable public String getPayloadConfigPath();
+ method public boolean isNetworkSupported();
method public boolean isVmConsoleInputSupported();
field @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") public static final String MICRODROID = "microdroid";
}
public static final class VirtualMachineConfig.Builder {
method @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder addExtraApk(@NonNull String);
+ method @NonNull @RequiresPermission(allOf={android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION, android.Manifest.permission.INTERNET}) public android.system.virtualmachine.VirtualMachineConfig.Builder setNetworkSupported(boolean);
method @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setOs(@NonNull String);
method @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadConfigPath(@NonNull String);
method @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setVendorDiskImage(@NonNull java.io.File);
@@ -31,6 +33,7 @@
field @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") public static final String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
field @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") public static final String FEATURE_LLPVM_CHANGES = "com.android.kvm.LLPVM_CHANGES";
field @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") public static final String FEATURE_MULTI_TENANT = "com.android.kvm.MULTI_TENANT";
+ field public static final String FEATURE_NETWORK = "com.android.kvm.NETWORK";
field @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") public static final String FEATURE_REMOTE_ATTESTATION = "com.android.kvm.REMOTE_ATTESTATION";
field @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") public static final String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
}
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java b/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
index 4d3bf2d..ef6a2fc 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -22,6 +22,7 @@
import static java.util.Objects.requireNonNull;
+import android.Manifest;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -77,9 +78,11 @@
private static final String TAG = "VirtualMachineConfig";
private static String[] EMPTY_STRING_ARRAY = {};
+ private static final String U_BOOT_PREBUILT_PATH = "/apex/com.android.virt/etc/u-boot.bin";
// These define the schema of the config file persisted on disk.
- private static final int VERSION = 8;
+ // Please bump up the version number when adding a new key.
+ private static final int VERSION = 9;
private static final String KEY_VERSION = "version";
private static final String KEY_PACKAGENAME = "packageName";
private static final String KEY_APKPATH = "apkPath";
@@ -98,6 +101,7 @@
private static final String KEY_VENDOR_DISK_IMAGE_PATH = "vendorDiskImagePath";
private static final String KEY_OS = "os";
private static final String KEY_EXTRA_APKS = "extraApks";
+ private static final String KEY_NETWORK_SUPPORTED = "networkSupported";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -205,6 +209,9 @@
/** OS name of the VM using payload binaries. */
@NonNull @OsName private final String mOs;
+ /** Whether to run the VM with supporting network feature or not. */
+ private final boolean mNetworkSupported;
+
@Retention(RetentionPolicy.SOURCE)
@StringDef(
prefix = "MICRODROID",
@@ -239,7 +246,8 @@
boolean vmConsoleInputSupported,
boolean connectVmConsole,
@Nullable File vendorDiskImage,
- @NonNull @OsName String os) {
+ @NonNull @OsName String os,
+ boolean networkSupported) {
// This is only called from Builder.build(); the builder handles parameter validation.
mPackageName = packageName;
mApkPath = apkPath;
@@ -262,6 +270,7 @@
mConnectVmConsole = connectVmConsole;
mVendorDiskImage = vendorDiskImage;
mOs = os;
+ mNetworkSupported = networkSupported;
}
/** Loads a config from a file. */
@@ -362,6 +371,8 @@
}
}
+ builder.setNetworkSupported(b.getBoolean(KEY_NETWORK_SUPPORTED));
+
return builder.build();
}
@@ -412,6 +423,7 @@
String[] extraApks = mExtraApks.toArray(new String[0]);
b.putStringArray(KEY_EXTRA_APKS, extraApks);
}
+ b.putBoolean(KEY_NETWORK_SUPPORTED, mNetworkSupported);
b.writeToStream(output);
}
@@ -588,6 +600,16 @@
}
/**
+ * Returns whether the network feature is supported to the VM or not.
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean isNetworkSupported() {
+ return mNetworkSupported;
+ }
+
+ /**
* Tests if this config is compatible with other config. Being compatible means that the configs
* can be interchangeably used for the same virtual machine; they do not change the VM identity
* or secrets. Such changes include varying the number of CPUs or the size of the RAM. Changes
@@ -653,6 +675,11 @@
Optional.ofNullable(customImageConfig.getBootloaderPath())
.map((path) -> openOrNull(new File(path), MODE_READ_ONLY))
.orElse(null);
+
+ if (config.kernel == null && config.bootloader == null) {
+ config.bootloader = openOrNull(new File(U_BOOT_PREBUILT_PATH), MODE_READ_ONLY);
+ }
+
config.params =
Optional.ofNullable(customImageConfig.getParams())
.map((params) -> TextUtils.join(" ", params))
@@ -682,6 +709,7 @@
config.cpuTopology = (byte) this.mCpuTopology;
config.consoleInputDevice = mConsoleInputDevice;
config.devices = EMPTY_STRING_ARRAY;
+ config.networkSupported = this.mNetworkSupported;
config.platformVersion = "~1.0";
return config;
}
@@ -733,18 +761,23 @@
vsConfig.cpuTopology = android.system.virtualizationservice.CpuTopology.ONE_CPU;
break;
}
- if (mVendorDiskImage != null) {
+
+ if (mVendorDiskImage != null || mNetworkSupported) {
VirtualMachineAppConfig.CustomConfig customConfig =
new VirtualMachineAppConfig.CustomConfig();
customConfig.devices = EMPTY_STRING_ARRAY;
- try {
- customConfig.vendorImage =
- ParcelFileDescriptor.open(mVendorDiskImage, MODE_READ_ONLY);
- } catch (FileNotFoundException e) {
- throw new VirtualMachineException(
- "Failed to open vendor disk image " + mVendorDiskImage.getAbsolutePath(),
- e);
+ if (mVendorDiskImage != null) {
+ try {
+ customConfig.vendorImage =
+ ParcelFileDescriptor.open(mVendorDiskImage, MODE_READ_ONLY);
+ } catch (FileNotFoundException e) {
+ throw new VirtualMachineException(
+ "Failed to open vendor disk image "
+ + mVendorDiskImage.getAbsolutePath(),
+ e);
+ }
}
+ customConfig.networkSupported = mNetworkSupported;
vsConfig.customConfig = customConfig;
}
return vsConfig;
@@ -826,6 +859,7 @@
private boolean mConnectVmConsole = false;
@Nullable private File mVendorDiskImage;
@NonNull @OsName private String mOs = DEFAULT_OS;
+ private boolean mNetworkSupported;
/**
* Creates a builder for the given context.
@@ -902,6 +936,10 @@
"debug level must be FULL to connect to the console");
}
+ if (mNetworkSupported && mProtectedVm) {
+ throw new IllegalStateException("network is not supported on pVM");
+ }
+
return new VirtualMachineConfig(
packageName,
apkPath,
@@ -919,7 +957,8 @@
mVmConsoleInputSupported,
mConnectVmConsole,
mVendorDiskImage,
- mOs);
+ mOs,
+ mNetworkSupported);
}
/**
@@ -1224,5 +1263,22 @@
mOs = requireNonNull(os, "os must not be null");
return this;
}
+
+ /**
+ * Sets whether to support network feature to VM. Default is {@code false}.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(
+ allOf = {
+ VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION,
+ Manifest.permission.INTERNET
+ })
+ @NonNull
+ public Builder setNetworkSupported(boolean networkSupported) {
+ mNetworkSupported = networkSupported;
+ return this;
+ }
}
}
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineManager.java b/java/framework/src/android/system/virtualmachine/VirtualMachineManager.java
index 4a9e943..abb2c81 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -123,6 +123,7 @@
FEATURE_DICE_CHANGES,
FEATURE_LLPVM_CHANGES,
FEATURE_MULTI_TENANT,
+ FEATURE_NETWORK,
FEATURE_REMOTE_ATTESTATION,
FEATURE_VENDOR_MODULES,
})
@@ -147,6 +148,13 @@
public static final String FEATURE_MULTI_TENANT = IVirtualizationService.FEATURE_MULTI_TENANT;
/**
+ * Feature to allow network features in VM.
+ *
+ * @hide
+ */
+ @TestApi public static final String FEATURE_NETWORK = IVirtualizationService.FEATURE_NETWORK;
+
+ /**
* Feature to allow remote attestation in Microdroid.
*
* @hide
diff --git a/libs/avf_features/src/lib.rs b/libs/avf_features/src/lib.rs
index c0faab0..1ebe2a4 100644
--- a/libs/avf_features/src/lib.rs
+++ b/libs/avf_features/src/lib.rs
@@ -16,7 +16,7 @@
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
IVirtualizationService::FEATURE_DICE_CHANGES, IVirtualizationService::FEATURE_LLPVM_CHANGES,
- IVirtualizationService::FEATURE_MULTI_TENANT,
+ IVirtualizationService::FEATURE_MULTI_TENANT, IVirtualizationService::FEATURE_NETWORK,
IVirtualizationService::FEATURE_REMOTE_ATTESTATION,
IVirtualizationService::FEATURE_VENDOR_MODULES,
};
@@ -28,6 +28,7 @@
FEATURE_DICE_CHANGES => cfg!(dice_changes),
FEATURE_LLPVM_CHANGES => cfg!(llpvm_changes),
FEATURE_MULTI_TENANT => cfg!(multi_tenant),
+ FEATURE_NETWORK => cfg!(network),
FEATURE_REMOTE_ATTESTATION => cfg!(remote_attestation),
FEATURE_VENDOR_MODULES => cfg!(vendor_modules),
_ => {
diff --git a/pvmfw/platform.dts b/pvmfw/platform.dts
index 99ecf8f..2df0768 100644
--- a/pvmfw/platform.dts
+++ b/pvmfw/platform.dts
@@ -366,6 +366,12 @@
0x4000 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 7) IRQ_TYPE_LEVEL_HIGH
0x4800 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 8) IRQ_TYPE_LEVEL_HIGH
0x5000 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 9) IRQ_TYPE_LEVEL_HIGH
+ 0x5800 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 10) IRQ_TYPE_LEVEL_HIGH
+ 0x6000 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 11) IRQ_TYPE_LEVEL_HIGH
+ 0x6800 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 12) IRQ_TYPE_LEVEL_HIGH
+ 0x7000 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 13) IRQ_TYPE_LEVEL_HIGH
+ 0x7800 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 14) IRQ_TYPE_LEVEL_HIGH
+ 0x8000 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 15) IRQ_TYPE_LEVEL_HIGH
>;
interrupt-map-mask = <0xf800 0x0 0x0 0x7
0xf800 0x0 0x0 0x7
@@ -376,6 +382,12 @@
0xf800 0x0 0x0 0x7
0xf800 0x0 0x0 0x7
0xf800 0x0 0x0 0x7
+ 0xf800 0x0 0x0 0x7
+ 0xf800 0x0 0x0 0x7
+ 0xf800 0x0 0x0 0x7
+ 0xf800 0x0 0x0 0x7
+ 0xf800 0x0 0x0 0x7
+ 0xf800 0x0 0x0 0x7
0xf800 0x0 0x0 0x7>;
};
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index 9206588..84dc14d 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -515,7 +515,7 @@
impl PciInfo {
const IRQ_MASK_CELLS: usize = 4;
const IRQ_MAP_CELLS: usize = 10;
- const MAX_IRQS: usize = 10;
+ const MAX_IRQS: usize = 16;
}
type PciAddrRange = AddressRange<(u32, u64), u64, u64>;
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 299d1c0..247aa6a 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -116,21 +116,6 @@
info!("Please disregard any previous libavb ERROR about initrd_normal.");
}
- if verified_boot_data.has_capability(Capability::RemoteAttest) {
- info!("Service VM capable of remote attestation detected");
- if service_vm_version::VERSION != verified_boot_data.rollback_index {
- // For RKP VM, we only boot if the version in the AVB footer of its kernel matches
- // the one embedded in pvmfw at build time.
- // This prevents the pvmfw from booting a roll backed RKP VM.
- error!(
- "Service VM version mismatch: expected {}, found {}",
- service_vm_version::VERSION,
- verified_boot_data.rollback_index
- );
- return Err(RebootReason::InvalidPayload);
- }
- }
-
let next_bcc = heap::aligned_boxed_slice(NEXT_BCC_SIZE, GUEST_PAGE_SIZE).ok_or_else(|| {
error!("Failed to allocate the next-stage BCC");
RebootReason::InternalError
@@ -154,16 +139,30 @@
error!("Expected positive rollback_index, found 0");
return Err(RebootReason::InvalidPayload);
};
- // `new_instance` cannot be known to pvmfw
+ (false, instance_hash.unwrap())
+ } else if verified_boot_data.has_capability(Capability::RemoteAttest) {
+ info!("Service VM capable of remote attestation detected, performing version checks");
+ if service_vm_version::VERSION != verified_boot_data.rollback_index {
+ // For RKP VM, we only boot if the version in the AVB footer of its kernel matches
+ // the one embedded in pvmfw at build time.
+ // This prevents the pvmfw from booting a roll backed RKP VM.
+ error!(
+ "Service VM version mismatch: expected {}, found {}",
+ service_vm_version::VERSION,
+ verified_boot_data.rollback_index
+ );
+ return Err(RebootReason::InvalidPayload);
+ }
(false, instance_hash.unwrap())
} else {
+ info!("Fallback to instance.img based rollback checks");
let (recorded_entry, mut instance_img, header_index) =
get_recorded_entry(&mut pci_root, cdi_seal).map_err(|e| {
error!("Failed to get entry from instance.img: {e}");
RebootReason::InternalError
})?;
let (new_instance, salt) = if let Some(entry) = recorded_entry {
- maybe_check_dice_measurements_match_entry(&dice_inputs, &entry)?;
+ check_dice_measurements_match_entry(&dice_inputs, &entry)?;
let salt = instance_hash.unwrap_or(entry.salt);
(false, salt)
} else {
@@ -244,21 +243,10 @@
Ok(bcc_range)
}
-fn maybe_check_dice_measurements_match_entry(
+fn check_dice_measurements_match_entry(
dice_inputs: &PartialInputs,
entry: &EntryBody,
) -> Result<(), RebootReason> {
- // The RKP VM is allowed to run if it has passed the verified boot check and
- // contains the expected version in its AVB footer.
- // The comparison below with the previous boot information is skipped to enable the
- // simultaneous update of the pvmfw and RKP VM.
- // For instance, when both the pvmfw and RKP VM are updated, the code hash of the
- // RKP VM will differ from the one stored in the instance image. In this case, the
- // RKP VM is still allowed to run.
- // This ensures that the updated RKP VM will retain the same CDIs in the next stage.
- if dice_inputs.rkp_vm_marker {
- return Ok(());
- }
ensure_dice_measurements_match_entry(dice_inputs, entry).map_err(|e| {
error!(
"Dice measurements do not match recorded entry. \
diff --git a/service_vm/test_apk/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java b/service_vm/test_apk/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java
index af99711..ff760b4 100644
--- a/service_vm/test_apk/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java
+++ b/service_vm/test_apk/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java
@@ -69,6 +69,9 @@
.that(isCuttlefish())
.isFalse();
assumeFeatureEnabled(VirtualMachineManager.FEATURE_REMOTE_ATTESTATION);
+ assume().withMessage("Test needs Remote Attestation support")
+ .that(getVirtualMachineManager().isRemoteAttestationSupported())
+ .isTrue();
VirtualMachineConfig.Builder builder =
newVmConfigBuilderWithPayloadBinary(VM_PAYLOAD_PATH)
diff --git a/tests/ferrochrome/Android.bp b/tests/ferrochrome/Android.bp
new file mode 100644
index 0000000..889f41e
--- /dev/null
+++ b/tests/ferrochrome/Android.bp
@@ -0,0 +1,13 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+sh_test_host {
+ name: "ferrochrome-tests",
+ src: "ferrochrome.sh",
+ test_options: {
+ unit_test: false,
+ },
+ per_testcase_directory: true,
+ data: ["assets/vm_config.json"],
+}
diff --git a/tests/ferrochrome/AndroidTest.xml b/tests/ferrochrome/AndroidTest.xml
new file mode 100644
index 0000000..3f902ea
--- /dev/null
+++ b/tests/ferrochrome/AndroidTest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2024 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.
+-->
+<configuration description="Host driven tests for ferrochrome">
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <!-- 'adb root' to enable vmlauncher -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ <option name="force-root" value="true"/>
+ </target_preparer>
+
+ <!-- Check presence of vmlauncher here, because we can't skip tests in shell test -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="throw-if-cmd-fail" value="true" />
+ <option name="run-command" value="pm list packages | grep vmlauncher" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.binary.ExecutableHostTest" >
+ <option name="binary" value="ferrochrome-tests" />
+ <option name="runtime-hint" value="10m" />
+ <option name="per-binary-timeout" value="20m" />
+ </test>
+</configuration>
+
diff --git a/tests/ferrochrome/assets/vm_config.json b/tests/ferrochrome/assets/vm_config.json
new file mode 100644
index 0000000..f8a3099
--- /dev/null
+++ b/tests/ferrochrome/assets/vm_config.json
@@ -0,0 +1,18 @@
+{
+ "name": "cros",
+ "kernel": "/data/local/tmp/ferrochrome/vmlinuz",
+ "disks": [
+ {
+ "image": "/data/local/tmp/ferrochrome/chromiumos_test_image.bin",
+ "partitions": [],
+ "writable": true
+ }
+ ],
+ "params": "root=/dev/vda3 rootwait noinitrd ro enforcing=0 cros_debug cros_secure",
+ "protected": false,
+ "cpu_topology": "match_host",
+ "platform_version": "~1.0",
+ "memory_mib" : 8096,
+ "console_input_device": "ttyS0"
+}
+
diff --git a/tests/ferrochrome/ferrochrome.sh b/tests/ferrochrome/ferrochrome.sh
new file mode 100755
index 0000000..4dde401
--- /dev/null
+++ b/tests/ferrochrome/ferrochrome.sh
@@ -0,0 +1,142 @@
+#!/bin/bash
+
+# Copyright 2024 Google Inc. All rights reserved.
+#
+# 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.
+
+## Booting tests for ferrochrome
+## Keep this file synced with docs/custom_vm.md
+
+set -e
+
+FECR_GS_URL="https://storage.googleapis.com/chromiumos-image-archive/ferrochrome-public"
+FECR_DEFAULT_VERSION="R127-15916.0.0"
+FECR_DEVICE_DIR="/data/local/tmp/ferrochrome"
+FECR_CONFIG_PATH="/data/local/tmp/vm_config.json" # hardcoded at VmLauncherApp
+FECR_CONSOLE_LOG_PATH="/data/data/\${pkg_name}/files/console.log"
+FECR_BOOT_COMPLETED_LOG="Have fun and send patches!"
+FECR_BOOT_TIMEOUT="300" # 5 minutes (300 seconds)
+AOSP_PKG_NAME="com.android.virtualization.vmlauncher"
+SIGNED_PKG_NAME="com.google.android.virtualization.vmlauncher"
+
+fecr_clean_up() {
+ trap - INT
+
+ if [[ -d ${fecr_dir} && -z ${fecr_keep} ]]; then
+ rm -rf ${fecr_dir}
+ fi
+}
+
+print_usage() {
+ echo "ferochrome.sh: Launches ferrochrome image"
+ echo ""
+ echo "By default, this downloads ferrochrome image with version ${FECR_DEFAULT_VERSION},"
+ echo "launches, and waits for boot completed."
+ echo "When done, removes downloaded image."
+ echo ""
+ echo "Usage: ferrochrome.sh [options]"
+ echo ""
+ echo "Options"
+ echo " --help or -h: This message"
+ echo " --dir \${dir}: Use ferrochrome images at the dir instead of downloading"
+ echo " --skip: Skipping downloading and/or pushing images"
+ echo " --version \${version}: ferrochrome version to be downloaded"
+ echo " --keep: Keep downloaded ferrochrome image"
+}
+
+
+fecr_version=""
+fecr_dir=""
+fecr_keep=""
+fecr_skip=""
+fecr_script_path=$(dirname ${0})
+
+# Parse parameters
+while (( "${#}" )); do
+ case "${1}" in
+ --version)
+ shift
+ fecr_version="${1}"
+ ;;
+ --dir)
+ shift
+ fecr_dir="${1}"
+ fecr_keep="true"
+ ;;
+ --keep)
+ fecr_keep="true"
+ ;;
+ --skip)
+ fecr_skip="true"
+ ;;
+ -h|--help)
+ print_usage
+ exit 0
+ ;;
+ *)
+ print_usage
+ exit 1
+ ;;
+ esac
+ shift
+done
+
+trap fecr_clean_up INT
+trap fecr_clean_up EXIT
+
+if [[ -z "${fecr_skip}" ]]; then
+ if [[ -z "${fecr_dir}" ]]; then
+ # Download fecr image archive, and extract necessary files
+ # DISCLAIMER: Image is too large (1.5G+ for compressed, 6.5G+ for uncompressed), so can't submit.
+ fecr_dir=$(mktemp -d)
+
+ echo "Downloading ferrochrome image to ${fecr_dir}"
+ fecr_version=${fecr_version:-${FECR_DEFAULT_VERSION}}
+ curl --output-dir ${fecr_dir} -O ${FECR_GS_URL}/${fecr_version}/image.zip
+ fi
+ if [[ ! -f "${fecr_dir}/chromiumos_test_image.bin" ]]; then
+ unzip ${fecr_dir}/image.zip chromiumos_test_image.bin boot_images/vmlinuz* -d ${fecr_dir} > /dev/null
+ fi
+
+ echo "Pushing ferrochrome image to ${FECR_DEVICE_DIR}"
+ adb shell mkdir -p ${FECR_DEVICE_DIR} > /dev/null || true
+ adb push ${fecr_dir}/chromiumos_test_image.bin ${FECR_DEVICE_DIR}
+ adb push ${fecr_dir}/boot_images/vmlinuz ${FECR_DEVICE_DIR}
+ adb push ${fecr_script_path}/assets/vm_config.json ${FECR_CONFIG_PATH}
+fi
+
+adb root > /dev/null
+adb shell pm list packages | grep ${AOSP_PKG_NAME} > /dev/null
+if [[ "${?}" == "0" ]]; then
+ pkg_name=${AOSP_PKG_NAME}
+else
+ pkg_name=${SIGNED_PKG_NAME}
+fi
+
+adb shell pm enable ${pkg_name}/${AOSP_PKG_NAME}.MainActivity > /dev/null
+adb shell pm grant ${pkg_name} android.permission.USE_CUSTOM_VIRTUAL_MACHINE > /dev/null
+adb shell pm clear ${pkg_name} > /dev/null
+
+echo "Starting ferrochrome"
+adb shell am start-activity ${pkg_name}/${AOSP_PKG_NAME}.MainActivity > /dev/null
+
+log_path="/data/data/${pkg_name}/files/console.log"
+fecr_start_time=${EPOCHSECONDS}
+
+while [[ $((EPOCHSECONDS - fecr_start_time)) -lt ${FECR_BOOT_TIMEOUT} ]]; do
+ adb shell grep -sF \""${FECR_BOOT_COMPLETED_LOG}"\" "${log_path}" && exit 0
+ sleep 10
+done
+
+echo "Ferrochrome failed to boot"
+exit 1
diff --git a/tests/helper/Android.bp b/tests/helper/Android.bp
index 41d1ba2..1c38d12 100644
--- a/tests/helper/Android.bp
+++ b/tests/helper/Android.bp
@@ -33,4 +33,5 @@
],
host_supported: true,
device_supported: false,
+ sdk_version: "test_current",
}
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 9d0b04b..80d1fc6 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -1038,7 +1038,7 @@
assumeFalse("Unlocked devices may have AVF debug policy", lockProp.equals("orange"));
// Test that AVF debug policy doesn't exist.
- boolean hasDebugPolicy = device.doesFileExist("/proc/device-tree/avf");
+ boolean hasDebugPolicy = device.doesFileExist("/proc/device-tree/avf/guest");
assertThat(hasDebugPolicy).isFalse();
}
diff --git a/tests/testapk/AndroidManifestV5.xml b/tests/testapk/AndroidManifestV5.xml
index 7d97680..b869586 100644
--- a/tests/testapk/AndroidManifestV5.xml
+++ b/tests/testapk/AndroidManifestV5.xml
@@ -18,6 +18,7 @@
android:versionCode="5">
<uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
<uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
+ <uses-permission android:name="android.permission.INTERNET" />
<uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
<uses-feature android:name="android.software.virtualization_framework" android:required="false" />
<queries>
diff --git a/tests/testapk/AndroidManifestV6.xml b/tests/testapk/AndroidManifestV6.xml
index 19d5674..c55da85 100644
--- a/tests/testapk/AndroidManifestV6.xml
+++ b/tests/testapk/AndroidManifestV6.xml
@@ -18,6 +18,7 @@
android:versionCode="6">
<uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
<uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
+ <uses-permission android:name="android.permission.INTERNET" />
<uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
<uses-feature android:name="android.software.virtualization_framework" android:required="false" />
<queries>
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 12a46f7..4d8dac6 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -571,8 +571,9 @@
assertThat(minimal.isProtectedVm()).isEqualTo(isProtectedVm());
assertThat(minimal.isEncryptedStorageEnabled()).isFalse();
assertThat(minimal.getEncryptedStorageBytes()).isEqualTo(0);
- assertThat(minimal.isVmOutputCaptured()).isEqualTo(false);
+ assertThat(minimal.isVmOutputCaptured()).isFalse();
assertThat(minimal.getOs()).isEqualTo("microdroid");
+ assertThat(minimal.isNetworkSupported()).isFalse();
// Maximal has everything that can be set to some non-default value. (And has different
// values than minimal for the required fields.)
@@ -589,6 +590,9 @@
.setEncryptedStorageBytes(1_000_000)
.setVmOutputCaptured(true)
.setOs("microdroid_gki-android14-6.1");
+ if (!mProtectedVm) {
+ maximalBuilder.setNetworkSupported(true);
+ }
VirtualMachineConfig maximal = maximalBuilder.build();
assertThat(maximal.getApkPath()).isEqualTo("/apk/path");
@@ -603,8 +607,11 @@
assertThat(maximal.isProtectedVm()).isEqualTo(isProtectedVm());
assertThat(maximal.isEncryptedStorageEnabled()).isTrue();
assertThat(maximal.getEncryptedStorageBytes()).isEqualTo(1_000_000);
- assertThat(maximal.isVmOutputCaptured()).isEqualTo(true);
+ assertThat(maximal.isVmOutputCaptured()).isTrue();
assertThat(maximal.getOs()).isEqualTo("microdroid_gki-android14-6.1");
+ if (!mProtectedVm) {
+ assertThat(maximal.isNetworkSupported()).isTrue();
+ }
assertThat(minimal.isCompatibleWith(maximal)).isFalse();
assertThat(minimal.isCompatibleWith(minimal)).isTrue();
@@ -659,6 +666,18 @@
.setVmConsoleInputSupported(true);
e = assertThrows(IllegalStateException.class, () -> captureInputOnNonDebuggable.build());
assertThat(e).hasMessageThat().contains("debug level must be FULL to use console input");
+
+ if (mProtectedVm) {
+ VirtualMachineConfig.Builder networkSupportedOnProtectedVm =
+ newVmConfigBuilderWithPayloadBinary("binary.so")
+ .setProtectedVm(mProtectedVm)
+ .setNetworkSupported(true);
+ e =
+ assertThrows(
+ IllegalStateException.class,
+ () -> networkSupportedOnProtectedVm.build());
+ assertThat(e).hasMessageThat().contains("network is not supported on pVM");
+ }
}
@Test
@@ -2300,6 +2319,49 @@
}
}
+ private VirtualMachineConfig buildVmConfigWithNetworkSupported() throws Exception {
+ return buildVmConfigWithNetworkSupported("MicrodroidTestNativeLib.so");
+ }
+
+ private VirtualMachineConfig buildVmConfigWithNetworkSupported(String binaryPath)
+ throws Exception {
+ assumeSupportedDevice();
+ assumeNonProtectedVM();
+ assumeFeatureEnabled(VirtualMachineManager.FEATURE_NETWORK);
+ VirtualMachineConfig config =
+ newVmConfigBuilderWithPayloadBinary(binaryPath)
+ .setNetworkSupported(true)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+ grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
+ return config;
+ }
+
+ @Test
+ public void configuringNetworkSupportedRequiresCustomPermission() throws Exception {
+ VirtualMachineConfig config = buildVmConfigWithNetworkSupported();
+ revokePermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
+
+ VirtualMachine vm =
+ forceCreateNewVirtualMachine(
+ "test_network_supported_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 bootsWithNetworkSupported() throws Exception {
+ VirtualMachineConfig config = buildVmConfigWithNetworkSupported();
+
+ VirtualMachine vm =
+ forceCreateNewVirtualMachine("test_boot_with_network_supported", config);
+ runVmTestService(TAG, vm, (ts, tr) -> {}).assertNoException();
+ }
+
private VirtualMachineConfig buildVmConfigWithVendor(File vendorDiskImage) throws Exception {
return buildVmConfigWithVendor(vendorDiskImage, "MicrodroidTestNativeLib.so");
}
diff --git a/tests/testapk_no_internet_perm/Android.bp b/tests/testapk_no_internet_perm/Android.bp
new file mode 100644
index 0000000..d23081f
--- /dev/null
+++ b/tests/testapk_no_internet_perm/Android.bp
@@ -0,0 +1,26 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "MicrodroidTestAppNoInternetPerm",
+ static_libs: [
+ "MicrodroidDeviceTestHelper",
+ "MicrodroidTestHelper",
+ "androidx.test.runner",
+ "androidx.test.ext.junit",
+ "com.android.microdroid.testservice-java",
+ "truth",
+ "compatibility-common-util-devicesidelib",
+ ],
+ jni_libs: [
+ "MicrodroidTestNativeLib",
+ ],
+ test_suites: [
+ "general-tests",
+ "cts",
+ ],
+ srcs: ["src/java/**/*.java"],
+ defaults: ["MicrodroidTestAppsDefaults"],
+ min_sdk_version: "34",
+}
diff --git a/tests/testapk_no_internet_perm/AndroidManifest.xml b/tests/testapk_no_internet_perm/AndroidManifest.xml
new file mode 100644
index 0000000..87b302a
--- /dev/null
+++ b/tests/testapk_no_internet_perm/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.microdroid.test_no_internet_perm">
+ <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
+ <uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
+ <uses-sdk android:minSdkVersion="34" android:targetSdkVersion="34" />
+ <uses-feature android:name="android.software.virtualization_framework" android:required="false" />
+ <application />
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.microdroid.test_no_internet_perm"
+ android:label="No Internet Permission Microdroid Test" />
+</manifest>
diff --git a/tests/testapk_no_internet_perm/AndroidTest.xml b/tests/testapk_no_internet_perm/AndroidTest.xml
new file mode 100644
index 0000000..61f8b8c
--- /dev/null
+++ b/tests/testapk_no_internet_perm/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+<configuration description="Runs Microdroid Tests with no internet permission">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="security" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="MicrodroidTestAppNoInternetPerm.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.microdroid.test_no_internet_perm" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="shell-timeout" value="300000" />
+ <option name="test-timeout" value="300000" />
+ </test>
+</configuration>
diff --git a/tests/testapk_no_internet_perm/src/java/com/android/microdroid/test/MicrodroidTestAppNoInternetPerm.java b/tests/testapk_no_internet_perm/src/java/com/android/microdroid/test/MicrodroidTestAppNoInternetPerm.java
new file mode 100644
index 0000000..767f745
--- /dev/null
+++ b/tests/testapk_no_internet_perm/src/java/com/android/microdroid/test/MicrodroidTestAppNoInternetPerm.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 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.
+ */
+
+package com.android.microdroid.test;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineManager;
+
+import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Test that the android.permission.MANAGE_VIRTUAL_MACHINE is enforced and that an app cannot launch
+ * a VM without said permission.
+ */
+@RunWith(Parameterized.class)
+public class MicrodroidTestAppNoInternetPerm extends MicrodroidDeviceTestBase {
+ private static final String TAG = "MicrodroidTestAppNoInternetPerm";
+
+ @Parameterized.Parameters(name = "protectedVm={0}")
+ public static Object[] protectedVmConfigs() {
+ return new Object[] {false, true};
+ }
+
+ @Parameterized.Parameter public boolean mProtectedVm;
+
+ @Before
+ public void setup() {
+ prepareTestSetup(mProtectedVm, null);
+ }
+
+ @Test
+ public void configuringNetworkSupportedRequiresInternetPermission() throws Exception {
+ assumeSupportedDevice();
+ assumeNonProtectedVM();
+ assumeFeatureEnabled(VirtualMachineManager.FEATURE_NETWORK);
+
+ VirtualMachineConfig config =
+ newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
+ .setNetworkSupported(true)
+ .build();
+
+ VirtualMachine vm =
+ forceCreateNewVirtualMachine(
+ "config_network_supported_req_internet_permission", config);
+ SecurityException e =
+ assertThrows(
+ SecurityException.class, () -> runVmTestService(TAG, vm, (ts, tr) -> {}));
+ assertThat(e).hasMessageThat().contains("android.permission.INTERNET permission");
+ }
+}
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 6408b84..4b03bac 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -14,10 +14,11 @@
//! Functions for running instances of `crosvm`.
-use crate::aidl::{remove_temporary_files, Cid, VirtualMachineCallbacks};
+use crate::aidl::{remove_temporary_files, Cid, GLOBAL_SERVICE, VirtualMachineCallbacks};
use crate::atom::{get_num_cpus, write_vm_exited_stats_sync};
use crate::debug_config::DebugConfig;
use anyhow::{anyhow, bail, Context, Error, Result};
+use binder::ParcelFileDescriptor;
use command_fds::CommandFdExt;
use lazy_static::lazy_static;
use libc::{sysconf, _SC_CLK_TCK};
@@ -34,7 +35,7 @@
use std::io::{self, Read};
use std::mem;
use std::num::{NonZeroU16, NonZeroU32};
-use std::os::unix::io::{AsRawFd, RawFd};
+use std::os::unix::io::{AsRawFd, OwnedFd, RawFd};
use std::os::unix::process::ExitStatusExt;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
@@ -241,6 +242,8 @@
let detect_hangup = config.detect_hangup;
let (failure_pipe_read, failure_pipe_write) = create_pipe()?;
let vfio_devices = config.vfio_devices.clone();
+ let tap =
+ if let Some(tap_file) = &config.tap { Some(tap_file.try_clone()?) } else { None };
// If this fails and returns an error, `self` will be left in the `Failed` state.
let child =
@@ -255,7 +258,7 @@
let child_clone = child.clone();
let instance_clone = instance.clone();
let monitor_vm_exit_thread = Some(thread::spawn(move || {
- instance_clone.monitor_vm_exit(child_clone, failure_pipe_read, vfio_devices);
+ instance_clone.monitor_vm_exit(child_clone, failure_pipe_read, vfio_devices, tap);
}));
if detect_hangup {
@@ -397,6 +400,7 @@
child: Arc<SharedChild>,
mut failure_pipe_read: File,
vfio_devices: Vec<VfioDevice>,
+ tap: Option<File>,
) {
let result = child.wait();
match &result {
@@ -456,6 +460,14 @@
error!("Error removing temporary files from {:?}: {}", self.temporary_directory, e);
});
+ if let Some(tap_file) = tap {
+ GLOBAL_SERVICE
+ .deleteTapInterface(&ParcelFileDescriptor::new(OwnedFd::from(tap_file)))
+ .unwrap_or_else(|e| {
+ error!("Error deleting TAP interface: {e:?}");
+ });
+ }
+
drop(vfio_devices); // Cleanup devices.
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index f8b5087..234d8d0 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -25,6 +25,7 @@
const String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
const String FEATURE_LLPVM_CHANGES = "com.android.kvm.LLPVM_CHANGES";
const String FEATURE_MULTI_TENANT = "com.android.kvm.MULTI_TENANT";
+ const String FEATURE_NETWORK = "com.android.kvm.NETWORK";
const String FEATURE_REMOTE_ATTESTATION = "com.android.kvm.REMOTE_ATTESTATION";
const String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index 4e6879d..0da7755 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -128,4 +128,10 @@
* @return file descriptor of the TAP network interface.
*/
ParcelFileDescriptor createTapInterface(String ifaceNameSuffix);
+
+ /**
+ * Delete TAP network interface created for a VM.
+ * @param file descriptor of the TAP network interface.
+ */
+ void deleteTapInterface(in ParcelFileDescriptor tapFd);
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVmnic.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVmnic.aidl
index 66739da..e3cc73a 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVmnic.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVmnic.aidl
@@ -22,4 +22,10 @@
* @return file descriptor of the TAP network interface.
*/
ParcelFileDescriptor createTapInterface(String ifaceNameSuffix);
+
+ /**
+ * Delete TAP network interface created for a VM.
+ * @param file descriptor of the TAP network interface.
+ */
+ void deleteTapInterface(in ParcelFileDescriptor tapFd);
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 2fc9b4c..241baa5 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -353,6 +353,7 @@
))
.with_log();
}
+ remote_provisioning::check_remote_attestation_is_supported()?;
info!("Received csr. Requestting attestation...");
let (key_blob, certificate_chain) = if test_mode {
check_use_custom_virtual_machine()?;
@@ -403,7 +404,8 @@
}
fn isRemoteAttestationSupported(&self) -> binder::Result<bool> {
- is_remote_provisioning_hal_declared()
+ Ok(is_remote_provisioning_hal_declared()?
+ && remote_provisioning::is_remote_attestation_supported())
}
fn getAssignableDevices(&self) -> binder::Result<Vec<AssignableDevice>> {
@@ -508,6 +510,7 @@
}
fn createTapInterface(&self, iface_name_suffix: &str) -> binder::Result<ParcelFileDescriptor> {
+ check_internet_permission()?;
check_use_custom_virtual_machine()?;
if !cfg!(network) {
return Err(Status::new_exception_str(
@@ -518,6 +521,19 @@
}
NETWORK_SERVICE.createTapInterface(iface_name_suffix)
}
+
+ fn deleteTapInterface(&self, tap_fd: &ParcelFileDescriptor) -> binder::Result<()> {
+ check_internet_permission()?;
+ check_use_custom_virtual_machine()?;
+ if !cfg!(network) {
+ return Err(Status::new_exception_str(
+ ExceptionCode::UNSUPPORTED_OPERATION,
+ Some("deleteTapInterface is not supported with the network feature disabled"),
+ ))
+ .with_log();
+ }
+ NETWORK_SERVICE.deleteTapInterface(tap_fd)
+ }
}
impl IVirtualizationMaintenance for VirtualizationServiceInternal {
@@ -909,6 +925,12 @@
check_permission("android.permission.USE_CUSTOM_VIRTUAL_MACHINE")
}
+/// Check whether the caller of the current Binder method is allowed to create socket and
+/// establish connection between the VM and the Internet.
+fn check_internet_permission() -> binder::Result<()> {
+ check_permission("android.permission.INTERNET")
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/virtualizationservice/src/remote_provisioning.rs b/virtualizationservice/src/remote_provisioning.rs
index c2c04df..490ff01 100644
--- a/virtualizationservice/src/remote_provisioning.rs
+++ b/virtualizationservice/src/remote_provisioning.rs
@@ -32,6 +32,7 @@
Strong,
};
use hypervisor_props::is_protected_vm_supported;
+use rustutils::system_properties;
use service_vm_comm::{RequestProcessingError, Response};
/// Constructs a binder object that implements `IRemotelyProvisionedComponent`.
@@ -49,7 +50,7 @@
#[allow(non_snake_case)]
impl IRemotelyProvisionedComponent for AvfRemotelyProvisionedComponent {
fn getHardwareInfo(&self) -> BinderResult<RpcHardwareInfo> {
- check_protected_vm_is_supported()?;
+ check_remote_attestation_is_supported()?;
Ok(RpcHardwareInfo {
versionNumber: 3,
@@ -65,7 +66,7 @@
testMode: bool,
macedPublicKey: &mut MacedPublicKey,
) -> BinderResult<Vec<u8>> {
- check_protected_vm_is_supported()?;
+ check_remote_attestation_is_supported()?;
if testMode {
return Err(Status::new_service_specific_error_str(
@@ -109,7 +110,7 @@
keysToSign: &[MacedPublicKey],
challenge: &[u8],
) -> BinderResult<Vec<u8>> {
- check_protected_vm_is_supported()?;
+ check_remote_attestation_is_supported()?;
const MAX_CHALLENGE_SIZE: usize = 64;
if challenge.len() > MAX_CHALLENGE_SIZE {
@@ -133,16 +134,27 @@
}
}
-fn check_protected_vm_is_supported() -> BinderResult<()> {
- if is_protected_vm_supported().unwrap_or(false) {
- Ok(())
- } else {
- Err(Status::new_exception_str(
+pub(crate) fn check_remote_attestation_is_supported() -> BinderResult<()> {
+ if !is_protected_vm_supported().unwrap_or(false) {
+ return Err(Status::new_exception_str(
ExceptionCode::UNSUPPORTED_OPERATION,
Some("Protected VM support is missing for this operation"),
))
- .with_log()
+ .with_log();
}
+ if !is_remote_attestation_supported() {
+ return Err(Status::new_exception_str(
+ ExceptionCode::UNSUPPORTED_OPERATION,
+ Some("Remote attestation is disabled"),
+ ))
+ .with_log();
+ }
+ Ok(())
+}
+
+pub(crate) fn is_remote_attestation_supported() -> bool {
+ // Remote attestation is enabled by default.
+ system_properties::read_bool("avf.remote_attestation.enabled", true).unwrap_or(true)
}
pub(crate) fn to_service_specific_error(response: Response) -> Status {
diff --git a/virtualizationservice/vmnic/src/aidl.rs b/virtualizationservice/vmnic/src/aidl.rs
index a206c25..69c37b8 100644
--- a/virtualizationservice/vmnic/src/aidl.rs
+++ b/virtualizationservice/vmnic/src/aidl.rs
@@ -22,19 +22,19 @@
use nix::{ioctl_write_int_bad, ioctl_write_ptr_bad};
use nix::sys::ioctl::ioctl_num_type;
use nix::sys::socket::{socket, AddressFamily, SockFlag, SockType};
-use std::ffi::CString;
+use std::ffi::{CStr, CString};
use std::fs::File;
use std::os::fd::{AsRawFd, RawFd};
use std::slice::from_raw_parts;
+const TUNGETIFF: ioctl_num_type = 0x800454d2u32 as c_int;
const TUNSETIFF: ioctl_num_type = 0x400454ca;
const TUNSETPERSIST: ioctl_num_type = 0x400454cb;
-const SIOCGIFFLAGS: ioctl_num_type = 0x00008913;
const SIOCSIFFLAGS: ioctl_num_type = 0x00008914;
+ioctl_write_ptr_bad!(ioctl_tungetiff, TUNGETIFF, ifreq);
ioctl_write_ptr_bad!(ioctl_tunsetiff, TUNSETIFF, ifreq);
ioctl_write_int_bad!(ioctl_tunsetpersist, TUNSETPERSIST);
-ioctl_write_ptr_bad!(ioctl_siocgifflags, SIOCGIFFLAGS, ifreq);
ioctl_write_ptr_bad!(ioctl_siocsifflags, SIOCSIFFLAGS, ifreq);
fn validate_ifname(ifname: &[c_char]) -> Result<()> {
@@ -44,32 +44,38 @@
Ok(())
}
-fn create_tap_interface(fd: RawFd, ifname: &[c_char]) -> Result<()> {
+fn create_tap_interface(fd: RawFd, sockfd: c_int, ifname: &[c_char]) -> Result<()> {
// SAFETY: All-zero is a valid value for the ifreq type.
let mut ifr: ifreq = unsafe { std::mem::zeroed() };
ifr.ifr_ifru.ifru_flags = (IFF_TAP | IFF_NO_PI | IFF_VNET_HDR) as c_short;
ifr.ifr_name[..ifname.len()].copy_from_slice(ifname);
- // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
- // state of this process in any way.
+ // SAFETY: It modifies the state in the kernel, not the state of this process in any way.
unsafe { ioctl_tunsetiff(fd, &ifr) }.context("Failed to ioctl TUNSETIFF")?;
- // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
- // state of this process in any way.
+ // SAFETY: It modifies the state in the kernel, not the state of this process in any way.
unsafe { ioctl_tunsetpersist(fd, 1) }.context("Failed to ioctl TUNSETPERSIST")?;
+ // SAFETY: ifr_ifru holds ifru_flags in its union field.
+ unsafe { ifr.ifr_ifru.ifru_flags |= IFF_UP as c_short };
+ // SAFETY: It modifies the state in the kernel, not the state of this process in any way.
+ unsafe { ioctl_siocsifflags(sockfd, &ifr) }.context("Failed to ioctl SIOCSIFFLAGS")?;
Ok(())
}
-fn bring_up_interface(sockfd: c_int, ifname: &[c_char]) -> Result<()> {
+fn get_tap_ifreq(fd: RawFd) -> Result<ifreq> {
// SAFETY: All-zero is a valid value for the ifreq type.
- let mut ifr: ifreq = unsafe { std::mem::zeroed() };
- ifr.ifr_name[..ifname.len()].copy_from_slice(ifname);
- // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
- // state of this process in any way.
- unsafe { ioctl_siocgifflags(sockfd, &ifr) }.context("Failed to ioctl SIOCGIFFLAGS")?;
- // SAFETY: After calling SIOCGIFFLAGS, ifr_ifru holds ifru_flags in its union field.
- unsafe { ifr.ifr_ifru.ifru_flags |= IFF_UP as c_short };
- // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
- // state of this process in any way.
- unsafe { ioctl_siocsifflags(sockfd, &ifr) }.context("Failed to ioctl SIOCGIFFLAGS")?;
+ let ifr: ifreq = unsafe { std::mem::zeroed() };
+ // SAFETY: Returned `ifr` of given file descriptor is set from TUNSETIFF ioctl while executing
+ // create_tap_interface(fd, sockfd, ifname). So the variable `ifr` should be safe.
+ unsafe { ioctl_tungetiff(fd, &ifr) }.context("Failed to ioctl TUNGETIFF")?;
+ Ok(ifr)
+}
+
+fn delete_tap_interface(fd: RawFd, sockfd: c_int, ifr: &mut ifreq) -> Result<()> {
+ // SAFETY: After calling TUNGETIFF, ifr_ifru holds ifru_flags in its union field.
+ unsafe { ifr.ifr_ifru.ifru_flags &= !IFF_UP as c_short };
+ // SAFETY: It modifies the state in the kernel, not the state of this process in any way.
+ unsafe { ioctl_siocsifflags(sockfd, ifr) }.context("Failed to ioctl SIOCSIFFLAGS")?;
+ // SAFETY: It modifies the state in the kernel, not the state of this process in any way.
+ unsafe { ioctl_tunsetpersist(fd, 0) }.context("Failed to ioctl TUNSETPERSIST")?;
Ok(())
}
@@ -102,18 +108,34 @@
let tunfd = File::open("/dev/tun")
.context("Failed to open /dev/tun")
.or_service_specific_exception(-1)?;
- create_tap_interface(tunfd.as_raw_fd(), ifname_bytes)
- .context(format!("Failed to create TAP interface: {ifname:#?}"))
- .or_service_specific_exception(-1)?;
-
let sock = socket(AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), None)
.context("Failed to create socket")
.or_service_specific_exception(-1)?;
- bring_up_interface(sock.as_raw_fd(), ifname_bytes)
- .context(format!("Failed to bring up TAP interface: {ifname:#?}"))
+ create_tap_interface(tunfd.as_raw_fd(), sock.as_raw_fd(), ifname_bytes)
+ .context(format!("Failed to create TAP interface: {ifname:#?}"))
.or_service_specific_exception(-1)?;
info!("Created TAP network interface: {ifname:#?}");
Ok(ParcelFileDescriptor::new(tunfd))
}
+
+ fn deleteTapInterface(&self, tapfd: &ParcelFileDescriptor) -> binder::Result<()> {
+ let tap = tapfd.as_raw_fd();
+ let mut tap_ifreq = get_tap_ifreq(tap)
+ .context("Failed to get ifreq of TAP interface")
+ .or_service_specific_exception(-1)?;
+ // SAFETY: tap_ifreq.ifr_name is null-terminated within IFNAMSIZ, validated when creating
+ // TAP interface.
+ let ifname = unsafe { CStr::from_ptr(tap_ifreq.ifr_name.as_ptr()) };
+
+ let sock = socket(AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), None)
+ .context("Failed to create socket")
+ .or_service_specific_exception(-1)?;
+ delete_tap_interface(tap, sock.as_raw_fd(), &mut tap_ifreq)
+ .context(format!("Failed to create TAP interface: {ifname:#?}"))
+ .or_service_specific_exception(-1)?;
+
+ info!("Deleted TAP network interface: {ifname:#?}");
+ Ok(())
+ }
}
diff --git a/vmbase/src/uart.rs b/vmbase/src/uart.rs
index 09d747f..e35555d 100644
--- a/vmbase/src/uart.rs
+++ b/vmbase/src/uart.rs
@@ -16,7 +16,6 @@
//! provided by crosvm, and won't work with real hardware.
use core::fmt::{self, Write};
-use core::ptr::write_volatile;
/// Minimal driver for an 8250 UART. This only implements enough to work with the emulated 8250
/// provided by crosvm, and won't work with real hardware.
@@ -41,7 +40,11 @@
// SAFETY: We know that the base address points to the control registers of a UART device
// which is appropriately mapped.
unsafe {
- write_volatile(self.base_address, byte);
+ core::arch::asm!(
+ "strb {value:w}, [{ptr}]",
+ value = in(reg) byte,
+ ptr = in(reg) self.base_address,
+ );
}
}
}