Merge "Notify VS when users or apps are removed" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index f146b4e..89d4552 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -12,6 +12,9 @@
"name": "MicrodroidTestApp"
},
{
+ "name": "MicrodroidTestAppNoPerm"
+ },
+ {
"name": "VmAttestationTestApp"
},
{
diff --git a/apex/Android.bp b/apex/Android.bp
index f2badfa..399ad97 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -81,7 +81,6 @@
// TODO(b/295593640) Unfortunately these are added to the apex even though they are unused.
// Once the build system is fixed, remove this.
unwanted_transitive_deps: [
- "libdrm",
"libsso",
"libutils",
],
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index 2ec097a..8ea9cd9 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -478,14 +478,38 @@
self.delete_and_next(next_offset)
}
- /// Returns the next node
+ /// Returns the next node. Use this API to travel descendant of a node.
+ ///
+ /// Returned depth is relative to the initial node that had called with any of next node APIs.
+ /// Returns None if end of FDT reached or depth becomes negative.
+ ///
+ /// See also: [`next_node_skip_subnodes`], and [`delete_and_next_node`]
pub fn next_node(self, depth: usize) -> Result<Option<(Self, usize)>> {
let next = self.fdt.next_node(self.offset, depth)?;
Ok(next.map(|(offset, depth)| (Self { fdt: self.fdt, offset }, depth)))
}
- /// Deletes this and returns the next node
+ /// Returns the next node skipping subnodes. Use this API to travel descendants of a node while
+ /// ignoring certain node.
+ ///
+ /// Returned depth is relative to the initial node that had called with any of next node APIs.
+ /// Returns None if end of FDT reached or depth becomes negative.
+ ///
+ /// See also: [`next_node`], and [`delete_and_next_node`]
+ pub fn next_node_skip_subnodes(self, depth: usize) -> Result<Option<(Self, usize)>> {
+ let next = self.fdt.next_node_skip_subnodes(self.offset, depth)?;
+
+ Ok(next.map(|(offset, depth)| (Self { fdt: self.fdt, offset }, depth)))
+ }
+
+ /// Deletes this and returns the next node. Use this API to travel descendants of a node while
+ /// removing certain node.
+ ///
+ /// Returned depth is relative to the initial node that had called with any of next node APIs.
+ /// Returns None if end of FDT reached or depth becomes negative.
+ ///
+ /// See also: [`next_node`], and [`next_node_skip_subnodes`]
pub fn delete_and_next_node(self, depth: usize) -> Result<Option<(Self, usize)>> {
let next_node = self.fdt.next_node_skip_subnodes(self.offset, depth)?;
if let Some((offset, depth)) = next_node {
diff --git a/pvmfw/src/device_assignment.rs b/pvmfw/src/device_assignment.rs
index e427710..2c47f9e 100644
--- a/pvmfw/src/device_assignment.rs
+++ b/pvmfw/src/device_assignment.rs
@@ -294,6 +294,26 @@
.map_or(false, |name| name == b"__overlay__")
}
+fn filter_dangling_symbols(fdt: &mut Fdt) -> Result<()> {
+ if let Some(symbols) = fdt.symbols()? {
+ let mut removed = vec![];
+ for prop in symbols.properties()? {
+ let path = CStr::from_bytes_with_nul(prop.value()?)
+ .map_err(|_| DeviceAssignmentError::Internal)?;
+ if fdt.node(path)?.is_none() {
+ let name = prop.name()?;
+ removed.push(CString::from(name));
+ }
+ }
+
+ let mut symbols = fdt.symbols_mut()?.unwrap();
+ for name in removed {
+ symbols.nop_property(&name)?;
+ }
+ }
+ Ok(())
+}
+
impl AsRef<Fdt> for VmDtbo {
fn as_ref(&self) -> &Fdt {
&self.0
@@ -744,7 +764,8 @@
device.patch(fdt, &pviommu_phandles)?;
}
- Ok(())
+ // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
+ filter_dangling_symbols(fdt)
}
}
@@ -1020,6 +1041,39 @@
}
#[test]
+ fn device_info_patch_no_pviommus() {
+ let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
+ let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
+ let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
+ let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+ let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+ let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
+
+ let hypervisor = MockHypervisor {
+ mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
+ iommu_tokens: BTreeMap::new(),
+ };
+ let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
+ device_info.filter(vm_dtbo).unwrap();
+
+ // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
+ unsafe {
+ platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
+ }
+ device_info.patch(platform_dt).unwrap();
+
+ let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu")).unwrap();
+ assert_eq!(None, compatible);
+
+ if let Some(symbols) = platform_dt.symbols().unwrap() {
+ for prop in symbols.properties().unwrap() {
+ let path = CStr::from_bytes_with_nul(prop.value().unwrap()).unwrap();
+ assert_ne!(None, platform_dt.node(path).unwrap());
+ }
+ }
+ }
+
+ #[test]
fn device_info_overlay_iommu() {
let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
diff --git a/tests/testapk_no_perm/Android.bp b/tests/testapk_no_perm/Android.bp
new file mode 100644
index 0000000..22616de
--- /dev/null
+++ b/tests/testapk_no_perm/Android.bp
@@ -0,0 +1,26 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "MicrodroidTestAppNoPerm",
+ 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: "33",
+}
diff --git a/tests/testapk_no_perm/AndroidManifest.xml b/tests/testapk_no_perm/AndroidManifest.xml
new file mode 100644
index 0000000..44aa92a
--- /dev/null
+++ b/tests/testapk_no_perm/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?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_perm">
+ <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
+ <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_perm"
+ android:label="No Permission Microdroid Test" />
+</manifest>
diff --git a/tests/testapk_no_perm/AndroidTest.xml b/tests/testapk_no_perm/AndroidTest.xml
new file mode 100644
index 0000000..d4a818f
--- /dev/null
+++ b/tests/testapk_no_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 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="MicrodroidTestAppNoPerm.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.microdroid.test_no_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_perm/src/java/com/android/microdroid/test/MicrodroidTestAppNoPerm.java b/tests/testapk_no_perm/src/java/com/android/microdroid/test/MicrodroidTestAppNoPerm.java
new file mode 100644
index 0000000..3c20a0f
--- /dev/null
+++ b/tests/testapk_no_perm/src/java/com/android/microdroid/test/MicrodroidTestAppNoPerm.java
@@ -0,0 +1,58 @@
+/*
+ * 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 android.system.virtualmachine.VirtualMachineConfig;
+
+import com.android.compatibility.common.util.CddTest;
+import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test that the android.permission.MANAGE_VIRTUAL_MACHINE is enforced and that an app cannot launch
+ * a VM without said permission.
+ */
+@RunWith(JUnit4.class)
+public class MicrodroidTestAppNoPerm extends MicrodroidDeviceTestBase {
+ @Test
+ @CddTest(
+ requirements = {
+ "9.17/C-1-1",
+ "9.17/C-1-2",
+ "9.17/C-1-4",
+ })
+ public void createVmRequiresPermission() {
+ assumeSupportedDevice();
+
+ VirtualMachineConfig config =
+ newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so").build();
+
+ SecurityException e =
+ assertThrows(
+ SecurityException.class,
+ () -> forceCreateNewVirtualMachine("test_vm_requires_permission", config));
+ assertThat(e)
+ .hasMessageThat()
+ .contains("android.permission.MANAGE_VIRTUAL_MACHINE permission");
+ }
+}