Merge changes Ia10f2b6e,If05b871b

* changes:
  Serialise number of CPUs.
  Don't fall back to unprotected mode if protected is not supported.
diff --git a/compos/Android.bp b/compos/Android.bp
index c54348a..0bcbcdd 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -28,6 +28,7 @@
         "libprotobuf",
         "libregex",
         "libring",
+        "librustutils",
         "libscopeguard",
     ],
     prefer_rlib: true,
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index cead5d0..18e163e 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -21,6 +21,17 @@
 /** {@hide} */
 interface ICompOsService {
     /**
+     * What type of compilation to perform.
+     */
+    @Backing(type="int")
+    enum CompilationMode {
+        /** Compile artifacts required by the current set of APEXes for use on reboot. */
+        NORMAL_COMPILE = 0,
+        /** Compile a full set of artifacts for test purposes. */
+        TEST_COMPILE = 1,
+    }
+
+    /**
      * Initializes the service with the supplied encrypted private key blob. The key cannot be
      * changed once initialized, so once initiailzed, a repeated call will fail with
      * EX_ILLEGAL_STATE.
@@ -37,6 +48,7 @@
      * through systemDirFd over AuthFS), and *CLASSPATH derived in the VM, to generate the same
      * odrefresh output artifacts to the output directory (through outputDirFd).
      *
+     * @param compilationMode The type of compilation to be performed
      * @param systemDirFd An fd referring to /system
      * @param outputDirFd An fd referring to the output directory, ART_APEX_DATA
      * @param stagingDirFd An fd referring to the staging directory, e.g. ART_APEX_DATA/staging
@@ -46,8 +58,9 @@
      * @param systemServerCompilerFilter The compiler filter used to compile system server
      * @return odrefresh exit code
      */
-    byte odrefresh(int systemDirFd, int outputDirFd, int stagingDirFd, String targetDirName,
-            String zygoteArch, String systemServerCompilerFilter);
+    byte odrefresh(CompilationMode compilation_mode, int systemDirFd, int outputDirFd,
+            int stagingDirFd, String targetDirName, String zygoteArch,
+            String systemServerCompilerFilter);
 
     /**
      * Generate a new public/private key pair suitable for signing CompOs output files.
diff --git a/compos/composd/src/odrefresh_task.rs b/compos/composd/src/odrefresh_task.rs
index 330f0ab..47ff590 100644
--- a/compos/composd/src/odrefresh_task.rs
+++ b/compos/composd/src/odrefresh_task.rs
@@ -23,7 +23,9 @@
 };
 use android_system_composd::binder::{Interface, Result as BinderResult, Strong};
 use anyhow::{Context, Result};
-use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
+use compos_aidl_interface::aidl::com::android::compos::ICompOsService::{
+    CompilationMode::CompilationMode, ICompOsService,
+};
 use compos_common::odrefresh::ExitCode;
 use log::{error, warn};
 use rustutils::system_properties;
@@ -68,6 +70,7 @@
 
     pub fn start(
         comp_os: Arc<CompOsInstance>,
+        compilation_mode: CompilationMode,
         target_dir_name: String,
         callback: &Strong<dyn ICompilationTaskCallback>,
     ) -> Result<OdrefreshTask> {
@@ -75,14 +78,19 @@
         let task = RunningTask { comp_os, callback: callback.clone() };
         let task = OdrefreshTask { running_task: Arc::new(Mutex::new(Some(task))) };
 
-        task.clone().start_thread(service, target_dir_name);
+        task.clone().start_thread(service, compilation_mode, target_dir_name);
 
         Ok(task)
     }
 
-    fn start_thread(self, service: Strong<dyn ICompOsService>, target_dir_name: String) {
+    fn start_thread(
+        self,
+        service: Strong<dyn ICompOsService>,
+        compilation_mode: CompilationMode,
+        target_dir_name: String,
+    ) {
         thread::spawn(move || {
-            let exit_code = run_in_vm(service, &target_dir_name);
+            let exit_code = run_in_vm(service, compilation_mode, &target_dir_name);
 
             let task = self.take();
             // We don't do the callback if cancel has already happened.
@@ -106,7 +114,11 @@
     }
 }
 
-fn run_in_vm(service: Strong<dyn ICompOsService>, target_dir_name: &str) -> Result<ExitCode> {
+fn run_in_vm(
+    service: Strong<dyn ICompOsService>,
+    compilation_mode: CompilationMode,
+    target_dir_name: &str,
+) -> Result<ExitCode> {
     let output_root = Path::new(ART_APEX_DATA);
 
     // We need to remove the target directory because odrefresh running in compos will create it
@@ -134,6 +146,7 @@
     let system_server_compiler_filter =
         system_properties::read("dalvik.vm.systemservercompilerfilter").unwrap_or_default();
     let exit_code = service.odrefresh(
+        compilation_mode,
         system_dir.as_raw_fd(),
         output_dir.as_raw_fd(),
         staging_dir.as_raw_fd(),
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
index 6cdcd85..f4121e7 100644
--- a/compos/composd/src/service.rs
+++ b/compos/composd/src/service.rs
@@ -28,6 +28,7 @@
     self, BinderFeatures, ExceptionCode, Interface, Status, Strong, ThreadState,
 };
 use anyhow::{Context, Result};
+use compos_aidl_interface::aidl::com::android::compos::ICompOsService::CompilationMode::CompilationMode;
 use compos_common::binder::to_binder_result;
 use rustutils::{users::AID_ROOT, users::AID_SYSTEM};
 use std::sync::Arc;
@@ -72,7 +73,12 @@
         let comp_os = self.instance_manager.start_pending_instance().context("Starting CompOS")?;
 
         let target_dir_name = "compos-pending".to_owned();
-        let task = OdrefreshTask::start(comp_os, target_dir_name, callback)?;
+        let task = OdrefreshTask::start(
+            comp_os,
+            CompilationMode::NORMAL_COMPILE,
+            target_dir_name,
+            callback,
+        )?;
 
         Ok(BnCompilationTask::new_binder(task, BinderFeatures::default()))
     }
@@ -84,7 +90,12 @@
         let comp_os = self.instance_manager.start_test_instance().context("Starting CompOS")?;
 
         let target_dir_name = "test-artifacts".to_owned();
-        let task = OdrefreshTask::start(comp_os, target_dir_name, callback)?;
+        let task = OdrefreshTask::start(
+            comp_os,
+            CompilationMode::TEST_COMPILE,
+            target_dir_name,
+            callback,
+        )?;
 
         Ok(BnCompilationTask::new_binder(task, BinderFeatures::default()))
     }
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index e8f55f8..7e3834a 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -18,6 +18,7 @@
 use log::{debug, info, warn};
 use minijail::{self, Minijail};
 use regex::Regex;
+use rustutils::system_properties;
 use std::collections::HashMap;
 use std::env;
 use std::ffi::OsString;
@@ -35,11 +36,13 @@
     IAuthFsService::IAuthFsService,
 };
 use authfs_aidl_interface::binder::Strong;
+use compos_aidl_interface::aidl::com::android::compos::ICompOsService::CompilationMode::CompilationMode;
 use compos_common::odrefresh::ExitCode;
 
 const FD_SERVER_PORT: i32 = 3264; // TODO: support dynamic port
 
 pub struct OdrefreshContext<'a> {
+    compilation_mode: CompilationMode,
     system_dir_fd: i32,
     output_dir_fd: i32,
     staging_dir_fd: i32,
@@ -50,6 +53,7 @@
 
 impl<'a> OdrefreshContext<'a> {
     pub fn new(
+        compilation_mode: CompilationMode,
         system_dir_fd: i32,
         output_dir_fd: i32,
         staging_dir_fd: i32,
@@ -57,6 +61,13 @@
         zygote_arch: &'a str,
         system_server_compiler_filter: &'a str,
     ) -> Result<Self> {
+        if compilation_mode != CompilationMode::NORMAL_COMPILE {
+            let debuggable = system_properties::read_bool("ro.boot.microdroid.debuggable", false)?;
+            if !debuggable {
+                bail!("Requested compilation mode only available in debuggable VMs");
+            }
+        }
+
         if system_dir_fd < 0 || output_dir_fd < 0 || staging_dir_fd < 0 {
             bail!("The remote FDs are expected to be non-negative");
         }
@@ -75,6 +86,7 @@
         // CompOS.
 
         Ok(Self {
+            compilation_mode,
             system_dir_fd,
             output_dir_fd,
             staging_dir_fd,
@@ -143,18 +155,21 @@
         ));
     }
 
-    args.push("--compile".to_string());
+    let compile_flag = match context.compilation_mode {
+        CompilationMode::NORMAL_COMPILE => "--compile",
+        CompilationMode::TEST_COMPILE => "--force-compile",
+        other => bail!("Unknown compilation mode {:?}", other),
+    };
+    args.push(compile_flag.to_string());
 
     debug!("Running odrefresh with args: {:?}", &args);
     let jail = spawn_jailed_task(odrefresh_path, &args, &odrefresh_vars.into_env())
         .context("Spawn odrefresh")?;
     let exit_code = match jail.wait() {
-        Ok(_) => Result::<u8>::Ok(0),
-        Err(minijail::Error::ReturnCode(exit_code)) => Ok(exit_code),
-        Err(e) => {
-            bail!("Unexpected minijail error: {}", e)
-        }
-    }?;
+        Ok(_) => 0,
+        Err(minijail::Error::ReturnCode(exit_code)) => exit_code,
+        Err(e) => bail!("Unexpected minijail error: {}", e),
+    };
 
     let exit_code = ExitCode::from_i32(exit_code.into())?;
     info!("odrefresh exited with {:?}", exit_code);
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 422f271..9d754a7 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -31,7 +31,7 @@
 use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::IAuthFsService;
 use compos_aidl_interface::aidl::com::android::compos::{
     CompOsKeyData::CompOsKeyData,
-    ICompOsService::{BnCompOsService, ICompOsService},
+    ICompOsService::{BnCompOsService, CompilationMode::CompilationMode, ICompOsService},
 };
 use compos_aidl_interface::binder::{
     BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Strong,
@@ -82,6 +82,7 @@
 
     fn odrefresh(
         &self,
+        compilation_mode: CompilationMode,
         system_dir_fd: i32,
         output_dir_fd: i32,
         staging_dir_fd: i32,
@@ -90,6 +91,7 @@
         system_server_compiler_filter: &str,
     ) -> BinderResult<i8> {
         let context = to_binder_result(OdrefreshContext::new(
+            compilation_mode,
             system_dir_fd,
             output_dir_fd,
             staging_dir_fd,
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 5a77198..f3bbf16 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -316,6 +316,7 @@
 
 #[derive(Debug, Serialize, Deserialize, PartialEq)]
 pub struct MicrodroidData {
+    pub salt: Vec<u8>, // Should be [u8; 64] but that isn't serializable.
     pub apk_data: ApkData,
     pub extra_apks_data: Vec<ApkData>,
     pub apex_data: Vec<ApexData>,
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 3d145ee..827f9ff 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -36,6 +36,7 @@
 use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
 use once_cell::sync::OnceCell;
 use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
+use rand::Fill;
 use ring::digest;
 use rustutils::system_properties;
 use rustutils::system_properties::PropertyWatcher;
@@ -195,7 +196,7 @@
             authorityHash: authority_hash,
             authorityDescriptor: None,
             mode: if is_debuggable()? { Mode::DEBUG } else { Mode::NORMAL },
-            hidden: [0; 64],
+            hidden: verified_data.salt.try_into().unwrap(),
         }])
         .context("IDiceMaintenance::demoteSelf failed")?;
     Ok(())
@@ -438,9 +439,19 @@
 
     info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
 
+    // Use the salt from a verified instance, or generate a salt for a new instance.
+    let salt = if let Some(saved_data) = saved_data {
+        saved_data.salt.clone()
+    } else {
+        let mut salt = vec![0u8; 64];
+        salt.as_mut_slice().try_fill(&mut rand::thread_rng())?;
+        salt
+    };
+
     // At this point, we can ensure that the root_hash from the idsig file is trusted, either by
     // fully verifying the APK or by comparing it with the saved root_hash.
     Ok(MicrodroidData {
+        salt,
         apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: main_apk_pubkey },
         extra_apks_data,
         apex_data: apex_data_from_payload,
@@ -471,8 +482,7 @@
     let mut prop = PropertyWatcher::new(APEX_CONFIG_DONE_PROP)?;
     loop {
         prop.wait()?;
-        let val = system_properties::read(APEX_CONFIG_DONE_PROP)?;
-        if val == "true" {
+        if system_properties::read_bool(APEX_CONFIG_DONE_PROP, false)? {
             break;
         }
     }
@@ -542,7 +552,7 @@
 
     // Start logging if enabled
     // TODO(b/200914564) set filterspec if debug_level is app_only
-    if system_properties::read(LOGD_ENABLED_PROP)? == "1" {
+    if system_properties::read_bool(LOGD_ENABLED_PROP, false)? {
         system_properties::write("ctl.start", "seriallogging")?;
     }
 
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index 208d61f..f15036c 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -24,4 +24,7 @@
 
     /* read a system property. */
     String readProperty(String prop);
+
+    /* get the VM's stable secret. */
+    byte[] insecurelyExposeSecret();
 }
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 4cca538..40d72fe 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -10,6 +10,7 @@
         "androidx.test.runner",
         "androidx.test.ext.junit",
         "com.android.microdroid.testservice-java",
+        "truth-prebuilt",
     ],
     libs: ["android.system.virtualmachine"],
     jni_libs: ["MicrodroidTestNativeLib"],
@@ -22,6 +23,7 @@
     name: "MicrodroidTestNativeLib",
     srcs: ["src/native/testbinary.cpp"],
     shared_libs: [
+        "android.security.dice-ndk",
         "android.system.virtualmachineservice-ndk",
         "com.android.microdroid.testservice-ndk",
         "libbase",
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 bd44a3c..803bdc6 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -15,14 +15,14 @@
  */
 package com.android.microdroid.test;
 
-import static org.hamcrest.core.Is.is;
-import static org.hamcrest.core.IsNot.not;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeNoException;
-import static org.junit.Assume.assumeThat;
 
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 
@@ -52,6 +52,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -212,8 +213,10 @@
     @Test
     public void changingDebugLevelInvalidatesVmIdentity()
             throws VirtualMachineException, InterruptedException, IOException {
-        assumeThat("Skip on Cuttlefish. b/195765441",
-                android.os.Build.DEVICE, is(not("vsoc_x86_64")));
+        assume()
+            .withMessage("Skip on Cuttlefish. b/195765441")
+            .that(android.os.Build.DEVICE)
+            .isNotEqualTo("vsoc_x86_64");
 
         VirtualMachineConfig.Builder builder =
                 new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json");
@@ -269,4 +272,64 @@
                 };
         listener.runToFinish(mInner.mVm);
     }
+
+    private byte[] launchVmAndGetSecret(String instanceName)
+            throws VirtualMachineException, InterruptedException {
+        VirtualMachineConfig.Builder builder =
+                new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json");
+        VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.NONE).build();
+        mInner.mVm = mInner.mVmm.getOrCreate(instanceName, normalConfig);
+        final CompletableFuture<byte[]> secret = new CompletableFuture<>();
+        VmEventListener listener =
+                new VmEventListener() {
+                    @Override
+                    public void onPayloadReady(VirtualMachine vm) {
+                        try {
+                            ITestService testService = ITestService.Stub.asInterface(
+                                    vm.connectToVsockServer(ITestService.SERVICE_PORT).get());
+                            secret.complete(testService.insecurelyExposeSecret());
+                        } catch (Exception e) {
+                            fail("Exception while connecting to service: " + e.toString());
+                        }
+                        // TODO(b/208639280): remove this sleep. For now, we need to wait for a few
+                        // seconds so that crosvm can actually persist instance.img.
+                        try {
+                            Thread.sleep(30 * 1000);
+                        } catch (InterruptedException e) { }
+                        forceStop(vm);
+                    }
+                };
+        listener.runToFinish(mInner.mVm);
+        return secret.getNow(null);
+    }
+
+    @Test
+    public void instancesOfSameVmHaveDifferentSecrets()
+            throws VirtualMachineException, InterruptedException {
+        assume()
+            .withMessage("Skip on Cuttlefish. b/195765441")
+            .that(android.os.Build.DEVICE)
+            .isNotEqualTo("vsoc_x86_64");
+
+        byte[] vm_a_secret = launchVmAndGetSecret("test_vm_a");
+        byte[] vm_b_secret = launchVmAndGetSecret("test_vm_b");
+        assertThat(vm_a_secret).isNotNull();
+        assertThat(vm_b_secret).isNotNull();
+        assertThat(vm_a_secret).isNotEqualTo(vm_b_secret);
+    }
+
+    @Test
+    public void sameInstanceKeepsSameSecrets()
+            throws VirtualMachineException, InterruptedException {
+        assume()
+            .withMessage("Skip on Cuttlefish. b/195765441")
+            .that(android.os.Build.DEVICE)
+            .isNotEqualTo("vsoc_x86_64");
+
+        byte[] vm_secret_first_boot = launchVmAndGetSecret("test_vm");
+        byte[] vm_secret_second_boot = launchVmAndGetSecret("test_vm");
+        assertThat(vm_secret_first_boot).isNotNull();
+        assertThat(vm_secret_second_boot).isNotNull();
+        assertThat(vm_secret_first_boot).isEqualTo(vm_secret_second_boot);
+    }
 }
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 301328a..417ff4a 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <aidl/android/security/dice/IDiceNode.h>
 #include <aidl/android/system/virtualmachineservice/IVirtualMachineService.h>
 #include <aidl/com/android/microdroid/testservice/BnTestService.h>
 #include <android-base/file.h>
@@ -32,6 +33,9 @@
 #include <binder_rpc_unstable.hpp>
 #include <string>
 
+using aidl::android::hardware::security::dice::BccHandover;
+using aidl::android::security::dice::IDiceNode;
+
 using aidl::android::system::virtualmachineservice::IVirtualMachineService;
 
 using android::base::ErrnoError;
@@ -74,6 +78,23 @@
 
             return ndk::ScopedAStatus::ok();
         }
+
+        ndk::ScopedAStatus insecurelyExposeSecret(std::vector<uint8_t>* out) override {
+            ndk::SpAIBinder binder(AServiceManager_getService("android.security.dice.IDiceNode"));
+            auto service = IDiceNode::fromBinder(binder);
+            if (service == nullptr) {
+                return ndk::ScopedAStatus::
+                        fromServiceSpecificErrorWithMessage(0, "Failed to find diced");
+            }
+            BccHandover handover;
+            auto deriveStatus = service->derive({}, &handover);
+            if (!deriveStatus.isOk()) {
+                return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(0,
+                                                                               "Failed call diced");
+            }
+            *out = {handover.cdiSeal.begin(), handover.cdiSeal.end()};
+            return ndk::ScopedAStatus::ok();
+        }
     };
     auto testService = ndk::SharedRefBase::make<TestService>();