blob: 9518c38ea9d82445116bdbc102a7d5a72a0ae61f [file] [log] [blame]
Elie Kheirallahea5dd522024-11-26 22:16:01 +00001// Copyright 2024, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Integration test for VMs on device.
16
17use android_system_virtualizationservice::{
18 aidl::android::system::virtualizationservice::{
Elie Kheirallahb4b2f242025-01-23 03:38:07 +000019 CpuOptions::CpuOptions, CpuOptions::CpuTopology::CpuTopology, DiskImage::DiskImage,
20 VirtualMachineConfig::VirtualMachineConfig,
Elie Kheirallahea5dd522024-11-26 22:16:01 +000021 VirtualMachineRawConfig::VirtualMachineRawConfig,
22 },
23 binder::{ParcelFileDescriptor, ProcessState},
24};
25use anyhow::anyhow;
26use anyhow::Context;
27use anyhow::Error;
Elie Kheirallah7f45f922025-02-11 22:46:50 +000028use anyhow::Result;
Elie Kheirallahea5dd522024-11-26 22:16:01 +000029use log::info;
30use std::fs::read_to_string;
31use std::fs::File;
32use std::io::Write;
33use std::process::Command;
34use vmclient::VmInstance;
35
36const VMBASE_EXAMPLE_KERNEL_PATH: &str = "vmbase_example_kernel.bin";
37const TEST_DISK_IMAGE_PATH: &str = "test_disk.img";
38const EMPTY_DISK_IMAGE_PATH: &str = "empty_disk.img";
39const GOLDEN_DEVICE_TREE: &str = "./goldens/dt_dump_golden.dts";
40const GOLDEN_DEVICE_TREE_PROTECTED: &str = "./goldens/dt_dump_protected_golden.dts";
41
42/// Runs an unprotected VM and validates it against a golden device tree.
43#[test]
44fn test_device_tree_compat() -> Result<(), Error> {
45 run_test(false, GOLDEN_DEVICE_TREE)
46}
47
48/// Runs a protected VM and validates it against a golden device tree.
49#[test]
Elie Kheirallah7f45f922025-02-11 22:46:50 +000050fn test_device_tree_protected_compat() -> Result<()> {
Elie Kheirallahea5dd522024-11-26 22:16:01 +000051 run_test(true, GOLDEN_DEVICE_TREE_PROTECTED)
52}
53
Elie Kheirallah7f45f922025-02-11 22:46:50 +000054fn run_test(protected: bool, golden_dt: &str) -> Result<()> {
Elie Kheirallahea5dd522024-11-26 22:16:01 +000055 let kernel = Some(open_payload(VMBASE_EXAMPLE_KERNEL_PATH)?);
56 android_logger::init_once(
57 android_logger::Config::default()
58 .with_tag("backcompat")
59 .with_max_level(log::LevelFilter::Debug),
60 );
61
62 // We need to start the thread pool for Binder to work properly, especially link_to_death.
63 ProcessState::start_thread_pool();
64
65 let virtmgr =
66 vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
67 let service = virtmgr.connect().context("Failed to connect to VirtualizationService")?;
68
69 // Make file for test disk image.
70 let mut test_image = File::options()
71 .create(true)
72 .read(true)
73 .write(true)
74 .truncate(true)
75 .open(TEST_DISK_IMAGE_PATH)
76 .with_context(|| format!("Failed to open test disk image {}", TEST_DISK_IMAGE_PATH))?;
77 // Write 4 sectors worth of 4-byte numbers counting up.
78 for i in 0u32..512 {
79 test_image.write_all(&i.to_le_bytes())?;
80 }
81 let test_image = ParcelFileDescriptor::new(test_image);
82 let disk_image = DiskImage { image: Some(test_image), writable: false, partitions: vec![] };
83
84 // Make file for empty test disk image.
85 let empty_image = File::options()
86 .create(true)
87 .read(true)
88 .write(true)
89 .truncate(true)
90 .open(EMPTY_DISK_IMAGE_PATH)
91 .with_context(|| format!("Failed to open empty disk image {}", EMPTY_DISK_IMAGE_PATH))?;
92 let empty_image = ParcelFileDescriptor::new(empty_image);
93 let empty_disk_image =
94 DiskImage { image: Some(empty_image), writable: false, partitions: vec![] };
95
96 let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
97 name: String::from("VmBaseTest"),
98 kernel,
99 disks: vec![disk_image, empty_disk_image],
100 protectedVm: protected,
101 memoryMib: 300,
Elie Kheirallahb4b2f242025-01-23 03:38:07 +0000102 cpuOptions: CpuOptions { cpuTopology: CpuTopology::CpuCount(1) },
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000103 platformVersion: "~1.0".to_string(),
104 ..Default::default()
105 });
106
107 let dump_dt = File::options()
108 .create(true)
109 .read(true)
110 .write(true)
111 .truncate(true)
112 .open("dump_dt.dtb")
113 .with_context(|| "Failed to open device tree dump file dump_dt.dtb")?;
Elie Kheirallah20674912025-03-07 17:16:51 +0000114 let is_updatable = service.isUpdatableVmSupported()?;
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000115 let vm = VmInstance::create(
116 service.as_ref(),
117 &config,
118 None,
119 /* consoleIn */ None,
120 None,
121 Some(dump_dt),
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000122 )
123 .context("Failed to create VM")?;
Jaewan Kimfcf98b22025-01-21 23:14:49 -0800124 vm.start(None).context("Failed to start VM")?;
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000125 info!("Started example VM.");
126
127 // Wait for VM to finish
128 let _ = vm.wait_for_death();
129
130 if !Command::new("./dtc_static")
131 .arg("-I")
132 .arg("dts")
133 .arg("-O")
134 .arg("dtb")
135 .arg("-qqq")
136 .arg("-f")
137 .arg("-s")
138 .arg("-o")
139 .arg("dump_dt_golden.dtb")
140 .arg(golden_dt)
141 .output()?
142 .status
143 .success()
144 {
145 return Err(anyhow!("failed to execute dtc"));
146 }
Elie Kheirallah7f45f922025-02-11 22:46:50 +0000147 let mut dtcompare_cmd = Command::new("./dtcompare");
148 dtcompare_cmd
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000149 .arg("--dt1")
150 .arg("dump_dt_golden.dtb")
151 .arg("--dt2")
152 .arg("dump_dt.dtb")
153 .arg("--ignore-path-value")
154 .arg("/chosen/kaslr-seed")
155 .arg("--ignore-path-value")
156 .arg("/chosen/rng-seed")
Elie Kheirallahb8b921b2025-01-21 17:15:54 +0000157 // TODO: b/391420337 Investigate if bootargs may mutate VM
158 .arg("--ignore-path-value")
159 .arg("/chosen/bootargs")
160 .arg("--ignore-path-value")
161 .arg("/config/kernel-size")
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000162 .arg("--ignore-path-value")
163 .arg("/avf/untrusted/instance-id")
164 .arg("--ignore-path-value")
Elie Kheirallah22494e52025-01-24 18:25:56 +0000165 .arg("/chosen/linux,initrd-start")
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000166 .arg("--ignore-path-value")
Elie Kheirallah22494e52025-01-24 18:25:56 +0000167 .arg("/chosen/linux,initrd-end")
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000168 .arg("--ignore-path")
Elie Kheirallah7f45f922025-02-11 22:46:50 +0000169 .arg("/avf/name");
170 // Check if Secretkeeper is advertised. If not, check the vendor API level. Secretkeeper is
171 // required as of 202504, and if missing, the test should fail.
172 // Otherwise, ignore the fields, as they are not required.
Elie Kheirallah20674912025-03-07 17:16:51 +0000173 if is_updatable {
Elie Kheirallah7f45f922025-02-11 22:46:50 +0000174 dtcompare_cmd.arg("--ignore-path-value").arg("/avf/secretkeeper_public_key");
175 } else if vsr_api_level()? >= 202504 {
176 return Err(anyhow!("Secretkeeper support missing on vendor API >= 202504. Secretkeeper needs to be implemented."));
177 } else {
178 dtcompare_cmd
179 .arg("--ignore-path")
180 .arg("/avf/secretkeeper_public_key")
181 .arg("--ignore-path")
182 .arg("/avf/untrusted/defer-rollback-protection");
183 }
184 let dtcompare_res = dtcompare_cmd.output().context("failed to execute dtcompare")?;
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000185 if !dtcompare_res.status.success() {
186 if !Command::new("./dtc_static")
187 .arg("-I")
188 .arg("dtb")
189 .arg("-O")
190 .arg("dts")
191 .arg("-qqq")
192 .arg("-f")
193 .arg("-s")
194 .arg("-o")
195 .arg("dump_dt_failed.dts")
196 .arg("dump_dt.dtb")
197 .output()?
198 .status
199 .success()
200 {
201 return Err(anyhow!("failed to execute dtc"));
202 }
203 let dt2 = read_to_string("dump_dt_failed.dts")?;
Frederick Mayle91dc8862025-02-07 21:01:48 -0800204 eprintln!(
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000205 "Device tree 2 does not match golden DT.\n
206 Device Tree 2: {}",
207 dt2
208 );
209 return Err(anyhow!(
Frederick Mayle91dc8862025-02-07 21:01:48 -0800210 "stdout: {}\n stderr: {}",
211 String::from_utf8_lossy(&dtcompare_res.stdout),
212 String::from_utf8_lossy(&dtcompare_res.stderr)
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000213 ));
214 }
215
216 Ok(())
217}
218
Elie Kheirallah7f45f922025-02-11 22:46:50 +0000219fn open_payload(path: &str) -> Result<ParcelFileDescriptor> {
Elie Kheirallahea5dd522024-11-26 22:16:01 +0000220 let file = File::open(path).with_context(|| format!("Failed to open VM image {path}"))?;
221 Ok(ParcelFileDescriptor::new(file))
222}
Elie Kheirallah7f45f922025-02-11 22:46:50 +0000223
224fn vsr_api_level() -> Result<i32> {
225 get_sysprop_i32("ro.vendor.api_level")
226}
227
228fn get_sysprop_i32(prop: &str) -> Result<i32> {
Elie Kheirallah20674912025-03-07 17:16:51 +0000229 let Some(val) = rustutils::system_properties::read(prop)? else {
230 return Ok(-1);
231 };
232 val.parse::<i32>().with_context(|| format!("Failed to read {prop}"))
Elie Kheirallah7f45f922025-02-11 22:46:50 +0000233}