Merge "Fix path traversal bug"
diff --git a/apex/Android.bp b/apex/Android.bp
index 4e64e50..596493a 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -104,6 +104,9 @@
host_required: [
"vm_shell",
],
+ apps: [
+ "EmptyPayloadApp",
+ ],
}
apex_defaults {
diff --git a/apex/empty-payload-apk/Android.bp b/apex/empty-payload-apk/Android.bp
new file mode 100644
index 0000000..70e6754
--- /dev/null
+++ b/apex/empty-payload-apk/Android.bp
@@ -0,0 +1,26 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+ name: "EmptyPayloadApp",
+ installable: true,
+ jni_libs: ["MicrodroidEmptyPayloadJniLib"],
+ apex_available: ["com.android.virt"],
+ sdk_version: "system_current",
+ jni_uses_platform_apis: true,
+ min_sdk_version: "UpsideDownCake",
+ target_sdk_version: "UpsideDownCake",
+ compile_multilib: "first",
+ stl: "none",
+}
+
+cc_library {
+ name: "MicrodroidEmptyPayloadJniLib",
+ srcs: ["empty_binary.cpp"],
+ shared_libs: ["libvm_payload#current"],
+ installable: true,
+ apex_available: ["com.android.virt"],
+ compile_multilib: "first",
+ stl: "none",
+}
diff --git a/apex/empty-payload-apk/AndroidManifest.xml b/apex/empty-payload-apk/AndroidManifest.xml
new file mode 100644
index 0000000..e649744
--- /dev/null
+++ b/apex/empty-payload-apk/AndroidManifest.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.microdroid.empty_payload">
+
+ <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
+ <uses-feature android:name="android.software.virtualization_framework" android:required="true" />
+ <application android:testOnly="true" android:hasCode="false" />
+
+</manifest>
diff --git a/apex/empty-payload-apk/empty_binary.cpp b/apex/empty-payload-apk/empty_binary.cpp
new file mode 100644
index 0000000..4308954
--- /dev/null
+++ b/apex/empty-payload-apk/empty_binary.cpp
@@ -0,0 +1,22 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <vm_main.h>
+#include <vm_payload.h>
+
+extern "C" int AVmPayload_main() {
+ // disable buffering to communicate seamlessly
+ setvbuf(stdin, nullptr, _IONBF, 0);
+ setvbuf(stdout, nullptr, _IONBF, 0);
+ setvbuf(stderr, nullptr, _IONBF, 0);
+
+ printf("Hello Microdroid\n");
+
+ AVmPayload_notifyPayloadReady();
+
+ // Wait forever to allow developer to interact with Microdroid shell
+ for (;;) {
+ pause();
+ }
+
+ return 0;
+}
diff --git a/docs/getting_started/index.md b/docs/getting_started/index.md
index 5f552f9..f184862 100644
--- a/docs/getting_started/index.md
+++ b/docs/getting_started/index.md
@@ -97,6 +97,15 @@
If you run into problems, inspect the logs produced by `atest`. Their location is printed at the
end. The `host_log_*.zip` file should contain the output of individual commands as well as VM logs.
+### Custom pvmfw
+
+Hostside tests, which run on the PC and extends `MicrodroidHostTestCaseBase`, can be run with
+a custom `pvmfw`. Use `--module-arg` to push `pvmfw` for individual test methods.
+
+```shell
+atest com.android.microdroid.test.MicrodroidHostTests -- --module-arg MicrodroidHostTestCases:set-option:pvmfw:pvmfw.img
+```
+
## Spawning your own VMs with custom kernel
You can spawn your own VMs by passing a JSON config file to the VirtualizationService via the `vm`
@@ -120,10 +129,30 @@
The `vm` command also has other subcommands for debugging; run `/apex/com.android.virt/bin/vm help`
for details.
+## Spawning your own VMs with custom pvmfw
+
+Set system property `hypervisor.pvmfw.path` to custom `pvmfw` on the device before using `vm` tool.
+`virtualizationservice` will pass the specified `pvmfw` to `crosvm` for protected VMs.
+
+```shell
+adb push pvmfw.img /data/local/tmp/pvmfw.img
+adb root # required for setprop
+adb shell setprop hypervisor.pvmfw.path /data/local/tmp/pvmfw.img
+```
+
## Spawning your own VMs with Microdroid
[Microdroid](../../microdroid/README.md) is a lightweight version of Android that is intended to run
-on pVM. You can manually run the demo app on top of Microdroid as follows:
+on pVM. You can run a Microdroid with empty payload using the following command:
+
+```shell
+adb shell /apex/com.android.virt/bin/vm run-microdroid --debug full
+```
+
+The `instance.img` and `apk.idsig` files will be stored in a subdirectory under
+`/data/local/tmp/microdroid`, that `vm` will create.
+
+Atlernatively, you can manually run the demo app on top of Microdroid as follows:
```shell
UNBUNDLED_BUILD_SDKS_FROM_SOURCE=true TARGET_BUILD_APPS=MicrodroidDemoApp m apps_only dist
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
index d7d2382..9c8311d 100644
--- a/encryptedstore/src/main.rs
+++ b/encryptedstore/src/main.rs
@@ -20,7 +20,8 @@
use anyhow::{ensure, Context, Result};
use clap::{arg, App};
-use dm::{crypt::CipherType, util};
+use dm::crypt::CipherType;
+use dm::util;
use log::info;
use std::ffi::CString;
use std::fs::{create_dir_all, OpenOptions};
@@ -45,7 +46,7 @@
.args(&[
arg!(--blkdevice <FILE> "the block device backing the encrypted storage")
.required(true),
- arg!(--key <KEY> "key (in hex) equivalent to 64 bytes)").required(true),
+ arg!(--key <KEY> "key (in hex) equivalent to 32 bytes)").required(true),
arg!(--mountpoint <MOUNTPOINT> "mount point for the storage").required(true),
])
.get_matches();
@@ -87,12 +88,11 @@
fn enable_crypt(data_device: &Path, key: &str, name: &str) -> Result<PathBuf> {
let dev_size = util::blkgetsize64(data_device)?;
let key = hex::decode(key).context("Unable to decode hex key")?;
- ensure!(key.len() == 64, "We need 64 bytes' key for aes-xts cipher for block encryption");
// Create the dm-crypt spec
let target = dm::crypt::DmCryptTargetBuilder::default()
.data_device(data_device, dev_size)
- .cipher(CipherType::AES256XTS) // TODO(b/259253336) Move to HCTR2 based encryption.
+ .cipher(CipherType::AES256HCTR2)
.key(&key)
.build()
.context("Couldn't build the DMCrypt target")?;
diff --git a/javalib/Android.bp b/javalib/Android.bp
index 8421231..71287f2 100644
--- a/javalib/Android.bp
+++ b/javalib/Android.bp
@@ -38,9 +38,6 @@
javacflags: [
// We use @GuardedBy and we want a test failure if our locking isn't consistent with it.
"-Xep:GuardedBy:ERROR",
- // JavaApiUsedByMainlineModule is quite spammy, and since we com.android.virt is not
- // an updatable module we don't need it.
- "-Xep:JavaApiUsedByMainlineModule:OFF",
],
},
diff --git a/libs/devicemapper/Android.bp b/libs/devicemapper/Android.bp
index 783fa79..9fa010c 100644
--- a/libs/devicemapper/Android.bp
+++ b/libs/devicemapper/Android.bp
@@ -13,6 +13,7 @@
"libbitflags",
"liblibc",
"libdata_model",
+ "libhex",
"libnix",
"libuuid",
],
@@ -33,6 +34,7 @@
defaults: ["libdm_rust.defaults"],
test_suites: ["general-tests"],
rustlibs: [
+ "librustutils",
"libscopeguard",
"libtempfile",
],
diff --git a/libs/devicemapper/src/crypt.rs b/libs/devicemapper/src/crypt.rs
index 9b715a5..b2e677a 100644
--- a/libs/devicemapper/src/crypt.rs
+++ b/libs/devicemapper/src/crypt.rs
@@ -17,10 +17,9 @@
/// `crypt` module implements the "crypt" target in the device mapper framework. Specifically,
/// it provides `DmCryptTargetBuilder` struct which is used to construct a `DmCryptTarget` struct
/// which is then given to `DeviceMapper` to create a mapper device.
-use crate::util::*;
use crate::DmTargetSpec;
-use anyhow::{bail, Context, Result};
+use anyhow::{ensure, Context, Result};
use data_model::DataInit;
use std::io::Write;
use std::mem::size_of;
@@ -32,10 +31,34 @@
// Documentation/admin-guide/device-mapper/dm-crypt.rst
/// Supported ciphers
+#[derive(Clone, Copy, Debug)]
pub enum CipherType {
- // TODO(b/253394457) Include ciphers with authenticated modes as well
+ // AES-256-HCTR2 takes a 32-byte key
+ AES256HCTR2,
+ // XTS requires key of twice the length of the underlying block cipher i.e., 64B for AES256
AES256XTS,
}
+impl CipherType {
+ fn get_kernel_crypto_name(&self) -> &str {
+ match *self {
+ // We use "plain64" as the IV/nonce generation algorithm -
+ // which basically is the sector number.
+ CipherType::AES256HCTR2 => "aes-hctr2-plain64",
+ CipherType::AES256XTS => "aes-xts-plain64",
+ }
+ }
+
+ fn get_required_key_size(&self) -> usize {
+ match *self {
+ CipherType::AES256HCTR2 => 32,
+ CipherType::AES256XTS => 64,
+ }
+ }
+
+ fn validata_key_size(&self, key_size: usize) -> bool {
+ key_size == self.get_required_key_size()
+ }
+}
pub struct DmCryptTarget(Box<[u8]>);
@@ -59,7 +82,7 @@
impl<'a> Default for DmCryptTargetBuilder<'a> {
fn default() -> Self {
DmCryptTargetBuilder {
- cipher: CipherType::AES256XTS,
+ cipher: CipherType::AES256HCTR2,
key: None,
iv_offset: 0,
device_path: None,
@@ -112,8 +135,13 @@
.to_str()
.context("data device path is not encoded in utf8")?;
- let key =
- if let Some(key) = self.key { hexstring_from(key) } else { bail!("key is not set") };
+ ensure!(self.key.is_some(), "key is not set");
+ // Unwrap is safe because we already made sure key.is_some()
+ ensure!(
+ self.cipher.validata_key_size(self.key.unwrap().len()),
+ format!("Invalid key size for cipher:{}", self.cipher.get_kernel_crypto_name())
+ );
+ let key = hex::encode(self.key.unwrap());
// Step2: serialize the information according to the spec, which is ...
// DmTargetSpec{...}
@@ -121,7 +149,7 @@
// <offset> [<#opt_params> <opt_params>]
let mut body = String::new();
use std::fmt::Write;
- write!(&mut body, "{} ", get_kernel_crypto_name(&self.cipher))?;
+ write!(&mut body, "{} ", self.cipher.get_kernel_crypto_name())?;
write!(&mut body, "{} ", key)?;
write!(&mut body, "{} ", self.iv_offset)?;
write!(&mut body, "{} ", device_path)?;
@@ -145,9 +173,3 @@
Ok(DmCryptTarget(buf.into_boxed_slice()))
}
}
-
-fn get_kernel_crypto_name(cipher: &CipherType) -> &str {
- match cipher {
- CipherType::AES256XTS => "aes-xts-plain64",
- }
-}
diff --git a/libs/devicemapper/src/lib.rs b/libs/devicemapper/src/lib.rs
index ebe71e4..9069eb2 100644
--- a/libs/devicemapper/src/lib.rs
+++ b/libs/devicemapper/src/lib.rs
@@ -234,12 +234,28 @@
#[cfg(test)]
mod tests {
use super::*;
- use crypt::DmCryptTargetBuilder;
+ use crypt::{CipherType, DmCryptTargetBuilder};
+ use rustutils::system_properties;
use std::fs::{read, File, OpenOptions};
use std::io::Write;
- const KEY: &[u8; 32] = b"thirtytwobyteslongreallylongword";
- const DIFFERENT_KEY: &[u8; 32] = b"drowgnolyllaergnolsetybowtytriht";
+ // Just a logical set of keys to make testing easy. This has no real meaning.
+ struct KeySet<'a> {
+ cipher: CipherType,
+ key: &'a [u8],
+ different_key: &'a [u8],
+ }
+
+ const KEY_SET_XTS: KeySet = KeySet {
+ cipher: CipherType::AES256XTS,
+ key: b"sixtyfourbyteslongsentencearerarebutletsgiveitatrycantbethathard",
+ different_key: b"drahtahtebtnacyrtatievigsteltuberareraecnetnesgnolsetybruofytxis",
+ };
+ const KEY_SET_HCTR2: KeySet = KeySet {
+ cipher: CipherType::AES256HCTR2,
+ key: b"thirtytwobyteslongreallylongword",
+ different_key: b"drowgnolyllaergnolsetybowtytriht",
+ };
// Create a file in given temp directory with given size
fn prepare_tmpfile(test_dir: &Path, filename: &str, sz: u64) -> PathBuf {
@@ -254,14 +270,54 @@
f.write_all(data).unwrap();
}
+ // TODO(b/250880499): delete_device() doesn't really delete it even without DM_DEFERRED_REMOVE.
+ // Hence, we have to create a new device with a different name for each test. Retrying
+ // the test on same machine without reboot will also fail.
fn delete_device(dm: &DeviceMapper, name: &str) -> Result<()> {
dm.delete_device_deferred(name)?;
wait_for_path_disappears(Path::new(MAPPER_DEV_ROOT).join(name))?;
Ok(())
}
+ // TODO(b/260692911): Find a better way to skip a test instead of silently passing it.
+ fn is_hctr2_supported() -> bool {
+ // hctr2 is NOT enabled in kernel 5.10 or lower. We run Microdroid tests on kernel versions
+ // 5.10 or above & therefore, we don't really care to skip test on other versions.
+ if let Some(version) = system_properties::read("ro.kernel.version")
+ .expect("Unable to read system property ro.kernel.version")
+ {
+ version != "5.10"
+ } else {
+ panic!("Could not read property: kernel.version!!");
+ }
+ }
+
#[test]
- fn mapping_again_keeps_data() {
+ fn mapping_again_keeps_data_xts() {
+ mapping_again_keeps_data(&KEY_SET_XTS, "name1");
+ }
+
+ #[test]
+ fn mapping_again_keeps_data_hctr2() {
+ if !is_hctr2_supported() {
+ return;
+ }
+ mapping_again_keeps_data(&KEY_SET_HCTR2, "name2");
+ }
+ #[test]
+ fn data_inaccessible_with_diff_key_xts() {
+ data_inaccessible_with_diff_key(&KEY_SET_XTS, "name3");
+ }
+
+ #[test]
+ fn data_inaccessible_with_diff_key_hctr2() {
+ if !is_hctr2_supported() {
+ return;
+ }
+ data_inaccessible_with_diff_key(&KEY_SET_HCTR2, "name4");
+ }
+
+ fn mapping_again_keeps_data(keyset: &KeySet, device: &str) {
// This test creates 2 different crypt devices using same key backed by same data_device
// -> Write data on dev1 -> Check the data is visible & same on dev2
let dm = DeviceMapper::new().unwrap();
@@ -278,28 +334,33 @@
/*writable*/ true,
)
.unwrap();
+ let device_diff = device.to_owned() + "_diff";
+
scopeguard::defer! {
loopdevice::detach(&data_device).unwrap();
- _ = delete_device(&dm, "crypt1");
- _ = delete_device(&dm, "crypt2");
+ _ = delete_device(&dm, device);
+ _ = delete_device(&dm, &device_diff);
}
- let target =
- DmCryptTargetBuilder::default().data_device(&data_device, sz).key(KEY).build().unwrap();
+ let target = DmCryptTargetBuilder::default()
+ .data_device(&data_device, sz)
+ .cipher(keyset.cipher)
+ .key(keyset.key)
+ .build()
+ .unwrap();
- let mut crypt_device = dm.create_crypt_device("crypt1", &target).unwrap();
+ let mut crypt_device = dm.create_crypt_device(device, &target).unwrap();
write_to_dev(&crypt_device, inputimg);
// Recreate another device using same target spec & check if the content is the same
- crypt_device = dm.create_crypt_device("crypt2", &target).unwrap();
+ crypt_device = dm.create_crypt_device(&device_diff, &target).unwrap();
let crypt = read(crypt_device).unwrap();
assert_eq!(inputimg.len(), crypt.len()); // fail early if the size doesn't match
assert_eq!(inputimg, crypt.as_slice());
}
- #[test]
- fn data_inaccessible_with_diff_key() {
+ fn data_inaccessible_with_diff_key(keyset: &KeySet, device: &str) {
// This test creates 2 different crypt devices using different keys backed
// by same data_device -> Write data on dev1 -> Check the data is visible but not the same on dev2
let dm = DeviceMapper::new().unwrap();
@@ -316,26 +377,32 @@
/*writable*/ true,
)
.unwrap();
+ let device_diff = device.to_owned() + "_diff";
scopeguard::defer! {
loopdevice::detach(&data_device).unwrap();
- _ = delete_device(&dm, "crypt3");
- _ = delete_device(&dm, "crypt4");
+ _ = delete_device(&dm, device);
+ _ = delete_device(&dm, &device_diff);
}
- let target =
- DmCryptTargetBuilder::default().data_device(&data_device, sz).key(KEY).build().unwrap();
+ let target = DmCryptTargetBuilder::default()
+ .data_device(&data_device, sz)
+ .cipher(keyset.cipher)
+ .key(keyset.key)
+ .build()
+ .unwrap();
let target2 = DmCryptTargetBuilder::default()
.data_device(&data_device, sz)
- .key(DIFFERENT_KEY)
+ .cipher(keyset.cipher)
+ .key(keyset.different_key)
.build()
.unwrap();
- let mut crypt_device = dm.create_crypt_device("crypt3", &target).unwrap();
+ let mut crypt_device = dm.create_crypt_device(device, &target).unwrap();
write_to_dev(&crypt_device, inputimg);
// Recreate the crypt device again diff key & check if the content is changed
- crypt_device = dm.create_crypt_device("crypt4", &target2).unwrap();
+ crypt_device = dm.create_crypt_device(&device_diff, &target2).unwrap();
let crypt = read(crypt_device).unwrap();
assert_ne!(inputimg, crypt.as_slice());
}
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index 01f7b36..ff1db63 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -16,6 +16,7 @@
//! to a bare-metal environment.
#![no_std]
+#![feature(let_else)] // Stabilized in 1.65.0
use core::ffi::{c_int, c_void, CStr};
use core::fmt;
@@ -136,6 +137,14 @@
}
}
+fn fdt_err_or_option(val: c_int) -> Result<Option<c_int>> {
+ match fdt_err(val) {
+ Ok(val) => Ok(Some(val)),
+ Err(FdtError::NotFound) => Ok(None),
+ Err(e) => Err(e),
+ }
+}
+
/// Value of a #address-cells property.
#[derive(Copy, Clone, Debug)]
enum AddrCells {
@@ -251,8 +260,8 @@
}
impl<'a> MemRegIterator<'a> {
- fn new(reg: RegIterator<'a>) -> Result<Self> {
- Ok(Self { reg })
+ fn new(reg: RegIterator<'a>) -> Self {
+ Self { reg }
}
}
@@ -285,45 +294,67 @@
}
/// Retrieve the standard (deprecated) device_type <string> property.
- pub fn device_type(&self) -> Result<&CStr> {
+ pub fn device_type(&self) -> Result<Option<&CStr>> {
self.getprop_str(CStr::from_bytes_with_nul(b"device_type\0").unwrap())
}
/// Retrieve the standard reg <prop-encoded-array> property.
- pub fn reg(&self) -> Result<RegIterator<'a>> {
- let parent = self.parent()?;
+ pub fn reg(&self) -> Result<Option<RegIterator<'a>>> {
+ let reg = CStr::from_bytes_with_nul(b"reg\0").unwrap();
- let addr_cells = parent.address_cells()?;
- let size_cells = parent.size_cells()?;
- let cells = self.getprop_cells(CStr::from_bytes_with_nul(b"reg\0").unwrap())?;
+ if let Some(cells) = self.getprop_cells(reg)? {
+ let parent = self.parent()?;
- Ok(RegIterator::new(cells, addr_cells, size_cells))
+ let addr_cells = parent.address_cells()?;
+ let size_cells = parent.size_cells()?;
+
+ Ok(Some(RegIterator::new(cells, addr_cells, size_cells)))
+ } else {
+ Ok(None)
+ }
}
/// Retrieve the value of a given <string> property.
- pub fn getprop_str(&self, name: &CStr) -> Result<&CStr> {
- CStr::from_bytes_with_nul(self.getprop(name)?).map_err(|_| FdtError::BadValue)
+ pub fn getprop_str(&self, name: &CStr) -> Result<Option<&CStr>> {
+ let value = if let Some(bytes) = self.getprop(name)? {
+ Some(CStr::from_bytes_with_nul(bytes).map_err(|_| FdtError::BadValue)?)
+ } else {
+ None
+ };
+ Ok(value)
}
/// Retrieve the value of a given property as an array of cells.
- pub fn getprop_cells(&self, name: &CStr) -> Result<CellIterator<'a>> {
- Ok(CellIterator::new(self.getprop(name)?))
+ pub fn getprop_cells(&self, name: &CStr) -> Result<Option<CellIterator<'a>>> {
+ if let Some(cells) = self.getprop(name)? {
+ Ok(Some(CellIterator::new(cells)))
+ } else {
+ Ok(None)
+ }
}
/// Retrieve the value of a given <u32> property.
- pub fn getprop_u32(&self, name: &CStr) -> Result<u32> {
- let prop = self.getprop(name)?.try_into().map_err(|_| FdtError::BadValue)?;
- Ok(u32::from_be_bytes(prop))
+ pub fn getprop_u32(&self, name: &CStr) -> Result<Option<u32>> {
+ let value = if let Some(bytes) = self.getprop(name)? {
+ Some(u32::from_be_bytes(bytes.try_into().map_err(|_| FdtError::BadValue)?))
+ } else {
+ None
+ };
+ Ok(value)
}
/// Retrieve the value of a given <u64> property.
- pub fn getprop_u64(&self, name: &CStr) -> Result<u64> {
- let prop = self.getprop(name)?.try_into().map_err(|_| FdtError::BadValue)?;
- Ok(u64::from_be_bytes(prop))
+ pub fn getprop_u64(&self, name: &CStr) -> Result<Option<u64>> {
+ let value = if let Some(bytes) = self.getprop(name)? {
+ Some(u64::from_be_bytes(bytes.try_into().map_err(|_| FdtError::BadValue)?))
+ } else {
+ None
+ };
+ Ok(value)
}
/// Retrieve the value of a given property.
- pub fn getprop(&self, name: &CStr) -> Result<&'a [u8]> {
+ pub fn getprop(&self, name: &CStr) -> Result<Option<&'a [u8]>> {
let mut len: i32 = 0;
// SAFETY - Accesses are constrained to the DT totalsize (validated by ctor) and the
// function respects the passed number of characters.
@@ -337,14 +368,21 @@
&mut len as *mut i32,
)
} as *const u8;
+
+ let Some(len) = fdt_err_or_option(len)? else {
+ return Ok(None); // Property was not found.
+ };
+ let len = usize::try_from(len).map_err(|_| FdtError::Internal)?;
+
if prop.is_null() {
- return fdt_err(len).and(Err(FdtError::Internal));
+ // We expected an error code in len but still received a valid value?!
+ return Err(FdtError::Internal);
}
- let len = usize::try_from(fdt_err(len)?).map_err(|_| FdtError::Internal)?;
- let base =
+
+ let offset =
(prop as usize).checked_sub(self.fdt.as_ptr() as usize).ok_or(FdtError::Internal)?;
- self.fdt.bytes.get(base..(base + len)).ok_or(FdtError::Internal)
+ Ok(Some(self.fdt.buffer.get(offset..(offset + len)).ok_or(FdtError::Internal)?))
}
/// Get reference to the containing device tree.
@@ -362,11 +400,7 @@
)
};
- match fdt_err(ret) {
- Ok(offset) => Ok(Some(Self { fdt: self.fdt, offset })),
- Err(FdtError::NotFound) => Ok(None),
- Err(e) => Err(e),
- }
+ Ok(fdt_err_or_option(ret)?.map(|offset| Self { fdt: self.fdt, offset }))
}
fn address_cells(&self) -> Result<AddrCells> {
@@ -384,6 +418,69 @@
}
}
+/// Mutable FDT node.
+pub struct FdtNodeMut<'a> {
+ fdt: &'a mut Fdt,
+ offset: c_int,
+}
+
+impl<'a> FdtNodeMut<'a> {
+ /// Append a property name-value (possibly empty) pair to the given node.
+ pub fn appendprop<T: AsRef<[u8]>>(&mut self, name: &CStr, value: &T) -> Result<()> {
+ // SAFETY - Accesses are constrained to the DT totalsize (validated by ctor).
+ let ret = unsafe {
+ libfdt_bindgen::fdt_appendprop(
+ self.fdt.as_mut_ptr(),
+ self.offset,
+ name.as_ptr(),
+ value.as_ref().as_ptr().cast::<c_void>(),
+ value.as_ref().len().try_into().map_err(|_| FdtError::BadValue)?,
+ )
+ };
+
+ fdt_err_expect_zero(ret)
+ }
+
+ /// Append a (address, size) pair property to the given node.
+ pub fn appendprop_addrrange(&mut self, name: &CStr, addr: u64, size: u64) -> Result<()> {
+ // SAFETY - Accesses are constrained to the DT totalsize (validated by ctor).
+ let ret = unsafe {
+ libfdt_bindgen::fdt_appendprop_addrrange(
+ self.fdt.as_mut_ptr(),
+ self.parent()?.offset,
+ self.offset,
+ name.as_ptr(),
+ addr,
+ size,
+ )
+ };
+
+ fdt_err_expect_zero(ret)
+ }
+
+ /// Get reference to the containing device tree.
+ pub fn fdt(&mut self) -> &mut Fdt {
+ self.fdt
+ }
+
+ /// Add a new subnode to the given node and return it as a FdtNodeMut on success.
+ pub fn add_subnode(&'a mut self, name: &CStr) -> Result<Self> {
+ // SAFETY - Accesses are constrained to the DT totalsize (validated by ctor).
+ let ret = unsafe {
+ libfdt_bindgen::fdt_add_subnode(self.fdt.as_mut_ptr(), self.offset, name.as_ptr())
+ };
+
+ Ok(Self { fdt: self.fdt, offset: fdt_err(ret)? })
+ }
+
+ fn parent(&'a self) -> Result<FdtNode<'a>> {
+ // SAFETY - Accesses (read-only) are constrained to the DT totalsize.
+ let ret = unsafe { libfdt_bindgen::fdt_parent_offset(self.fdt.as_ptr(), self.offset) };
+
+ Ok(FdtNode { fdt: &*self.fdt, offset: fdt_err(ret)? })
+ }
+}
+
/// Iterator over nodes sharing a same compatible string.
pub struct CompatibleIterator<'a> {
node: FdtNode<'a>,
@@ -411,10 +508,10 @@
}
}
-/// Wrapper around low-level read-only libfdt functions.
+/// Wrapper around low-level libfdt functions.
#[repr(transparent)]
pub struct Fdt {
- bytes: [u8],
+ buffer: [u8],
}
impl Fdt {
@@ -428,6 +525,16 @@
Ok(fdt)
}
+ /// Wraps a mutable slice containing a Flattened Device Tree.
+ ///
+ /// Fails if the FDT does not pass validation.
+ pub fn from_mut_slice(fdt: &mut [u8]) -> Result<&mut Self> {
+ // SAFETY - The FDT will be validated before it is returned.
+ let fdt = unsafe { Self::unchecked_from_mut_slice(fdt) };
+ fdt.check_full()?;
+ Ok(fdt)
+ }
+
/// Wraps a slice containing a Flattened Device Tree.
///
/// # Safety
@@ -437,35 +544,71 @@
mem::transmute::<&[u8], &Self>(fdt)
}
+ /// Wraps a mutable slice containing a Flattened Device Tree.
+ ///
+ /// # Safety
+ ///
+ /// The returned FDT might be invalid, only use on slices containing a valid DT.
+ pub unsafe fn unchecked_from_mut_slice(fdt: &mut [u8]) -> &mut Self {
+ mem::transmute::<&mut [u8], &mut Self>(fdt)
+ }
+
+ /// Make the whole slice containing the DT available to libfdt.
+ pub fn unpack(&mut self) -> Result<()> {
+ // SAFETY - "Opens" the DT in-place (supported use-case) by updating its header and
+ // internal structures to make use of the whole self.fdt slice but performs no accesses
+ // outside of it and leaves the DT in a state that will be detected by other functions.
+ let ret = unsafe {
+ libfdt_bindgen::fdt_open_into(
+ self.as_ptr(),
+ self.as_mut_ptr(),
+ self.capacity().try_into().map_err(|_| FdtError::Internal)?,
+ )
+ };
+ fdt_err_expect_zero(ret)
+ }
+
+ /// Pack the DT to take a minimum amount of memory.
+ ///
+ /// Doesn't shrink the underlying memory slice.
+ pub fn pack(&mut self) -> Result<()> {
+ // SAFETY - "Closes" the DT in-place by updating its header and relocating its structs.
+ let ret = unsafe { libfdt_bindgen::fdt_pack(self.as_mut_ptr()) };
+ fdt_err_expect_zero(ret)
+ }
+
/// Return an iterator of memory banks specified the "/memory" node.
///
/// NOTE: This does not support individual "/memory@XXXX" banks.
- pub fn memory(&self) -> Result<MemRegIterator> {
+ pub fn memory(&self) -> Result<Option<MemRegIterator>> {
let memory = CStr::from_bytes_with_nul(b"/memory\0").unwrap();
let device_type = CStr::from_bytes_with_nul(b"memory\0").unwrap();
- let node = self.node(memory)?;
- if node.device_type()? != device_type {
- return Err(FdtError::BadValue);
- }
+ if let Some(node) = self.node(memory)? {
+ if node.device_type()? != Some(device_type) {
+ return Err(FdtError::BadValue);
+ }
+ let reg = node.reg()?.ok_or(FdtError::BadValue)?;
- MemRegIterator::new(node.reg()?)
+ Ok(Some(MemRegIterator::new(reg)))
+ } else {
+ Ok(None)
+ }
}
/// Retrieve the standard /chosen node.
- pub fn chosen(&self) -> Result<FdtNode> {
+ pub fn chosen(&self) -> Result<Option<FdtNode>> {
self.node(CStr::from_bytes_with_nul(b"/chosen\0").unwrap())
}
/// Get the root node of the tree.
pub fn root(&self) -> Result<FdtNode> {
- self.node(CStr::from_bytes_with_nul(b"/\0").unwrap())
+ self.node(CStr::from_bytes_with_nul(b"/\0").unwrap())?.ok_or(FdtError::Internal)
}
/// Find a tree node by its full path.
- pub fn node(&self, path: &CStr) -> Result<FdtNode> {
- let offset = self.path_offset(path)?;
- Ok(FdtNode { fdt: self, offset })
+ pub fn node(&self, path: &CStr) -> Result<Option<FdtNode>> {
+ Ok(self.path_offset(path)?.map(|offset| FdtNode { fdt: self, offset }))
}
/// Iterate over nodes with a given compatible string.
@@ -473,7 +616,17 @@
CompatibleIterator::new(self, compatible)
}
- fn path_offset(&self, path: &CStr) -> Result<c_int> {
+ /// Get the mutable root node of the tree.
+ pub fn root_mut(&mut self) -> Result<FdtNodeMut> {
+ self.node_mut(CStr::from_bytes_with_nul(b"/\0").unwrap())?.ok_or(FdtError::Internal)
+ }
+
+ /// Find a mutable tree node by its full path.
+ pub fn node_mut(&mut self, path: &CStr) -> Result<Option<FdtNodeMut>> {
+ Ok(self.path_offset(path)?.map(|offset| FdtNodeMut { fdt: self, offset }))
+ }
+
+ fn path_offset(&self, path: &CStr) -> Result<Option<c_int>> {
let len = path.to_bytes().len().try_into().map_err(|_| FdtError::BadPath)?;
// SAFETY - Accesses are constrained to the DT totalsize (validated by ctor) and the
// function respects the passed number of characters.
@@ -482,11 +635,11 @@
libfdt_bindgen::fdt_path_offset_namelen(self.as_ptr(), path.as_ptr(), len)
};
- fdt_err(ret)
+ fdt_err_or_option(ret)
}
fn check_full(&self) -> Result<()> {
- let len = self.bytes.len();
+ let len = self.buffer.len();
// SAFETY - Only performs read accesses within the limits of the slice. If successful, this
// call guarantees to other unsafe calls that the header contains a valid totalsize (w.r.t.
// 'len' i.e. the self.fdt slice) that those C functions can use to perform bounds
@@ -499,4 +652,12 @@
fn as_ptr(&self) -> *const c_void {
self as *const _ as *const c_void
}
+
+ fn as_mut_ptr(&mut self) -> *mut c_void {
+ self as *mut _ as *mut c_void
+ }
+
+ fn capacity(&self) -> usize {
+ self.buffer.len()
+ }
}
diff --git a/libs/vbmeta/Android.bp b/libs/vbmeta/Android.bp
index c5078c2..a487097 100644
--- a/libs/vbmeta/Android.bp
+++ b/libs/vbmeta/Android.bp
@@ -27,7 +27,11 @@
"libanyhow",
"libtempfile",
],
- data: ["tests/data/*"],
+ data: [
+ ":avb_testkey_rsa2048",
+ ":avb_testkey_rsa4096",
+ ":avb_testkey_rsa8192",
+ ],
required: ["avbtool"],
test_suites: ["general-tests"],
test_options: {
diff --git a/libs/vbmeta/src/lib.rs b/libs/vbmeta/src/lib.rs
index 887844c..8e81ea4 100644
--- a/libs/vbmeta/src/lib.rs
+++ b/libs/vbmeta/src/lib.rs
@@ -238,7 +238,7 @@
Ok(())
}
- fn test_signed_image(algorithm: &str, key: &str) -> Result<()> {
+ fn signed_image_has_valid_vbmeta(algorithm: &str, key: &str) -> Result<()> {
let test_dir = TempDir::new().unwrap();
let test_file = test_dir.path().join("test.img");
let mut cmd = Command::new("./avbtool");
@@ -289,16 +289,16 @@
#[test]
fn test_rsa2048_signed_image() -> Result<()> {
- test_signed_image("SHA256_RSA2048", "tests/data/testkey_rsa2048.pem")
+ signed_image_has_valid_vbmeta("SHA256_RSA2048", "data/testkey_rsa2048.pem")
}
#[test]
fn test_rsa4096_signed_image() -> Result<()> {
- test_signed_image("SHA256_RSA4096", "tests/data/testkey_rsa4096.pem")
+ signed_image_has_valid_vbmeta("SHA256_RSA4096", "data/testkey_rsa4096.pem")
}
#[test]
fn test_rsa8192_signed_image() -> Result<()> {
- test_signed_image("SHA256_RSA8192", "tests/data/testkey_rsa8192.pem")
+ signed_image_has_valid_vbmeta("SHA256_RSA8192", "data/testkey_rsa8192.pem")
}
}
diff --git a/libs/vbmeta/tests/data/testkey_rsa2048.pem b/libs/vbmeta/tests/data/testkey_rsa2048.pem
deleted file mode 100644
index 867dcff..0000000
--- a/libs/vbmeta/tests/data/testkey_rsa2048.pem
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEowIBAAKCAQEAxlVR3TIkouAOvH79vaJTgFhpfvVKQIeVkFRZPVXK/zY0Gvrh
-4JAqGjJoW/PfrQv5sdD36qtHH3a+G5hLZ6Ni+t/mtfjucxZfuLGC3kmJ1T3XqEKZ
-gXXI2IR7vVSoImREvDQGEDyJwtHzLANlkbGg0cghVhWZSCAndO8BenalC2v94/rt
-DfkPekH6dgU3Sf40T0sBSeSY94mOzTaqOR2pfV1rWlLRdWmo33zeHBv52Rlbt0dM
-uXAureXWiHztkm5GCBC1dgM+CaxNtizNEgC91KcD0xuRCCM2WxH+r1lpszyIJDct
-YbrFmVEYl/kjQpafhy7Nsk1fqSTyRdriZSYmTQIDAQABAoIBAQC+kJgaCuX8wYAn
-SXWQ0fmdZlXnMNRpcF0a0pD0SAzGb1RdYBXMaXiqtyhiwc53PPxsCDdNecjayIMd
-jJVXPTwLhTruOgMS/bp3gcgWwV34UHV4LJXGOGAE+jbS0hbDBMiudOYmj6RmVshp
-z9G1zZCSQNMXHaWsEYkX59XpzzoB384nRul2QgEtwzUNR9XlpzgtJBLk3SACkvsN
-mQ/DW8IWHXLg8vLn1LzVJ2e3B16H4MoE2TCHxqfMgr03IDRRJogkenQuQsFhevYT
-o/mJyHSWavVgzMHG9I5m+eepF4Wyhj1Y4WyKAuMI+9dHAX/h7Lt8XFCQCh5DbkVG
-zGr34sWBAoGBAOs7n7YZqNaaguovfIdRRsxxZr1yJAyDsr6w3yGImDZYju4c4WY9
-5esO2kP3FA4p0c7FhQF5oOb1rBuHEPp36cpL4aGeK87caqTfq63WZAujoTZpr9Lp
-BRbkL7w/xG7jpQ/clpA8sHzHGQs/nelxoOtC7E118FiRgvD/jdhlMyL9AoGBANfX
-vyoN1pplfT2xR8QOjSZ+Q35S/+SAtMuBnHx3l0qH2bbBjcvM1MNDWjnRDyaYhiRu
-i+KA7tqfib09+XpB3g5D6Ov7ls/Ldx0S/VcmVWtia2HK8y8iLGtokoBZKQ5AaFX2
-iQU8+tC4h69GnJYQKqNwgCUzh8+gHX5Y46oDiTmRAoGAYpOx8lX+czB8/Da6MNrW
-mIZNT8atZLEsDs2ANEVRxDSIcTCZJId7+m1W+nRoaycLTWNowZ1+2ErLvR10+AGY
-b7Ys79Wg9idYaY9yGn9lnZsMzAiuLeyIvXcSqgjvAKlVWrhOQFOughvNWvFl85Yy
-oWSCMlPiTLtt7CCsCKsgKuECgYBgdIp6GZsIfkgclKe0hqgvRoeU4TR3gcjJlM9A
-lBTo+pKhaBectplx9RxR8AnsPobbqwcaHnIfAuKDzjk5mEvKZjClnFXF4HAHbyAF
-nRzZEy9XkWFhc80T5rRpZO7C7qdxmu2aiKixM3V3L3/0U58qULEDbubHMw9bEhAT
-PudI8QKBgHEEiMm/hr9T41hbQi/LYanWnlFw1ue+osKuF8bXQuxnnHNuFT/c+9/A
-vWhgqG6bOEHu+p/IPrYm4tBMYlwsyh4nXCyGgDJLbLIfzKwKAWCtH9LwnyDVhOow
-GH9shdR+sW3Ew97xef02KAH4VlNANEmBV4sQNqWWvsYrcFm2rOdL
------END RSA PRIVATE KEY-----
diff --git a/libs/vbmeta/tests/data/testkey_rsa4096.pem b/libs/vbmeta/tests/data/testkey_rsa4096.pem
deleted file mode 100644
index 26db5c3..0000000
--- a/libs/vbmeta/tests/data/testkey_rsa4096.pem
+++ /dev/null
@@ -1,51 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-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
------END RSA PRIVATE KEY-----
diff --git a/libs/vbmeta/tests/data/testkey_rsa8192.pem b/libs/vbmeta/tests/data/testkey_rsa8192.pem
deleted file mode 100644
index a383428..0000000
--- a/libs/vbmeta/tests/data/testkey_rsa8192.pem
+++ /dev/null
@@ -1,99 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-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
------END RSA PRIVATE KEY-----
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 94ef940..7d04557 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -74,6 +74,12 @@
# some services can be started.
trigger late-fs
+ # Wait for microdroid_manager to finish setting up sysprops from the payload config.
+ # Some further actions in the boot sequence might depend on the sysprops from the payloag,
+ # e.g. microdroid.config.enable_authfs configures whether to run authfs_service after
+ # /data is mounted.
+ wait_for_prop microdroid_manager.config_done 1
+
trigger post-fs-data
# Load persist properties and override properties (if enabled) from /data.
@@ -132,6 +138,13 @@
mkdir /data/local 0751 root root
mkdir /data/local/tmp 0771 shell shell
+on post-fs-data && property:microdroid_manager.authfs.enabled=1
+ start authfs_service
+
+on boot
+ # Mark boot completed. This will notify microdroid_manager to run payload.
+ setprop dev.bootcomplete 1
+
service tombstone_transmit /system/bin/tombstone_transmit.microdroid -cid 2 -port 2000 -remove_tombstones_after_transmitting
user system
group system
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index a706dbe..4f94bb4 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -85,7 +85,7 @@
const ENCRYPTEDSTORE_BACKING_DEVICE: &str = "/dev/block/by-name/encryptedstore";
const ENCRYPTEDSTORE_BIN: &str = "/system/bin/encryptedstore";
const ENCRYPTEDSTORE_KEY_IDENTIFIER: &str = "encryptedstore_key";
-const ENCRYPTEDSTORE_KEYSIZE: u32 = 64;
+const ENCRYPTEDSTORE_KEYSIZE: u32 = 32;
const ENCRYPTEDSTORE_MOUNTPOINT: &str = "/mnt/encryptedstore";
#[derive(thiserror::Error, Debug)]
@@ -417,9 +417,10 @@
mount_extra_apks(&config)?;
// Wait until apex config is done. (e.g. linker configuration for apexes)
- // TODO(jooyung): wait until sys.boot_completed?
wait_for_apex_config_done()?;
+ setup_config_sysprops(&config)?;
+
// Start tombstone_transmit if enabled
if config.export_tombstones {
control_service("start", "tombstone_transmit")?;
@@ -427,11 +428,6 @@
control_service("stop", "tombstoned")?;
}
- // Start authfs if enabled
- if config.enable_authfs {
- control_service("start", "authfs_service")?;
- }
-
// Wait until zipfuse has mounted the APK so we can access the payload
wait_for_property_true(APK_MOUNT_DONE_PROP).context("Failed waiting for APK mount done")?;
@@ -442,7 +438,8 @@
ensure!(exitcode.success(), "Unable to prepare encrypted storage. Exitcode={}", exitcode);
}
- system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
+ wait_for_property_true("dev.bootcomplete").context("failed waiting for dev.bootcomplete")?;
+ info!("boot completed, time to run payload");
exec_task(task, service).context("Failed to run payload")
}
@@ -682,6 +679,16 @@
Ok(())
}
+fn setup_config_sysprops(config: &VmPayloadConfig) -> Result<()> {
+ if config.enable_authfs {
+ system_properties::write("microdroid_manager.authfs.enabled", "1")
+ .context("failed to write microdroid_manager.authfs.enabled")?;
+ }
+ system_properties::write("microdroid_manager.config_done", "1")
+ .context("failed to write microdroid_manager.config_done")?;
+ Ok(())
+}
+
// Waits until linker config is generated
fn wait_for_apex_config_done() -> Result<()> {
wait_for_property_true(APEX_CONFIG_DONE_PROP).context("Failed waiting for apex config done")
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 77de696..b78e077 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -12,11 +12,17 @@
"legacy",
],
rustlibs: [
+ "libaarch64_paging",
"libbuddy_system_allocator",
+ "liblibfdt",
"liblog_rust_nostd",
"libpvmfw_embedded_key",
+ "libtinyvec_nostd",
"libvmbase",
],
+ static_libs: [
+ "libarm-optimized-routines-mem",
+ ],
apex_available: ["com.android.virt"],
}
diff --git a/pvmfw/idmap.S b/pvmfw/idmap.S
index ec3ceaf..2ef0d42 100644
--- a/pvmfw/idmap.S
+++ b/pvmfw/idmap.S
@@ -40,13 +40,9 @@
/* level 1 */
.quad .L_BLOCK_DEV | 0x0 // 1 GB of device mappings
.quad .L_TT_TYPE_TABLE + 0f // Unmapped device memory, and pVM firmware
- .quad .L_TT_TYPE_TABLE + 1f // up to 1 GB of DRAM
- .fill 509, 8, 0x0 // 509 GB of remaining VA space
+ .fill 510, 8, 0x0 // 510 GB of remaining VA space
/* level 2 */
0: .fill 510, 8, 0x0
.quad .L_BLOCK_MEM_XIP | 0x7fc00000 // pVM firmware image
.quad .L_BLOCK_MEM | 0x7fe00000 // Writable memory for stack, heap &c.
-1: .quad .L_BLOCK_RO | 0x80000000 // DT provided by VMM
- .quad .L_BLOCK_RO | 0x80200000 // 2 MB of DRAM containing payload image
- .fill 510, 8, 0x0
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
new file mode 100644
index 0000000..0f2a39c
--- /dev/null
+++ b/pvmfw/src/config.rs
@@ -0,0 +1,200 @@
+// Copyright 2022, 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.
+
+//! Support for the pvmfw configuration data format.
+
+use crate::helpers;
+use core::fmt;
+use core::mem;
+use core::num::NonZeroUsize;
+use core::ops;
+use core::result;
+
+#[repr(C, packed)]
+#[derive(Clone, Copy, Debug)]
+struct Header {
+ magic: u32,
+ version: u32,
+ total_size: u32,
+ flags: u32,
+ entries: [HeaderEntry; Entry::COUNT],
+}
+
+#[derive(Debug)]
+pub enum Error {
+ /// Reserved region can't fit configuration header.
+ BufferTooSmall,
+ /// Header doesn't contain the expect magic value.
+ InvalidMagic,
+ /// Version of the header isn't supported.
+ UnsupportedVersion(u16, u16),
+ /// Header sets flags incorrectly or uses reserved flags.
+ InvalidFlags(u32),
+ /// Header describes configuration data that doesn't fit in the expected buffer.
+ InvalidSize(usize),
+ /// Header entry is invalid.
+ InvalidEntry(Entry),
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::BufferTooSmall => write!(f, "Reserved region is smaller than config header"),
+ Self::InvalidMagic => write!(f, "Wrong magic number"),
+ Self::UnsupportedVersion(x, y) => write!(f, "Version {x}.{y} not supported"),
+ Self::InvalidFlags(v) => write!(f, "Flags value {v:#x} is incorrect or reserved"),
+ Self::InvalidSize(sz) => write!(f, "Total size ({sz:#x}) overflows reserved region"),
+ Self::InvalidEntry(e) => write!(f, "Entry {e:?} is invalid"),
+ }
+ }
+}
+
+pub type Result<T> = result::Result<T, Error>;
+
+impl Header {
+ const MAGIC: u32 = u32::from_ne_bytes(*b"pvmf");
+ const PADDED_SIZE: usize =
+ helpers::unchecked_align_up(mem::size_of::<Self>(), mem::size_of::<u64>());
+
+ pub const fn version(major: u16, minor: u16) -> u32 {
+ ((major as u32) << 16) | (minor as u32)
+ }
+
+ pub const fn version_tuple(&self) -> (u16, u16) {
+ ((self.version >> 16) as u16, self.version as u16)
+ }
+
+ pub fn total_size(&self) -> usize {
+ self.total_size as usize
+ }
+
+ pub fn body_size(&self) -> usize {
+ self.total_size() - Self::PADDED_SIZE
+ }
+
+ fn get(&self, entry: Entry) -> HeaderEntry {
+ self.entries[entry as usize]
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum Entry {
+ Bcc = 0,
+ DebugPolicy = 1,
+}
+
+impl Entry {
+ const COUNT: usize = 2;
+}
+
+#[repr(packed)]
+#[derive(Clone, Copy, Debug)]
+struct HeaderEntry {
+ offset: u32,
+ size: u32,
+}
+
+impl HeaderEntry {
+ pub fn is_empty(&self) -> bool {
+ self.offset() == 0 && self.size() == 0
+ }
+
+ pub fn fits_in(&self, max_size: usize) -> bool {
+ (Header::PADDED_SIZE..max_size).contains(&self.offset())
+ && NonZeroUsize::new(self.size())
+ .and_then(|s| s.checked_add(self.offset()))
+ .filter(|&x| x.get() <= max_size)
+ .is_some()
+ }
+
+ pub fn as_body_range(&self) -> ops::Range<usize> {
+ let start = self.offset() - Header::PADDED_SIZE;
+
+ start..(start + self.size())
+ }
+
+ pub fn offset(&self) -> usize {
+ self.offset as usize
+ }
+
+ pub fn size(&self) -> usize {
+ self.size as usize
+ }
+}
+
+#[derive(Debug)]
+pub struct Config<'a> {
+ header: &'a Header,
+ body: &'a mut [u8],
+}
+
+impl<'a> Config<'a> {
+ /// Take ownership of a pvmfw configuration consisting of its header and following entries.
+ ///
+ /// SAFETY - 'data' should respect the alignment of Header.
+ pub unsafe fn new(data: &'a mut [u8]) -> Result<Self> {
+ let header = data.get(..Header::PADDED_SIZE).ok_or(Error::BufferTooSmall)?;
+
+ let header = &*(header.as_ptr() as *const Header);
+
+ if header.magic != Header::MAGIC {
+ return Err(Error::InvalidMagic);
+ }
+
+ if header.version != Header::version(1, 0) {
+ let (major, minor) = header.version_tuple();
+ return Err(Error::UnsupportedVersion(major, minor));
+ }
+
+ if header.flags != 0 {
+ return Err(Error::InvalidFlags(header.flags));
+ }
+
+ let total_size = header.total_size();
+
+ // BCC is a mandatory entry of the configuration data.
+ if !header.get(Entry::Bcc).fits_in(total_size) {
+ return Err(Error::InvalidEntry(Entry::Bcc));
+ }
+
+ // Debug policy is optional.
+ let dp = header.get(Entry::DebugPolicy);
+ if !dp.is_empty() && !dp.fits_in(total_size) {
+ return Err(Error::InvalidEntry(Entry::DebugPolicy));
+ }
+
+ let body = data
+ .get_mut(Header::PADDED_SIZE..)
+ .ok_or(Error::BufferTooSmall)?
+ .get_mut(..header.body_size())
+ .ok_or(Error::InvalidSize(total_size))?;
+
+ Ok(Self { header, body })
+ }
+
+ /// Get slice containing the platform BCC.
+ pub fn get_bcc_mut(&mut self) -> &mut [u8] {
+ &mut self.body[self.header.get(Entry::Bcc).as_body_range()]
+ }
+
+ /// Get slice containing the platform debug policy.
+ pub fn get_debug_policy(&mut self) -> Option<&mut [u8]> {
+ let entry = self.header.get(Entry::DebugPolicy);
+ if entry.is_empty() {
+ None
+ } else {
+ Some(&mut self.body[entry.as_body_range()])
+ }
+ }
+}
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index c0ad878..b840488 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -14,13 +14,20 @@
//! Low-level entry and exit points of pvmfw.
+use crate::config;
+use crate::fdt;
use crate::heap;
use crate::helpers;
+use crate::memory::MemoryTracker;
use crate::mmio_guard;
+use crate::mmu;
use core::arch::asm;
+use core::num::NonZeroUsize;
use core::slice;
use log::debug;
use log::error;
+use log::info;
+use log::warn;
use log::LevelFilter;
use vmbase::{console, layout, logger, main, power::reboot};
@@ -28,8 +35,16 @@
enum RebootReason {
/// A malformed BCC was received.
InvalidBcc,
+ /// An invalid configuration was appended to pvmfw.
+ InvalidConfig,
/// An unexpected internal error happened.
InternalError,
+ /// The provided FDT was invalid.
+ InvalidFdt,
+ /// The provided payload was invalid.
+ InvalidPayload,
+ /// The provided ramdisk was invalid.
+ InvalidRamdisk,
}
main!(start);
@@ -48,6 +63,98 @@
// if we reach this point and return, vmbase::entry::rust_entry() will call power::shutdown().
}
+struct MemorySlices<'a> {
+ fdt: &'a mut libfdt::Fdt,
+ kernel: &'a [u8],
+ ramdisk: Option<&'a [u8]>,
+}
+
+impl<'a> MemorySlices<'a> {
+ fn new(
+ fdt: usize,
+ payload: usize,
+ payload_size: usize,
+ memory: &mut MemoryTracker,
+ ) -> Result<Self, RebootReason> {
+ // SAFETY - SIZE_2MB is non-zero.
+ const FDT_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(helpers::SIZE_2MB) };
+ // TODO - Only map the FDT as read-only, until we modify it right before jump_to_payload()
+ // e.g. by generating a DTBO for a template DT in main() and, on return, re-map DT as RW,
+ // overwrite with the template DT and apply the DTBO.
+ let range = memory.alloc_mut(fdt, FDT_SIZE).map_err(|e| {
+ error!("Failed to allocate the FDT range: {e}");
+ RebootReason::InternalError
+ })?;
+
+ // SAFETY - The tracker validated the range to be in main memory, mapped, and not overlap.
+ let fdt = unsafe { slice::from_raw_parts_mut(range.start as *mut u8, range.len()) };
+ let fdt = libfdt::Fdt::from_mut_slice(fdt).map_err(|e| {
+ error!("Failed to spawn the FDT wrapper: {e}");
+ RebootReason::InvalidFdt
+ })?;
+
+ debug!("Fdt passed validation!");
+
+ let memory_range = fdt
+ .memory()
+ .map_err(|e| {
+ error!("Failed to get /memory from the DT: {e}");
+ RebootReason::InvalidFdt
+ })?
+ .ok_or_else(|| {
+ error!("Node /memory was found empty");
+ RebootReason::InvalidFdt
+ })?
+ .next()
+ .ok_or_else(|| {
+ error!("Failed to read the memory size from the FDT");
+ RebootReason::InternalError
+ })?;
+
+ debug!("Resizing MemoryTracker to range {memory_range:#x?}");
+
+ memory.shrink(&memory_range).map_err(|_| {
+ error!("Failed to use memory range value from DT: {memory_range:#x?}");
+ RebootReason::InvalidFdt
+ })?;
+
+ let payload_size = NonZeroUsize::new(payload_size).ok_or_else(|| {
+ error!("Invalid payload size: {payload_size:#x}");
+ RebootReason::InvalidPayload
+ })?;
+
+ let payload_range = memory.alloc(payload, payload_size).map_err(|e| {
+ error!("Failed to obtain the payload range: {e}");
+ RebootReason::InternalError
+ })?;
+ // SAFETY - The tracker validated the range to be in main memory, mapped, and not overlap.
+ let kernel =
+ unsafe { slice::from_raw_parts(payload_range.start as *const u8, payload_range.len()) };
+
+ let ramdisk_range = fdt::initrd_range(fdt).map_err(|e| {
+ error!("An error occurred while locating the ramdisk in the device tree: {e}");
+ RebootReason::InternalError
+ })?;
+
+ let ramdisk = if let Some(r) = ramdisk_range {
+ debug!("Located ramdisk at {r:?}");
+ let r = memory.alloc_range(&r).map_err(|e| {
+ error!("Failed to obtain the initrd range: {e}");
+ RebootReason::InvalidRamdisk
+ })?;
+
+ // SAFETY - The region was validated by memory to be in main memory, mapped, and
+ // not overlap.
+ Some(unsafe { slice::from_raw_parts(r.start as *const u8, r.len()) })
+ } else {
+ info!("Couldn't locate the ramdisk from the device tree");
+ None
+ };
+
+ Ok(Self { fdt, kernel, ramdisk })
+ }
+}
+
/// Sets up the environment for main() and wraps its result for start().
///
/// Provide the abstractions necessary for start() to abort the pVM boot and for main() to run with
@@ -63,14 +170,6 @@
logger::init(LevelFilter::Info).map_err(|_| RebootReason::InternalError)?;
- const FDT_MAX_SIZE: usize = helpers::SIZE_2MB;
- // TODO: Check that the FDT is fully contained in RAM.
- // SAFETY - We trust the VMM, for now.
- let fdt = unsafe { slice::from_raw_parts_mut(fdt as *mut u8, FDT_MAX_SIZE) };
- // TODO: Check that the payload is fully contained in RAM and doesn't overlap with the FDT.
- // SAFETY - We trust the VMM, for now.
- let payload = unsafe { slice::from_raw_parts(payload as *const u8, payload_size) };
-
// Use debug!() to avoid printing to the UART if we failed to configure it as only local
// builds that have tweaked the logger::init() call will actually attempt to log the message.
@@ -86,13 +185,45 @@
// SAFETY - We only get the appended payload from here, once. It is mapped and the linker
// script prevents it from overlapping with other objects.
- let bcc = as_bcc(unsafe { get_appended_data_slice() }).ok_or_else(|| {
+ let appended_data = unsafe { get_appended_data_slice() };
+
+ // Up to this point, we were using the built-in static (from .rodata) page tables.
+
+ let mut page_table = mmu::PageTable::from_static_layout().map_err(|e| {
+ error!("Failed to set up the dynamic page tables: {e}");
+ RebootReason::InternalError
+ })?;
+
+ const CONSOLE_LEN: usize = 1; // vmbase::uart::Uart only uses one u8 register.
+ let uart_range = console::BASE_ADDRESS..(console::BASE_ADDRESS + CONSOLE_LEN);
+ page_table.map_device(&uart_range).map_err(|e| {
+ error!("Failed to remap the UART as a dynamic page table entry: {e}");
+ RebootReason::InternalError
+ })?;
+
+ // SAFETY - We only get the appended payload from here, once. It is statically mapped and the
+ // linker script prevents it from overlapping with other objects.
+ let mut appended = unsafe { AppendedPayload::new(appended_data) }.ok_or_else(|| {
+ error!("No valid configuration found");
+ RebootReason::InvalidConfig
+ })?;
+
+ let bcc = appended.get_bcc_mut().ok_or_else(|| {
error!("Invalid BCC");
RebootReason::InvalidBcc
})?;
+ debug!("Activating dynamic page table...");
+ // SAFETY - page_table duplicates the static mappings for everything that the Rust code is
+ // aware of so activating it shouldn't have any visible effect.
+ unsafe { page_table.activate() };
+ debug!("... Success!");
+
+ let mut memory = MemoryTracker::new(page_table);
+ let slices = MemorySlices::new(fdt, payload, payload_size, &mut memory)?;
+
// This wrapper allows main() to be blissfully ignorant of platform details.
- crate::main(fdt, payload, bcc);
+ crate::main(slices.fdt, slices.kernel, slices.ramdisk, bcc);
// TODO: Overwrite BCC before jumping to payload to avoid leaking our sealing key.
@@ -169,13 +300,50 @@
slice::from_raw_parts_mut(base as *mut u8, size)
}
-fn as_bcc(data: &mut [u8]) -> Option<&mut [u8]> {
- const BCC_SIZE: usize = helpers::SIZE_4KB;
+enum AppendedPayload<'a> {
+ /// Configuration data.
+ Config(config::Config<'a>),
+ /// Deprecated raw BCC, as used in Android T.
+ LegacyBcc(&'a mut [u8]),
+}
- if cfg!(feature = "legacy") {
+impl<'a> AppendedPayload<'a> {
+ /// SAFETY - 'data' should respect the alignment of config::Header.
+ unsafe fn new(data: &'a mut [u8]) -> Option<Self> {
+ if Self::is_valid_config(data) {
+ Some(Self::Config(config::Config::new(data).unwrap()))
+ } else if cfg!(feature = "legacy") {
+ const BCC_SIZE: usize = helpers::SIZE_4KB;
+ warn!("Assuming the appended data at {:?} to be a raw BCC", data.as_ptr());
+ Some(Self::LegacyBcc(&mut data[..BCC_SIZE]))
+ } else {
+ None
+ }
+ }
+
+ unsafe fn is_valid_config(data: &mut [u8]) -> bool {
+ // This function is necessary to prevent the borrow checker from getting confused
+ // about the ownership of data in new(); see https://users.rust-lang.org/t/78467.
+ let addr = data.as_ptr();
+ config::Config::new(data)
+ .map_err(|e| warn!("Invalid configuration data at {addr:?}: {e}"))
+ .is_ok()
+ }
+
+ #[allow(dead_code)] // TODO(b/232900974)
+ fn get_debug_policy(&mut self) -> Option<&mut [u8]> {
+ match self {
+ Self::Config(ref mut cfg) => cfg.get_debug_policy(),
+ Self::LegacyBcc(_) => None,
+ }
+ }
+
+ fn get_bcc_mut(&mut self) -> Option<&mut [u8]> {
+ let bcc = match self {
+ Self::LegacyBcc(ref mut bcc) => bcc,
+ Self::Config(ref mut cfg) => cfg.get_bcc_mut(),
+ };
// TODO(b/256148034): return None if BccHandoverParse(bcc) != kDiceResultOk.
- Some(&mut data[..BCC_SIZE])
- } else {
- None
+ Some(bcc)
}
}
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
new file mode 100644
index 0000000..5b9efd2
--- /dev/null
+++ b/pvmfw/src/fdt.rs
@@ -0,0 +1,32 @@
+// Copyright 2022, 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.
+
+//! High-level FDT functions.
+
+use core::ffi::CStr;
+use core::ops::Range;
+
+/// Extract from /chosen the address range containing the pre-loaded ramdisk.
+pub fn initrd_range(fdt: &libfdt::Fdt) -> libfdt::Result<Option<Range<usize>>> {
+ let start = CStr::from_bytes_with_nul(b"linux,initrd-start\0").unwrap();
+ let end = CStr::from_bytes_with_nul(b"linux,initrd-end\0").unwrap();
+
+ 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)));
+ }
+ }
+
+ Ok(None)
+}
diff --git a/pvmfw/src/helpers.rs b/pvmfw/src/helpers.rs
index 59cf9f3..f1ff36d 100644
--- a/pvmfw/src/helpers.rs
+++ b/pvmfw/src/helpers.rs
@@ -14,6 +14,8 @@
//! Miscellaneous helper functions.
+use core::arch::asm;
+
pub const SIZE_4KB: usize = 4 << 10;
pub const SIZE_2MB: usize = 2 << 20;
@@ -24,6 +26,13 @@
addr & !(alignment - 1)
}
+/// Computes the smallest multiple of the provided alignment larger or equal to the address.
+///
+/// Note: the result is undefined if alignment isn't a power of two and may wrap to 0.
+pub const fn unchecked_align_up(addr: usize, alignment: usize) -> usize {
+ unchecked_align_down(addr + alignment - 1, alignment)
+}
+
/// Safe wrapper around unchecked_align_up() that validates its assumptions and doesn't wrap.
pub const fn align_up(addr: usize, alignment: usize) -> Option<usize> {
if !alignment.is_power_of_two() {
@@ -39,3 +48,30 @@
pub const fn page_4kb_of(addr: usize) -> usize {
unchecked_align_down(addr, SIZE_4KB)
}
+
+#[inline]
+fn min_dcache_line_size() -> usize {
+ const DMINLINE_SHIFT: usize = 16;
+ const DMINLINE_MASK: usize = 0xf;
+ let ctr_el0: usize;
+
+ unsafe { asm!("mrs {x}, ctr_el0", x = out(reg) ctr_el0) }
+
+ // DminLine: log2 of the number of words in the smallest cache line of all the data caches.
+ let dminline = (ctr_el0 >> DMINLINE_SHIFT) & DMINLINE_MASK;
+
+ 1 << dminline
+}
+
+/// Flush `size` bytes of data cache by virtual address.
+#[inline]
+pub fn flush_region(start: usize, size: usize) {
+ let line_size = min_dcache_line_size();
+ let end = start + size;
+ let start = unchecked_align_down(start, line_size);
+
+ for line in (start..end).step_by(line_size) {
+ // SAFETY - Clearing cache lines shouldn't have Rust-visible side effects.
+ unsafe { asm!("dc cvau, {x}", x = in(reg) line) }
+ }
+}
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 8178d0b..6810fda 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -17,26 +17,32 @@
#![no_main]
#![no_std]
#![feature(default_alloc_error_handler)]
+#![feature(ptr_const_cast)] // Stabilized in 1.65.0
mod avb;
+mod config;
mod entry;
mod exceptions;
+mod fdt;
mod heap;
mod helpers;
+mod memory;
mod mmio_guard;
+mod mmu;
mod smccc;
use avb::PUBLIC_KEY;
use log::{debug, info};
-fn main(fdt: &mut [u8], payload: &[u8], bcc: &[u8]) {
+fn main(fdt: &libfdt::Fdt, signed_kernel: &[u8], ramdisk: Option<&[u8]>, bcc: &[u8]) {
info!("pVM firmware");
- debug!(
- "fdt_address={:#018x}, payload_start={:#018x}, payload_size={:#018x}",
- fdt.as_ptr() as usize,
- payload.as_ptr() as usize,
- payload.len(),
- );
+ debug!("FDT: {:?}", fdt as *const libfdt::Fdt);
+ debug!("Signed kernel: {:?} ({:#x} bytes)", signed_kernel.as_ptr(), signed_kernel.len());
+ if let Some(rd) = ramdisk {
+ debug!("Ramdisk: {:?} ({:#x} bytes)", rd.as_ptr(), rd.len());
+ } else {
+ debug!("Ramdisk: None");
+ }
debug!("BCC: {:?} ({:#x} bytes)", bcc.as_ptr(), bcc.len());
debug!("AVB public key: addr={:?}, size={:#x} ({1})", PUBLIC_KEY.as_ptr(), PUBLIC_KEY.len());
info!("Starting payload...");
diff --git a/pvmfw/src/memory.rs b/pvmfw/src/memory.rs
new file mode 100644
index 0000000..ca0b886
--- /dev/null
+++ b/pvmfw/src/memory.rs
@@ -0,0 +1,190 @@
+// Copyright 2022, 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.
+
+//! Low-level allocation and tracking of main memory.
+
+use crate::helpers;
+use crate::mmu;
+use core::cmp::max;
+use core::cmp::min;
+use core::fmt;
+use core::num::NonZeroUsize;
+use core::ops::Range;
+use core::result;
+use log::error;
+use tinyvec::ArrayVec;
+
+type MemoryRange = Range<usize>;
+
+#[derive(Clone, Copy, Debug, Default)]
+enum MemoryType {
+ #[default]
+ ReadOnly,
+ ReadWrite,
+}
+
+#[derive(Clone, Debug, Default)]
+struct MemoryRegion {
+ range: MemoryRange,
+ mem_type: MemoryType,
+}
+
+impl MemoryRegion {
+ /// True if the instance overlaps with the passed range.
+ pub fn overlaps(&self, range: &MemoryRange) -> bool {
+ let our: &MemoryRange = self.as_ref();
+ max(our.start, range.start) < min(our.end, range.end)
+ }
+
+ /// True if the instance is fully contained within the passed range.
+ pub fn is_within(&self, range: &MemoryRange) -> bool {
+ let our: &MemoryRange = self.as_ref();
+ self.as_ref() == &(max(our.start, range.start)..min(our.end, range.end))
+ }
+}
+
+impl AsRef<MemoryRange> for MemoryRegion {
+ fn as_ref(&self) -> &MemoryRange {
+ &self.range
+ }
+}
+
+/// Tracks non-overlapping slices of main memory.
+pub struct MemoryTracker {
+ regions: ArrayVec<[MemoryRegion; MemoryTracker::CAPACITY]>,
+ total: MemoryRange,
+ page_table: mmu::PageTable,
+}
+
+/// Errors for MemoryTracker operations.
+#[derive(Debug, Clone)]
+pub enum MemoryTrackerError {
+ /// Tried to modify the memory base address.
+ DifferentBaseAddress,
+ /// Tried to shrink to a larger memory size.
+ SizeTooLarge,
+ /// Tracked regions would not fit in memory size.
+ SizeTooSmall,
+ /// Reached limit number of tracked regions.
+ Full,
+ /// Region is out of the tracked memory address space.
+ OutOfRange,
+ /// New region overlaps with tracked regions.
+ Overlaps,
+ /// Region couldn't be mapped.
+ FailedToMap,
+}
+
+impl fmt::Display for MemoryTrackerError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::DifferentBaseAddress => write!(f, "Received different base address"),
+ Self::SizeTooLarge => write!(f, "Tried to shrink to a larger memory size"),
+ Self::SizeTooSmall => write!(f, "Tracked regions would not fit in memory size"),
+ Self::Full => write!(f, "Reached limit number of tracked regions"),
+ Self::OutOfRange => write!(f, "Region is out of the tracked memory address space"),
+ Self::Overlaps => write!(f, "New region overlaps with tracked regions"),
+ Self::FailedToMap => write!(f, "Failed to map the new region"),
+ }
+ }
+}
+
+type Result<T> = result::Result<T, MemoryTrackerError>;
+
+impl MemoryTracker {
+ const CAPACITY: usize = 5;
+ /// Base of the system's contiguous "main" memory.
+ const BASE: usize = 0x8000_0000;
+ /// First address that can't be translated by a level 1 TTBR0_EL1.
+ const MAX_ADDR: usize = 1 << 39;
+
+ /// Create a new instance from an active page table, covering the maximum RAM size.
+ pub fn new(page_table: mmu::PageTable) -> Self {
+ Self { total: Self::BASE..Self::MAX_ADDR, page_table, regions: ArrayVec::new() }
+ }
+
+ /// Resize the total RAM size.
+ ///
+ /// This function fails if it contains regions that are not included within the new size.
+ pub fn shrink(&mut self, range: &MemoryRange) -> Result<()> {
+ if range.start != self.total.start {
+ return Err(MemoryTrackerError::DifferentBaseAddress);
+ }
+ if self.total.end < range.end {
+ return Err(MemoryTrackerError::SizeTooLarge);
+ }
+ if !self.regions.iter().all(|r| r.is_within(range)) {
+ return Err(MemoryTrackerError::SizeTooSmall);
+ }
+
+ self.total = range.clone();
+ Ok(())
+ }
+
+ /// Allocate the address range for a const slice; returns None if failed.
+ pub fn alloc_range(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
+ self.page_table.map_rodata(range).map_err(|e| {
+ error!("Error during range allocation: {e}");
+ MemoryTrackerError::FailedToMap
+ })?;
+ self.add(MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadOnly })
+ }
+
+ /// Allocate the address range for a mutable slice; returns None if failed.
+ pub fn alloc_range_mut(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
+ self.page_table.map_data(range).map_err(|e| {
+ error!("Error during mutable range allocation: {e}");
+ MemoryTrackerError::FailedToMap
+ })?;
+ self.add(MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadWrite })
+ }
+
+ /// Allocate the address range for a const slice; returns None if failed.
+ pub fn alloc(&mut self, base: usize, size: NonZeroUsize) -> Result<MemoryRange> {
+ self.alloc_range(&(base..(base + size.get())))
+ }
+
+ /// Allocate the address range for a mutable slice; returns None if failed.
+ pub fn alloc_mut(&mut self, base: usize, size: NonZeroUsize) -> Result<MemoryRange> {
+ self.alloc_range_mut(&(base..(base + size.get())))
+ }
+
+ fn add(&mut self, region: MemoryRegion) -> Result<MemoryRange> {
+ if !region.is_within(&self.total) {
+ return Err(MemoryTrackerError::OutOfRange);
+ }
+ if self.regions.iter().any(|r| r.overlaps(region.as_ref())) {
+ return Err(MemoryTrackerError::Overlaps);
+ }
+ if self.regions.try_push(region).is_some() {
+ return Err(MemoryTrackerError::Full);
+ }
+
+ Ok(self.regions.last().unwrap().as_ref().clone())
+ }
+}
+
+impl Drop for MemoryTracker {
+ fn drop(&mut self) {
+ for region in self.regions.iter() {
+ match region.mem_type {
+ MemoryType::ReadWrite => {
+ // TODO: Use page table's dirty bit to only flush pages that were touched.
+ helpers::flush_region(region.range.start, region.range.len())
+ }
+ MemoryType::ReadOnly => {}
+ }
+ }
+ }
+}
diff --git a/pvmfw/src/mmu.rs b/pvmfw/src/mmu.rs
new file mode 100644
index 0000000..fa94e85
--- /dev/null
+++ b/pvmfw/src/mmu.rs
@@ -0,0 +1,86 @@
+// Copyright 2022, 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.
+
+//! Memory management.
+
+use crate::helpers;
+use aarch64_paging::idmap::IdMap;
+use aarch64_paging::paging::Attributes;
+use aarch64_paging::paging::MemoryRegion;
+use aarch64_paging::MapError;
+use core::ops::Range;
+use vmbase::layout;
+
+// We assume that:
+// - MAIR_EL1.Attr0 = "Device-nGnRE memory" (0b0000_0100)
+// - MAIR_EL1.Attr1 = "Normal memory, Outer & Inner WB Non-transient, R/W-Allocate" (0b1111_1111)
+const MEMORY: Attributes = Attributes::NORMAL.union(Attributes::NON_GLOBAL);
+const DEVICE: Attributes = Attributes::DEVICE_NGNRE.union(Attributes::EXECUTE_NEVER);
+const CODE: Attributes = MEMORY.union(Attributes::READ_ONLY);
+const DATA: Attributes = MEMORY.union(Attributes::EXECUTE_NEVER);
+const RODATA: Attributes = DATA.union(Attributes::READ_ONLY);
+
+/// High-level API for managing MMU mappings.
+pub struct PageTable {
+ idmap: IdMap,
+}
+
+fn appended_payload_range() -> Range<usize> {
+ let start = helpers::align_up(layout::binary_end(), helpers::SIZE_4KB).unwrap();
+ // pvmfw is contained in a 2MiB region so the payload can't be larger than the 2MiB alignment.
+ let end = helpers::align_up(start, helpers::SIZE_2MB).unwrap();
+
+ start..end
+}
+
+impl PageTable {
+ const ASID: usize = 1;
+ const ROOT_LEVEL: usize = 1;
+
+ /// Creates an instance pre-populated with pvmfw's binary layout.
+ pub fn from_static_layout() -> Result<Self, MapError> {
+ let mut page_table = Self { idmap: IdMap::new(Self::ASID, Self::ROOT_LEVEL) };
+
+ page_table.map_code(&layout::text_range())?;
+ page_table.map_data(&layout::writable_region())?;
+ page_table.map_rodata(&layout::rodata_range())?;
+ page_table.map_data(&appended_payload_range())?;
+
+ Ok(page_table)
+ }
+
+ pub unsafe fn activate(&mut self) {
+ self.idmap.activate()
+ }
+
+ pub fn map_device(&mut self, range: &Range<usize>) -> Result<(), MapError> {
+ self.map_range(range, DEVICE)
+ }
+
+ pub fn map_data(&mut self, range: &Range<usize>) -> Result<(), MapError> {
+ self.map_range(range, DATA)
+ }
+
+ pub fn map_code(&mut self, range: &Range<usize>) -> Result<(), MapError> {
+ self.map_range(range, CODE)
+ }
+
+ pub fn map_rodata(&mut self, range: &Range<usize>) -> Result<(), MapError> {
+ self.map_range(range, RODATA)
+ }
+
+ fn map_range(&mut self, range: &Range<usize>, attr: Attributes) -> Result<(), MapError> {
+ self.idmap.map_range(&MemoryRegion::new(range.start, range.end), attr)
+ }
+}
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index e5aa908..8816dbd 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -29,6 +29,7 @@
import com.android.microdroid.test.common.DeviceProperties;
import com.android.microdroid.test.common.MetricsProcessor;
import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.TestDevice;
@@ -41,12 +42,15 @@
import java.util.Arrays;
public abstract class MicrodroidHostTestCaseBase extends BaseHostJUnit4Test {
+
protected static final String TEST_ROOT = "/data/local/tmp/virt/";
protected static final String LOG_PATH = TEST_ROOT + "log.txt";
protected static final String CONSOLE_PATH = TEST_ROOT + "console.txt";
private static final int TEST_VM_ADB_PORT = 8000;
private static final String MICRODROID_SERIAL = "localhost:" + TEST_VM_ADB_PORT;
private static final String INSTANCE_IMG = "instance.img";
+ private static final String PVMFW_IMG_PATH = TEST_ROOT + "pvmfw.img";
+ private static final String PVMFW_IMG_PATH_PROP = "hypervisor.pvmfw.path";
private static final long MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES = 5;
protected static final long MICRODROID_COMMAND_TIMEOUT_MILLIS = 30000;
@@ -55,6 +59,19 @@
(int) (MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES * 60 * 1000
/ MICRODROID_COMMAND_RETRY_INTERVAL_MILLIS);
+ @Option(
+ name = "pvmfw",
+ description =
+ "Custom pvmfw.img path on host device."
+ + " If present, it will be pushed to "
+ + PVMFW_IMG_PATH,
+ mandatory = false)
+ private static String sCustomPvmfwPathOnHost = "";
+
+ private static boolean isEmptyText(String str) {
+ return str == null || str.length() == 0;
+ }
+
public static void prepareVirtualizationTestSetup(ITestDevice androidDevice)
throws DeviceNotAvailableException {
CommandRunner android = new CommandRunner(androidDevice);
@@ -67,6 +84,13 @@
// remove any leftover files under test root
android.tryRun("rm", "-rf", TEST_ROOT + "*");
+
+ // prepare custom pvmfw.img if necessary
+ if (!isEmptyText(sCustomPvmfwPathOnHost)) {
+ runOnHost("adb", "root");
+ runOnHost("adb", "push", sCustomPvmfwPathOnHost, PVMFW_IMG_PATH);
+ runOnHost("adb", "shell", "setprop", PVMFW_IMG_PATH_PROP, PVMFW_IMG_PATH);
+ }
}
public static void cleanUpVirtualizationTestSetup(ITestDevice androidDevice)
@@ -80,6 +104,10 @@
android.tryRun("killall", "crosvm");
android.tryRun("stop", "virtualizationservice");
android.tryRun("rm", "-rf", "/data/misc/virtualizationservice/*");
+
+ if (!isEmptyText(sCustomPvmfwPathOnHost)) {
+ runOnHost("adb", "shell", "setprop", PVMFW_IMG_PATH_PROP, "\"\"");
+ }
}
protected boolean isCuttlefish() {
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index fc579d8..679f6e7 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -498,6 +498,7 @@
})
public void sameInstanceKeepsSameCdis() throws Exception {
assumeSupportedKernel();
+ assume().withMessage("Skip on CF. Too Slow. b/257270529").that(isCuttlefish()).isFalse();
grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
VirtualMachineConfig normalConfig =
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 85a57c9..13e5c70 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -67,6 +67,8 @@
const MILLIS_PER_SEC: i64 = 1000;
+const SYSPROP_CUSTOM_PVMFW_PATH: &str = "hypervisor.pvmfw.path";
+
lazy_static! {
/// If the VM doesn't move to the Started state within this amount time, a hang-up error is
/// triggered.
@@ -601,7 +603,12 @@
}
if config.protected {
- command.arg("--protected-vm");
+ match system_properties::read(SYSPROP_CUSTOM_PVMFW_PATH)? {
+ Some(pvmfw_path) if !pvmfw_path.is_empty() => {
+ command.arg("--protected-vm-with-firmware").arg(pvmfw_path)
+ }
+ _ => command.arg("--protected-vm"),
+ };
// 3 virtio-console devices + vsock = 4.
let virtio_pci_device_count = 4 + config.disks.len();
diff --git a/vm/Android.bp b/vm/Android.bp
index 7b016d4..95ef082 100644
--- a/vm/Android.bp
+++ b/vm/Android.bp
@@ -17,6 +17,7 @@
"liblibc",
"liblog_rust",
"libmicrodroid_payload_config",
+ "librand",
"librustutils",
"libserde_json",
"libserde",
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 89d56d4..b5046fb 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -27,7 +27,7 @@
use clap::Parser;
use create_idsig::command_create_idsig;
use create_partition::command_create_partition;
-use run::{command_run, command_run_app};
+use run::{command_run, command_run_app, command_run_microdroid};
use rustutils::system_properties;
use std::path::{Path, PathBuf};
@@ -110,6 +110,65 @@
#[clap(long = "extra-idsig")]
extra_idsigs: Vec<PathBuf>,
},
+ /// Run a virtual machine with Microdroid inside
+ RunMicrodroid {
+ /// Path to the directory where VM-related files (e.g. instance.img, apk.idsig, etc.) will
+ /// be stored. If not specified a random directory under /data/local/tmp/microdroid will be
+ /// created and used.
+ #[clap(long)]
+ work_dir: Option<PathBuf>,
+
+ /// Name of VM
+ #[clap(long)]
+ name: Option<String>,
+
+ /// Detach VM from the terminal and run in the background
+ #[clap(short, long)]
+ daemonize: bool,
+
+ /// Path to the file backing the storage.
+ /// Created if the option is used but the path does not exist in the device.
+ #[clap(long)]
+ storage: Option<PathBuf>,
+
+ /// Size of the storage. Used only if --storage is supplied but path does not exist
+ /// Default size is 10*1024*1024
+ #[clap(long)]
+ storage_size: Option<u64>,
+
+ /// Path to file for VM console output.
+ #[clap(long)]
+ console: Option<PathBuf>,
+
+ /// Path to file for VM log output.
+ #[clap(long)]
+ log: Option<PathBuf>,
+
+ /// Path to file where ramdump is recorded on kernel panic
+ #[clap(long)]
+ ramdump: Option<PathBuf>,
+
+ /// Debug level of the VM. Supported values: "none" (default), "app_only", and "full".
+ #[clap(long, default_value = "none", value_parser = parse_debug_level)]
+ debug: DebugLevel,
+
+ /// Run VM in protected mode.
+ #[clap(short, long)]
+ protected: bool,
+
+ /// Memory size (in MiB) of the VM. If unspecified, defaults to the value of `memory_mib`
+ /// in the VM config file.
+ #[clap(short, long)]
+ mem: Option<u32>,
+
+ /// Number of vCPUs in the VM. If unspecified, defaults to 1.
+ #[clap(long)]
+ cpus: Option<u32>,
+
+ /// Comma separated list of task profile names to apply to the VM
+ #[clap(long)]
+ task_profiles: Vec<String>,
+ },
/// Run a virtual machine
Run {
/// Path to VM config JSON
@@ -238,6 +297,36 @@
task_profiles,
&extra_idsigs,
),
+ Opt::RunMicrodroid {
+ name,
+ work_dir,
+ storage,
+ storage_size,
+ daemonize,
+ console,
+ log,
+ ramdump,
+ debug,
+ protected,
+ mem,
+ cpus,
+ task_profiles,
+ } => command_run_microdroid(
+ name,
+ service.as_ref(),
+ work_dir,
+ storage.as_deref(),
+ storage_size,
+ daemonize,
+ console.as_deref(),
+ log.as_deref(),
+ ramdump.as_deref(),
+ debug,
+ protected,
+ mem,
+ cpus,
+ task_profiles,
+ ),
Opt::Run { name, config, daemonize, cpus, task_profiles, console, log } => {
command_run(
name,
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 01b916b..3f25bba 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -23,13 +23,16 @@
VirtualMachinePayloadConfig::VirtualMachinePayloadConfig,
VirtualMachineState::VirtualMachineState,
};
-use anyhow::{bail, Context, Error};
+use anyhow::{anyhow, bail, Context, Error};
use binder::ParcelFileDescriptor;
use microdroid_payload_config::VmPayloadConfig;
+use rand::{distributions::Alphanumeric, Rng};
+use std::fs;
use std::fs::File;
use std::io;
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::path::{Path, PathBuf};
+use std::process::Command;
use vmclient::{ErrorCode, VmInstance};
use vmconfig::{open_parcel_file, VmConfig};
use zip::ZipArchive;
@@ -144,6 +147,83 @@
run(service, &config, &payload_config_str, daemonize, console_path, log_path, ramdump_path)
}
+const EMPTY_PAYLOAD_APK: &str = "com.android.microdroid.empty_payload";
+
+fn find_empty_payload_apk_path() -> Result<PathBuf, Error> {
+ let output = Command::new("/system/bin/pm")
+ .arg("path")
+ .arg(EMPTY_PAYLOAD_APK)
+ .output()
+ .context("failed to execute pm path")?;
+ let output_str = String::from_utf8(output.stdout).context("failed to parse output")?;
+ match output_str.strip_prefix("package:") {
+ None => Err(anyhow!("Unexpected output {}", output_str)),
+ Some(apk_path) => Ok(PathBuf::from(apk_path.trim())),
+ }
+}
+
+fn create_work_dir() -> Result<PathBuf, Error> {
+ let s: String =
+ rand::thread_rng().sample_iter(&Alphanumeric).take(17).map(char::from).collect();
+ let work_dir = PathBuf::from("/data/local/tmp/microdroid").join(s);
+ println!("creating work dir {}", work_dir.display());
+ fs::create_dir_all(&work_dir).context("failed to mkdir")?;
+ Ok(work_dir)
+}
+
+/// Run a VM with Microdroid
+#[allow(clippy::too_many_arguments)]
+pub fn command_run_microdroid(
+ name: Option<String>,
+ service: &dyn IVirtualizationService,
+ work_dir: Option<PathBuf>,
+ storage: Option<&Path>,
+ storage_size: Option<u64>,
+ daemonize: bool,
+ console_path: Option<&Path>,
+ log_path: Option<&Path>,
+ ramdump_path: Option<&Path>,
+ debug_level: DebugLevel,
+ protected: bool,
+ mem: Option<u32>,
+ cpus: Option<u32>,
+ task_profiles: Vec<String>,
+) -> Result<(), Error> {
+ let apk = find_empty_payload_apk_path()
+ .context(anyhow!("failed to find path for {} apk", EMPTY_PAYLOAD_APK))?;
+ println!("found path for {} apk: {}", EMPTY_PAYLOAD_APK, apk.display());
+
+ let work_dir = work_dir.unwrap_or(create_work_dir()?);
+ let idsig = work_dir.join("apk.idsig");
+ println!("apk.idsig path: {}", idsig.display());
+ let instance_img = work_dir.join("instance.img");
+ println!("instance.img path: {}", instance_img.display());
+
+ let payload_path = "MicrodroidEmptyPayloadJniLib.so";
+ let extra_sig = [];
+ command_run_app(
+ name,
+ service,
+ &apk,
+ &idsig,
+ &instance_img,
+ storage,
+ storage_size,
+ /* config_path= */ None,
+ Some(payload_path.to_owned()),
+ daemonize,
+ console_path,
+ log_path,
+ ramdump_path,
+ debug_level,
+ protected,
+ mem,
+ cpus,
+ task_profiles,
+ &extra_sig,
+ )
+}
+
/// Run a VM from the given configuration file.
#[allow(clippy::too_many_arguments)]
pub fn command_run(
diff --git a/vmbase/example/idmap.S b/vmbase/example/idmap.S
index 7fc5d5e..71a6ade 100644
--- a/vmbase/example/idmap.S
+++ b/vmbase/example/idmap.S
@@ -44,7 +44,7 @@
.fill 509, 8, 0x0 // 509 GiB of remaining VA space
/* level 2 */
-0: .quad .L_BLOCK_RO | 0x80000000 // DT provided by VMM
+0: .quad .L_BLOCK_MEM | 0x80000000 // DT provided by VMM
.quad .L_BLOCK_MEM_XIP | 0x80200000 // 2 MiB of DRAM containing image
.quad .L_BLOCK_MEM | 0x80400000 // 2 MiB of writable DRAM
.fill 509, 8, 0x0
diff --git a/vmbase/example/src/main.rs b/vmbase/example/src/main.rs
index dcff6e1..bb64651 100644
--- a/vmbase/example/src/main.rs
+++ b/vmbase/example/src/main.rs
@@ -151,17 +151,41 @@
let reader = Fdt::from_slice(fdt).unwrap();
info!("FDT passed verification.");
- for reg in reader.memory().unwrap() {
+ for reg in reader.memory().unwrap().unwrap() {
info!("memory @ {reg:#x?}");
}
let compatible = CStr::from_bytes_with_nul(b"ns16550a\0").unwrap();
for c in reader.compatible_nodes(compatible).unwrap() {
- let reg = c.reg().unwrap().next().unwrap();
+ let reg = c.reg().unwrap().unwrap().next().unwrap();
info!("node compatible with '{}' at {reg:?}", compatible.to_str().unwrap());
}
+ let writer = Fdt::from_mut_slice(fdt).unwrap();
+ writer.unpack().unwrap();
+ info!("FDT successfully unpacked.");
+
+ let path = CStr::from_bytes_with_nul(b"/memory\0").unwrap();
+ let mut node = writer.node_mut(path).unwrap().unwrap();
+ let name = CStr::from_bytes_with_nul(b"child\0").unwrap();
+ let mut child = node.add_subnode(name).unwrap();
+ info!("Created subnode '{}/{}'.", path.to_str().unwrap(), name.to_str().unwrap());
+
+ let name = CStr::from_bytes_with_nul(b"str-property\0").unwrap();
+ child.appendprop(name, b"property-value\0").unwrap();
+ info!("Appended property '{}'.", name.to_str().unwrap());
+
+ let name = CStr::from_bytes_with_nul(b"pair-property\0").unwrap();
+ let addr = 0x0123_4567u64;
+ let size = 0x89ab_cdefu64;
+ child.appendprop_addrrange(name, addr, size).unwrap();
+ info!("Appended property '{}'.", name.to_str().unwrap());
+
+ let writer = child.fdt();
+ writer.pack().unwrap();
+ info!("FDT successfully packed.");
+
info!("FDT checks done.");
}