Merge "If rollback_index is specified use it in the dice chain of pVM" into main
diff --git a/docs/mainline_module_payload.md b/docs/mainline_module_payload.md
index 84617f0..a7601f6 100644
--- a/docs/mainline_module_payload.md
+++ b/docs/mainline_module_payload.md
@@ -1,5 +1,7 @@
# Delivery Microdroid pVM payload via Mainline modules
+Note: this feature is under development, use it with cauition!
+
There are several additional challenges when a Microdroid pVM payload is
delivered inside a Mainline module.
@@ -13,7 +15,20 @@
To work around this challenge, payloads delivered via Mainline modules are
expected to request
`android.permission.USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION` privileged
-permission.
+permission. Additionally they need to specify a
+`android.system.virtualmachine.ROLLBACK_INDEX` property in their manifest, e.g.:
-TODO(ioffe): add more context on how permission is used once the implementation
-is done.
+```xml
+<uses-permission android:name="android.permission.USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION" />
+<application>
+ <property android:name="android.system.virtualmachine.ROLLBACK_INDEX" android:value="1" />
+</application>
+```
+
+If apk manifest has both permission and the property specified then the value of
+the `android.system.virtualmachine.ROLLBACK_INDEX` property is used by
+`microdroid_manager` when constructing the payload node of the dice chain.
+
+Please check the tests prefixed with `relaxedRollbackProtectionScheme` to get
+more context on the behaviour.
+
diff --git a/guest/microdroid_manager/src/dice.rs b/guest/microdroid_manager/src/dice.rs
index dd5375f..7952210 100644
--- a/guest/microdroid_manager/src/dice.rs
+++ b/guest/microdroid_manager/src/dice.rs
@@ -100,7 +100,10 @@
fn for_apk(apk: &ApkData) -> Self {
Self {
name: format!("apk:{}", apk.package_name),
- version: apk.version_code,
+ // Ideally we would want to log both rollback_index and apk version code in dice. There
+ // is even a separate field called security_version_code, but it looks like it is not
+ // used in subcomponents, so for now log the rollback index as version code.
+ version: apk.rollback_index.map(u64::from).unwrap_or(apk.version_code),
code_hash: apk.root_hash.clone(),
authority_hash: apk.cert_hash.clone(),
}
diff --git a/guest/microdroid_manager/src/instance.rs b/guest/microdroid_manager/src/instance.rs
index d3a597a..a5f0d66 100644
--- a/guest/microdroid_manager/src/instance.rs
+++ b/guest/microdroid_manager/src/instance.rs
@@ -290,6 +290,7 @@
pub cert_hash: Vec<u8>,
pub package_name: String,
pub version_code: u64,
+ pub rollback_index: Option<u32>,
}
impl ApkData {
diff --git a/guest/microdroid_manager/src/verify.rs b/guest/microdroid_manager/src/verify.rs
index ec8d66e..2d46b1f 100644
--- a/guest/microdroid_manager/src/verify.rs
+++ b/guest/microdroid_manager/src/verify.rs
@@ -203,6 +203,7 @@
cert_hash,
package_name: manifest_info.package,
version_code: manifest_info.version_code,
+ rollback_index: manifest_info.rollback_index,
})
}
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 94f7ced..67249b4 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -538,6 +538,11 @@
public BootResult tryBootVm(String logTag, String vmName)
throws VirtualMachineException, InterruptedException {
VirtualMachine vm = getVirtualMachineManager().get(vmName);
+ return tryBootVm(logTag, vm);
+ }
+
+ public BootResult tryBootVm(String logTag, VirtualMachine vm)
+ throws VirtualMachineException, InterruptedException {
final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
final CompletableFuture<Integer> deathReason = new CompletableFuture<>();
final CompletableFuture<Long> endTime = new CompletableFuture<>();
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 859007a..d9f74dc 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -64,7 +64,9 @@
DATA = [
":MicrodroidTestAppUpdated",
- ":MicrodroidTestHelperAppRelaxedRollbackProtection_correct_V5",
+ ":MicrodroidTestHelperAppRelaxedRollbackProtection_V5",
+ ":MicrodroidTestHelperAppRelaxedRollbackProtection_V6",
+ ":MicrodroidTestHelperAppRelaxedRollbackProtection_V7_inc_rollback_version",
":MicrodroidTestHelperAppRelaxedRollbackProtection_no_permission",
":MicrodroidTestHelperAppRelaxedRollbackProtection_no_rollback_index",
":MicrodroidVmShareApp",
@@ -73,7 +75,7 @@
]
android_test_helper_app {
- name: "MicrodroidTestHelperAppRelaxedRollbackProtection_correct_V5",
+ name: "MicrodroidTestHelperAppRelaxedRollbackProtection_V5",
defaults: ["MicrodroidTestAppsDefaults"],
manifest: "AndroidManifestV5_relaxed_rollback_protection.xml",
jni_libs: [
@@ -84,6 +86,28 @@
}
android_test_helper_app {
+ name: "MicrodroidTestHelperAppRelaxedRollbackProtection_V6",
+ defaults: ["MicrodroidTestAppsDefaults"],
+ manifest: "AndroidManifestV6_relaxed_rollback_protection.xml",
+ jni_libs: [
+ "MicrodroidTestNativeLib",
+ "MicrodroidTestNativeLibWithLibIcu",
+ ],
+ min_sdk_version: "33",
+}
+
+android_test_helper_app {
+ name: "MicrodroidTestHelperAppRelaxedRollbackProtection_V7_inc_rollback_version",
+ defaults: ["MicrodroidTestAppsDefaults"],
+ manifest: "AndroidManifestV7_relaxed_rollback_protection_inc_rollback_version.xml",
+ jni_libs: [
+ "MicrodroidTestNativeLib",
+ "MicrodroidTestNativeLibWithLibIcu",
+ ],
+ min_sdk_version: "33",
+}
+
+android_test_helper_app {
name: "MicrodroidTestHelperAppRelaxedRollbackProtection_no_rollback_index",
defaults: ["MicrodroidTestAppsDefaults"],
manifest: "AndroidManifestV5_relaxed_rollback_protection_no_rollback_index.xml",
diff --git a/tests/testapk/AndroidManifestV5_relaxed_rollback_protection_no_permission.xml b/tests/testapk/AndroidManifestV5_relaxed_rollback_protection_no_permission.xml
index 91de2a0..c165a2d 100644
--- a/tests/testapk/AndroidManifestV5_relaxed_rollback_protection_no_permission.xml
+++ b/tests/testapk/AndroidManifestV5_relaxed_rollback_protection_no_permission.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
+<!-- Copyright (C) 2025 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.
diff --git a/tests/testapk/AndroidManifestV5_relaxed_rollback_protection_no_rollback_index.xml b/tests/testapk/AndroidManifestV5_relaxed_rollback_protection_no_rollback_index.xml
index 3d6d734..432da67 100644
--- a/tests/testapk/AndroidManifestV5_relaxed_rollback_protection_no_rollback_index.xml
+++ b/tests/testapk/AndroidManifestV5_relaxed_rollback_protection_no_rollback_index.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
+<!-- Copyright (C) 2025 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.
diff --git a/tests/testapk/AndroidManifestV6_relaxed_rollback_protection.xml b/tests/testapk/AndroidManifestV6_relaxed_rollback_protection.xml
new file mode 100644
index 0000000..3cc4dd9
--- /dev/null
+++ b/tests/testapk/AndroidManifestV6_relaxed_rollback_protection.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 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_relaxed_rollback_protection_scheme"
+ 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.USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION" />
+ <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
+ <uses-feature android:name="android.software.virtualization_framework" android:required="false" />
+ <application>
+ <property android:name="android.system.virtualmachine.ROLLBACK_INDEX" android:value="1" />
+ </application>
+</manifest>
diff --git a/tests/testapk/AndroidManifestV7_relaxed_rollback_protection_inc_rollback_version.xml b/tests/testapk/AndroidManifestV7_relaxed_rollback_protection_inc_rollback_version.xml
new file mode 100644
index 0000000..40be01a
--- /dev/null
+++ b/tests/testapk/AndroidManifestV7_relaxed_rollback_protection_inc_rollback_version.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 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_relaxed_rollback_protection_scheme"
+ android:versionCode="7" >
+ <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.USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION" />
+ <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
+ <uses-feature android:name="android.software.virtualization_framework" android:required="false" />
+ <application>
+ <property android:name="android.system.virtualmachine.ROLLBACK_INDEX" android:value="2" />
+ </application>
+</manifest>
diff --git a/tests/testapk/AndroidTestTemplate.xml b/tests/testapk/AndroidTestTemplate.xml
index 64ddbbe..78d8b15 100644
--- a/tests/testapk/AndroidTestTemplate.xml
+++ b/tests/testapk/AndroidTestTemplate.xml
@@ -30,7 +30,9 @@
<option name="cleanup" value="true" />
<option name="push" value="test_microdroid_vendor_image.img->/data/local/tmp/cts/microdroid/test_microdroid_vendor_image.img" />
<option name="push" value="test_microdroid_vendor_image_unsigned.img->/data/local/tmp/cts/microdroid/test_microdroid_vendor_image_unsigned.img" />
- <option name="push" value="MicrodroidTestHelperAppRelaxedRollbackProtection_correct_V5.apk->/data/local/tmp/cts/microdroid/MicrodroidTestHelperAppRelaxedRollbackProtection_correct_V5.apk" />
+ <option name="push" value="MicrodroidTestHelperAppRelaxedRollbackProtection_V5.apk->/data/local/tmp/cts/microdroid/MicrodroidTestHelperAppRelaxedRollbackProtection_V5.apk" />
+ <option name="push" value="MicrodroidTestHelperAppRelaxedRollbackProtection_V6.apk->/data/local/tmp/cts/microdroid/MicrodroidTestHelperAppRelaxedRollbackProtection_V6.apk" />
+ <option name="push" value="MicrodroidTestHelperAppRelaxedRollbackProtection_V7_inc_rollback_version.apk->/data/local/tmp/cts/microdroid/MicrodroidTestHelperAppRelaxedRollbackProtection_V7_inc_rollback_version.apk" />
<option name="push" value="MicrodroidTestHelperAppRelaxedRollbackProtection_no_permission.apk->/data/local/tmp/cts/microdroid/MicrodroidTestHelperAppRelaxedRollbackProtection_no_permission.apk" />
<option name="push" value="MicrodroidTestHelperAppRelaxedRollbackProtection_no_rollback_index.apk->/data/local/tmp/cts/microdroid/MicrodroidTestHelperAppRelaxedRollbackProtection_no_rollback_index.apk" />
</target_preparer>
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 521ee9a..e4a3ff6 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -2861,6 +2861,116 @@
VirtualMachineCallback.STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED);
}
+ @Test
+ public void relaxedRollbackProtectionScheme_rollbackVersionDoesNotChange() throws Exception {
+ assumeSupportedDevice();
+ // Relaxed rollback protection scheme only makes sense if VM updates are supported.
+ assumeTrue("Missing Updatable VM support", isUpdatableVmSupported());
+
+ installApp("MicrodroidTestHelperAppRelaxedRollbackProtection_V6.apk");
+
+ Context testHelperAppCtx =
+ getContext()
+ .createPackageContext(
+ RELAXED_ROLLBACK_PROTECTION_SCHEME_TEST_PACKAGE_NAME, 0);
+
+ VirtualMachineConfig config =
+ new VirtualMachineConfig.Builder(testHelperAppCtx)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+ .setProtectedVm(isProtectedVm())
+ .setOs(os())
+ .setEncryptedStorageBytes(1 * 1024 * 1024)
+ .build();
+
+ VirtualMachine vm =
+ forceCreateNewVirtualMachine("test_rollback_version_does_not_change", config);
+ TestResults testResults =
+ runVmTestService(
+ TAG,
+ vm,
+ (ts, tr) -> {
+ ts.writeToFile(
+ /* content= */ EXAMPLE_STRING,
+ /* path= */ "/mnt/encryptedstore/test_file");
+ });
+ testResults.assertNoException();
+
+ // Simulate a rollback by installing a downgraded version of the helper apk.
+ installApp("MicrodroidTestHelperAppRelaxedRollbackProtection_V5.apk", "-d");
+
+ testResults =
+ runVmTestService(
+ TAG,
+ vm,
+ (ts, tr) -> {
+ tr.mFileContent = ts.readFromFile("/mnt/encryptedstore/test_file");
+ });
+ testResults.assertNoException();
+ assertThat(testResults.mFileContent).isEqualTo(EXAMPLE_STRING);
+ }
+
+ @Test
+ public void relaxedRollbackProtectionScheme_rollbackVersionChanges() throws Exception {
+ assumeSupportedDevice();
+ // Relaxed rollback protection scheme only makes sense if VM updates are supported.
+ assumeTrue("Missing Updatable VM support", isUpdatableVmSupported());
+ assumeProtectedVM();
+
+ installApp("MicrodroidTestHelperAppRelaxedRollbackProtection_V5.apk");
+
+ Context testHelperAppCtx =
+ getContext()
+ .createPackageContext(
+ RELAXED_ROLLBACK_PROTECTION_SCHEME_TEST_PACKAGE_NAME, 0);
+
+ VirtualMachineConfig config =
+ new VirtualMachineConfig.Builder(testHelperAppCtx)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+ .setProtectedVm(isProtectedVm())
+ .setOs(os())
+ .setEncryptedStorageBytes(1 * 1024 * 1024)
+ .build();
+
+ VirtualMachine vm = forceCreateNewVirtualMachine("test_rollback_version_changes", config);
+
+ TestResults testResults =
+ runVmTestService(
+ TAG,
+ vm,
+ (ts, tr) -> {
+ ts.writeToFile(
+ /* content= */ EXAMPLE_STRING,
+ /* path= */ "/mnt/encryptedstore/test_file");
+ });
+ testResults.assertNoException();
+
+ installApp("MicrodroidTestHelperAppRelaxedRollbackProtection_V7_inc_rollback_version.apk");
+
+ testResults =
+ runVmTestService(
+ TAG,
+ vm,
+ (ts, tr) -> {
+ tr.mFileContent = ts.readFromFile("/mnt/encryptedstore/test_file");
+ });
+ testResults.assertNoException();
+ assertThat(testResults.mFileContent).isEqualTo(EXAMPLE_STRING);
+
+ assertThat(vm.getStatus()).isEqualTo(VirtualMachine.STATUS_STOPPED);
+
+ // Simulate a rollback by installing a downgraded version of the helper apk.
+ installApp("MicrodroidTestHelperAppRelaxedRollbackProtection_V6.apk", "-d");
+
+ // Now pVM shouldn't boot.
+ BootResult bootResult = tryBootVm(TAG, vm);
+ assertThat(bootResult.deathReason)
+ .isEqualTo(
+ // TODO(ioffe): this should probably be payload verification error?
+ VirtualMachineCallback.STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR);
+ }
+
private static class VmShareServiceConnection implements ServiceConnection {
private final CountDownLatch mLatch = new CountDownLatch(1);
@@ -2956,14 +3066,15 @@
assertThat(e).hasMessageThat().contains(expectedContents);
}
- private void installApp(String apkName) throws Exception {
+ private void installApp(String apkName, String... additionalArgs) throws Exception {
String apkFile = new File("/data/local/tmp/cts/microdroid/", apkName).getAbsolutePath();
UiAutomation uai = InstrumentationRegistry.getInstrumentation().getUiAutomation();
Log.i(TAG, "Installing apk " + apkFile);
// We read the output of the shell command not only to see if it succeeds, but also to make
// sure that the installation finishes. This avoids a race condition when test tries to
// create a context of the installed package before the installation finished.
- try (ParcelFileDescriptor pfd = uai.executeShellCommand("pm install " + apkFile)) {
+ String installCmd = "pm install " + String.join(" ", additionalArgs) + " " + apkFile;
+ try (ParcelFileDescriptor pfd = uai.executeShellCommand(installCmd)) {
try (InputStream is = new FileInputStream(pfd.getFileDescriptor())) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
String line;