Introduce DICE unit tests

Add unit tests for conversion from verified boot data to information
in the DICE chain.

Refactor the build files, and the code slightly for testability.

Also switch to use the dice_changes flag rather than llpvm_changes for
including security version. (It doesn't make much difference, but it
seems marginally more precise.)

Clarify a safety comment.

See cl/585652299 for updating the list of tests.

Bug: 300911665
Test: atest libpvmfw.dice.test
Change-Id: I35fdb41bca38aaea6c253ab5bfb173dbb0a8e928
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 103619f..f49bbce 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -45,22 +45,69 @@
     cmd: "touch $(out)",
 }
 
-rust_test {
-    name: "libpvmfw.bootargs.test",
-    host_supported: true,
-    // For now, only bootargs.rs is written to be conditionally compiled with std.
-    srcs: ["src/bootargs.rs"],
+rust_defaults {
+    name: "libpvmfw.test.defaults",
     defaults: ["avf_build_flags_rust"],
     test_suites: ["general-tests"],
     test_options: {
         unit_test: true,
     },
+    prefer_rlib: true,
     rustlibs: [
         "libcstr",
+    ],
+}
+
+rust_test {
+    name: "libpvmfw.bootargs.test",
+    host_supported: true,
+    // For now, only bootargs.rs is written to be conditionally compiled with std.
+    srcs: ["src/bootargs.rs"],
+    defaults: ["libpvmfw.test.defaults"],
+    rustlibs: [
         "libzeroize",
     ],
 }
 
+rust_test {
+    name: "libpvmfw.device_assignment.test",
+    srcs: ["src/device_assignment.rs"],
+    defaults: ["libpvmfw.test.defaults"],
+    rustlibs: [
+        "liblibfdt",
+        "liblog_rust",
+        "libpvmfw_fdt_template",
+    ],
+    data: [
+        ":test_pvmfw_devices_vm_dtbo",
+        ":test_pvmfw_devices_vm_dtbo_without_symbols",
+        ":test_pvmfw_devices_with_rng",
+        ":test_pvmfw_devices_with_rng_iommu",
+        ":test_pvmfw_devices_with_multiple_devices_iommus",
+        ":test_pvmfw_devices_with_iommu_sharing",
+        ":test_pvmfw_devices_with_iommu_id_conflict",
+    ],
+    // To use libpvmfw_fdt_template for testing
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+}
+
+rust_test {
+    name: "libpvmfw.dice.test",
+    srcs: ["src/dice.rs"],
+    defaults: ["libpvmfw.test.defaults"],
+    rustlibs: [
+        "libcbor_util",
+        "libciborium",
+        "libdiced_open_dice_nostd",
+        "libpvmfw_avb_nostd",
+    ],
+}
+
 genrule {
     name: "test_pvmfw_devices_vm_dtbo",
     defaults: ["dts_to_dtb"],
@@ -110,39 +157,6 @@
     out: ["test_pvmfw_devices_with_iommu_id_conflict.dtb"],
 }
 
-rust_test {
-    name: "libpvmfw.device_assignment.test",
-    srcs: ["src/device_assignment.rs"],
-    defaults: ["avf_build_flags_rust"],
-    test_suites: ["general-tests"],
-    test_options: {
-        unit_test: true,
-    },
-    prefer_rlib: true,
-    rustlibs: [
-        "libcstr",
-        "liblibfdt",
-        "liblog_rust",
-        "libpvmfw_fdt_template",
-    ],
-    data: [
-        ":test_pvmfw_devices_vm_dtbo",
-        ":test_pvmfw_devices_vm_dtbo_without_symbols",
-        ":test_pvmfw_devices_with_rng",
-        ":test_pvmfw_devices_with_rng_iommu",
-        ":test_pvmfw_devices_with_multiple_devices_iommus",
-        ":test_pvmfw_devices_with_iommu_sharing",
-        ":test_pvmfw_devices_with_iommu_id_conflict",
-    ],
-    // To use libpvmfw_fdt_template for testing
-    enabled: false,
-    target: {
-        android_arm64: {
-            enabled: true,
-        },
-    },
-}
-
 cc_binary {
     name: "pvmfw",
     defaults: ["vmbase_elf_defaults"],
diff --git a/pvmfw/TEST_MAPPING b/pvmfw/TEST_MAPPING
index f21318e..e948400 100644
--- a/pvmfw/TEST_MAPPING
+++ b/pvmfw/TEST_MAPPING
@@ -10,6 +10,9 @@
     },
     {
       "name" : "libpvmfw.device_assignment.test"
+    },
+    {
+      "name" : "libpvmfw.dice.test"
     }
   ]
 }
diff --git a/pvmfw/src/dice.rs b/pvmfw/src/dice.rs
index c4259ba..99bf589 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -14,16 +14,13 @@
 
 //! Support for DICE derivation and BCC generation.
 
-use core::ffi::c_void;
 use core::mem::size_of;
-use core::slice;
 use cstr::cstr;
 use diced_open_dice::{
     bcc_format_config_descriptor, bcc_handover_main_flow, hash, Config, DiceConfigValues, DiceMode,
     Hash, InputValues, HIDDEN_SIZE,
 };
 use pvmfw_avb::{Capability, DebugLevel, Digest, VerifiedBootData};
-use vmbase::memory::flushed_zeroize;
 
 fn to_dice_mode(debug_level: DebugLevel) -> DiceMode {
     match debug_level {
@@ -68,16 +65,7 @@
         next_bcc: &mut [u8],
     ) -> diced_open_dice::Result<()> {
         let mut config_descriptor_buffer = [0; 128];
-        let config_values = DiceConfigValues {
-            component_name: Some(cstr!("vm_entry")),
-            security_version: if cfg!(llpvm_changes) { Some(self.security_version) } else { None },
-            rkp_vm_marker: self.rkp_vm_marker,
-            ..Default::default()
-        };
-
-        let config_descriptor_size =
-            bcc_format_config_descriptor(&config_values, &mut config_descriptor_buffer)?;
-        let config = &config_descriptor_buffer[..config_descriptor_size];
+        let config = self.generate_config_descriptor(&mut config_descriptor_buffer)?;
 
         let dice_inputs = InputValues::new(
             self.code_hash,
@@ -89,17 +77,138 @@
         let _ = bcc_handover_main_flow(current_bcc_handover, &dice_inputs, next_bcc)?;
         Ok(())
     }
+
+    fn generate_config_descriptor<'a>(
+        &self,
+        config_descriptor_buffer: &'a mut [u8],
+    ) -> diced_open_dice::Result<&'a [u8]> {
+        let config_values = DiceConfigValues {
+            component_name: Some(cstr!("vm_entry")),
+            security_version: if cfg!(dice_changes) { Some(self.security_version) } else { None },
+            rkp_vm_marker: self.rkp_vm_marker,
+            ..Default::default()
+        };
+        let config_descriptor_size =
+            bcc_format_config_descriptor(&config_values, config_descriptor_buffer)?;
+        let config = &config_descriptor_buffer[..config_descriptor_size];
+        Ok(config)
+    }
 }
 
 /// Flushes data caches over the provided address range.
 ///
 /// # Safety
 ///
-/// The provided address and size must be to a valid address range (typically on the stack, .bss,
-/// .data, or provided BCC).
+/// The provided address and size must be to an address range that is valid for read and write
+/// (typically on the stack, .bss, .data, or provided BCC) from a single allocation
+/// (e.g. stack array).
 #[no_mangle]
-unsafe extern "C" fn DiceClearMemory(_ctx: *mut c_void, size: usize, addr: *mut c_void) {
-    // SAFETY: We must trust that the slice will be valid arrays/variables on the C code stack.
+#[cfg(not(test))]
+unsafe extern "C" fn DiceClearMemory(
+    _ctx: *mut core::ffi::c_void,
+    size: usize,
+    addr: *mut core::ffi::c_void,
+) {
+    use core::slice;
+    use vmbase::memory::flushed_zeroize;
+
+    // SAFETY: We require our caller to provide a valid range within a single object. The open-dice
+    // always calls this on individual stack-allocated arrays which ensures that.
     let region = unsafe { slice::from_raw_parts_mut(addr as *mut u8, size) };
     flushed_zeroize(region)
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use ciborium::Value;
+    use std::collections::HashMap;
+    use std::vec;
+
+    const COMPONENT_NAME_KEY: i64 = -70002;
+    const COMPONENT_VERSION_KEY: i64 = -70003;
+    const RESETTABLE_KEY: i64 = -70004;
+    const SECURITY_VERSION_KEY: i64 = -70005;
+    const RKP_VM_MARKER_KEY: i64 = -70006;
+
+    const BASE_VB_DATA: VerifiedBootData = VerifiedBootData {
+        debug_level: DebugLevel::None,
+        kernel_digest: [1u8; size_of::<Digest>()],
+        initrd_digest: Some([2u8; size_of::<Digest>()]),
+        public_key: b"public key",
+        capabilities: vec![],
+        rollback_index: 42,
+    };
+
+    #[test]
+    fn base_data_conversion() {
+        let vb_data = BASE_VB_DATA;
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+
+        assert_eq!(inputs.mode, DiceMode::kDiceModeNormal);
+        assert_eq!(inputs.security_version, 42);
+        assert!(!inputs.rkp_vm_marker);
+
+        // TODO(b/313608219): Consider checks for code_hash and possibly auth_hash.
+    }
+
+    #[test]
+    fn debuggable_conversion() {
+        let vb_data = VerifiedBootData { debug_level: DebugLevel::Full, ..BASE_VB_DATA };
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+
+        assert_eq!(inputs.mode, DiceMode::kDiceModeDebug);
+    }
+
+    #[test]
+    fn rkp_vm_conversion() {
+        let vb_data =
+            VerifiedBootData { capabilities: vec![Capability::RemoteAttest], ..BASE_VB_DATA };
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+
+        assert!(inputs.rkp_vm_marker);
+    }
+
+    #[test]
+    fn base_config_descriptor() {
+        let vb_data = BASE_VB_DATA;
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+        let config_map = decode_config_descriptor(&inputs);
+
+        assert_eq!(config_map.get(&COMPONENT_NAME_KEY).unwrap().as_text().unwrap(), "vm_entry");
+        assert_eq!(config_map.get(&COMPONENT_VERSION_KEY), None);
+        assert_eq!(config_map.get(&RESETTABLE_KEY), None);
+        if cfg!(dice_changes) {
+            assert_eq!(
+                config_map.get(&SECURITY_VERSION_KEY).unwrap().as_integer().unwrap(),
+                42.into()
+            );
+        } else {
+            assert_eq!(config_map.get(&SECURITY_VERSION_KEY), None);
+        }
+        assert_eq!(config_map.get(&RKP_VM_MARKER_KEY), None);
+    }
+
+    #[test]
+    fn config_descriptor_with_rkp_vm() {
+        let vb_data =
+            VerifiedBootData { capabilities: vec![Capability::RemoteAttest], ..BASE_VB_DATA };
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+        let config_map = decode_config_descriptor(&inputs);
+
+        assert!(config_map.get(&RKP_VM_MARKER_KEY).unwrap().is_null());
+    }
+
+    fn decode_config_descriptor(inputs: &PartialInputs) -> HashMap<i64, Value> {
+        let mut buffer = [0; 128];
+        let config_descriptor = inputs.generate_config_descriptor(&mut buffer).unwrap();
+
+        let cbor_map =
+            cbor_util::deserialize::<Value>(config_descriptor).unwrap().into_map().unwrap();
+
+        cbor_map
+            .into_iter()
+            .map(|(k, v)| ((k.into_integer().unwrap().try_into().unwrap()), v))
+            .collect()
+    }
+}