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",