Merge "libavf: implement AVirtualMachineRawConfig_addCustomMemoryBackingFile" into main
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index ef76822..f684a2a 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -1568,9 +1568,17 @@
     ) -> binder::Result<()> {
         // Don't check permission. The owner of the VM might have passed this binder object to
         // others.
-        //
-        // TODO: Should this give an error if the VM is already dead?
-        self.instance.callbacks.add(callback.clone());
+
+        // Only register callback if it may be notified.
+        // This also ensures no cyclic reference between callback and VmInstance after VM is died.
+        let vm_state = self.instance.vm_state.lock().unwrap();
+        if matches!(*vm_state, VmState::Dead) {
+            warn!("Ignoring registerCallback() after VM is died");
+        } else {
+            self.instance.callbacks.add(callback.clone());
+        }
+        drop(vm_state);
+
         Ok(())
     }
 
@@ -1736,12 +1744,17 @@
 
     /// Call all registered callbacks to say that the VM has died.
     pub fn callback_on_died(&self, cid: Cid, reason: DeathReason) {
-        let callbacks = &*self.0.lock().unwrap();
-        for callback in callbacks {
+        let mut callbacks = self.0.lock().unwrap();
+        for callback in &*callbacks {
             if let Err(e) = callback.onDied(cid as i32, reason) {
                 error!("Error notifying exit of VM CID {}: {:?}", cid, e);
             }
         }
+
+        // Nothing to notify afterward because VM cannot be restarted.
+        // Explicitly clear callbacks to prevent potential cyclic references
+        // between callback and VmInstance.
+        (*callbacks).clear();
     }
 
     /// Add a new callback to the set.
@@ -2011,16 +2024,22 @@
     let mut reader = BufReader::new(File::from(read_fd));
     let write_fd = File::from(write_fd);
 
+    let mut buf = vec![];
     std::thread::spawn(move || loop {
-        let mut buf = vec![];
+        buf.clear();
+        buf.shrink_to(1024);
         match reader.read_until(b'\n', &mut buf) {
             Ok(0) => {
                 // EOF
                 return;
             }
-            Ok(size) => {
-                if buf[size - 1] == b'\n' {
+            Ok(_size) => {
+                if buf.last() == Some(&b'\n') {
                     buf.pop();
+                    // Logs sent via TTY usually end lines with "\r\n".
+                    if buf.last() == Some(&b'\r') {
+                        buf.pop();
+                    }
                 }
                 info!("{}: {}", &tag, &String::from_utf8_lossy(&buf));
             }
diff --git a/build/debian/build.sh b/build/debian/build.sh
index 7a93349..6facfcf 100755
--- a/build/debian/build.sh
+++ b/build/debian/build.sh
@@ -216,8 +216,17 @@
 
 package_custom_kernel() {
 	if [[ "$use_custom_kernel" != 1 ]]; then
-		echo "linux-headers-generic" >> "${config_space}/package_config/AVF"
+		# NOTE: Install generic headers for the default Debian kernel.
+		cat > "${config_space}/package_config/LAST" <<EOF
+PACKAGES install
+linux-headers-generic
+EOF
 		return
+	else
+		# NOTE: Prevent FAI from installing a default Debian kernel, by removing
+		#       linux-image meta package names from arch-specific class files.
+		sed -i "/linux-image.*-${debian_arch}/d" \
+		    "${config_space}/package_config/${debian_arch^^}"
 	fi
 
 	local deb_base_url="https://deb.debian.org/debian"
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
index 43f0338..7476fc3 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
+++ b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
@@ -5,7 +5,7 @@
 cd "${KOKORO_ARTIFACTS_DIR}/git/avf/build/debian/"
 sudo losetup -D
 grep vmx /proc/cpuinfo || true
-sudo ./build.sh -r -a aarch64
+sudo ./build.sh -a aarch64 -k -r
 sudo mv images.tar.gz ${KOKORO_ARTIFACTS_DIR} || true
 mkdir -p ${KOKORO_ARTIFACTS_DIR}/logs
 sudo cp -r /var/log/fai/* ${KOKORO_ARTIFACTS_DIR}/logs || true
diff --git a/build/debian/vm_config.json.aarch64 b/build/debian/vm_config.json.aarch64
index d41a29c..96254f8 100644
--- a/build/debian/vm_config.json.aarch64
+++ b/build/debian/vm_config.json.aarch64
@@ -34,5 +34,6 @@
     "debuggable": true,
     "console_out": true,
     "console_input_device": "ttyS0",
-    "network": true
+    "network": true,
+    "auto_memory_balloon": true
 }
diff --git a/guest/authfs/Android.bp b/guest/authfs/Android.bp
index d7a8322..28e0f4a 100644
--- a/guest/authfs/Android.bp
+++ b/guest/authfs/Android.bp
@@ -47,5 +47,8 @@
 rust_binary {
     name: "authfs",
     defaults: ["authfs_defaults"],
-    apex_available: ["com.android.virt"],
+    // //apex_available:platform is necessary here to counteract the
+    // com.android.virt in crosvm_defaults and make authfs available
+    // to the platform so it can be embedded in the microdroid image.
+    apex_available: ["//apex_available:platform"],
 }
diff --git a/guest/encryptedstore/src/main.rs b/guest/encryptedstore/src/main.rs
index 983e3e9..dd4ee3b 100644
--- a/guest/encryptedstore/src/main.rs
+++ b/guest/encryptedstore/src/main.rs
@@ -120,6 +120,7 @@
         .key(&key)
         .opt_param("sector_size:4096")
         .opt_param("iv_large_sectors")
+        .opt_param("allow_discards") // This allows re-compaction of underlying disk img in host
         .build()
         .context("Couldn't build the DMCrypt target")?;
     let dm = dm::DeviceMapper::new()?;
@@ -176,7 +177,7 @@
 fn mount(source: &Path, mountpoint: &Path) -> Result<()> {
     create_dir_all(mountpoint).with_context(|| format!("Failed to create {:?}", &mountpoint))?;
     let mount_options = CString::new(
-        "fscontext=u:object_r:encryptedstore_fs:s0,context=u:object_r:encryptedstore_file:s0",
+        "fscontext=u:object_r:encryptedstore_fs:s0,context=u:object_r:encryptedstore_file:s0,discard",
     )
     .unwrap();
     let source = CString::new(source.as_os_str().as_bytes())?;
diff --git a/guest/microdroid_manager/Android.bp b/guest/microdroid_manager/Android.bp
index 1824c20..dd164c7 100644
--- a/guest/microdroid_manager/Android.bp
+++ b/guest/microdroid_manager/Android.bp
@@ -33,6 +33,7 @@
         "libdice_policy_builder",
         "libdiced_open_dice",
         "libdiced_sample_inputs",
+        "libexplicitkeydice",
         "libglob",
         "libhex",
         "libitertools",
diff --git a/guest/microdroid_manager/src/vm_secret.rs b/guest/microdroid_manager/src/vm_secret.rs
index 5999122..6331074 100644
--- a/guest/microdroid_manager/src/vm_secret.rs
+++ b/guest/microdroid_manager/src/vm_secret.rs
@@ -22,11 +22,11 @@
 use coset::{CoseKey, CborSerializable, CborOrdering};
 use dice_policy_builder::{TargetEntry, ConstraintSpec, ConstraintType, policy_for_dice_chain, MissingAction, WILDCARD_FULL_ARRAY};
 use diced_open_dice::{DiceArtifacts, OwnedDiceArtifacts};
+use explicitkeydice::OwnedDiceArtifactsWithExplicitKey;
 use keystore2_crypto::ZVec;
 use openssl::hkdf::hkdf;
 use openssl::md::Md;
 use openssl::sha;
-use secretkeeper_client::dice::OwnedDiceArtifactsWithExplicitKey;
 use secretkeeper_client::SkSession;
 use secretkeeper_comm::data_types::{Id, ID_SIZE, Secret, SECRET_SIZE};
 use secretkeeper_comm::data_types::response::Response;
diff --git a/guest/pvmfw/src/fdt.rs b/guest/pvmfw/src/fdt.rs
index fac9a9a..6716130 100644
--- a/guest/pvmfw/src/fdt.rs
+++ b/guest/pvmfw/src/fdt.rs
@@ -29,7 +29,6 @@
 use core::mem::size_of;
 use core::ops::Range;
 use hypervisor_backends::get_device_assigner;
-use hypervisor_backends::get_mem_sharer;
 use libfdt::AddressRange;
 use libfdt::CellIterator;
 use libfdt::Fdt;
@@ -79,33 +78,44 @@
     }
 }
 
-/// Extract from /config the address range containing the pre-loaded kernel. Absence of /config is
-/// not an error.
+/// Extract from /config the address range containing the pre-loaded kernel.
+///
+/// Absence of /config is not an error. However, an error is returned if only one of the two
+/// properties is present.
 pub fn read_kernel_range_from(fdt: &Fdt) -> libfdt::Result<Option<Range<usize>>> {
     let addr = c"kernel-address";
     let size = c"kernel-size";
 
     if let Some(config) = fdt.node(c"/config")? {
-        if let (Some(addr), Some(size)) = (config.getprop_u32(addr)?, config.getprop_u32(size)?) {
-            let addr = addr as usize;
-            let size = size as usize;
-
-            return Ok(Some(addr..(addr + size)));
+        match (config.getprop_u32(addr)?, config.getprop_u32(size)?) {
+            (None, None) => {}
+            (Some(addr), Some(size)) => {
+                let addr = addr as usize;
+                let size = size as usize;
+                return Ok(Some(addr..(addr + size)));
+            }
+            _ => return Err(FdtError::NotFound),
         }
     }
 
     Ok(None)
 }
 
-/// Extract from /chosen the address range containing the pre-loaded ramdisk. Absence is not an
-/// error as there can be initrd-less VM.
+/// Extract from /chosen the address range containing the pre-loaded ramdisk.
+///
+/// Absence is not an error as there can be initrd-less VM. However, an error is returned if only
+/// one of the two properties is present.
 pub fn read_initrd_range_from(fdt: &Fdt) -> libfdt::Result<Option<Range<usize>>> {
     let start = c"linux,initrd-start";
     let end = c"linux,initrd-end";
 
     if let Some(chosen) = fdt.chosen()? {
-        if let (Some(start), Some(end)) = (chosen.getprop_u32(start)?, chosen.getprop_u32(end)?) {
-            return Ok(Some((start as usize)..(end as usize)));
+        match (chosen.getprop_u32(start)?, chosen.getprop_u32(end)?) {
+            (None, None) => {}
+            (Some(start), Some(end)) => {
+                return Ok(Some((start as usize)..(end as usize)));
+            }
+            _ => return Err(FdtError::NotFound),
         }
     }
 
@@ -165,7 +175,7 @@
 /// Only one memory range is expected with the crosvm setup for now.
 fn read_and_validate_memory_range(
     fdt: &Fdt,
-    guest_page_size: usize,
+    alignment: usize,
 ) -> Result<Range<usize>, RebootReason> {
     let mut memory = fdt.memory().map_err(|e| {
         error!("Failed to read memory range from DT: {e}");
@@ -182,14 +192,19 @@
         );
     }
     let base = range.start;
+    if base % alignment != 0 {
+        error!("Memory base address {:#x} is not aligned to {:#x}", base, alignment);
+        return Err(RebootReason::InvalidFdt);
+    }
+    // For simplicity, force a hardcoded memory base, for now.
     if base != MEM_START {
         error!("Memory base address {:#x} is not {:#x}", base, MEM_START);
         return Err(RebootReason::InvalidFdt);
     }
 
     let size = range.len();
-    if size % guest_page_size != 0 {
-        error!("Memory size {:#x} is not a multiple of page size {:#x}", size, guest_page_size);
+    if size % alignment != 0 {
+        error!("Memory size {:#x} is not aligned to {:#x}", size, alignment);
         return Err(RebootReason::InvalidFdt);
     }
 
@@ -871,18 +886,18 @@
 fn validate_swiotlb_info(
     swiotlb_info: &SwiotlbInfo,
     memory: &Range<usize>,
-    guest_page_size: usize,
+    alignment: usize,
 ) -> Result<(), RebootReason> {
     let size = swiotlb_info.size;
     let align = swiotlb_info.align;
 
-    if size == 0 || (size % guest_page_size) != 0 {
+    if size == 0 || (size % alignment) != 0 {
         error!("Invalid swiotlb size {:#x}", size);
         return Err(RebootReason::InvalidFdt);
     }
 
-    if let Some(align) = align.filter(|&a| a % guest_page_size != 0) {
-        error!("Invalid swiotlb alignment {:#x}", align);
+    if let Some(align) = align.filter(|&a| a % alignment != 0) {
+        error!("Swiotlb alignment {:#x} not aligned to {:#x}", align, alignment);
         return Err(RebootReason::InvalidFdt);
     }
 
@@ -891,6 +906,10 @@
             error!("Invalid swiotlb range: addr:{addr:#x} size:{size:#x}");
             return Err(RebootReason::InvalidFdt);
         }
+        if (addr % alignment) != 0 {
+            error!("Swiotlb address {:#x} not aligned to {:#x}", addr, alignment);
+            return Err(RebootReason::InvalidFdt);
+        }
     }
     if let Some(range) = swiotlb_info.fixed_range() {
         if !range.is_within(memory) {
@@ -1034,6 +1053,7 @@
     vm_dtbo: Option<&mut [u8]>,
     vm_ref_dt: Option<&[u8]>,
     guest_page_size: usize,
+    hyp_page_size: Option<usize>,
 ) -> Result<DeviceTreeInfo, RebootReason> {
     let vm_dtbo = match vm_dtbo {
         Some(vm_dtbo) => Some(VmDtbo::from_mut_slice(vm_dtbo).map_err(|e| {
@@ -1043,7 +1063,7 @@
         None => None,
     };
 
-    let info = parse_device_tree(fdt, vm_dtbo.as_deref(), guest_page_size)?;
+    let info = parse_device_tree(fdt, vm_dtbo.as_deref(), guest_page_size, hyp_page_size)?;
 
     fdt.clone_from(FDT_TEMPLATE).map_err(|e| {
         error!("Failed to instantiate FDT from the template DT: {e}");
@@ -1100,13 +1120,16 @@
     fdt: &Fdt,
     vm_dtbo: Option<&VmDtbo>,
     guest_page_size: usize,
+    hyp_page_size: Option<usize>,
 ) -> Result<DeviceTreeInfo, RebootReason> {
     let initrd_range = read_initrd_range_from(fdt).map_err(|e| {
         error!("Failed to read initrd range from DT: {e}");
         RebootReason::InvalidFdt
     })?;
 
-    let memory_range = read_and_validate_memory_range(fdt, guest_page_size)?;
+    // Ensure that MMIO_GUARD can't be used to inadvertently map some memory as MMIO.
+    let memory_alignment = max(hyp_page_size, Some(guest_page_size)).unwrap();
+    let memory_range = read_and_validate_memory_range(fdt, memory_alignment)?;
 
     let bootargs = read_bootargs_from(fdt).map_err(|e| {
         error!("Failed to read bootargs from DT: {e}");
@@ -1159,34 +1182,27 @@
             error!("Swiotlb info missing from DT");
             RebootReason::InvalidFdt
         })?;
-    validate_swiotlb_info(&swiotlb_info, &memory_range, guest_page_size)?;
+    // Ensure that MEM_SHARE won't inadvertently map beyond the shared region.
+    let swiotlb_alignment = max(hyp_page_size, Some(guest_page_size)).unwrap();
+    validate_swiotlb_info(&swiotlb_info, &memory_range, swiotlb_alignment)?;
 
-    let device_assignment = match vm_dtbo {
-        Some(vm_dtbo) => {
-            if let Some(hypervisor) = get_device_assigner() {
-                // TODO(ptosi): Cache the (single?) granule once, in vmbase.
-                let granule = get_mem_sharer()
-                    .ok_or_else(|| {
-                        error!("No MEM_SHARE found during device assignment validation");
-                        RebootReason::InternalError
-                    })?
-                    .granule()
-                    .map_err(|e| {
-                        error!("Failed to get granule for device assignment validation: {e}");
-                        RebootReason::InternalError
-                    })?;
-                DeviceAssignmentInfo::parse(fdt, vm_dtbo, hypervisor, granule).map_err(|e| {
-                    error!("Failed to parse device assignment from DT and VM DTBO: {e}");
-                    RebootReason::InvalidFdt
-                })?
-            } else {
-                warn!(
-                    "Device assignment is ignored because device assigning hypervisor is missing"
-                );
-                None
-            }
+    let device_assignment = if let Some(vm_dtbo) = vm_dtbo {
+        if let Some(hypervisor) = get_device_assigner() {
+            let granule = hyp_page_size.ok_or_else(|| {
+                error!("No granule found during device assignment validation");
+                RebootReason::InternalError
+            })?;
+
+            DeviceAssignmentInfo::parse(fdt, vm_dtbo, hypervisor, granule).map_err(|e| {
+                error!("Failed to parse device assignment from DT and VM DTBO: {e}");
+                RebootReason::InvalidFdt
+            })?
+        } else {
+            warn!("Device assignment is ignored because device assigning hypervisor is missing");
+            None
         }
-        None => None,
+    } else {
+        None
     };
 
     let untrusted_props = parse_untrusted_props(fdt).map_err(|e| {
diff --git a/guest/pvmfw/src/main.rs b/guest/pvmfw/src/main.rs
index 9c67be8..9afbcc3 100644
--- a/guest/pvmfw/src/main.rs
+++ b/guest/pvmfw/src/main.rs
@@ -41,6 +41,7 @@
 use alloc::boxed::Box;
 use bssl_avf::Digester;
 use diced_open_dice::{bcc_handover_parse, DiceArtifacts, DiceContext, Hidden, VM_KEY_ALGORITHM};
+use hypervisor_backends::get_mem_sharer;
 use libfdt::Fdt;
 use log::{debug, error, info, trace, warn};
 use pvmfw_avb::verify_payload;
@@ -98,7 +99,17 @@
     }
 
     let guest_page_size = verified_boot_data.page_size.unwrap_or(SIZE_4KB);
-    let _ = sanitize_device_tree(untrusted_fdt, vm_dtbo, vm_ref_dt, guest_page_size)?;
+    // TODO(ptosi): Cache the (single?) granule once, in vmbase.
+    let hyp_page_size = if let Some(mem_sharer) = get_mem_sharer() {
+        Some(mem_sharer.granule().map_err(|e| {
+            error!("Failed to get granule size: {e}");
+            RebootReason::InternalError
+        })?)
+    } else {
+        None
+    };
+    let _ =
+        sanitize_device_tree(untrusted_fdt, vm_dtbo, vm_ref_dt, guest_page_size, hyp_page_size)?;
     let fdt = untrusted_fdt; // DT has now been sanitized.
 
     let next_bcc_size = guest_page_size;
diff --git a/libs/libavf/Android.bp b/libs/libavf/Android.bp
index c958796..aceb927 100644
--- a/libs/libavf/Android.bp
+++ b/libs/libavf/Android.bp
@@ -40,8 +40,30 @@
     defaults: ["libavf.default"],
 }
 
+soong_config_module_type {
+    name: "virt_cc_defaults",
+    module_type: "cc_defaults",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "avf_enabled",
+    ],
+    properties: [
+        "apex_available",
+    ],
+}
+
+virt_cc_defaults {
+    name: "libavf_apex_available_defaults",
+    soong_config_variables: {
+        avf_enabled: {
+            apex_available: ["com.android.virt"],
+        },
+    },
+}
+
 cc_library {
     name: "libavf",
+    defaults: ["libavf_apex_available_defaults"],
     llndk: {
         symbol_file: "libavf.map.txt",
         moved_to_apex: true,
@@ -53,7 +75,6 @@
         "liblog",
     ],
     export_include_dirs: ["include"],
-    apex_available: ["com.android.virt"],
     version_script: "libavf.map.txt",
     stubs: {
         symbol_file: "libavf.map.txt",