Merge "microdroid: init.rc: add tracefs for debug boot"
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 5cd4af8..5d36f16 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -16,6 +16,8 @@
 
 package com.android.virt.fs;
 
+import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -40,7 +42,9 @@
 import org.junit.After;
 import org.junit.AssumptionViolatedException;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
 import java.util.Optional;
@@ -100,6 +104,9 @@
 
     private ExecutorService mThreadPool = Executors.newCachedThreadPool();
 
+    @Rule public TestLogData mTestLogs = new TestLogData();
+    @Rule public TestName mTestName = new TestName();
+
     @BeforeClassWithInfo
     public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
         assertNotNull(testInfo.getDevice());
@@ -170,10 +177,18 @@
     @After
     public void tearDown() throws Exception {
         sAndroid.tryRun("killall fd_server");
-        sAndroid.run("rm -rf " + TEST_OUTPUT_DIR);
-
         tryRunOnMicrodroid("killall authfs");
         tryRunOnMicrodroid("umount " + MOUNT_DIR);
+
+        // Even though we only run one VM for the whole class, and could have collect the VM log
+        // after all tests are done, TestLogData doesn't seem to work at class level. Hence,
+        // collect recent logs manually for each test method.
+        String vmRecentLog = TEST_OUTPUT_DIR + "/vm_recent.log";
+        sAndroid.tryRun("tail -n 50 " + LOG_PATH + " > " + vmRecentLog);
+        archiveLogThenDelete(mTestLogs, getDevice(), vmRecentLog,
+                "vm_recent.log-" + mTestName.getMethodName());
+
+        sAndroid.run("rm -rf " + TEST_OUTPUT_DIR);
     }
 
     @Test
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index cfe72cb..6773eb7 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -16,6 +16,8 @@
 
 package android.compos.test;
 
+import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.platform.test.annotations.RootPermissionTest;
@@ -28,7 +30,9 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
 @RootPermissionTest
@@ -41,6 +45,8 @@
     private static final String COMPOS_VERIFY_BIN =
             "/apex/com.android.compos/bin/compos_verify";
 
+    private static final String COMPOS_APEXDATA_DIR = "/data/misc/apexdata/com.android.compos";
+
     /** Output directory of odrefresh */
     private static final String TEST_ARTIFACTS_DIR = "test-artifacts";
 
@@ -61,6 +67,9 @@
             "dalvik.vm.systemservercompilerfilter";
     private String mBackupSystemServerCompilerFilter;
 
+    @Rule public TestLogData mTestLogs = new TestLogData();
+    @Rule public TestName mTestName = new TestName();
+
     @Before
     public void setUp() throws Exception {
         testIfDeviceIsCapable(getDevice());
@@ -77,6 +86,11 @@
     public void tearDown() throws Exception {
         killVmAndReconnectAdb();
 
+        archiveLogThenDelete(mTestLogs, getDevice(), COMPOS_APEXDATA_DIR + "/vm_console.log",
+                "vm_console.log-" + mTestName.getMethodName());
+        archiveLogThenDelete(mTestLogs, getDevice(), COMPOS_APEXDATA_DIR + "/vm.log",
+                "vm.log-" + mTestName.getMethodName());
+
         CommandRunner android = new CommandRunner(getDevice());
 
         // Clear up any CompOS instance files we created
diff --git a/compos/verify/verify.rs b/compos/verify/verify.rs
index 184b9ff..7b77c18 100644
--- a/compos/verify/verify.rs
+++ b/compos/verify/verify.rs
@@ -22,7 +22,8 @@
 use compos_aidl_interface::binder::ProcessState;
 use compos_common::compos_client::{VmInstance, VmParameters};
 use compos_common::odrefresh::{
-    CURRENT_ARTIFACTS_SUBDIR, ODREFRESH_OUTPUT_ROOT_DIR, TEST_ARTIFACTS_SUBDIR,
+    CURRENT_ARTIFACTS_SUBDIR, ODREFRESH_OUTPUT_ROOT_DIR, PENDING_ARTIFACTS_SUBDIR,
+    TEST_ARTIFACTS_SUBDIR,
 };
 use compos_common::{
     COMPOS_DATA_ROOT, CURRENT_INSTANCE_DIR, IDSIG_FILE, IDSIG_MANIFEST_APK_FILE,
@@ -62,7 +63,7 @@
                 .long("instance")
                 .takes_value(true)
                 .required(true)
-                .possible_values(&["current", "test"]),
+                .possible_values(&["current", "pending", "test"]),
         )
         .arg(clap::Arg::with_name("debug").long("debug"))
         .get_matches();
@@ -70,6 +71,7 @@
     let debug_mode = matches.is_present("debug");
     let (instance_dir, artifacts_dir) = match matches.value_of("instance").unwrap() {
         "current" => (CURRENT_INSTANCE_DIR, CURRENT_ARTIFACTS_SUBDIR),
+        "pending" => (CURRENT_INSTANCE_DIR, PENDING_ARTIFACTS_SUBDIR),
         "test" => (TEST_INSTANCE_DIR, TEST_ARTIFACTS_SUBDIR),
         _ => unreachable!("Unexpected instance name"),
     };
@@ -90,8 +92,8 @@
     let info = artifacts_dir.join("compos.info");
     let signature = artifacts_dir.join("compos.info.signature");
 
-    let info = read_small_file(&info)?;
-    let signature = read_small_file(&signature)?;
+    let info = read_small_file(&info).context("Failed to read compos.info")?;
+    let signature = read_small_file(&signature).context("Failed to read compos.info signature")?;
 
     // We need to start the thread pool to be able to receive Binder callbacks
     ProcessState::start_thread_pool();
diff --git a/docs/getting_started/index.md b/docs/getting_started/index.md
index ac5d38b..f598034 100644
--- a/docs/getting_started/index.md
+++ b/docs/getting_started/index.md
@@ -90,7 +90,7 @@
 
 ```shell
 banchan com.android.virt aosp_arm64   // or aosp_x86_64 if the device is cuttlefish
-m apps_only dist
+UNBUNDLED_BUILD_SDKS_FROM_SOURCE=true m apps_only dist
 adb install out/dist/com.android.virt.apex
 adb reboot
 ```
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index a93a801..3ae4b1a 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -212,6 +212,7 @@
 microdroid_boot_cmdline = [
     "panic=-1",
     "bootconfig",
+    "ioremap_guard",
 ]
 
 bootimg {
diff --git a/microdroid/README.md b/microdroid/README.md
index 783f300..a652139 100644
--- a/microdroid/README.md
+++ b/microdroid/README.md
@@ -31,7 +31,7 @@
 
 ```sh
 banchan com.android.virt aosp_arm64
-UNBUNDLED_BUILD_SDKS_FROM_SOURCES=true m apps_only dist
+UNBUNDLED_BUILD_SDKS_FROM_SOURCE=true m apps_only dist
 adb install out/dist/com.android.virt.apex
 adb reboot
 ```
diff --git a/microdroid/dice/service.rs b/microdroid/dice/service.rs
index 8cb5cc3..8199c7c 100644
--- a/microdroid/dice/service.rs
+++ b/microdroid/dice/service.rs
@@ -30,6 +30,7 @@
 use std::slice;
 use std::sync::Arc;
 
+const AVF_STRICT_BOOT: &str = "/sys/firmware/devicetree/base/chosen/avf,strict-boot";
 const DICE_HAL_SERVICE_NAME: &str = "android.hardware.security.dice.IDiceDevice/default";
 
 /// Artifacts that are mapped into the process address space from the driver.
@@ -135,16 +136,19 @@
 
 #[derive(Clone, Serialize, Deserialize)]
 enum DriverArtifactManager {
+    Invalid,
     Driver(PathBuf),
     Updated(RawArtifacts),
 }
 
 impl DriverArtifactManager {
     fn new(driver_path: &Path) -> Self {
-        // TODO(218793274): fail if driver is missing in protected mode
         if driver_path.exists() {
             log::info!("Using DICE values from driver");
             Self::Driver(driver_path.to_path_buf())
+        } else if Path::new(AVF_STRICT_BOOT).exists() {
+            log::error!("Strict boot requires DICE value from driver but none were found");
+            Self::Invalid
         } else {
             log::warn!("Using sample DICE values");
             let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()
@@ -164,11 +168,15 @@
         F: FnOnce(&dyn DiceArtifacts) -> Result<T>,
     {
         match self {
+            Self::Invalid => bail!("No DICE artifacts available."),
             Self::Driver(driver_path) => f(&MappedDriverArtifacts::new(driver_path.as_path())?),
             Self::Updated(raw_artifacts) => f(raw_artifacts),
         }
     }
     fn update(self, new_artifacts: &impl DiceArtifacts) -> Result<Self> {
+        if let Self::Invalid = self {
+            bail!("Cannot update invalid DICE artifacts.");
+        }
         if let Self::Driver(driver_path) = self {
             // Writing to the device wipes the artifcates. The string is ignored
             // by the driver but included for documentation.
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index b644285..9e159d2 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -61,6 +61,8 @@
 const DM_MOUNTED_APK_PATH: &str = "/dev/block/mapper/microdroid-apk";
 const APKDMVERITY_BIN: &str = "/system/bin/apkdmverity";
 const ZIPFUSE_BIN: &str = "/system/bin/zipfuse";
+const AVF_STRICT_BOOT: &str = "/sys/firmware/devicetree/base/chosen/avf,strict-boot";
+const AVF_NEW_INSTANCE: &str = "/sys/firmware/devicetree/base/chosen/avf,new-instance";
 
 /// The CID representing the host VM
 const VMADDR_CID_HOST: u32 = 2;
@@ -193,12 +195,35 @@
     Ok(())
 }
 
+fn is_strict_boot() -> bool {
+    Path::new(AVF_STRICT_BOOT).exists()
+}
+
+fn is_new_instance() -> bool {
+    Path::new(AVF_NEW_INSTANCE).exists()
+}
+
 fn try_run_payload(service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
     let metadata = load_metadata().context("Failed to load payload metadata")?;
 
     let mut instance = InstanceDisk::new().context("Failed to load instance.img")?;
     let saved_data = instance.read_microdroid_data().context("Failed to read identity data")?;
 
+    if is_strict_boot() {
+        // Provisioning must happen on the first boot and never again.
+        if is_new_instance() {
+            ensure!(
+                saved_data.is_none(),
+                MicrodroidError::InvalidConfig("Found instance data on first boot.".to_string())
+            );
+        } else {
+            ensure!(
+                saved_data.is_some(),
+                MicrodroidError::InvalidConfig("Instance data not found.".to_string())
+            );
+        };
+    }
+
     // Verify the payload before using it.
     let verified_data =
         verify_payload(&metadata, saved_data.as_ref()).context("Payload verification failed")?;
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index 8e86fd1..40be248 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -16,6 +16,8 @@
 
 package android.virt.test;
 
+import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
+
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -28,6 +30,8 @@
 import com.android.tradefed.device.ITestDevice;
 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;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
@@ -46,6 +50,7 @@
 public abstract class VirtualizationTestCaseBase extends BaseHostJUnit4Test {
     protected static final String TEST_ROOT = "/data/local/tmp/virt/";
     protected static final String VIRT_APEX = "/apex/com.android.virt/";
+    protected static final String LOG_PATH = TEST_ROOT + "log.txt";
     private static final int TEST_VM_ADB_PORT = 8000;
     private static final String MICRODROID_SERIAL = "localhost:" + TEST_VM_ADB_PORT;
     private static final String INSTANCE_IMG = "instance.img";
@@ -105,6 +110,16 @@
         assumeTrue("Requires VM support", testDevice.supportsMicrodroid());
     }
 
+    public static void archiveLogThenDelete(TestLogData logs, ITestDevice device, String remotePath,
+            String localName) throws DeviceNotAvailableException {
+        File logFile = device.pullFile(remotePath);
+        if (logFile != null) {
+            logs.addTestLog(localName, LogDataType.TEXT, new FileInputStreamSource(logFile));
+            // Delete to avoid confusing logs from a previous run, just in case.
+            device.deleteFile(remotePath);
+        }
+    }
+
     // Run an arbitrary command in the host side and returns the result
     private static String runOnHost(String... cmd) {
         return runOnHostWithTimeout(10000, cmd);
@@ -270,7 +285,7 @@
         final String outApkIdsigPath = TEST_ROOT + apkName + ".idsig";
 
         final String instanceImg = TEST_ROOT + INSTANCE_IMG;
-        final String logPath = TEST_ROOT + "log.txt";
+        final String logPath = LOG_PATH;
         final String debugFlag = debug ? "--debug full" : "";
 
         // Run the VM
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 51d62cb..f305372 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -16,6 +16,8 @@
 
 package android.virt.test;
 
+import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
+
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertEquals;
@@ -40,7 +42,9 @@
 import org.json.JSONObject;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
 import java.io.File;
@@ -65,6 +69,9 @@
     private static final int NUM_VCPUS = 3;
     private static final String CPU_AFFINITY = "0,1,2";
 
+    @Rule public TestLogData mTestLogs = new TestLogData();
+    @Rule public TestName mTestName = new TestName();
+
     // TODO(b/176805428): remove this
     private boolean isCuttlefish() throws Exception {
         String productName = getDevice().getProperty("ro.product.name");
@@ -257,7 +264,7 @@
         final String configPath = TEST_ROOT + "raw_config.json";
         getDevice().pushString(config.toString(), configPath);
 
-        final String logPath = TEST_ROOT + "log";
+        final String logPath = LOG_PATH;
         final String ret = android.runWithTimeout(
                 60 * 1000,
                 VIRT_APEX + "bin/vm run",
@@ -436,6 +443,7 @@
     public void shutdown() throws Exception {
         cleanUpVirtualizationTestSetup(getDevice());
 
-        getDevice().uninstallPackage(PACKAGE_NAME);
+        archiveLogThenDelete(mTestLogs, getDevice(), LOG_PATH,
+                "vm.log-" + mTestName.getMethodName());
     }
 }