virtmgr: Create and pass VM reference DT instead of vendor DTBO
Bug: 318431695
Test: atest virtualizationmanager_device_test, and launch a VM
Change-Id: I8181e794e1e670aad6ac7d57fc61bc36ec6809f3
diff --git a/virtualizationmanager/Android.bp b/virtualizationmanager/Android.bp
index f58e999..48b5cd1 100644
--- a/virtualizationmanager/Android.bp
+++ b/virtualizationmanager/Android.bp
@@ -36,10 +36,10 @@
"libbase_rust",
"libbinder_rs",
"libclap",
+ "libcstr",
"libcommand_fds",
"libdisk",
"libglob",
- "libhex",
"libhypervisor_props",
"liblazy_static",
"liblibc",
@@ -60,12 +60,12 @@
"libshared_child",
"libstatslog_virtualization_rust",
"libtombstoned_client_rust",
- "libvbmeta_rust",
"libvm_control",
"libvmconfig",
"libzip",
"libvsock",
"liblibfdt",
+ "libfsfdt",
// TODO(b/202115393) stabilize the interface
"packagemanager_aidl-rust",
],
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 34994f8..1b1cabd 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -22,6 +22,7 @@
use crate::debug_config::DebugConfig;
use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images, add_microdroid_vendor_image};
use crate::selinux::{getfilecon, SeContext};
+use crate::reference_dt;
use android_os_permissions_aidl::aidl::android::os::IPermissionController;
use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::{
Certificate::Certificate,
@@ -71,7 +72,6 @@
use disk::QcowFile;
use glob::glob;
use lazy_static::lazy_static;
-use libfdt::Fdt;
use log::{debug, error, info, warn};
use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
use nix::unistd::pipe;
@@ -80,7 +80,7 @@
use semver::VersionReq;
use std::collections::HashSet;
use std::convert::TryInto;
-use std::ffi::{CStr, CString};
+use std::ffi::CStr;
use std::fs::{canonicalize, read_dir, remove_file, File, OpenOptions};
use std::io::{BufRead, BufReader, Error, ErrorKind, Seek, SeekFrom, Write};
use std::iter;
@@ -89,7 +89,6 @@
use std::os::unix::raw::pid_t;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, Weak};
-use vbmeta::VbMetaImage;
use vmconfig::VmConfig;
use vsock::VsockStream;
use zip::ZipArchive;
@@ -117,9 +116,6 @@
const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
-/// Rough size for storing root digest of vendor hash descriptor into DTBO.
-const EMPTY_VENDOR_DT_OVERLAY_BUF_SIZE: usize = 10000;
-
/// crosvm requires all partitions to be a multiple of 4KiB.
const PARTITION_GRANULARITY_BYTES: u64 = 4096;
@@ -395,25 +391,12 @@
check_gdb_allowed(config)?;
}
- let vendor_hashtree_descriptor_root_digest =
- extract_vendor_hashtree_descriptor_root_digest(config)
- .context("Failed to extract root digest of vendor")
- .or_service_specific_exception(-1)?;
- let dtbo_vendor = if let Some(vendor_hashtree_descriptor_root_digest) =
- vendor_hashtree_descriptor_root_digest
- {
- let root_digest_hex = hex::encode(vendor_hashtree_descriptor_root_digest);
- let dtbo_for_vendor_image = temporary_directory.join("dtbo_vendor");
- create_dtbo_for_vendor_image(root_digest_hex.as_bytes(), &dtbo_for_vendor_image)
- .context("Failed to write root digest of vendor")
- .or_service_specific_exception(-1)?;
- let file = File::open(dtbo_for_vendor_image)
- .context("Failed to open dtbo_vendor")
- .or_service_specific_exception(-1)?;
- Some(file)
- } else {
- None
- };
+ let reference_dt = reference_dt::parse_reference_dt(&temporary_directory)
+ .context("Failed to create VM reference DT")
+ .or_service_specific_exception(-1)?;
+ if reference_dt.is_none() {
+ warn!("VM reference DT doesn't exist");
+ }
let debug_level = match config {
VirtualMachineConfig::AppConfig(config) => config.debugLevel,
@@ -530,7 +513,7 @@
.getDtboFile()?
.as_ref()
.try_clone()
- .context("Failed to create File from ParcelFileDescriptor")
+ .context("Failed to create VM DTBO from ParcelFileDescriptor")
.or_binder_exception(ExceptionCode::BAD_PARCELABLE)?,
);
(devices, Some(dtbo_file))
@@ -563,7 +546,7 @@
gdb_port,
vfio_devices,
dtbo,
- dtbo_vendor,
+ reference_dt,
};
let instance = Arc::new(
VmInstance::new(
@@ -582,80 +565,6 @@
}
}
-fn extract_vendor_hashtree_descriptor_root_digest(
- config: &VirtualMachineConfig,
-) -> Result<Option<Vec<u8>>> {
- let VirtualMachineConfig::AppConfig(config) = config else {
- return Ok(None);
- };
- let Some(custom_config) = &config.customConfig else {
- return Ok(None);
- };
- let Some(file) = custom_config.vendorImage.as_ref() else {
- return Ok(None);
- };
-
- let file = clone_file(file)?;
- let size = file.metadata().context("Failed to get metadata from microdroid-vendor.img")?.len();
- let vbmeta = VbMetaImage::verify_reader_region(&file, 0, size)
- .context("Failed to get vbmeta from microdroid-vendor.img")?;
-
- for descriptor in vbmeta.descriptors()?.iter() {
- if let vbmeta::Descriptor::Hashtree(_) = descriptor {
- return Ok(Some(descriptor.to_hashtree()?.root_digest().to_vec()));
- }
- }
- Err(anyhow!("No root digest is extracted from microdroid-vendor.img"))
-}
-
-fn create_dtbo_for_vendor_image(
- vendor_hashtree_descriptor_root_digest: &[u8],
- dtbo: &PathBuf,
-) -> Result<()> {
- if dtbo.exists() {
- return Err(anyhow!("DTBO file already exists"));
- }
-
- let mut buf = vec![0; EMPTY_VENDOR_DT_OVERLAY_BUF_SIZE];
- let fdt = Fdt::create_empty_tree(buf.as_mut_slice())
- .map_err(|e| anyhow!("Failed to create FDT: {:?}", e))?;
- let mut root = fdt.root_mut().map_err(|e| anyhow!("Failed to get root node: {:?}", e))?;
-
- let fragment_node_name = CString::new("fragment@0")?;
- let mut fragment_node = root
- .add_subnode(fragment_node_name.as_c_str())
- .map_err(|e| anyhow!("Failed to create fragment node: {:?}", e))?;
- let target_path_prop_name = CString::new("target-path")?;
- let target_path = CString::new("/")?;
- fragment_node
- .setprop(target_path_prop_name.as_c_str(), target_path.to_bytes_with_nul())
- .map_err(|e| anyhow!("Failed to set target-path: {:?}", e))?;
- let overlay_node_name = CString::new("__overlay__")?;
- let mut overlay_node = fragment_node
- .add_subnode(overlay_node_name.as_c_str())
- .map_err(|e| anyhow!("Failed to create overlay node: {:?}", e))?;
-
- let avf_node_name = CString::new("avf")?;
- let mut avf_node = overlay_node
- .add_subnode(avf_node_name.as_c_str())
- .map_err(|e| anyhow!("Failed to create avf node: {:?}", e))?;
- let vendor_hashtree_descriptor_root_digest_name =
- CString::new("vendor_hashtree_descriptor_root_digest")?;
- avf_node
- .setprop(
- vendor_hashtree_descriptor_root_digest_name.as_c_str(),
- vendor_hashtree_descriptor_root_digest,
- )
- .map_err(|e| {
- anyhow!("Failed to set avf/vendor_hashtree_descriptor_root_digest: {:?}", e)
- })?;
-
- fdt.pack().map_err(|e| anyhow!("Failed to pack fdt: {:?}", e))?;
- let mut file = File::create(dtbo)?;
- file.write_all(fdt.as_slice())?;
- Ok(file.flush()?)
-}
-
fn write_zero_filler(zero_filler_path: &Path) -> Result<()> {
let file = OpenOptions::new()
.create_new(true)
@@ -1601,65 +1510,6 @@
assert_eq!(vm_config.params, Some("foo=5 bar=42".to_owned()))
}
- #[test]
- fn test_create_dtbo_for_vendor_image() -> Result<()> {
- let vendor_hashtree_descriptor_root_digest = String::from("foo");
- let vendor_hashtree_descriptor_root_digest =
- vendor_hashtree_descriptor_root_digest.as_bytes();
-
- let tmp_dir = tempfile::TempDir::new()?;
- let dtbo_path = tmp_dir.path().to_path_buf().join("bar");
-
- create_dtbo_for_vendor_image(vendor_hashtree_descriptor_root_digest, &dtbo_path)?;
-
- let data = std::fs::read(dtbo_path)?;
- let fdt = Fdt::from_slice(&data).unwrap();
-
- let fragment_node_path = CString::new("/fragment@0")?;
- let fragment_node = fdt.node(fragment_node_path.as_c_str()).unwrap();
- let Some(fragment_node) = fragment_node else {
- bail!("fragment_node shouldn't be None.");
- };
- let target_path_prop_name = CString::new("target-path")?;
- let target_path_from_dtbo =
- fragment_node.getprop(target_path_prop_name.as_c_str()).unwrap();
- let target_path_expected = CString::new("/")?;
- assert_eq!(target_path_from_dtbo, Some(target_path_expected.to_bytes_with_nul()));
-
- let avf_node_path = CString::new("/fragment@0/__overlay__/avf")?;
- let avf_node = fdt.node(avf_node_path.as_c_str()).unwrap();
- let Some(avf_node) = avf_node else {
- bail!("avf_node shouldn't be None.");
- };
- let vendor_hashtree_descriptor_root_digest_name =
- CString::new("vendor_hashtree_descriptor_root_digest")?;
- let digest_from_dtbo =
- avf_node.getprop(vendor_hashtree_descriptor_root_digest_name.as_c_str()).unwrap();
- assert_eq!(digest_from_dtbo, Some(vendor_hashtree_descriptor_root_digest));
-
- tmp_dir.close()?;
- Ok(())
- }
-
- #[test]
- fn test_create_dtbo_for_vendor_image_throws_error_if_already_exists() -> Result<()> {
- let vendor_hashtree_descriptor_root_digest = String::from("foo");
- let vendor_hashtree_descriptor_root_digest =
- vendor_hashtree_descriptor_root_digest.as_bytes();
-
- let tmp_dir = tempfile::TempDir::new()?;
- let dtbo_path = tmp_dir.path().to_path_buf().join("bar");
-
- create_dtbo_for_vendor_image(vendor_hashtree_descriptor_root_digest, &dtbo_path)?;
-
- let ret_second_trial =
- create_dtbo_for_vendor_image(vendor_hashtree_descriptor_root_digest, &dtbo_path);
- assert!(ret_second_trial.is_err(), "should fail");
-
- tmp_dir.close()?;
- Ok(())
- }
-
fn test_extract_os_name_from_config_path(
path: &Path,
expected_result: Option<&str>,
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index fe8a587..84c60bd 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -118,7 +118,7 @@
pub gdb_port: Option<NonZeroU16>,
pub vfio_devices: Vec<VfioDevice>,
pub dtbo: Option<File>,
- pub dtbo_vendor: Option<File>,
+ pub reference_dt: Option<File>,
}
/// A disk image to pass to crosvm for a VM.
@@ -896,8 +896,10 @@
.arg("--socket")
.arg(add_preserved_fd(&mut preserved_fds, &control_server_socket.as_raw_descriptor()));
- if let Some(dtbo_vendor) = &config.dtbo_vendor {
- command.arg("--device-tree-overlay").arg(add_preserved_fd(&mut preserved_fds, dtbo_vendor));
+ if let Some(reference_dt) = &config.reference_dt {
+ command
+ .arg("--device-tree-overlay")
+ .arg(add_preserved_fd(&mut preserved_fds, reference_dt));
}
append_platform_devices(&mut command, &mut preserved_fds, &config)?;
diff --git a/virtualizationmanager/src/main.rs b/virtualizationmanager/src/main.rs
index 01c74f5..2e542c3 100644
--- a/virtualizationmanager/src/main.rs
+++ b/virtualizationmanager/src/main.rs
@@ -20,6 +20,7 @@
mod crosvm;
mod debug_config;
mod payload;
+mod reference_dt;
mod selinux;
use crate::aidl::{GLOBAL_SERVICE, VirtualizationService};
diff --git a/virtualizationmanager/src/reference_dt.rs b/virtualizationmanager/src/reference_dt.rs
new file mode 100644
index 0000000..797ee3c
--- /dev/null
+++ b/virtualizationmanager/src/reference_dt.rs
@@ -0,0 +1,93 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Functions for VM reference DT
+
+use anyhow::{anyhow, Result};
+use cstr::cstr;
+use fsfdt::FsFdt;
+use libfdt::Fdt;
+use std::fs;
+use std::fs::File;
+use std::path::Path;
+
+const VM_REFERENCE_DT_ON_HOST_PATH: &str = "/proc/device-tree/avf/reference";
+const VM_REFERENCE_DT_NAME: &str = "vm_reference_dt.dtbo";
+const VM_REFERENCE_DT_MAX_SIZE: usize = 2000;
+
+// Parses to VM reference if exists.
+// TODO(b/318431695): Allow to parse from custom VM reference DT
+pub(crate) fn parse_reference_dt(out_dir: &Path) -> Result<Option<File>> {
+ parse_reference_dt_internal(
+ Path::new(VM_REFERENCE_DT_ON_HOST_PATH),
+ &out_dir.join(VM_REFERENCE_DT_NAME),
+ )
+}
+
+fn parse_reference_dt_internal(dir_path: &Path, fdt_path: &Path) -> Result<Option<File>> {
+ if !dir_path.exists() || fs::read_dir(dir_path)?.next().is_none() {
+ return Ok(None);
+ }
+
+ let mut data = vec![0_u8; VM_REFERENCE_DT_MAX_SIZE];
+
+ let fdt = Fdt::create_empty_tree(&mut data)
+ .map_err(|e| anyhow!("Failed to create an empty DT, {e:?}"))?;
+ let mut root = fdt.root_mut().map_err(|e| anyhow!("Failed to find the DT root, {e:?}"))?;
+ let mut fragment = root
+ .add_subnode(cstr!("fragment@0"))
+ .map_err(|e| anyhow!("Failed to create the fragment@0, {e:?}"))?;
+ fragment
+ .setprop(cstr!("target-path"), b"/\0")
+ .map_err(|e| anyhow!("Failed to set target-path, {e:?}"))?;
+ fragment
+ .add_subnode(cstr!("__overlay__"))
+ .map_err(|e| anyhow!("Failed to create the __overlay__, {e:?}"))?;
+
+ fdt.append(cstr!("/fragment@0/__overlay__"), dir_path)?;
+
+ fdt.pack().map_err(|e| anyhow!("Failed to pack VM reference DT, {e:?}"))?;
+ fs::write(fdt_path, fdt.as_slice())?;
+
+ Ok(Some(File::open(fdt_path)?))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_parse_reference_dt_from_empty_dir() {
+ let empty_dir = tempfile::TempDir::new().unwrap();
+ let test_dir = tempfile::TempDir::new().unwrap();
+
+ let empty_dir_path = empty_dir.path();
+ let fdt_path = test_dir.path().join("test.dtb");
+
+ let fdt_file = parse_reference_dt_internal(empty_dir_path, &fdt_path).unwrap();
+
+ assert!(fdt_file.is_none());
+ }
+
+ #[test]
+ fn test_parse_reference_dt_from_empty_reference() {
+ let fdt_file = parse_reference_dt_internal(
+ Path::new("/this/path/would/not/exists"),
+ Path::new("test.dtb"),
+ )
+ .unwrap();
+
+ assert!(fdt_file.is_none());
+ }
+}