Merge "[x509] Include Apk/Apex info in attestation certificate extension" into main
diff --git a/Android.bp b/Android.bp
index 9c17c7f..54919d4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -61,5 +61,5 @@
 genrule_defaults {
     name: "dts_to_dtb",
     tools: ["dtc"],
-    cmd: "$(location dtc) -I dts -O dtb $(in) -o $(out)",
+    cmd: "FILES=($(in)) && $(location dtc) -I dts -O dtb $${FILES[-1]} -o $(out)",
 }
diff --git a/libs/apkverify/Android.bp b/libs/apkverify/Android.bp
index 1c18d2d..4c5a622 100644
--- a/libs/apkverify/Android.bp
+++ b/libs/apkverify/Android.bp
@@ -48,10 +48,12 @@
     test_suites: ["general-tests"],
     rustlibs: [
         "libandroid_logger",
+        "libanyhow",
         "libapkverify",
         "libapkzip",
         "libbyteorder",
         "liblog_rust",
+        "libopenssl",
         "libzip",
     ],
     data: ["tests/data/*"],
diff --git a/libs/apkverify/src/lib.rs b/libs/apkverify/src/lib.rs
index 6af8122..1f3c74f 100644
--- a/libs/apkverify/src/lib.rs
+++ b/libs/apkverify/src/lib.rs
@@ -26,5 +26,5 @@
 mod v4;
 
 pub use algorithms::{HashAlgorithm, SignatureAlgorithmID};
-pub use v3::{get_public_key_der, verify};
+pub use v3::{extract_signed_data, verify, SignedData};
 pub use v4::{get_apk_digest, V4Signature};
diff --git a/libs/apkverify/src/v3.rs b/libs/apkverify/src/v3.rs
index 8a8ad73..88644c7 100644
--- a/libs/apkverify/src/v3.rs
+++ b/libs/apkverify/src/v3.rs
@@ -44,13 +44,9 @@
     public_key: PKey<pkey::Public>,
 }
 
-impl Signer {
-    fn sdk_range(&self) -> RangeInclusive<u32> {
-        self.min_sdk..=self.max_sdk
-    }
-}
-
-struct SignedData {
+/// Contains the signed data part of an APK v3 signature.
+#[derive(Debug)]
+pub struct SignedData {
     digests: LengthPrefixed<Vec<LengthPrefixed<Digest>>>,
     certificates: LengthPrefixed<Vec<LengthPrefixed<X509Certificate>>>,
     min_sdk: u32,
@@ -59,20 +55,6 @@
     additional_attributes: LengthPrefixed<Vec<LengthPrefixed<AdditionalAttributes>>>,
 }
 
-impl SignedData {
-    fn sdk_range(&self) -> RangeInclusive<u32> {
-        self.min_sdk..=self.max_sdk
-    }
-
-    fn find_digest_by_algorithm(&self, algorithm_id: SignatureAlgorithmID) -> Result<&Digest> {
-        Ok(self
-            .digests
-            .iter()
-            .find(|&dig| dig.signature_algorithm_id == Some(algorithm_id))
-            .context(format!("Digest not found for algorithm: {:?}", algorithm_id))?)
-    }
-}
-
 #[derive(Debug)]
 pub(crate) struct Signature {
     /// Option is used here to allow us to ignore unsupported algorithm.
@@ -80,6 +62,7 @@
     signature: LengthPrefixed<Bytes>,
 }
 
+#[derive(Debug)]
 struct Digest {
     signature_algorithm_id: Option<SignatureAlgorithmID>,
     digest: LengthPrefixed<Bytes>,
@@ -88,19 +71,19 @@
 type X509Certificate = Bytes;
 type AdditionalAttributes = Bytes;
 
-/// Verifies APK Signature Scheme v3 signatures of the provided APK and returns the public key
-/// associated with the signer in DER format.
-pub fn verify<P: AsRef<Path>>(apk_path: P, current_sdk: u32) -> Result<Box<[u8]>> {
+/// Verifies APK Signature Scheme v3 signatures of the provided APK and returns the SignedData from
+/// the signature.
+pub fn verify<P: AsRef<Path>>(apk_path: P, current_sdk: u32) -> Result<SignedData> {
     let apk = File::open(apk_path.as_ref())?;
     let (signer, mut sections) = extract_signer_and_apk_sections(apk, current_sdk)?;
     signer.verify(&mut sections)
 }
 
-/// Gets the public key (in DER format) that was used to sign the given APK/APEX file
-pub fn get_public_key_der<P: AsRef<Path>>(apk_path: P, current_sdk: u32) -> Result<Box<[u8]>> {
+/// Extracts the SignedData from the signature of the given APK. (The signature is not verified.)
+pub fn extract_signed_data<P: AsRef<Path>>(apk_path: P, current_sdk: u32) -> Result<SignedData> {
     let apk = File::open(apk_path.as_ref())?;
     let (signer, _) = extract_signer_and_apk_sections(apk, current_sdk)?;
-    Ok(signer.public_key.public_key_to_der()?.into_boxed_slice())
+    signer.parse_signed_data()
 }
 
 pub(crate) fn extract_signer_and_apk_sections<R: Read + Seek>(
@@ -123,6 +106,10 @@
 }
 
 impl Signer {
+    fn sdk_range(&self) -> RangeInclusive<u32> {
+        self.min_sdk..=self.max_sdk
+    }
+
     /// Selects the signature that has the strongest supported `SignatureAlgorithmID`.
     /// The strongest signature is used in both v3 verification and v4 apk digest computation.
     pub(crate) fn strongest_signature(&self) -> Result<&Signature> {
@@ -143,27 +130,33 @@
         Ok(digest.digest.as_ref().to_vec().into_boxed_slice())
     }
 
-    /// Verifies the strongest signature from signatures against signed data using public key.
-    /// Returns the verified signed data.
-    fn verify_signature(&self, strongest: &Signature) -> Result<SignedData> {
-        let mut verifier = strongest
+    /// Verifies a signature over the signed data using the public key.
+    fn verify_signature(&self, signature: &Signature) -> Result<()> {
+        let mut verifier = signature
             .signature_algorithm_id
             .context("Unsupported algorithm")?
             .new_verifier(&self.public_key)?;
         verifier.update(&self.signed_data)?;
-        ensure!(verifier.verify(&strongest.signature)?, "Signature is invalid.");
-        // It is now safe to parse signed data.
+        ensure!(verifier.verify(&signature.signature)?, "Signature is invalid.");
+        Ok(())
+    }
+
+    /// Returns the signed data, converted from bytes.
+    fn parse_signed_data(&self) -> Result<SignedData> {
         self.signed_data.slice(..).read()
     }
 
     /// The steps in this method implements APK Signature Scheme v3 verification step 3.
-    fn verify<R: Read + Seek>(&self, sections: &mut ApkSections<R>) -> Result<Box<[u8]>> {
+    fn verify<R: Read + Seek>(&self, sections: &mut ApkSections<R>) -> Result<SignedData> {
         // 1. Choose the strongest supported signature algorithm ID from signatures.
         let strongest = self.strongest_signature()?;
 
         // 2. Verify the corresponding signature from signatures against signed data using public
         // key.
-        let verified_signed_data = self.verify_signature(strongest)?;
+        self.verify_signature(strongest)?;
+
+        // It is now safe to parse signed data.
+        let verified_signed_data = self.parse_signed_data()?;
 
         // 3. Verify the min and max SDK versions in the signed data match those specified for the
         //    signer.
@@ -199,8 +192,7 @@
 
         // 7. Verify that public key of the first certificate of certificates is identical to public
         //    key.
-        let cert = verified_signed_data.certificates.first().context("No certificates listed")?;
-        let cert = X509::from_der(cert.as_ref())?;
+        let cert = X509::from_der(verified_signed_data.first_certificate_der()?)?;
         ensure!(
             cert.public_key()?.public_eq(&self.public_key),
             "Public key mismatch between certificate and signature record"
@@ -209,7 +201,29 @@
         // TODO(b/245914104)
         // 8. If the proof-of-rotation attribute exists for the signer verify that the
         // struct is valid and this signer is the last certificate in the list.
-        Ok(self.public_key.public_key_to_der()?.into_boxed_slice())
+
+        Ok(verified_signed_data)
+    }
+}
+
+impl SignedData {
+    /// Returns the first X.509 certificate in the signed data, encoded in DER form. (All other
+    /// certificates are ignored for v3; this certificate describes the public key that was actually
+    /// used to sign the APK.)
+    pub fn first_certificate_der(&self) -> Result<&[u8]> {
+        Ok(self.certificates.first().context("No certificates listed")?)
+    }
+
+    fn sdk_range(&self) -> RangeInclusive<u32> {
+        self.min_sdk..=self.max_sdk
+    }
+
+    fn find_digest_by_algorithm(&self, algorithm_id: SignatureAlgorithmID) -> Result<&Digest> {
+        Ok(self
+            .digests
+            .iter()
+            .find(|&dig| dig.signature_algorithm_id == Some(algorithm_id))
+            .context(format!("Digest not found for algorithm: {:?}", algorithm_id))?)
     }
 }
 
diff --git a/libs/apkverify/tests/apkverify_test.rs b/libs/apkverify/tests/apkverify_test.rs
index 0d8e020..441b708 100644
--- a/libs/apkverify/tests/apkverify_test.rs
+++ b/libs/apkverify/tests/apkverify_test.rs
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
+use anyhow::Result;
 use apkverify::{
-    get_apk_digest, get_public_key_der, testing::assert_contains, verify, SignatureAlgorithmID,
+    extract_signed_data, get_apk_digest, testing::assert_contains, verify, SignatureAlgorithmID,
 };
 use apkzip::zip_sections;
 use byteorder::{LittleEndian, ReadBytesExt};
 use log::info;
+use openssl::x509::X509;
 use std::fmt::Write;
 use std::io::{Seek, SeekFrom};
 use std::{fs, matches, path::Path};
@@ -286,22 +288,30 @@
 /// * public key extracted from apk without verification
 /// * expected public key from the corresponding .der file
 fn validate_apk_public_key<P: AsRef<Path>>(apk_path: P) {
-    let public_key_from_verification = verify(&apk_path, SDK_INT);
-    let public_key_from_verification =
-        public_key_from_verification.expect("Error in verification result");
+    let signed_data_from_verification =
+        verify(&apk_path, SDK_INT).expect("Error in verification result");
+    let cert_from_verification = signed_data_from_verification.first_certificate_der().unwrap();
+    let public_key_from_verification = public_key_der_from_cert(cert_from_verification).unwrap();
 
     let expected_public_key_path = format!("{}.der", apk_path.as_ref().to_str().unwrap());
     assert_bytes_eq_to_data_in_file(&public_key_from_verification, expected_public_key_path);
 
-    let public_key_from_apk = get_public_key_der(&apk_path, SDK_INT);
-    let public_key_from_apk =
-        public_key_from_apk.expect("Error when extracting public key from apk");
+    let signed_data_from_apk = extract_signed_data(&apk_path, SDK_INT)
+        .expect("Error when extracting signed data from apk");
+    let cert_from_apk = signed_data_from_apk.first_certificate_der().unwrap();
+    // If the two certficiates are byte for byte identical (which they should be), then so are
+    // the public keys embedded in them.
     assert_eq!(
-        public_key_from_verification, public_key_from_apk,
-        "Public key extracted directly from apk does not match the public key from verification."
+        cert_from_verification, cert_from_apk,
+        "Certificate extracted directly from apk does not match the certificate from verification."
     );
 }
 
+fn public_key_der_from_cert(cert_der: &[u8]) -> Result<Vec<u8>> {
+    let cert = X509::from_der(cert_der)?;
+    Ok(cert.public_key()?.public_key_to_der()?)
+}
+
 /// Validates that the following apk_digest are equal:
 /// * apk_digest directly extracted from apk without computation
 /// * computed apk_digest
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index e6ddfb9..0cf7013 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -30,17 +30,17 @@
     payload_metadata: &PayloadMetadata,
 ) -> Result<OwnedDiceArtifacts> {
     let subcomponents = build_subcomponent_list(instance_data);
-    let config_descriptor = format_payload_config_descriptor(payload_metadata, &subcomponents)
+    let config_descriptor = format_payload_config_descriptor(payload_metadata, subcomponents)
         .context("Building config descriptor")?;
 
     // Calculate compound digests of code and authorities
     let mut code_hash_ctx = Sha512::new();
     let mut authority_hash_ctx = Sha512::new();
     code_hash_ctx.update(instance_data.apk_data.root_hash.as_ref());
-    authority_hash_ctx.update(instance_data.apk_data.pubkey.as_ref());
+    authority_hash_ctx.update(instance_data.apk_data.cert_hash.as_ref());
     for extra_apk in &instance_data.extra_apks_data {
         code_hash_ctx.update(extra_apk.root_hash.as_ref());
-        authority_hash_ctx.update(extra_apk.pubkey.as_ref());
+        authority_hash_ctx.update(extra_apk.cert_hash.as_ref());
     }
     for apex in &instance_data.apex_data {
         code_hash_ctx.update(apex.root_digest.as_ref());
@@ -57,42 +57,40 @@
     dice.derive(code_hash, &config_descriptor, authority_hash, debuggable, hidden)
 }
 
-struct Subcomponent<'a> {
+struct Subcomponent {
     name: String,
     version: u64,
-    code_hash: &'a [u8],
-    authority_hash: Box<[u8]>,
+    code_hash: Vec<u8>,
+    authority_hash: Vec<u8>,
 }
 
-impl<'a> Subcomponent<'a> {
-    fn to_value(&self) -> Result<Value> {
+impl Subcomponent {
+    fn into_value(self) -> Result<Value> {
         Ok(cbor!({
            1 => self.name,
            2 => self.version,
-           3 => self.code_hash,
-           4 => self.authority_hash
+           3 => Value::Bytes(self.code_hash),
+           4 => Value::Bytes(self.authority_hash),
         })?)
     }
 
-    fn for_apk(apk: &'a ApkData) -> Self {
+    fn for_apk(apk: &ApkData) -> Self {
         Self {
             name: format!("apk:{}", apk.package_name),
             version: apk.version_code,
-            code_hash: &apk.root_hash,
-            authority_hash:
-                // TODO(b/305925597): Hash the certificate not the pubkey
-                Box::new(sha512(&apk.pubkey)),
+            code_hash: apk.root_hash.clone(),
+            authority_hash: apk.cert_hash.clone(),
         }
     }
 
-    fn for_apex(apex: &'a ApexData) -> Self {
+    fn for_apex(apex: &ApexData) -> Self {
         // Note that this is only reachable if the dice_changes flag is on, in which case
         // the manifest data will always be present.
         Self {
             name: format!("apex:{}", apex.manifest_name.as_ref().unwrap()),
             version: apex.manifest_version.unwrap() as u64,
-            code_hash: &apex.root_digest,
-            authority_hash: Box::new(sha512(&apex.public_key)),
+            code_hash: apex.root_digest.clone(),
+            authority_hash: sha512(&apex.public_key).to_vec(),
         }
     }
 }
@@ -113,7 +111,7 @@
 // of the format.
 fn format_payload_config_descriptor(
     payload: &PayloadMetadata,
-    subcomponents: &[Subcomponent],
+    subcomponents: Vec<Subcomponent>,
 ) -> Result<Vec<u8>> {
     let mut map = Vec::new();
     map.push((cbor!(-70002)?, cbor!("Microdroid payload")?));
@@ -129,7 +127,7 @@
 
     if !subcomponents.is_empty() {
         let values =
-            subcomponents.iter().map(Subcomponent::to_value).collect::<Result<Vec<_>>>()?;
+            subcomponents.into_iter().map(Subcomponent::into_value).collect::<Result<Vec<_>>>()?;
         map.push((cbor!(-71002)?, cbor!(values)?));
     }
 
@@ -141,7 +139,7 @@
     use super::*;
     use microdroid_metadata::PayloadConfig;
 
-    const NO_SUBCOMPONENTS: [Subcomponent; 0] = [];
+    const NO_SUBCOMPONENTS: Vec<Subcomponent> = Vec::new();
 
     fn assert_eq_bytes(expected: &[u8], actual: &[u8]) {
         assert_eq!(
@@ -157,7 +155,7 @@
     fn payload_metadata_with_path_formats_correctly() -> Result<()> {
         let payload_metadata = PayloadMetadata::ConfigPath("/config_path".to_string());
         let config_descriptor =
-            format_payload_config_descriptor(&payload_metadata, &NO_SUBCOMPONENTS)?;
+            format_payload_config_descriptor(&payload_metadata, NO_SUBCOMPONENTS)?;
         static EXPECTED_CONFIG_DESCRIPTOR: &[u8] = &[
             0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72,
             0x6f, 0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01,
@@ -176,7 +174,7 @@
         };
         let payload_metadata = PayloadMetadata::Config(payload_config);
         let config_descriptor =
-            format_payload_config_descriptor(&payload_metadata, &NO_SUBCOMPONENTS)?;
+            format_payload_config_descriptor(&payload_metadata, NO_SUBCOMPONENTS)?;
         static EXPECTED_CONFIG_DESCRIPTOR: &[u8] = &[
             0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72,
             0x6f, 0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01,
@@ -190,31 +188,30 @@
     #[test]
     fn payload_metadata_with_subcomponents_formats_correctly() -> Result<()> {
         let payload_metadata = PayloadMetadata::ConfigPath("/config_path".to_string());
-        let subcomponents = [
+        let subcomponents = vec![
             Subcomponent {
                 name: "apk1".to_string(),
                 version: 1,
-                code_hash: &[42u8],
-                authority_hash: Box::new([17u8]),
+                code_hash: vec![42, 43],
+                authority_hash: vec![17],
             },
             Subcomponent {
                 name: "apk2".to_string(),
                 version: 0x1000_0000_0001,
-                code_hash: &[43u8],
-                authority_hash: Box::new([19u8]),
+                code_hash: vec![43],
+                authority_hash: vec![19, 20],
             },
         ];
-        let config_descriptor =
-            format_payload_config_descriptor(&payload_metadata, &subcomponents)?;
+        let config_descriptor = format_payload_config_descriptor(&payload_metadata, subcomponents)?;
         // Verified using cbor.me.
         static EXPECTED_CONFIG_DESCRIPTOR: &[u8] = &[
             0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72,
             0x6f, 0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01,
             0x15, 0x57, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x61, 0x74,
             0x68, 0x3a, 0x00, 0x01, 0x15, 0x59, 0x82, 0xa4, 0x01, 0x64, 0x61, 0x70, 0x6b, 0x31,
-            0x02, 0x01, 0x03, 0x81, 0x18, 0x2a, 0x04, 0x81, 0x11, 0xa4, 0x01, 0x64, 0x61, 0x70,
-            0x6b, 0x32, 0x02, 0x1b, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x81,
-            0x18, 0x2b, 0x04, 0x81, 0x13,
+            0x02, 0x01, 0x03, 0x42, 0x2a, 0x2b, 0x04, 0x41, 0x11, 0xa4, 0x01, 0x64, 0x61, 0x70,
+            0x6b, 0x32, 0x02, 0x1b, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x41,
+            0x2b, 0x04, 0x42, 0x13, 0x14,
         ];
         assert_eq_bytes(EXPECTED_CONFIG_DESCRIPTOR, &config_descriptor);
         Ok(())
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index b0fc03d..7a9f0e0 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -287,20 +287,18 @@
 
 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
 pub struct ApkData {
-    pub root_hash: Box<RootHash>,
-    pub pubkey: Box<[u8]>,
+    pub root_hash: Vec<u8>,
+    pub cert_hash: Vec<u8>,
     pub package_name: String,
     pub version_code: u64,
 }
 
 impl ApkData {
     pub fn root_hash_eq(&self, root_hash: &[u8]) -> bool {
-        self.root_hash.as_ref() == root_hash
+        self.root_hash == root_hash
     }
 }
 
-pub type RootHash = [u8];
-
 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
 pub struct ApexData {
     pub name: String,
diff --git a/microdroid_manager/src/verify.rs b/microdroid_manager/src/verify.rs
index e63530b..445c1ae 100644
--- a/microdroid_manager/src/verify.rs
+++ b/microdroid_manager/src/verify.rs
@@ -12,16 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::instance::{ApexData, ApkData, MicrodroidData, RootHash};
+use crate::instance::{ApexData, ApkData, MicrodroidData};
 use crate::payload::{get_apex_data_from_payload, to_metadata};
 use crate::{is_strict_boot, is_verified_boot, MicrodroidError};
 use anyhow::{anyhow, ensure, Context, Result};
 use apkmanifest::get_manifest_info;
-use apkverify::{get_public_key_der, verify, V4Signature};
+use apkverify::{extract_signed_data, verify, V4Signature};
 use glob::glob;
 use itertools::sorted;
 use log::{info, warn};
 use microdroid_metadata::{write_metadata, Metadata};
+use openssl::sha::sha512;
 use rand::Fill;
 use rustutils::system_properties;
 use std::fs::OpenOptions;
@@ -190,10 +191,10 @@
 
 fn get_data_from_apk(
     apk_path: &str,
-    root_hash: Box<RootHash>,
+    root_hash: Box<[u8]>,
     root_hash_trustful: bool,
 ) -> Result<ApkData> {
-    let pubkey = get_public_key_from_apk(apk_path, root_hash_trustful)?;
+    let cert_hash = get_cert_hash_from_apk(apk_path, root_hash_trustful)?.to_vec();
     // Read package name etc from the APK manifest. In the unlikely event that they aren't present
     // we use the default values. We simply put these values in the DICE node for the payload, and
     // users of that can decide how to handle blank information - there's no reason for us
@@ -203,8 +204,8 @@
         .unwrap_or_default();
 
     Ok(ApkData {
-        root_hash,
-        pubkey,
+        root_hash: root_hash.into(),
+        cert_hash,
         package_name: manifest_info.package,
         version_code: manifest_info.version_code,
     })
@@ -233,21 +234,22 @@
     Ok(())
 }
 
-fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<RootHash>> {
+fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<[u8]>> {
     Ok(V4Signature::from_idsig_path(idsig_path)?.hashing_info.raw_root_hash)
 }
 
-fn get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>> {
+fn get_cert_hash_from_apk(apk: &str, root_hash_trustful: bool) -> Result<[u8; 64]> {
     let current_sdk = get_current_sdk()?;
 
-    if !root_hash_trustful {
+    let signed_data = if !root_hash_trustful {
         verify(apk, current_sdk).context(MicrodroidError::PayloadVerificationFailed(format!(
             "failed to verify {}",
             apk
         )))
     } else {
-        get_public_key_der(apk, current_sdk)
-    }
+        extract_signed_data(apk, current_sdk)
+    }?;
+    Ok(sha512(signed_data.first_certificate_der()?))
 }
 
 fn get_current_sdk() -> Result<u32> {
@@ -260,7 +262,7 @@
     apk: &'a str,
     idsig: &'a str,
     name: &'a str,
-    saved_root_hash: Option<&'a RootHash>,
+    saved_root_hash: Option<&'a [u8]>,
 }
 
 fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index c6befb4..d267e2e 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -81,10 +81,10 @@
         ":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",
+        ":test_pvmfw_devices_without_iommus",
     ],
     // To use libpvmfw_fdt_template for testing
     enabled: false,
@@ -121,37 +121,43 @@
     out: ["test_pvmfw_devices_vm_dtbo_without_symbols.dtbo"],
 }
 
+genrule_defaults {
+    name: "test_device_assignment_dts_to_dtb",
+    defaults: ["dts_to_dtb"],
+    srcs: ["testdata/test_crosvm_dt_base.dtsi"],
+}
+
 genrule {
     name: "test_pvmfw_devices_with_rng",
-    defaults: ["dts_to_dtb"],
+    defaults: ["test_device_assignment_dts_to_dtb"],
     srcs: ["testdata/test_pvmfw_devices_with_rng.dts"],
     out: ["test_pvmfw_devices_with_rng.dtb"],
 }
 
 genrule {
-    name: "test_pvmfw_devices_with_rng_iommu",
-    defaults: ["dts_to_dtb"],
-    srcs: ["testdata/test_pvmfw_devices_with_rng_iommu.dts"],
-    out: ["test_pvmfw_devices_with_rng_iommu.dtb"],
+    name: "test_pvmfw_devices_without_iommus",
+    defaults: ["test_device_assignment_dts_to_dtb"],
+    srcs: ["testdata/test_pvmfw_devices_without_iommus.dts"],
+    out: ["test_pvmfw_devices_without_iommus.dtb"],
 }
 
 genrule {
     name: "test_pvmfw_devices_with_multiple_devices_iommus",
-    defaults: ["dts_to_dtb"],
+    defaults: ["test_device_assignment_dts_to_dtb"],
     srcs: ["testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts"],
     out: ["test_pvmfw_devices_with_multiple_devices_iommus.dtb"],
 }
 
 genrule {
     name: "test_pvmfw_devices_with_iommu_sharing",
-    defaults: ["dts_to_dtb"],
+    defaults: ["test_device_assignment_dts_to_dtb"],
     srcs: ["testdata/test_pvmfw_devices_with_iommu_sharing.dts"],
     out: ["test_pvmfw_devices_with_iommu_sharing.dtb"],
 }
 
 genrule {
     name: "test_pvmfw_devices_with_iommu_id_conflict",
-    defaults: ["dts_to_dtb"],
+    defaults: ["test_device_assignment_dts_to_dtb"],
     srcs: ["testdata/test_pvmfw_devices_with_iommu_id_conflict.dts"],
     out: ["test_pvmfw_devices_with_iommu_id_conflict.dtb"],
 }
diff --git a/pvmfw/src/device_assignment.rs b/pvmfw/src/device_assignment.rs
index 60bf21c..14f1fe5 100644
--- a/pvmfw/src/device_assignment.rs
+++ b/pvmfw/src/device_assignment.rs
@@ -452,8 +452,8 @@
     const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
     const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
         "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
+    const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
     const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
-    const FDT_WITH_IOMMU_FILE_PATH: &str = "test_pvmfw_devices_with_rng_iommu.dtb";
     const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
         "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
     const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
@@ -464,7 +464,7 @@
         path: CString,
         reg: Vec<u8>,
         interrupts: Vec<u8>,
-        iommus: Vec<u32>, // pvIOMMU ids
+        iommus: Vec<u32>, // pvIOMMU id and vSID
     }
 
     impl AssignedDeviceNode {
@@ -536,6 +536,26 @@
     }
 
     #[test]
+    fn device_info_assigned_info_without_iommus() {
+        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 fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+        let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
+
+        let expected = [AssignedDeviceInfo {
+            node_path: CString::new("/backlight").unwrap(),
+            dtbo_node_path: cstr!("/fragment@backlight/__overlay__/backlight").into(),
+            reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
+            interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
+            iommus: vec![],
+        }];
+
+        assert_eq!(device_info.assigned_devices, expected);
+    }
+
+    #[test]
     fn device_info_assigned_info() {
         let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
         let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
@@ -549,7 +569,7 @@
             dtbo_node_path: cstr!("/fragment@rng/__overlay__/rng").into(),
             reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
             interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
-            iommus: vec![],
+            iommus: vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))],
         }];
 
         assert_eq!(device_info.assigned_devices, expected);
@@ -585,13 +605,19 @@
         let light = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/light")).unwrap();
         assert_eq!(light, None);
 
+        let led = vm_dtbo.node(cstr!("/fragment@led/__overlay__/led")).unwrap();
+        assert_eq!(led, None);
+
+        let backlight = vm_dtbo.node(cstr!("/fragment@backlight/__overlay__/backlight")).unwrap();
+        assert_eq!(backlight, None);
+
         let symbols_node = vm_dtbo.symbols().unwrap();
         assert_eq!(symbols_node, None);
     }
 
     #[test]
     fn device_info_patch() {
-        let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
+        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();
@@ -610,14 +636,14 @@
         // Note: Intentionally not using AssignedDeviceNode for matching all props.
         type FdtResult<T> = libfdt::Result<T>;
         let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
-            (Ok(cstr!("android,rng,ignore-gctrl-reset")), Ok(Vec::new())),
-            (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,rng\0"))),
+            (Ok(cstr!("android,backlight,ignore-gctrl-reset")), Ok(Vec::new())),
+            (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,backlight\0"))),
             (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
             (Ok(cstr!("iommus")), Ok(Vec::new())),
             (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
         ];
 
-        let rng_node = platform_dt.node(cstr!("/rng")).unwrap().unwrap();
+        let rng_node = platform_dt.node(cstr!("/backlight")).unwrap().unwrap();
         let mut properties: Vec<_> = rng_node
             .properties()
             .unwrap()
@@ -634,7 +660,7 @@
 
     #[test]
     fn device_info_overlay_iommu() {
-        let mut fdt_data = fs::read(FDT_WITH_IOMMU_FILE_PATH).unwrap();
+        let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
         let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
         let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
         let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
@@ -691,13 +717,13 @@
                 path: CString::new("/rng").unwrap(),
                 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
                 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
-                iommus: vec![0x4, 0xFF0, 0x9, 0xFF1],
+                iommus: vec![0x4, 0xFF0],
             },
             AssignedDeviceNode {
                 path: CString::new("/light").unwrap(),
                 reg: into_fdt_prop(vec![0x100, 0x9]),
                 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
-                iommus: vec![0x40, 0xFFA, 0x50, 0xFFB, 0x60, 0xFFC],
+                iommus: vec![0x40, 0xFFA, 0x50, 0xFFB],
             },
         ];
 
@@ -706,7 +732,7 @@
             assert_eq!(node, Ok(expected));
         }
         let pviommus = collect_pviommus(platform_dt);
-        assert_eq!(pviommus, Ok(vec![0x4, 0x9, 0x40, 0x50, 0x60]));
+        assert_eq!(pviommus, Ok(vec![0x4, 0x40, 0x50]));
     }
 
     #[test]
@@ -734,13 +760,13 @@
                 path: CString::new("/rng").unwrap(),
                 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
                 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
-                iommus: vec![0x4, 0xFF0, 0x9, 0xFF1],
+                iommus: vec![0x4, 0xFF0],
             },
             AssignedDeviceNode {
-                path: CString::new("/light").unwrap(),
+                path: CString::new("/led").unwrap(),
                 reg: into_fdt_prop(vec![0x100, 0x9]),
                 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
-                iommus: vec![0x9, 0xFF1, 0x40, 0xFFA],
+                iommus: vec![0x4, 0xFF0],
             },
         ];
 
@@ -750,7 +776,7 @@
         }
 
         let pviommus = collect_pviommus(platform_dt);
-        assert_eq!(pviommus, Ok(vec![0x4, 0x9, 0x40]));
+        assert_eq!(pviommus, Ok(vec![0x4]));
     }
 
     #[test]
diff --git a/pvmfw/testdata/test_crosvm_dt_base.dtsi b/pvmfw/testdata/test_crosvm_dt_base.dtsi
new file mode 100644
index 0000000..0c1a311
--- /dev/null
+++ b/pvmfw/testdata/test_crosvm_dt_base.dtsi
@@ -0,0 +1,152 @@
+/dts-v1/;
+/plugin/;
+
+// This is generated manually by removing unassigned pvIOMMU nodes
+// from patched platform.dts.
+
+/ {
+	interrupt-parent = <0x01>;
+	compatible = "linux,dummy-virt";
+	#address-cells = <0x02>;
+	#size-cells = <0x02>;
+
+	chosen {
+		bootargs = "panic=-1 crashkernel=31M";
+		linux,initrd-end = <0x811d6cb8>;
+		linux,initrd-start = <0x81000000>;
+		stdout-path = "/uart@3f8";
+		1,pci-probe-only = <0x01>;
+		kaslr-seed = <0x00 0x00>;
+		avf,strict-boot;
+		avf,new-instance;
+	};
+
+	memory {
+		device_type = "memory";
+		reg = <0x00 0x80000000 0x00 0x10000000>;
+	};
+
+	reserved-memory {
+		#address-cells = <0x02>;
+		#size-cells = <0x02>;
+		ranges;
+
+		restricted_dma_reserved {
+			compatible = "restricted-dma-pool";
+			size = <0x00 0xe00000>;
+			alignment = <0x00 0x1000>;
+			phandle = <0x02>;
+		};
+
+		dice {
+			compatible = "google,open-dice";
+			no-map;
+			reg = <0x00 0x7fe25000 0x00 0x1000>;
+		};
+	};
+
+	cpus {
+		#address-cells = <0x01>;
+		#size-cells = <0x00>;
+
+		cpu@0 {
+			device_type = "cpu";
+			compatible = "arm,arm-v8";
+			enable-method = "psci";
+			reg = <0x00>;
+		};
+	};
+
+	intc {
+		compatible = "arm,gic-v3";
+		#address-cells = <0x02>;
+		#size-cells = <0x02>;
+		#interrupt-cells = <0x03>;
+		interrupt-controller;
+		reg = <0x00 0x3fff0000 0x00 0x10000 0x00 0x3ffd0000 0x00 0x20000>;
+		phandle = <0x01>;
+	};
+
+	timer {
+		compatible = "arm,armv8-timer";
+		always-on;
+		interrupts = <0x01 0x0d 0x108 0x01 0x0e 0x108 0x01 0x0b 0x108 0x01 0x0a 0x108>;
+	};
+
+	uart@2e8 {
+		compatible = "ns16550a";
+		reg = <0x00 0x2e8 0x00 0x08>;
+		clock-frequency = <0x1c2000>;
+		interrupts = <0x00 0x02 0x01>;
+	};
+
+	uart@2f8 {
+		compatible = "ns16550a";
+		reg = <0x00 0x2f8 0x00 0x08>;
+		clock-frequency = <0x1c2000>;
+		interrupts = <0x00 0x02 0x01>;
+	};
+
+	uart@3e8 {
+		compatible = "ns16550a";
+		reg = <0x00 0x3e8 0x00 0x08>;
+		clock-frequency = <0x1c2000>;
+		interrupts = <0x00 0x00 0x01>;
+	};
+
+	uart@3f8 {
+		compatible = "ns16550a";
+		reg = <0x00 0x3f8 0x00 0x08>;
+		clock-frequency = <0x1c2000>;
+		interrupts = <0x00 0x00 0x01>;
+	};
+
+	psci {
+		compatible = "arm,psci-1.0";
+		method = "hvc";
+	};
+
+	pci {
+		compatible = "pci-host-cam-generic";
+		device_type = "pci";
+		#address-cells = <0x03>;
+		#size-cells = <0x02>;
+		#interrupt-cells = <0x01>;
+		dma-coherent;
+		memory-region = <0x02>;
+		ranges = <0x3000000 0x00 0x2000000 0x00 0x2000000 0x00 0x2000000 0x3000000 0x00 0x90800000 0x00 0x90800000 0xff 0x6f800000>;
+		bus-range = <0x00 0x00>;
+		reg = <0x00 0x10000 0x00 0x1000000>;
+		interrupt-map = <0x800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x04 0x04 0x1000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x05 0x04 0x1800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x06 0x04 0x2000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x07 0x04 0x2800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x08 0x04 0x3000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x09 0x04 0x3800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x0a 0x04 0x4000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x0b 0x04 0x4800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x0c 0x04>;
+		interrupt-map-mask = <0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07>;
+	};
+
+	pclk@3M {
+		compatible = "fixed-clock";
+		clock-frequency = <0x2fefd8>;
+		#clock-cells = <0x00>;
+		phandle = <0x03>;
+	};
+
+	rtc@2000 {
+		compatible = "arm,primecell";
+		arm,primecell-periphid = <0x41030>;
+		reg = <0x00 0x2000 0x00 0x1000>;
+		interrupts = <0x00 0x01 0x04>;
+		clock-names = "apb_pclk";
+		clocks = <0x03>;
+	};
+
+	vmwdt@3000 {
+		compatible = "qemu,vcpu-stall-detector";
+		reg = <0x00 0x3000 0x00 0x1000>;
+		clock-frequency = <0x0a>;
+		timeout-sec = <0x08>;
+	};
+
+	__symbols__ {
+		swiotlb = "/reserved-memory/restricted_dma_reserved";
+		intc = "/intc";
+		clk = "/pclk@3M";
+	};
+};
diff --git a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts
index e85b55b..da08694 100644
--- a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts
@@ -21,6 +21,33 @@
 			light {
 				compatible = "android,light";
 				version = <0x1 0x2>;
+				android,pvmfw,phy-reg = <0x0 0xF00000 0x1000>;
+				android,pvmfw,phy-iommu = <0x0 0x40000>, <0x0 0x50000>;
+				android,pvmfw,phy-sid = <4>, <5>;
+			};
+		};
+	};
+
+	fragment@led {
+		target-path = "/";
+		__overlay__ {
+			led {
+				compatible = "android,led";
+				prop = <0x555>;
+				android,pvmfw,phy-reg = <0x0 0x12000000 0x1000>;
+				android,pvmfw,phy-iommu = <0x0 0x12E40000>;
+				android,pvmfw,phy-sid = <3>;
+			};
+		};
+	};
+
+	fragment@backlight {
+		target-path = "/";
+		__overlay__ {
+			backlight {
+				compatible = "android,backlight";
+				android,backlight,ignore-gctrl-reset;
+				android,pvmfw,phy-reg = <0x0 0x300 0x100>;
 			};
 		};
 	};
@@ -28,5 +55,7 @@
 	__symbols__ {
 		rng = "/fragment@rng/__overlay__/rng";
 		sensor = "/fragment@sensor/__overlay__/light";
+		led = "/fragment@led/__overlay__/led";
+		backlight = "/fragment@backlight/__overlay__/backlight";
 	};
 };
diff --git a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
index 08444ac..18b9e79 100644
--- a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
@@ -21,6 +21,22 @@
 			light {
 				compatible = "android,light";
 				version = <0x1 0x2>;
+				android,pvmfw,phy-reg = <0x0 0xF00000 0x1000>;
+				android,pvmfw,phy-iommu = <0x0 0x40000>, <0x0 0x50000>;
+				android,pvmfw,phy-sid = <4>, <5>;
+			};
+		};
+	};
+
+	fragment@led {
+		target-path = "/";
+		__overlay__ {
+			led {
+				compatible = "android,led";
+				prop;
+				android,pvmfw,phy-reg = <0x0 0x12F00000 0x1000>;
+				android,pvmfw,phy-iommu = <0x0 0x20000>, <0x0 0x30000>;
+				android,pvmfw,phy-sid = <7>, <8>;
 			};
 		};
 	};
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts b/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
index 199a5ce..70b633c 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
@@ -1,54 +1,16 @@
 /dts-v1/;
 /plugin/;
 
+/include/ "test_crosvm_dt_base.dtsi"
+
 / {
-	chosen {
-		stdout-path = "/uart@3f8";
-		linux,pci-probe-only = <1>;
-	};
-
-	memory {
-		device_type = "memory";
-		reg = <0x00 0x80000000 0xFFFFFFFF>;
-	};
-
-	reserved-memory {
-		#address-cells = <2>;
-		#size-cells = <2>;
-		ranges;
-		swiotlb: restricted_dma_reserved {
-			compatible = "restricted-dma-pool";
-			reg = <0xFFFFFFFF>;
-			size = <0xFFFFFFFF>;
-			alignment = <0xFFFFFFFF>;
-		};
-
-		dice {
-			compatible = "google,open-dice";
-			no-map;
-			reg = <0xFFFFFFFF>;
-		};
-	};
-
-	cpus {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		cpu@0 {
-			device_type = "cpu";
-		};
-		cpu@1 {
-			device_type = "cpu";
-		    reg = <0x00 0x80000000 0xFFFFFFFF>;
-		};
-    };
-
     rng@90000000 {
         compatible = "android,rng";
         reg = <0x0 0x9 0x0 0xFF>;
         interrupts = <0x0 0xF 0x4>;
         google,eh,ignore-gctrl-reset;
         status = "okay";
-        iommus = <&pviommu_0>, <&pviommu_1>;
+        iommus = <&pviommu_0 0x0>, <&pviommu_1 0x1>;
     };
 
     pviommu_0: pviommu0 {
@@ -67,7 +29,7 @@
         compatible = "android,light";
         reg = <0x100 0x9>;
         interrupts = <0x0 0xF 0x5>;
-        iommus = <&pviommu_0>, <&pviommu_a>, <&pviommu_b>;
+        iommus = <&pviommu_a 0xA>, <&pviommu_b 0xB>;
     };
 
     pviommu_a: pviommua {
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
index 4906064..7c6d2f2 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
@@ -1,61 +1,23 @@
 /dts-v1/;
 /plugin/;
 
+/include/ "test_crosvm_dt_base.dtsi"
+
 / {
-	chosen {
-		stdout-path = "/uart@3f8";
-		linux,pci-probe-only = <1>;
-	};
-
-	memory {
-		device_type = "memory";
-		reg = <0x00 0x80000000 0xFFFFFFFF>;
-	};
-
-	reserved-memory {
-		#address-cells = <2>;
-		#size-cells = <2>;
-		ranges;
-		swiotlb: restricted_dma_reserved {
-			compatible = "restricted-dma-pool";
-			reg = <0xFFFFFFFF>;
-			size = <0xFFFFFFFF>;
-			alignment = <0xFFFFFFFF>;
-		};
-
-		dice {
-			compatible = "google,open-dice";
-			no-map;
-			reg = <0xFFFFFFFF>;
-		};
-	};
-
-	cpus {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		cpu@0 {
-			device_type = "cpu";
-		};
-		cpu@1 {
-			device_type = "cpu";
-		    reg = <0x00 0x80000000 0xFFFFFFFF>;
-		};
-    };
-
     rng@90000000 {
         compatible = "android,rng";
         reg = <0x0 0x9 0x0 0xFF>;
         interrupts = <0x0 0xF 0x4>;
         google,eh,ignore-gctrl-reset;
         status = "okay";
-        iommus = <&pviommu_0 0xFF0>, <&pviommu_1 0xFF1>;
+        iommus = <&pviommu_0 0xFF0>;
     };
 
-    light@70000000 {
+    led@70000000 {
         compatible = "android,light";
         reg = <0x100 0x9>;
         interrupts = <0x0 0xF 0x5>;
-        iommus = <&pviommu_1 0xFF1>, <&pviommu_a 0xFFA>;
+        iommus = <&pviommu_0 0xFF0>;
     };
 
     pviommu_0: pviommu0 {
@@ -63,16 +25,4 @@
         id = <0x4>;
         #iommu-cells = <1>;
     };
-
-    pviommu_1: pviommu1 {
-        compatible = "pkvm,pviommu";
-        id = <0x9>;
-        #iommu-cells = <1>;
-    };
-
-    pviommu_a: pviommua {
-        compatible = "pkvm,pviommu";
-        id = <0x40>;
-        #iommu-cells = <1>;
-    };
 };
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts b/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
index 959cd23..76c99c9 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
@@ -1,54 +1,15 @@
 /dts-v1/;
 /plugin/;
 
+/include/ "test_crosvm_dt_base.dtsi"
 / {
-	chosen {
-		stdout-path = "/uart@3f8";
-		linux,pci-probe-only = <1>;
-	};
-
-	memory {
-		device_type = "memory";
-		reg = <0x00 0x80000000 0xFFFFFFFF>;
-	};
-
-	reserved-memory {
-		#address-cells = <2>;
-		#size-cells = <2>;
-		ranges;
-		swiotlb: restricted_dma_reserved {
-			compatible = "restricted-dma-pool";
-			reg = <0xFFFFFFFF>;
-			size = <0xFFFFFFFF>;
-			alignment = <0xFFFFFFFF>;
-		};
-
-		dice {
-			compatible = "google,open-dice";
-			no-map;
-			reg = <0xFFFFFFFF>;
-		};
-	};
-
-	cpus {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		cpu@0 {
-			device_type = "cpu";
-		};
-		cpu@1 {
-			device_type = "cpu";
-		    reg = <0x00 0x80000000 0xFFFFFFFF>;
-		};
-    };
-
     rng@90000000 {
         compatible = "android,rng";
         reg = <0x0 0x9 0x0 0xFF>;
         interrupts = <0x0 0xF 0x4>;
         google,eh,ignore-gctrl-reset;
         status = "okay";
-        iommus = <&pviommu_0 0xFF0>, <&pviommu_1 0xFF1>;
+        iommus = <&pviommu_0 0xFF0>;
     };
 
     pviommu_0: pviommu0 {
@@ -57,17 +18,11 @@
         #iommu-cells = <1>;
     };
 
-    pviommu_1: pviommu1 {
-        compatible = "pkvm,pviommu";
-        id = <0x9>;
-        #iommu-cells = <1>;
-    };
-
     light@70000000 {
         compatible = "android,light";
         reg = <0x100 0x9>;
         interrupts = <0x0 0xF 0x5>;
-        iommus = <&pviommu_a 0xFFA>, <&pviommu_b 0xFFB>, <&pviommu_c 0xFFC>;
+        iommus = <&pviommu_a 0xFFA>, <&pviommu_b 0xFFB>;
     };
 
     pviommu_a: pviommua {
@@ -81,10 +36,4 @@
         id = <0x50>;
         #iommu-cells = <1>;
     };
-
-    pviommu_c: pviommuc {
-        compatible = "pkvm,pviommu";
-        id = <0x60>;
-        #iommu-cells = <1>;
-    };
 };
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_rng.dts b/pvmfw/testdata/test_pvmfw_devices_with_rng.dts
index f24fd65..a987098 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_rng.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_rng.dts
@@ -1,52 +1,21 @@
 /dts-v1/;
 /plugin/;
 
+/include/ "test_crosvm_dt_base.dtsi"
+
 / {
-	chosen {
-		stdout-path = "/uart@3f8";
-		linux,pci-probe-only = <1>;
-	};
-
-	memory {
-		device_type = "memory";
-		reg = <0x00 0x80000000 0xFFFFFFFF>;
-	};
-
-	reserved-memory {
-		#address-cells = <2>;
-		#size-cells = <2>;
-		ranges;
-		swiotlb: restricted_dma_reserved {
-			compatible = "restricted-dma-pool";
-			reg = <0xFFFFFFFF>;
-			size = <0xFFFFFFFF>;
-			alignment = <0xFFFFFFFF>;
-		};
-
-		dice {
-			compatible = "google,open-dice";
-			no-map;
-			reg = <0xFFFFFFFF>;
-		};
-	};
-
-	cpus {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		cpu@0 {
-			device_type = "cpu";
-		};
-		cpu@1 {
-			device_type = "cpu";
-		    reg = <0x00 0x80000000 0xFFFFFFFF>;
-		};
-    };
-
     rng@90000000 {
         compatible = "android,rng";
         reg = <0x0 0x9 0x0 0xFF>;
         interrupts = <0x0 0xF 0x4>;
         google,eh,ignore-gctrl-reset;
         status = "okay";
+        iommus = <&pviommu_0 0xFF0>;
+    };
+
+    pviommu_0: pviommu0 {
+        compatible = "pkvm,pviommu";
+        id = <0x4>;
+        #iommu-cells = <1>;
     };
 };
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_rng_iommu.dts b/pvmfw/testdata/test_pvmfw_devices_with_rng_iommu.dts
deleted file mode 100644
index 8c04b39..0000000
--- a/pvmfw/testdata/test_pvmfw_devices_with_rng_iommu.dts
+++ /dev/null
@@ -1,59 +0,0 @@
-/dts-v1/;
-/plugin/;
-
-/ {
-	chosen {
-		stdout-path = "/uart@3f8";
-		linux,pci-probe-only = <1>;
-	};
-
-	memory {
-		device_type = "memory";
-		reg = <0x00 0x80000000 0xFFFFFFFF>;
-	};
-
-	reserved-memory {
-		#address-cells = <2>;
-		#size-cells = <2>;
-		ranges;
-		swiotlb: restricted_dma_reserved {
-			compatible = "restricted-dma-pool";
-			reg = <0xFFFFFFFF>;
-			size = <0xFFFFFFFF>;
-			alignment = <0xFFFFFFFF>;
-		};
-
-		dice {
-			compatible = "google,open-dice";
-			no-map;
-			reg = <0xFFFFFFFF>;
-		};
-	};
-
-	cpus {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		cpu@0 {
-			device_type = "cpu";
-		};
-		cpu@1 {
-			device_type = "cpu";
-		    reg = <0x00 0x80000000 0xFFFFFFFF>;
-		};
-    };
-
-    rng@90000000 {
-        compatible = "android,rng";
-        reg = <0x0 0x9 0x0 0xFF>;
-        interrupts = <0x0 0xF 0x4>;
-        google,eh,ignore-gctrl-reset;
-        status = "okay";
-        iommus = <&pviommu_0 0xFF0>;
-    };
-
-    pviommu_0: pviommu0 {
-        compatible = "pkvm,pviommu";
-        id = <0x4>;
-        #iommu-cells = <1>;
-    };
-};
diff --git a/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts b/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
new file mode 100644
index 0000000..2036c9c
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
@@ -0,0 +1,14 @@
+/dts-v1/;
+/plugin/;
+
+/include/ "test_crosvm_dt_base.dtsi"
+
+/ {
+    backlight@90000000 {
+        compatible = "android,backlight";
+        reg = <0x0 0x9 0x0 0xFF>;
+        interrupts = <0x0 0xF 0x4>;
+        google,eh,ignore-gctrl-reset;
+        status = "okay";
+    };
+};