Merge "avb: move Rust bindgen to libavb" into main
diff --git a/OWNERS b/OWNERS
index 310add7..e560cec 100644
--- a/OWNERS
+++ b/OWNERS
@@ -12,16 +12,19 @@
# Other owners
alanstokes@google.com
aliceywang@google.com
-ardb@google.com
-ascull@google.com
inseob@google.com
+jaewan@google.com
+jakobvukalovic@google.com
jeffv@google.com
jooyung@google.com
-mzyngier@google.com
+keirf@google.com
ptosi@google.com
qperret@google.com
qwandor@google.com
-serbanc@google.com
+sebastianene@google.com
+seungjaeyoo@google.com
shikhapanwar@google.com
+smostafa@google.com
tabba@google.com
+vdonnefort@google.com
victorhsieh@google.com
diff --git a/apex/Android.bp b/apex/Android.bp
index 765372a..ccbdb3b 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -103,7 +103,6 @@
"microdroid_initrd_normal",
"microdroid.json",
"microdroid_kernel",
- // rialto_bin is a prebuilt target wrapping the signed bare-metal service VM.
"rialto_bin",
],
host_required: [
diff --git a/apex/virtualizationservice.rc b/apex/virtualizationservice.rc
index be90904..8283594 100644
--- a/apex/virtualizationservice.rc
+++ b/apex/virtualizationservice.rc
@@ -22,7 +22,7 @@
service vfio_handler /apex/com.android.virt/bin/vfio_handler
user root
- group root
+ group system
interface aidl android.system.virtualizationservice_internal.IVfioHandler
disabled
oneshot
diff --git a/compos/common/binder.rs b/compos/common/binder.rs
index d3550f7..aea0072 100644
--- a/compos/common/binder.rs
+++ b/compos/common/binder.rs
@@ -16,7 +16,7 @@
//! Helper for converting Error types to what Binder expects
-use binder::{Result as BinderResult, Status};
+use binder::{IntoBinderResult, Result as BinderResult};
use log::warn;
use std::fmt::Debug;
@@ -24,9 +24,9 @@
/// preserving the content as far as possible.
/// Also log the error if there is one.
pub fn to_binder_result<T, E: Debug>(result: Result<T, E>) -> BinderResult<T> {
- result.map_err(|e| {
+ result.or_service_specific_exception_with(-1, |e| {
let message = format!("{:?}", e);
- warn!("Returning binder error: {}", &message);
- Status::new_service_specific_error_str(-1, Some(message))
+ warn!("Returning binder error: {message}");
+ message
})
}
diff --git a/compos/src/artifact_signer.rs b/compos/src/artifact_signer.rs
index 908e438..76da00a 100644
--- a/compos/src/artifact_signer.rs
+++ b/compos/src/artifact_signer.rs
@@ -63,7 +63,7 @@
/// with accompanying sigature file.
pub fn write_info_and_signature(self, info_path: &Path) -> Result<()> {
let mut info = OdsignInfo::new();
- info.file_hashes.extend(self.file_digests.into_iter());
+ info.file_hashes.extend(self.file_digests);
let bytes = info.write_to_bytes()?;
let signature = compos_key::sign(&bytes)?;
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 8febd52..fe83ba2 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -33,7 +33,9 @@
use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::{
IAuthFsService, AUTHFS_SERVICE_SOCKET_NAME,
};
-use binder::{BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, Strong};
+use binder::{
+ BinderFeatures, ExceptionCode, Interface, IntoBinderResult, Result as BinderResult, Strong,
+};
use compos_aidl_interface::aidl::com::android::compos::ICompOsService::{
BnCompOsService, ICompOsService, OdrefreshArgs::OdrefreshArgs,
};
@@ -66,29 +68,23 @@
fn initializeSystemProperties(&self, names: &[String], values: &[String]) -> BinderResult<()> {
let mut initialized = self.initialized.write().unwrap();
if initialized.is_some() {
- return Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_STATE,
- Some(format!("Already initialized: {:?}", initialized)),
- ));
+ return Err(format!("Already initialized: {initialized:?}"))
+ .or_binder_exception(ExceptionCode::ILLEGAL_STATE);
}
*initialized = Some(false);
if names.len() != values.len() {
- return Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_ARGUMENT,
- Some(format!(
- "Received inconsistent number of keys ({}) and values ({})",
- names.len(),
- values.len()
- )),
- ));
+ return Err(format!(
+ "Received inconsistent number of keys ({}) and values ({})",
+ names.len(),
+ values.len()
+ ))
+ .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
}
for (name, value) in zip(names, values) {
if !is_system_property_interesting(name) {
- return Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_ARGUMENT,
- Some(format!("Received invalid system property {}", &name)),
- ));
+ return Err(format!("Received invalid system property {name}"))
+ .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
}
let result = system_properties::write(name, value);
if result.is_err() {
@@ -103,10 +99,8 @@
fn odrefresh(&self, args: &OdrefreshArgs) -> BinderResult<i8> {
let initialized = *self.initialized.read().unwrap();
if !initialized.unwrap_or(false) {
- return Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_STATE,
- Some("Service has not been initialized"),
- ));
+ return Err("Service has not been initialized")
+ .or_binder_exception(ExceptionCode::ILLEGAL_STATE);
}
to_binder_result(self.do_odrefresh(args))
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 244d34e..4851321 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -192,7 +192,7 @@
.runTimedCmd(
10000,
validator.getAbsolutePath(),
- "verify-dice-chain",
+ "dice-chain",
bcc_file.getAbsolutePath());
assertWithMessage("hwtrust failed").about(command_results()).that(result).isSuccess();
}
diff --git a/docs/getting_started.md b/docs/getting_started.md
index d970c12..74f2012 100644
--- a/docs/getting_started.md
+++ b/docs/getting_started.md
@@ -99,7 +99,7 @@
payload using the following command:
```shell
-package/modules/Virtualization/vm/vm_shell.sh start-microdroid --auto-connect -- --protected
+packages/modules/Virtualization/vm/vm_shell.sh start-microdroid --auto-connect -- --protected
```
You will see the log messages like the below.
diff --git a/encryptedstore/Android.bp b/encryptedstore/Android.bp
index 94ebcfc..8ba5016 100644
--- a/encryptedstore/Android.bp
+++ b/encryptedstore/Android.bp
@@ -14,6 +14,7 @@
"libclap",
"libhex",
"liblog_rust",
+ "libmicrodroid_uids",
"libnix",
"libdm_rust",
],
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
index 1a16f49..2a698ea 100644
--- a/encryptedstore/src/main.rs
+++ b/encryptedstore/src/main.rs
@@ -21,24 +21,32 @@
use anyhow::{ensure, Context, Result};
use clap::arg;
use dm::{crypt::CipherType, util};
-use log::info;
+use log::{error, info};
use std::ffi::CString;
use std::fs::{create_dir_all, OpenOptions};
use std::io::{Error, Read, Write};
use std::os::unix::ffi::OsStrExt;
-use std::os::unix::fs::FileTypeExt;
+use std::os::unix::fs::{FileTypeExt, PermissionsExt};
use std::path::{Path, PathBuf};
use std::process::Command;
const MK2FS_BIN: &str = "/system/bin/mke2fs";
const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
-fn main() -> Result<()> {
+fn main() {
android_logger::init_once(
android_logger::Config::default()
.with_tag("encryptedstore")
.with_min_level(log::Level::Info),
);
+
+ if let Err(e) = try_main() {
+ error!("{:?}", e);
+ std::process::exit(1)
+ }
+}
+
+fn try_main() -> Result<()> {
info!("Starting encryptedstore binary");
let matches = clap_command().get_matches();
@@ -47,10 +55,12 @@
let key = matches.get_one::<String>("key").unwrap();
let mountpoint = Path::new(matches.get_one::<String>("mountpoint").unwrap());
// Note this error context is used in MicrodroidTests.
- encryptedstore_init(blkdevice, key, mountpoint).context(format!(
- "Unable to initialize encryptedstore on {:?} & mount at {:?}",
- blkdevice, mountpoint
- ))?;
+ encryptedstore_init(blkdevice, key, mountpoint).with_context(|| {
+ format!(
+ "Unable to initialize encryptedstore on {:?} & mount at {:?}",
+ blkdevice, mountpoint
+ )
+ })?;
Ok(())
}
@@ -65,7 +75,7 @@
fn encryptedstore_init(blkdevice: &Path, key: &str, mountpoint: &Path) -> Result<()> {
ensure!(
std::fs::metadata(blkdevice)
- .context(format!("Failed to get metadata of {:?}", blkdevice))?
+ .with_context(|| format!("Failed to get metadata of {:?}", blkdevice))?
.file_type()
.is_block_device(),
"The path:{:?} is not of a block device",
@@ -82,7 +92,12 @@
info!("Freshly formatting the crypt device");
format_ext4(&crypt_device)?;
}
- mount(&crypt_device, mountpoint).context(format!("Unable to mount {:?}", crypt_device))?;
+ mount(&crypt_device, mountpoint)
+ .with_context(|| format!("Unable to mount {:?}", crypt_device))?;
+ if needs_formatting {
+ std::fs::set_permissions(mountpoint, PermissionsExt::from_mode(0o770))
+ .context("Failed to chmod root directory")?;
+ }
Ok(())
}
@@ -124,6 +139,11 @@
}
fn format_ext4(device: &Path) -> Result<()> {
+ let root_dir_uid_gid = format!(
+ "root_owner={}:{}",
+ microdroid_uids::ROOT_UID,
+ microdroid_uids::MICRODROID_PAYLOAD_GID
+ );
let mkfs_options = [
"-j", // Create appropriate sized journal
/* metadata_csum: enabled for filesystem integrity
@@ -131,20 +151,22 @@
* 64bit: larger fields afforded by this feature enable full-strength checksumming.
*/
"-O metadata_csum, extents, 64bit",
- "-b 4096", // block size in the filesystem
+ "-b 4096", // block size in the filesystem,
+ "-E",
+ &root_dir_uid_gid,
];
let mut cmd = Command::new(MK2FS_BIN);
let status = cmd
.args(mkfs_options)
.arg(device)
.status()
- .context(format!("failed to execute {}", MK2FS_BIN))?;
+ .with_context(|| format!("failed to execute {}", MK2FS_BIN))?;
ensure!(status.success(), "mkfs failed with {:?}", status);
Ok(())
}
fn mount(source: &Path, mountpoint: &Path) -> Result<()> {
- create_dir_all(mountpoint).context(format!("Failed to create {:?}", &mountpoint))?;
+ create_dir_all(mountpoint).with_context(|| format!("Failed to create {:?}", &mountpoint))?;
let mount_options = CString::new(
"fscontext=u:object_r:encryptedstore_fs:s0,context=u:object_r:encryptedstore_file:s0",
)
diff --git a/libs/avflog/Android.bp b/libs/avflog/Android.bp
new file mode 100644
index 0000000..1ddfc7a
--- /dev/null
+++ b/libs/avflog/Android.bp
@@ -0,0 +1,30 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "libavflog.defaults",
+ crate_name: "avflog",
+ host_supported: true,
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ rustlibs: [
+ "liblog_rust",
+ ],
+}
+
+rust_library {
+ name: "libavflog",
+ defaults: ["libavflog.defaults"],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+}
+
+rust_test {
+ name: "libavflog.test",
+ defaults: ["libavflog.defaults"],
+ prefer_rlib: true,
+ test_suites: ["general-tests"],
+}
diff --git a/libs/avflog/TEST_MAPPING b/libs/avflog/TEST_MAPPING
new file mode 100644
index 0000000..921c4d8
--- /dev/null
+++ b/libs/avflog/TEST_MAPPING
@@ -0,0 +1,9 @@
+// When adding or removing tests here, don't forget to amend _all_modules list in
+// wireless/android/busytown/ath_config/configs/prod/avf/tests.gcl
+{
+ "avf-presubmit" : [
+ {
+ "name" : "libavflog.test"
+ }
+ ]
+}
diff --git a/libs/avflog/src/lib.rs b/libs/avflog/src/lib.rs
new file mode 100644
index 0000000..27c8628
--- /dev/null
+++ b/libs/avflog/src/lib.rs
@@ -0,0 +1,71 @@
+// Copyright 2023, 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.
+
+//! Provides random utilities for components in AVF
+
+use log::error;
+use std::fmt::Debug;
+
+/// Convenient trait for logging an error while returning it
+pub trait LogResult<T, E> {
+ /// If this is `Err`, the error is debug-formatted and is logged via `error!`.
+ fn with_log(self) -> Result<T, E>;
+}
+
+impl<T, E: Debug> LogResult<T, E> for Result<T, E> {
+ fn with_log(self) -> Result<T, E> {
+ self.map_err(|e| {
+ error!("{e:?}");
+ e
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use log::{LevelFilter, Log, Metadata, Record};
+ use std::cell::RefCell;
+ use std::io::{Error, ErrorKind};
+
+ struct TestLogger {
+ last_log: RefCell<String>,
+ }
+ static TEST_LOGGER: TestLogger = TestLogger { last_log: RefCell::new(String::new()) };
+
+ // SAFETY: TestLogger is used only inside the test which is single-treaded.
+ unsafe impl Sync for TestLogger {}
+
+ impl Log for TestLogger {
+ fn enabled(&self, _metadata: &Metadata) -> bool {
+ true
+ }
+ fn log(&self, record: &Record) {
+ *self.last_log.borrow_mut() = format!("{}", record.args());
+ }
+ fn flush(&self) {}
+ }
+
+ #[test]
+ fn test_logresult_emits_error_log() {
+ log::set_logger(&TEST_LOGGER).unwrap();
+ log::set_max_level(LevelFilter::Info);
+
+ let e = Error::from(ErrorKind::NotFound);
+ let res: Result<(), Error> = Err(e).with_log();
+
+ assert!(res.is_err());
+ assert_eq!(TEST_LOGGER.last_log.borrow().as_str(), "Kind(NotFound)");
+ }
+}
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index afc36d0..a305e03 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -16,8 +16,6 @@
//! to a bare-metal environment.
#![no_std]
-#![deny(unsafe_op_in_unsafe_fn)]
-#![deny(clippy::undocumented_unsafe_blocks)]
mod iterators;
diff --git a/libs/microdroid_uids/Android.bp b/libs/microdroid_uids/Android.bp
new file mode 100644
index 0000000..497948d
--- /dev/null
+++ b/libs/microdroid_uids/Android.bp
@@ -0,0 +1,15 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library {
+ name: "libmicrodroid_uids",
+ crate_name: "microdroid_uids",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ // TODO(b/296393106): Figure out how/when to enable this
+ // cfgs: ["payload_not_root"],
+ apex_available: [
+ "com.android.virt",
+ ],
+}
diff --git a/libs/microdroid_uids/src/lib.rs b/libs/microdroid_uids/src/lib.rs
new file mode 100644
index 0000000..1f09c65
--- /dev/null
+++ b/libs/microdroid_uids/src/lib.rs
@@ -0,0 +1,24 @@
+// Copyright 2023 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.
+
+//! User and group IDs within Microdroid
+
+/// Always the user ID of Root.
+pub const ROOT_UID: u32 = 0;
+
+/// Group ID shared by all payload users.
+pub const MICRODROID_PAYLOAD_GID: u32 = if cfg!(payload_not_root) { 6000 } else { 0 };
+
+/// User ID for the initial payload user.
+pub const MICRODROID_PAYLOAD_UID: u32 = if cfg!(payload_not_root) { 6000 } else { 0 };
diff --git a/libs/service_vm_comm/Android.bp b/libs/service_vm_comm/Android.bp
new file mode 100644
index 0000000..18397c5
--- /dev/null
+++ b/libs/service_vm_comm/Android.bp
@@ -0,0 +1,36 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "libservice_vm_comm_defaults",
+ crate_name: "service_vm_comm",
+ srcs: ["src/lib.rs"],
+ prefer_rlib: true,
+ apex_available: [
+ "com.android.virt",
+ ],
+}
+
+rust_library_rlib {
+ name: "libservice_vm_comm_nostd",
+ defaults: ["libservice_vm_comm_defaults"],
+ no_stdlibs: true,
+ stdlibs: [
+ "libcore.rust_sysroot",
+ ],
+ rustlibs: [
+ "libserde_nostd",
+ ],
+}
+
+rust_library {
+ name: "libservice_vm_comm",
+ defaults: ["libservice_vm_comm_defaults"],
+ rustlibs: [
+ "libserde",
+ ],
+ features: [
+ "std",
+ ],
+}
diff --git a/libs/service_vm_comm/src/lib.rs b/libs/service_vm_comm/src/lib.rs
new file mode 100644
index 0000000..c3d3ed5
--- /dev/null
+++ b/libs/service_vm_comm/src/lib.rs
@@ -0,0 +1,24 @@
+// Copyright 2023, 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.
+
+//! This library contains the communication protocol used between the host
+//! and the service VM.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+extern crate alloc;
+
+mod message;
+
+pub use message::{Request, Response};
diff --git a/libs/service_vm_comm/src/message.rs b/libs/service_vm_comm/src/message.rs
new file mode 100644
index 0000000..ebbefcb
--- /dev/null
+++ b/libs/service_vm_comm/src/message.rs
@@ -0,0 +1,39 @@
+// Copyright 2023, 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.
+
+//! This module contains the requests and responses definitions exchanged
+//! between the host and the service VM.
+
+use alloc::vec::Vec;
+
+use serde::{Deserialize, Serialize};
+
+/// Represents a request to be sent to the service VM.
+///
+/// Each request has a corresponding response item.
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub enum Request {
+ /// Reverse the order of the bytes in the provided byte array.
+ /// Currently this is only used for testing.
+ Reverse(Vec<u8>),
+}
+
+/// Represents a response to a request sent to the service VM.
+///
+/// Each response corresponds to a specific request.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum Response {
+ /// Reverse the order of the bytes in the provided byte array.
+ Reverse(Vec<u8>),
+}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 2d3f084..1e594b7 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -54,6 +54,8 @@
deps: [
"init_second_stage.microdroid",
"microdroid_build_prop",
+ "microdroid_etc_passwd",
+ "microdroid_etc_group",
"microdroid_init_debug_policy",
"microdroid_init_rc",
"microdroid_ueventd_rc",
@@ -156,6 +158,20 @@
installable: false, // avoid collision with system partition's ueventd.rc
}
+prebuilt_etc {
+ name: "microdroid_etc_passwd",
+ src: "microdroid_passwd",
+ filename: "passwd",
+ installable: false,
+}
+
+prebuilt_etc {
+ name: "microdroid_etc_group",
+ src: "microdroid_group",
+ filename: "group",
+ installable: false,
+}
+
prebuilt_root {
name: "microdroid_build_prop",
filename: "build.prop",
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 42033d6..c257cdb 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -12,6 +12,11 @@
# Cgroups are mounted right before early-init using list from /etc/cgroups.json
on early-init
+ # Android doesn't need kernel module autoloading, and it causes SELinux
+ # denials. So disable it by setting modprobe to the empty string. Note: to
+ # explicitly set a sysctl to an empty string, a trailing newline is needed.
+ write /proc/sys/kernel/modprobe \n
+
# set RLIMIT_NICE to allow priorities from 19 to -20
setrlimit nice 40 40
@@ -28,6 +33,10 @@
on init
mkdir /mnt/apk 0755 system system
mkdir /mnt/extra-apk 0755 root root
+
+ # Allow the payload access to the console (default is 0600)
+ chmod 0666 /dev/console
+
# Microdroid_manager starts apkdmverity/zipfuse/apexd
start microdroid_manager
diff --git a/microdroid/microdroid_group b/microdroid/microdroid_group
new file mode 100644
index 0000000..4eb8fa5
--- /dev/null
+++ b/microdroid/microdroid_group
@@ -0,0 +1 @@
+system_payload::6000:
diff --git a/microdroid/microdroid_passwd b/microdroid/microdroid_passwd
new file mode 100644
index 0000000..bd15182
--- /dev/null
+++ b/microdroid/microdroid_passwd
@@ -0,0 +1 @@
+system_payload_0::6000:6000:::
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index d854d54..fe0cf6a 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -15,6 +15,7 @@
"android.system.virtualization.payload-rust",
"libandroid_logger",
"libanyhow",
+ "libavflog",
"libapexutil_rust",
"libapkverify",
"libbinder_rs",
@@ -31,6 +32,7 @@
"liblog_rust",
"libmicrodroid_metadata",
"libmicrodroid_payload_config",
+ "libmicrodroid_uids",
"libnix",
"libonce_cell",
"libopenssl",
diff --git a/microdroid_manager/microdroid_manager.rc b/microdroid_manager/microdroid_manager.rc
index e257547..da38564 100644
--- a/microdroid_manager/microdroid_manager.rc
+++ b/microdroid_manager/microdroid_manager.rc
@@ -8,8 +8,8 @@
# TODO(jooyung) remove this when microdroid_manager becomes a daemon
oneshot
# CAP_SYS_BOOT is required to exec kexecload from microdroid_manager
- # CAP_SETCAP is required to allow microdroid_manager to drop capabilities
+ # CAP_SETPCAP is required to allow microdroid_manager to drop capabilities
# before executing the payload
- capabilities AUDIT_CONTROL SYS_ADMIN SYS_BOOT SETPCAP
+ capabilities AUDIT_CONTROL SYS_ADMIN SYS_BOOT SETPCAP SETUID SETGID
user root
socket vm_payload_service stream 0666 system system
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index 8e078ea..27ec7a5 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -164,11 +164,11 @@
/// https://cs.android.com/android/platform/superproject/+/master:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/ProtectedData.aidl
/// {
/// -70002: "Microdroid payload",
-/// ? -71000: tstr // payload_config_path
+/// ? -71000: tstr ; payload_config_path
/// ? -71001: PayloadConfig
/// }
/// PayloadConfig = {
-/// 1: tstr // payload_binary_name
+/// 1: tstr ; payload_binary_name
/// }
pub fn format_payload_config_descriptor(payload: &PayloadMetadata) -> Result<Vec<u8>> {
const MICRODROID_PAYLOAD_COMPONENT_NAME: &str = "Microdroid payload";
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 319d2df..a48d540 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -528,8 +528,6 @@
}
impl Zipfuse {
- const MICRODROID_PAYLOAD_UID: u32 = 0; // TODO(b/264861173) should be non-root
- const MICRODROID_PAYLOAD_GID: u32 = 0; // TODO(b/264861173) should be non-root
fn mount(
&mut self,
noexec: MountForExec,
@@ -542,9 +540,13 @@
if let MountForExec::Disallowed = noexec {
cmd.arg("--noexec");
}
+ // Let root own the files in APK, so we can access them, but set the group to
+ // allow all payloads to have access too.
+ let (uid, gid) = (microdroid_uids::ROOT_UID, microdroid_uids::MICRODROID_PAYLOAD_GID);
+
cmd.args(["-p", &ready_prop, "-o", option]);
- cmd.args(["-u", &Self::MICRODROID_PAYLOAD_UID.to_string()]);
- cmd.args(["-g", &Self::MICRODROID_PAYLOAD_GID.to_string()]);
+ cmd.args(["-u", &uid.to_string()]);
+ cmd.args(["-g", &gid.to_string()]);
cmd.arg(zip_path).arg(mount_dir);
self.ready_properties.push(ready_prop);
cmd.spawn().with_context(|| format!("Failed to run zipfuse for {mount_dir:?}"))
@@ -850,10 +852,15 @@
fn exec_task(task: &Task, service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
info!("executing main task {:?}...", task);
let mut command = match task.type_ {
- TaskType::Executable => Command::new(&task.command),
+ TaskType::Executable => {
+ // TODO(b/296393106): Run system payloads as non-root.
+ Command::new(&task.command)
+ }
TaskType::MicrodroidLauncher => {
let mut command = Command::new("/system/bin/microdroid_launcher");
command.arg(find_library_path(&task.command)?);
+ command.uid(microdroid_uids::MICRODROID_PAYLOAD_UID);
+ command.gid(microdroid_uids::MICRODROID_PAYLOAD_GID);
command
}
};
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index bcddc3a..1e0b574 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -18,10 +18,11 @@
use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME};
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
-use anyhow::Result;
-use binder::{Interface, BinderFeatures, ExceptionCode, Status, Strong};
+use anyhow::{anyhow, Context, Result};
+use avflog::LogResult;
+use binder::{Interface, BinderFeatures, ExceptionCode, Strong, IntoBinderResult};
use diced_open_dice::{DiceArtifacts, OwnedDiceArtifacts};
-use log::{error, info};
+use log::info;
use rpcbinder::RpcServer;
use std::os::unix::io::OwnedFd;
@@ -39,7 +40,8 @@
fn getVmInstanceSecret(&self, identifier: &[u8], size: i32) -> binder::Result<Vec<u8>> {
if !(0..=32).contains(&size) {
- return Err(Status::new_exception(ExceptionCode::ILLEGAL_ARGUMENT, None));
+ return Err(anyhow!("size {size} not in range (0..=32)"))
+ .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
}
// Use a fixed salt to scope the derivation to this API. It was randomly generated.
let salt = [
@@ -48,10 +50,10 @@
0xB7, 0xA8, 0x43, 0x92,
];
let mut secret = vec![0; size.try_into().unwrap()];
- derive_sealing_key(&self.dice, &salt, identifier, &mut secret).map_err(|e| {
- error!("Failed to derive VM instance secret: {:?}", e);
- Status::new_service_specific_error(-1, None)
- })?;
+ derive_sealing_key(&self.dice, &salt, identifier, &mut secret)
+ .context("Failed to derive VM instance secret")
+ .with_log()
+ .or_service_specific_exception(-1)?;
Ok(secret)
}
@@ -60,7 +62,7 @@
if let Some(bcc) = self.dice.bcc() {
Ok(bcc.to_vec())
} else {
- Err(Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some("bcc is none")))
+ Err(anyhow!("bcc is none")).or_binder_exception(ExceptionCode::ILLEGAL_STATE)
}
}
@@ -91,8 +93,9 @@
if self.allow_restricted_apis {
Ok(())
} else {
- error!("Use of restricted APIs is not allowed");
- Err(Status::new_exception_str(ExceptionCode::SECURITY, Some("Use of restricted APIs")))
+ Err(anyhow!("Use of restricted APIs is not allowed"))
+ .with_log()
+ .or_binder_exception(ExceptionCode::SECURITY)
}
}
}
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index bbe00b5..1aa5935 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -7,8 +7,6 @@
crate_name: "pvmfw",
defaults: ["vmbase_ffi_defaults"],
srcs: ["src/main.rs"],
- // Require unsafe blocks for inside unsafe functions.
- flags: ["-Dunsafe_op_in_unsafe_fn"],
features: [
"legacy",
],
diff --git a/pvmfw/README.md b/pvmfw/README.md
index 386036d..698972a 100644
--- a/pvmfw/README.md
+++ b/pvmfw/README.md
@@ -139,6 +139,10 @@
| offset = (SECOND - HEAD) |
| size = (SECOND_END - SECOND) |
+-------------------------------+
+| [Entry 2] | <-- Entry 2 is present since version 1.1
+| offset = (THIRD - HEAD) |
+| size = (THIRD_END - SECOND) |
++-------------------------------+
| ... |
+-------------------------------+
| [Entry n] |
@@ -152,6 +156,10 @@
| {Second blob: DP} |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ <-- SECOND_END
| (Padding to 8-byte alignment) |
++===============================+ <-- THIRD
+| {Third blob: VM DTBO} |
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ <-- THIRD_END
+| (Padding to 8-byte alignment) |
+===============================+
| ... |
+===============================+ <-- TAIL
@@ -177,6 +185,11 @@
- entry 1 may point to a [DTBO] to be applied to the pVM device tree. See
[debug policy][debug_policy] for an example.
+In version 1.1, new blob is added.
+
+- entry 2 may point to a [DTBO] that describes VM DTBO for device assignment.
+ pvmfw will provision assigned devices with the VM DTBO.
+
[header]: src/config.rs
[DTBO]: https://android.googlesource.com/platform/external/dtc/+/refs/heads/master/Documentation/dt-object-internal.txt
[debug_policy]: ../docs/debug/README.md#debug-policy
diff --git a/pvmfw/avb/Android.bp b/pvmfw/avb/Android.bp
index 49c4717..4efee6a 100644
--- a/pvmfw/avb/Android.bp
+++ b/pvmfw/avb/Android.bp
@@ -7,8 +7,6 @@
crate_name: "pvmfw_avb",
srcs: ["src/lib.rs"],
prefer_rlib: true,
- // Require unsafe blocks for inside unsafe functions.
- flags: ["-Dunsafe_op_in_unsafe_fn"],
rustlibs: [
"libavb_bindgen_nostd",
"libtinyvec_nostd",
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
index 4086af7..d0a6b7f 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -18,7 +18,9 @@
use core::mem;
use core::ops::Range;
use core::result;
-use vmbase::util::unchecked_align_up;
+use log::{info, warn};
+use static_assertions::const_assert_eq;
+use vmbase::util::RangeExt;
use zerocopy::{FromBytes, LayoutVerified};
/// Configuration data header.
@@ -28,13 +30,11 @@
/// Magic number; must be `Header::MAGIC`.
magic: u32,
/// Version of the header format.
- version: u32,
+ version: Version,
/// Total size of the configuration data.
total_size: u32,
/// Feature flags; currently reserved and must be zero.
flags: u32,
- /// (offset, size) pairs used to locate individual entries appended to the header.
- entries: [HeaderEntry; Entry::COUNT],
}
#[derive(Debug)]
@@ -46,15 +46,13 @@
/// 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),
+ UnsupportedVersion(Version),
/// Header describes configuration data that doesn't fit in the expected buffer.
InvalidSize(usize),
/// Header entry is missing.
MissingEntry(Entry),
- /// Header entry is invalid.
- InvalidEntry(Entry, EntryError),
+ /// Range described by entry does not fit within config data.
+ EntryOutOfBounds(Entry, Range<usize>, Range<usize>),
}
impl fmt::Display for Error {
@@ -63,110 +61,69 @@
Self::BufferTooSmall => write!(f, "Reserved region is smaller than config header"),
Self::HeaderMisaligned => write!(f, "Reserved region is misaligned"),
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::UnsupportedVersion(v) => write!(f, "Version {v} not supported"),
Self::InvalidSize(sz) => write!(f, "Total size ({sz:#x}) overflows reserved region"),
Self::MissingEntry(entry) => write!(f, "Mandatory {entry:?} entry is missing"),
- Self::InvalidEntry(entry, e) => write!(f, "Invalid {entry:?} entry: {e}"),
+ Self::EntryOutOfBounds(entry, range, limits) => {
+ write!(
+ f,
+ "Entry {entry:?} out of bounds: {range:#x?} must be within range {limits:#x?}"
+ )
+ }
}
}
}
pub type Result<T> = result::Result<T, Error>;
-#[derive(Debug)]
-pub enum EntryError {
- /// Offset isn't between the fixed minimum value and size of configuration data.
- InvalidOffset(usize),
- /// Size must be zero when offset is and not be when it isn't.
- InvalidSize(usize),
- /// Entry isn't fully within the configuration data structure.
- OutOfBounds { offset: usize, size: usize, limit: usize },
-}
-
-impl fmt::Display for EntryError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Self::InvalidOffset(offset) => write!(f, "Invalid offset: {offset:#x?}"),
- Self::InvalidSize(sz) => write!(f, "Invalid size: {sz:#x?}"),
- Self::OutOfBounds { offset, size, limit } => {
- let range = Header::PADDED_SIZE..*limit;
- let entry = *offset..(*offset + *size);
- write!(f, "Out of bounds: {entry:#x?} must be within range {range:#x?}")
- }
- }
- }
-}
-
impl Header {
const MAGIC: u32 = u32::from_ne_bytes(*b"pvmf");
- const VERSION_1_0: u32 = Self::version(1, 0);
- const PADDED_SIZE: usize = 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)
- }
+ const VERSION_1_0: Version = Version { major: 1, minor: 0 };
+ const VERSION_1_1: Version = Version { major: 1, minor: 1 };
pub fn total_size(&self) -> usize {
self.total_size as usize
}
- pub fn body_size(&self) -> usize {
- self.total_size() - Self::PADDED_SIZE
+ pub fn body_lowest_bound(&self) -> Result<usize> {
+ let entries_offset = mem::size_of::<Self>();
+
+ // Ensure that the entries are properly aligned and do not require padding.
+ const_assert_eq!(mem::align_of::<Header>() % mem::align_of::<HeaderEntry>(), 0);
+ const_assert_eq!(mem::size_of::<Header>() % mem::align_of::<HeaderEntry>(), 0);
+
+ let entries_size = self.entry_count()?.checked_mul(mem::size_of::<HeaderEntry>()).unwrap();
+
+ Ok(entries_offset.checked_add(entries_size).unwrap())
}
- fn get_body_range(&self, entry: Entry) -> Result<Option<Range<usize>>> {
- let e = self.entries[entry as usize];
- let offset = e.offset as usize;
- let size = e.size as usize;
-
- match self._get_body_range(offset, size) {
- Ok(r) => Ok(r),
- Err(EntryError::InvalidSize(0)) => {
- // As our bootloader currently uses this (non-compliant) case, permit it for now.
- log::warn!("Config entry {entry:?} uses non-zero offset with zero size");
- // TODO(b/262181812): Either make this case valid or fix the bootloader.
- Ok(None)
+ pub fn entry_count(&self) -> Result<usize> {
+ let last_entry = match self.version {
+ Self::VERSION_1_0 => Entry::DebugPolicy,
+ Self::VERSION_1_1 => Entry::VmDtbo,
+ v @ Version { major: 1, .. } => {
+ const LATEST: Version = Header::VERSION_1_1;
+ warn!("Parsing unknown config data version {v} as version {LATEST}");
+ return Ok(Entry::COUNT);
}
- Err(e) => Err(Error::InvalidEntry(entry, e)),
- }
- }
+ v => return Err(Error::UnsupportedVersion(v)),
+ };
- fn _get_body_range(
- &self,
- offset: usize,
- size: usize,
- ) -> result::Result<Option<Range<usize>>, EntryError> {
- match (offset, size) {
- (0, 0) => Ok(None),
- (0, size) | (_, size @ 0) => Err(EntryError::InvalidSize(size)),
- _ => {
- let start = offset
- .checked_sub(Header::PADDED_SIZE)
- .ok_or(EntryError::InvalidOffset(offset))?;
- let end = start
- .checked_add(size)
- .filter(|x| *x <= self.body_size())
- .ok_or(EntryError::OutOfBounds { offset, size, limit: self.total_size() })?;
-
- Ok(Some(start..end))
- }
- }
+ Ok(last_entry as usize + 1)
}
}
#[derive(Clone, Copy, Debug)]
pub enum Entry {
- Bcc = 0,
- DebugPolicy = 1,
+ Bcc,
+ DebugPolicy,
+ VmDtbo,
+ #[allow(non_camel_case_types)] // TODO: Use mem::variant_count once stable.
+ _VARIANT_COUNT,
}
impl Entry {
- const COUNT: usize = 2;
+ const COUNT: usize = Self::_VARIANT_COUNT as usize;
}
#[repr(packed)]
@@ -176,59 +133,111 @@
size: u32,
}
+impl HeaderEntry {
+ pub fn as_range(&self) -> Option<Range<usize>> {
+ let size = usize::try_from(self.size).unwrap();
+ if size != 0 {
+ let offset = self.offset.try_into().unwrap();
+ // Allow overflows here for the Range to properly describe the entry (validated later).
+ Some(offset..(offset + size))
+ } else {
+ None
+ }
+ }
+}
+
+#[repr(C, packed)]
+#[derive(Clone, Copy, Debug, Eq, FromBytes, PartialEq)]
+pub struct Version {
+ minor: u16,
+ major: u16,
+}
+
+impl fmt::Display for Version {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ // Copy the fields to local variables to prevent unaligned access.
+ let (major, minor) = (self.major, self.minor);
+ write!(f, "{}.{}", major, minor)
+ }
+}
+
#[derive(Debug)]
pub struct Config<'a> {
body: &'a mut [u8],
- bcc_range: Range<usize>,
- dp_range: Option<Range<usize>>,
+ ranges: [Option<Range<usize>>; Entry::COUNT],
}
impl<'a> Config<'a> {
/// Take ownership of a pvmfw configuration consisting of its header and following entries.
- pub fn new(data: &'a mut [u8]) -> Result<Self> {
- let header = data.get(..Header::PADDED_SIZE).ok_or(Error::BufferTooSmall)?;
+ pub fn new(bytes: &'a mut [u8]) -> Result<Self> {
+ const HEADER_SIZE: usize = mem::size_of::<Header>();
+ if bytes.len() < HEADER_SIZE {
+ return Err(Error::BufferTooSmall);
+ }
- let (header, _) =
- LayoutVerified::<_, Header>::new_from_prefix(header).ok_or(Error::HeaderMisaligned)?;
+ let (header, rest) =
+ LayoutVerified::<_, Header>::new_from_prefix(bytes).ok_or(Error::HeaderMisaligned)?;
let header = header.into_ref();
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));
+ let header_flags = header.flags;
+ if header_flags != 0 {
+ warn!("Ignoring unknown config flags: {header_flags:#x}");
}
- if header.flags != 0 {
- return Err(Error::InvalidFlags(header.flags));
- }
+ info!("pvmfw config version: {}", header.version);
- let bcc_range =
- header.get_body_range(Entry::Bcc)?.ok_or(Error::MissingEntry(Entry::Bcc))?;
- let dp_range = header.get_body_range(Entry::DebugPolicy)?;
+ // Validate that we won't get an invalid alignment in the following due to padding to u64.
+ const_assert_eq!(HEADER_SIZE % mem::size_of::<u64>(), 0);
- let body_size = header.body_size();
+ // Ensure that Header::total_size isn't larger than anticipated by the caller and resize
+ // the &[u8] to catch OOB accesses to entries/blobs.
let total_size = header.total_size();
- let body = data
- .get_mut(Header::PADDED_SIZE..)
- .ok_or(Error::BufferTooSmall)?
- .get_mut(..body_size)
- .ok_or(Error::InvalidSize(total_size))?;
+ let rest = if let Some(rest_size) = total_size.checked_sub(HEADER_SIZE) {
+ rest.get_mut(..rest_size).ok_or(Error::InvalidSize(total_size))?
+ } else {
+ return Err(Error::InvalidSize(total_size));
+ };
- Ok(Self { body, bcc_range, dp_range })
+ let (header_entries, body) =
+ LayoutVerified::<_, [HeaderEntry]>::new_slice_from_prefix(rest, header.entry_count()?)
+ .ok_or(Error::BufferTooSmall)?;
+
+ // Validate that we won't get an invalid alignment in the following due to padding to u64.
+ const_assert_eq!(mem::size_of::<HeaderEntry>() % mem::size_of::<u64>(), 0);
+
+ let limits = header.body_lowest_bound()?..total_size;
+ let ranges = [
+ // TODO: Find a way to do this programmatically even if the trait
+ // `core::marker::Copy` is not implemented for `core::ops::Range<usize>`.
+ Self::validated_body_range(Entry::Bcc, &header_entries, &limits)?,
+ Self::validated_body_range(Entry::DebugPolicy, &header_entries, &limits)?,
+ Self::validated_body_range(Entry::VmDtbo, &header_entries, &limits)?,
+ ];
+
+ Ok(Self { body, ranges })
}
/// Get slice containing the platform BCC.
- pub fn get_entries(&mut self) -> (&mut [u8], Option<&mut [u8]>) {
- let bcc_start = self.bcc_range.start;
- let bcc_end = self.bcc_range.len();
+ pub fn get_entries(&mut self) -> Result<(&mut [u8], Option<&mut [u8]>)> {
+ // This assumes that the blobs are in-order w.r.t. the entries.
+ let bcc_range = self.get_entry_range(Entry::Bcc).ok_or(Error::MissingEntry(Entry::Bcc))?;
+ let dp_range = self.get_entry_range(Entry::DebugPolicy);
+ let vm_dtbo_range = self.get_entry_range(Entry::VmDtbo);
+ // TODO(b/291191157): Provision device assignment with this.
+ if let Some(vm_dtbo_range) = vm_dtbo_range {
+ info!("Found VM DTBO at {:?}", vm_dtbo_range);
+ }
+ let bcc_start = bcc_range.start;
+ let bcc_end = bcc_range.len();
let (_, rest) = self.body.split_at_mut(bcc_start);
let (bcc, rest) = rest.split_at_mut(bcc_end);
- let dp = if let Some(dp_range) = &self.dp_range {
- let dp_start = dp_range.start.checked_sub(self.bcc_range.end).unwrap();
+ let dp = if let Some(dp_range) = dp_range {
+ let dp_start = dp_range.start.checked_sub(bcc_range.end).unwrap();
let dp_end = dp_range.len();
let (_, rest) = rest.split_at_mut(dp_start);
let (dp, _) = rest.split_at_mut(dp_end);
@@ -237,6 +246,31 @@
None
};
- (bcc, dp)
+ Ok((bcc, dp))
+ }
+
+ pub fn get_entry_range(&self, entry: Entry) -> Option<Range<usize>> {
+ self.ranges[entry as usize].clone()
+ }
+
+ fn validated_body_range(
+ entry: Entry,
+ header_entries: &[HeaderEntry],
+ limits: &Range<usize>,
+ ) -> Result<Option<Range<usize>>> {
+ if let Some(header_entry) = header_entries.get(entry as usize) {
+ if let Some(r) = header_entry.as_range() {
+ return if r.start <= r.end && r.is_within(limits) {
+ let start = r.start - limits.start;
+ let end = r.end - limits.start;
+
+ Ok(Some(start..end))
+ } else {
+ Err(Error::EntryOutOfBounds(entry, r, limits.clone()))
+ };
+ }
+ }
+
+ Ok(None)
}
}
diff --git a/pvmfw/src/dice.rs b/pvmfw/src/dice.rs
index 28271d3..9542429 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -18,8 +18,8 @@
use core::mem::size_of;
use core::slice;
use diced_open_dice::{
- bcc_format_config_descriptor, bcc_handover_main_flow, hash, Config, DiceMode, Hash,
- InputValues, HIDDEN_SIZE,
+ bcc_format_config_descriptor, bcc_handover_main_flow, hash, Config, DiceConfigValues, DiceMode,
+ Hash, InputValues, HIDDEN_SIZE,
};
use pvmfw_avb::{DebugLevel, Digest, VerifiedBootData};
use vmbase::cstr;
@@ -63,12 +63,10 @@
next_bcc: &mut [u8],
) -> diced_open_dice::Result<()> {
let mut config_descriptor_buffer = [0; 128];
- let config_descriptor_size = bcc_format_config_descriptor(
- Some(cstr!("vm_entry")),
- None, // component_version
- false, // resettable
- &mut config_descriptor_buffer,
- )?;
+ let config_values =
+ DiceConfigValues { component_name: Some(cstr!("vm_entry")), ..Default::default() };
+ let config_descriptor_size =
+ bcc_format_config_descriptor(&config_values, &mut config_descriptor_buffer)?;
let config = &config_descriptor_buffer[..config_descriptor_size];
let dice_inputs = InputValues::new(
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 9c929a9..3efa61e 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -207,7 +207,10 @@
RebootReason::InvalidConfig
})?;
- let (bcc_slice, debug_policy) = appended.get_entries();
+ let (bcc_slice, debug_policy) = appended.get_entries().map_err(|e| {
+ error!("Failed to obtained the config entries: {e}");
+ RebootReason::InvalidConfig
+ })?;
// Up to this point, we were using the built-in static (from .rodata) page tables.
MEMORY.lock().replace(MemoryTracker::new(
@@ -427,10 +430,10 @@
}
}
- fn get_entries(&mut self) -> (&mut [u8], Option<&mut [u8]>) {
+ fn get_entries(&mut self) -> config::Result<(&mut [u8], Option<&mut [u8]>)> {
match self {
Self::Config(ref mut cfg) => cfg.get_entries(),
- Self::LegacyBcc(ref mut bcc) => (bcc, None),
+ Self::LegacyBcc(ref mut bcc) => Ok((bcc, None)),
}
}
}
diff --git a/pvmfw/src/memory.rs b/pvmfw/src/memory.rs
index 27ab719..06158dd 100644
--- a/pvmfw/src/memory.rs
+++ b/pvmfw/src/memory.rs
@@ -14,8 +14,6 @@
//! Low-level allocation and tracking of main memory.
-#![deny(unsafe_op_in_unsafe_fn)]
-
use crate::helpers::PVMFW_PAGE_SIZE;
use aarch64_paging::paging::VirtualAddress;
use aarch64_paging::MapError;
diff --git a/rialto/Android.bp b/rialto/Android.bp
index ed9a284..55423ea 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -9,10 +9,14 @@
defaults: ["vmbase_ffi_defaults"],
rustlibs: [
"libaarch64_paging",
+ "libciborium_io_nostd",
+ "libciborium_nostd",
"libhyp",
"libfdtpci",
"liblibfdt",
"liblog_rust_nostd",
+ "libservice_vm_comm_nostd",
+ "libtinyvec_nostd",
"libvirtio_drivers",
"libvmbase",
],
@@ -76,6 +80,7 @@
}
prebuilt_etc {
+ // rialto_bin is a prebuilt target wrapping the signed bare-metal service VM.
name: "rialto_bin",
filename: "rialto.bin",
target: {
@@ -97,9 +102,11 @@
"android.system.virtualizationservice-rust",
"libandroid_logger",
"libanyhow",
+ "libciborium",
"liblibc",
"liblog_rust",
"libnix",
+ "libservice_vm_comm",
"libvmclient",
"libvsock",
],
diff --git a/rialto/src/communication.rs b/rialto/src/communication.rs
index f00393d..ee4ecdb 100644
--- a/rialto/src/communication.rs
+++ b/rialto/src/communication.rs
@@ -14,72 +14,193 @@
//! Supports for the communication between rialto and host.
-use crate::error::{Error, Result};
+use crate::error::Result;
+use ciborium_io::{Read, Write};
+use core::hint::spin_loop;
+use core::mem;
+use core::result;
use log::info;
+use service_vm_comm::{Request, Response};
+use tinyvec::ArrayVec;
use virtio_drivers::{
self,
device::socket::{
- SingleConnectionManager, SocketError, VirtIOSocket, VsockAddr, VsockEventType,
+ SocketError, VirtIOSocket, VsockAddr, VsockConnectionManager, VsockEventType,
},
transport::Transport,
Hal,
};
-const MAX_RECV_BUFFER_SIZE_BYTES: usize = 64;
+const WRITE_BUF_CAPACITY: usize = 512;
-pub struct DataChannel<H: Hal, T: Transport> {
- connection_manager: SingleConnectionManager<H, T>,
+pub struct VsockStream<H: Hal, T: Transport> {
+ connection_manager: VsockConnectionManager<H, T>,
+ /// Peer address. The same port is used on rialto and peer for convenience.
+ peer_addr: VsockAddr,
+ write_buf: ArrayVec<[u8; WRITE_BUF_CAPACITY]>,
}
-impl<H: Hal, T: Transport> From<VirtIOSocket<H, T>> for DataChannel<H, T> {
- fn from(socket_device_driver: VirtIOSocket<H, T>) -> Self {
- Self { connection_manager: SingleConnectionManager::new(socket_device_driver) }
+impl<H: Hal, T: Transport> VsockStream<H, T> {
+ pub fn new(
+ socket_device_driver: VirtIOSocket<H, T>,
+ peer_addr: VsockAddr,
+ ) -> virtio_drivers::Result<Self> {
+ let mut vsock_stream = Self {
+ connection_manager: VsockConnectionManager::new(socket_device_driver),
+ peer_addr,
+ write_buf: ArrayVec::default(),
+ };
+ vsock_stream.connect()?;
+ Ok(vsock_stream)
}
-}
-impl<H: Hal, T: Transport> DataChannel<H, T> {
- /// Connects to the given destination.
- pub fn connect(&mut self, destination: VsockAddr) -> virtio_drivers::Result {
- // Use the same port on rialto and host for convenience.
- self.connection_manager.connect(destination, destination.port)?;
- self.connection_manager.wait_for_connect()?;
- info!("Connected to the destination {destination:?}");
+ fn connect(&mut self) -> virtio_drivers::Result {
+ self.connection_manager.connect(self.peer_addr, self.peer_addr.port)?;
+ self.wait_for_connect()?;
+ info!("Connected to the peer {:?}", self.peer_addr);
Ok(())
}
- /// Processes the received requests and sends back a reply.
- pub fn handle_incoming_request(&mut self) -> Result<()> {
- let mut buffer = [0u8; MAX_RECV_BUFFER_SIZE_BYTES];
-
- // TODO(b/274441673): Handle the scenario when the given buffer is too short.
- let len = self.wait_for_recv(&mut buffer).map_err(Error::ReceivingDataFailed)?;
-
- // TODO(b/291732060): Implement the communication protocol.
- // Just reverse the received message for now.
- buffer[..len].reverse();
- self.connection_manager.send(&buffer[..len])?;
- Ok(())
- }
-
- fn wait_for_recv(&mut self, buffer: &mut [u8]) -> virtio_drivers::Result<usize> {
+ fn wait_for_connect(&mut self) -> virtio_drivers::Result {
loop {
- match self.connection_manager.wait_for_recv(buffer)?.event_type {
- VsockEventType::Disconnected { .. } => {
- return Err(SocketError::ConnectionFailed.into())
+ if let Some(event) = self.poll_event_from_peer()? {
+ match event {
+ VsockEventType::Connected => return Ok(()),
+ VsockEventType::Disconnected { .. } => {
+ return Err(SocketError::ConnectionFailed.into())
+ }
+ // We shouldn't receive the following event before the connection is
+ // established.
+ VsockEventType::ConnectionRequest | VsockEventType::Received { .. } => {
+ return Err(SocketError::InvalidOperation.into())
+ }
+ // We can receive credit requests and updates at any time.
+ // This can be ignored as the connection manager handles them in poll().
+ VsockEventType::CreditRequest | VsockEventType::CreditUpdate => {}
}
- VsockEventType::Received { length, .. } => return Ok(length),
- VsockEventType::Connected
- | VsockEventType::ConnectionRequest
- | VsockEventType::CreditRequest
- | VsockEventType::CreditUpdate => {}
+ } else {
+ spin_loop();
}
}
}
+ pub fn read_request(&mut self) -> Result<Request> {
+ Ok(ciborium::from_reader(self)?)
+ }
+
+ pub fn write_response(&mut self, response: &Response) -> Result<()> {
+ Ok(ciborium::into_writer(response, self)?)
+ }
+
/// Shuts down the data channel.
- pub fn force_close(&mut self) -> virtio_drivers::Result {
- self.connection_manager.force_close()?;
+ pub fn shutdown(&mut self) -> virtio_drivers::Result {
+ self.connection_manager.force_close(self.peer_addr, self.peer_addr.port)?;
info!("Connection shutdown.");
Ok(())
}
+
+ fn recv(&mut self, buffer: &mut [u8]) -> virtio_drivers::Result<usize> {
+ self.connection_manager.recv(self.peer_addr, self.peer_addr.port, buffer)
+ }
+
+ fn wait_for_send(&mut self, buffer: &[u8]) -> virtio_drivers::Result {
+ const INSUFFICIENT_BUFFER_SPACE_ERROR: virtio_drivers::Error =
+ virtio_drivers::Error::SocketDeviceError(SocketError::InsufficientBufferSpaceInPeer);
+ loop {
+ match self.connection_manager.send(self.peer_addr, self.peer_addr.port, buffer) {
+ Ok(_) => return Ok(()),
+ Err(INSUFFICIENT_BUFFER_SPACE_ERROR) => {
+ self.poll()?;
+ }
+ Err(e) => return Err(e),
+ }
+ }
+ }
+
+ fn wait_for_recv(&mut self) -> virtio_drivers::Result {
+ loop {
+ match self.poll()? {
+ Some(VsockEventType::Received { .. }) => return Ok(()),
+ _ => spin_loop(),
+ }
+ }
+ }
+
+ /// Polls the rx queue after the connection is established with the peer, this function
+ /// rejects some invalid events. The valid events are handled inside the connection
+ /// manager.
+ fn poll(&mut self) -> virtio_drivers::Result<Option<VsockEventType>> {
+ if let Some(event) = self.poll_event_from_peer()? {
+ match event {
+ VsockEventType::Disconnected { .. } => Err(SocketError::ConnectionFailed.into()),
+ VsockEventType::Connected | VsockEventType::ConnectionRequest => {
+ Err(SocketError::InvalidOperation.into())
+ }
+ // When there is a received event, the received data is buffered in the
+ // connection manager's internal receive buffer, so we don't need to do
+ // anything here.
+ // The credit request and updates also handled inside the connection
+ // manager.
+ VsockEventType::Received { .. }
+ | VsockEventType::CreditRequest
+ | VsockEventType::CreditUpdate => Ok(Some(event)),
+ }
+ } else {
+ Ok(None)
+ }
+ }
+
+ fn poll_event_from_peer(&mut self) -> virtio_drivers::Result<Option<VsockEventType>> {
+ Ok(self.connection_manager.poll()?.map(|event| {
+ assert_eq!(event.source, self.peer_addr);
+ assert_eq!(event.destination.port, self.peer_addr.port);
+ event.event_type
+ }))
+ }
+}
+
+impl<H: Hal, T: Transport> Read for VsockStream<H, T> {
+ type Error = virtio_drivers::Error;
+
+ fn read_exact(&mut self, data: &mut [u8]) -> result::Result<(), Self::Error> {
+ let mut start = 0;
+ while start < data.len() {
+ let len = self.recv(&mut data[start..])?;
+ let len = if len == 0 {
+ self.wait_for_recv()?;
+ self.recv(&mut data[start..])?
+ } else {
+ len
+ };
+ start += len;
+ }
+ Ok(())
+ }
+}
+
+impl<H: Hal, T: Transport> Write for VsockStream<H, T> {
+ type Error = virtio_drivers::Error;
+
+ fn write_all(&mut self, data: &[u8]) -> result::Result<(), Self::Error> {
+ if data.len() >= self.write_buf.capacity() - self.write_buf.len() {
+ self.flush()?;
+ if data.len() >= self.write_buf.capacity() {
+ self.wait_for_send(data)?;
+ return Ok(());
+ }
+ }
+ self.write_buf.extend_from_slice(data);
+ Ok(())
+ }
+
+ fn flush(&mut self) -> result::Result<(), Self::Error> {
+ if !self.write_buf.is_empty() {
+ // We need to take the memory from self.write_buf to a temporary
+ // buffer to avoid borrowing `*self` as mutable and immutable on
+ // the same time in `self.wait_for_send(&self.write_buf)`.
+ let buffer = mem::take(&mut self.write_buf);
+ self.wait_for_send(&buffer)?;
+ }
+ Ok(())
+ }
}
diff --git a/rialto/src/error.rs b/rialto/src/error.rs
index 461870b..23667ed 100644
--- a/rialto/src/error.rs
+++ b/rialto/src/error.rs
@@ -23,7 +23,10 @@
pub type Result<T> = result::Result<T, Error>;
-#[derive(Clone, Debug)]
+type CiboriumSerError = ciborium::ser::Error<virtio_drivers::Error>;
+type CiboriumDeError = ciborium::de::Error<virtio_drivers::Error>;
+
+#[derive(Debug)]
pub enum Error {
/// Hypervisor error.
Hypervisor(HypervisorError),
@@ -43,8 +46,10 @@
MissingVirtIOSocketDevice,
/// Failed VirtIO driver operation.
VirtIODriverOperationFailed(virtio_drivers::Error),
- /// Failed to receive data.
- ReceivingDataFailed(virtio_drivers::Error),
+ /// Failed to serialize.
+ SerializationFailed(CiboriumSerError),
+ /// Failed to deserialize.
+ DeserializationFailed(CiboriumDeError),
}
impl fmt::Display for Error {
@@ -65,7 +70,8 @@
Self::VirtIODriverOperationFailed(e) => {
write!(f, "Failed VirtIO driver operation: {e}")
}
- Self::ReceivingDataFailed(e) => write!(f, "Failed to receive data: {e}"),
+ Self::SerializationFailed(e) => write!(f, "Failed to serialize: {e}"),
+ Self::DeserializationFailed(e) => write!(f, "Failed to deserialize: {e}"),
}
}
}
@@ -105,3 +111,15 @@
Self::VirtIODriverOperationFailed(e)
}
}
+
+impl From<CiboriumSerError> for Error {
+ fn from(e: CiboriumSerError) -> Self {
+ Self::SerializationFailed(e)
+ }
+}
+
+impl From<CiboriumDeError> for Error {
+ fn from(e: CiboriumDeError) -> Self {
+ Self::DeserializationFailed(e)
+ }
+}
diff --git a/rialto/src/main.rs b/rialto/src/main.rs
index 5c6649a..42d39c4 100644
--- a/rialto/src/main.rs
+++ b/rialto/src/main.rs
@@ -20,11 +20,13 @@
mod communication;
mod error;
mod exceptions;
+mod requests;
extern crate alloc;
-use crate::communication::DataChannel;
+use crate::communication::VsockStream;
use crate::error::{Error, Result};
+use ciborium_io::Write;
use core::num::NonZeroUsize;
use core::slice;
use fdtpci::PciInfo;
@@ -137,10 +139,11 @@
let socket_device = find_socket_device::<HalImpl>(&mut pci_root)?;
debug!("Found socket device: guest cid = {:?}", socket_device.guest_cid());
- let mut data_channel = DataChannel::from(socket_device);
- data_channel.connect(host_addr())?;
- data_channel.handle_incoming_request()?;
- data_channel.force_close()?;
+ let mut vsock_stream = VsockStream::new(socket_device, host_addr())?;
+ let response = requests::process_request(vsock_stream.read_request()?);
+ vsock_stream.write_response(&response)?;
+ vsock_stream.flush()?;
+ vsock_stream.shutdown()?;
Ok(())
}
diff --git a/rialto/src/requests/api.rs b/rialto/src/requests/api.rs
new file mode 100644
index 0000000..11fdde4
--- /dev/null
+++ b/rialto/src/requests/api.rs
@@ -0,0 +1,31 @@
+// Copyright 2023, 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.
+
+//! This module contains the main API for the request processing module.
+
+use alloc::vec::Vec;
+use service_vm_comm::{Request, Response};
+
+/// Processes a request and returns the corresponding response.
+/// This function serves as the entry point for the request processing
+/// module.
+pub fn process_request(request: Request) -> Response {
+ match request {
+ Request::Reverse(v) => Response::Reverse(reverse(v)),
+ }
+}
+
+fn reverse(payload: Vec<u8>) -> Vec<u8> {
+ payload.into_iter().rev().collect()
+}
diff --git a/rialto/src/requests/mod.rs b/rialto/src/requests/mod.rs
new file mode 100644
index 0000000..ca22777
--- /dev/null
+++ b/rialto/src/requests/mod.rs
@@ -0,0 +1,19 @@
+// Copyright 2023, 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.
+
+//! This module contains functions for the request processing.
+
+mod api;
+
+pub use api::process_request;
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 8089016..2bd8968 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -22,10 +22,11 @@
},
binder::{ParcelFileDescriptor, ProcessState},
};
-use anyhow::{anyhow, bail, Context, Error};
+use anyhow::{anyhow, bail, Context, Result};
use log::info;
+use service_vm_comm::{Request, Response};
use std::fs::File;
-use std::io::{self, BufRead, BufReader, Read, Write};
+use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::os::unix::io::FromRawFd;
use std::panic;
use std::thread;
@@ -44,7 +45,7 @@
const INSTANCE_IMG_SIZE: i64 = 1024 * 1024; // 1MB
#[test]
-fn boot_rialto_in_protected_vm_successfully() -> Result<(), Error> {
+fn boot_rialto_in_protected_vm_successfully() -> Result<()> {
boot_rialto_successfully(
SIGNED_RIALTO_PATH,
true, // protected_vm
@@ -52,14 +53,14 @@
}
#[test]
-fn boot_rialto_in_unprotected_vm_successfully() -> Result<(), Error> {
+fn boot_rialto_in_unprotected_vm_successfully() -> Result<()> {
boot_rialto_successfully(
UNSIGNED_RIALTO_PATH,
false, // protected_vm
)
}
-fn boot_rialto_successfully(rialto_path: &str, protected_vm: bool) -> Result<(), Error> {
+fn boot_rialto_successfully(rialto_path: &str, protected_vm: bool) -> Result<()> {
android_logger::init_once(
android_logger::Config::default().with_tag("rialto").with_min_level(log::Level::Debug),
);
@@ -169,27 +170,31 @@
Ok(writer)
}
-fn try_check_socket_connection(port: u32) -> Result<(), Error> {
+fn try_check_socket_connection(port: u32) -> Result<()> {
info!("Setting up the listening socket on port {port}...");
let listener = VsockListener::bind_with_cid_port(VMADDR_CID_HOST, port)?;
info!("Listening on port {port}...");
- let Some(Ok(mut vsock_stream)) = listener.incoming().next() else {
- bail!("Failed to get vsock_stream");
- };
+ let mut vsock_stream =
+ listener.incoming().next().ok_or_else(|| anyhow!("Failed to get vsock_stream"))??;
info!("Accepted connection {:?}", vsock_stream);
-
- let message = "Hello from host";
- vsock_stream.write_all(message.as_bytes())?;
- vsock_stream.flush()?;
- info!("Sent message: {:?}.", message);
-
- let mut buffer = vec![0u8; 30];
vsock_stream.set_read_timeout(Some(Duration::from_millis(1_000)))?;
- let len = vsock_stream.read(&mut buffer)?;
- assert_eq!(message.len(), len);
- buffer[..len].reverse();
- assert_eq!(message.as_bytes(), &buffer[..len]);
+ const WRITE_BUFFER_CAPACITY: usize = 512;
+ let mut buffer = BufWriter::with_capacity(WRITE_BUFFER_CAPACITY, vsock_stream.clone());
+
+ // TODO(b/292080257): Test with message longer than the receiver's buffer capacity
+ // 1024 bytes once the guest virtio-vsock driver fixes the credit update in recv().
+ let message = "abc".repeat(166);
+ let request = Request::Reverse(message.as_bytes().to_vec());
+ ciborium::into_writer(&request, &mut buffer)?;
+ buffer.flush()?;
+ info!("Sent request: {request:?}.");
+
+ let response: Response = ciborium::from_reader(&mut vsock_stream)?;
+ info!("Received response: {response:?}.");
+
+ let expected_response: Vec<u8> = message.as_bytes().iter().rev().cloned().collect();
+ assert_eq!(Response::Reverse(expected_response), response);
Ok(())
}
diff --git a/service_vm/client_apk/src/main.rs b/service_vm/client_apk/src/main.rs
index 1f8db96..672dd4a 100644
--- a/service_vm/client_apk/src/main.rs
+++ b/service_vm/client_apk/src/main.rs
@@ -49,12 +49,7 @@
fn request_certificate(csr: &[u8]) -> Vec<u8> {
// SAFETY: It is safe as we only request the size of the certificate in this call.
let certificate_size = unsafe {
- AVmPayload_requestCertificate(
- csr.as_ptr() as *const c_void,
- csr.len(),
- [].as_mut_ptr() as *mut c_void,
- 0,
- )
+ AVmPayload_requestCertificate(csr.as_ptr() as *const c_void, csr.len(), [].as_mut_ptr(), 0)
};
let mut certificate = vec![0u8; certificate_size];
// SAFETY: It is safe as we only write the data into the given buffer within the buffer
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index 8d467cd..e81f6d7 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -55,6 +55,9 @@
/** Returns a mask of effective capabilities that the process running the payload binary has. */
String[] getEffectiveCapabilities();
+ /* Return the uid of the process running the binary. */
+ int getUid();
+
/* write the content into the specified file. */
void writeToFile(String content, String path);
diff --git a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
index f98d1d9..f5656e2 100644
--- a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
+++ b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
@@ -81,12 +81,9 @@
private boolean mNeedTearDown = false;
- private boolean mNeedToRestartPkvmStatus = false;
-
@Before
public void setUp() throws Exception {
mNeedTearDown = false;
- mNeedToRestartPkvmStatus = false;
assumeDeviceIsCapable(getDevice());
mNeedTearDown = true;
@@ -104,11 +101,6 @@
// sees, so we can't rely on that - b/268688303.)
return;
}
- // Restore PKVM status and reboot to prevent previous staged session, if switched.
- if (mNeedToRestartPkvmStatus) {
- setPKVMStatusWithRebootToBootloader(true);
- rebootFromBootloaderAndWaitBootCompleted();
- }
CommandRunner android = new CommandRunner(getDevice());
@@ -117,16 +109,6 @@
}
@Test
- public void testBootEnablePKVM() throws Exception {
- enableDisablePKVMTestHelper(true);
- }
-
- @Test
- public void testBootDisablePKVM() throws Exception {
- enableDisablePKVMTestHelper(false);
- }
-
- @Test
public void testBootWithCompOS() throws Exception {
composTestHelper(true);
}
@@ -424,36 +406,6 @@
throw new IllegalArgumentException("Failed to get boot time info.");
}
- private void enableDisablePKVMTestHelper(boolean isEnable) throws Exception {
- assumePKVMStatusSwitchSupported();
-
- List<Double> bootDmesgTime = new ArrayList<>(ROUND_COUNT);
- Map<String, List<Double>> bootloaderTime = new HashMap<>();
-
- setPKVMStatusWithRebootToBootloader(isEnable);
- rebootFromBootloaderAndWaitBootCompleted();
- for (int round = 0; round < ROUND_COUNT; ++round) {
- getDevice().nonBlockingReboot();
- waitForBootCompleted();
-
- updateBootloaderTimeInfo(bootloaderTime);
-
- double elapsedSec = getDmesgBootTime();
- bootDmesgTime.add(elapsedSec);
- }
-
- String suffix = "";
- if (isEnable) {
- suffix = "enable";
- } else {
- suffix = "disable";
- }
-
- reportMetric(bootDmesgTime, "dmesg_boot_time_with_pkvm_" + suffix, "s");
- reportAggregatedMetrics(bootloaderTime,
- "bootloader_time_with_pkvm_" + suffix, "ms");
- }
-
private void composTestHelper(boolean isWithCompos) throws Exception {
assumeFalse("Skip on CF; too slow", isCuttlefish());
@@ -481,29 +433,6 @@
reportMetric(bootDmesgTime, "dmesg_boot_time_" + suffix, "s");
}
- private void assumePKVMStatusSwitchSupported() throws Exception {
- assumeFalse("Skip on CF; can't reboot to bootloader", isCuttlefish());
-
- // This is an overkill. The intention is to exclude remote_device_proxy, which uses
- // different serial for fastboot. But there's no good way to distinguish from regular IP
- // transport. This is currently not a problem until someone really needs to run the test
- // over regular IP transport.
- assumeFalse("Skip over IP (overkill for remote_device_proxy)", getDevice().isAdbTcp());
-
- if (!getDevice().isStateBootloaderOrFastbootd()) {
- getDevice().rebootIntoBootloader();
- }
- getDevice().waitForDeviceBootloader();
-
- CommandResult result;
- result = getDevice().executeFastbootCommand("oem", "pkvm", "status");
- rebootFromBootloaderAndWaitBootCompleted();
- assumeFalse(result.getStderr().contains("Invalid oem command"));
- // Skip the test if running on a build with pkvm_enabler. Disabling pKVM
- // for such builds results in a bootloop.
- assumeTrue(result.getStderr().contains("misc=auto"));
- }
-
private void reportMetric(List<Double> data, String name, String unit) {
CLog.d("Report metric " + name + "(" + unit + ") : " + data.toString());
Map<String, Double> stats = mMetricsProcessor.computeStats(data, name, unit);
@@ -513,50 +442,6 @@
}
}
- private void reportAggregatedMetrics(Map<String, List<Double>> bootloaderTime,
- String prefix, String unit) {
-
- for (Map.Entry<String, List<Double>> entry : bootloaderTime.entrySet()) {
- reportMetric(entry.getValue(), prefix + "_" + entry.getKey(), unit);
- }
- }
-
- private void setPKVMStatusWithRebootToBootloader(boolean isEnable) throws Exception {
- mNeedToRestartPkvmStatus = true;
-
- if (!getDevice().isStateBootloaderOrFastbootd()) {
- getDevice().rebootIntoBootloader();
- }
- getDevice().waitForDeviceBootloader();
-
- CommandResult result;
- if (isEnable) {
- result = getDevice().executeFastbootCommand("oem", "pkvm", "enable");
- } else {
- result = getDevice().executeFastbootCommand("oem", "pkvm", "disable");
- }
-
- result = getDevice().executeFastbootCommand("oem", "pkvm", "status");
- CLog.i("Gets PKVM status : " + result);
-
- String expectedOutput = "";
-
- if (isEnable) {
- expectedOutput = "pkvm is enabled";
- } else {
- expectedOutput = "pkvm is disabled";
- }
- assertWithMessage("Failed to set PKVM status. Reason: " + result)
- .that(result.toString()).ignoringCase().contains(expectedOutput);
- }
-
- private void rebootFromBootloaderAndWaitBootCompleted() throws Exception {
- getDevice().executeFastbootCommand("reboot");
- getDevice().waitForDeviceOnline(BOOT_COMPLETE_TIMEOUT_MS);
- getDevice().waitForBootComplete(BOOT_COMPLETE_TIMEOUT_MS);
- getDevice().enableAdbRoot();
- }
-
private void waitForBootCompleted() throws Exception {
getDevice().waitForDeviceOnline(BOOT_COMPLETE_TIMEOUT_MS);
getDevice().waitForBootComplete(BOOT_COMPLETE_TIMEOUT_MS);
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index e6d90ea..9f03ab7 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -451,6 +451,7 @@
public String mApkContentsPath;
public String mEncryptedStoragePath;
public String[] mEffectiveCapabilities;
+ public int mUid;
public String mFileContent;
public byte[] mBcc;
public long[] mTimings;
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/Pvmfw.java b/tests/hostside/helper/java/com/android/microdroid/test/host/Pvmfw.java
index 95eaa58..d752108 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/Pvmfw.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/Pvmfw.java
@@ -33,20 +33,24 @@
private static final int SIZE_8B = 8; // 8 bytes
private static final int SIZE_4K = 4 << 10; // 4 KiB, PAGE_SIZE
private static final int BUFFER_SIZE = 1024;
- private static final int HEADER_SIZE = Integer.BYTES * 8; // Header has 8 integers.
private static final int HEADER_MAGIC = 0x666d7670;
- private static final int HEADER_VERSION = getVersion(1, 0);
+ private static final int HEADER_DEFAULT_VERSION = getVersion(1, 0);
private static final int HEADER_FLAGS = 0;
@NonNull private final File mPvmfwBinFile;
@NonNull private final File mBccFile;
@Nullable private final File mDebugPolicyFile;
+ private final int mVersion;
private Pvmfw(
- @NonNull File pvmfwBinFile, @NonNull File bccFile, @Nullable File debugPolicyFile) {
+ @NonNull File pvmfwBinFile,
+ @NonNull File bccFile,
+ @Nullable File debugPolicyFile,
+ int version) {
mPvmfwBinFile = Objects.requireNonNull(pvmfwBinFile);
mBccFile = Objects.requireNonNull(bccFile);
mDebugPolicyFile = debugPolicyFile;
+ mVersion = version;
}
/**
@@ -56,17 +60,22 @@
public void serialize(@NonNull File outFile) throws IOException {
Objects.requireNonNull(outFile);
- int bccOffset = HEADER_SIZE;
+ int headerSize = alignTo(getHeaderSize(mVersion), SIZE_8B);
+ int bccOffset = headerSize;
int bccSize = (int) mBccFile.length();
int debugPolicyOffset = alignTo(bccOffset + bccSize, SIZE_8B);
int debugPolicySize = mDebugPolicyFile == null ? 0 : (int) mDebugPolicyFile.length();
int totalSize = debugPolicyOffset + debugPolicySize;
+ if (hasVmDtbo(mVersion)) {
+ // Add VM DTBO size as well.
+ totalSize += Integer.BYTES * 2;
+ }
- ByteBuffer header = ByteBuffer.allocate(HEADER_SIZE).order(LITTLE_ENDIAN);
+ ByteBuffer header = ByteBuffer.allocate(headerSize).order(LITTLE_ENDIAN);
header.putInt(HEADER_MAGIC);
- header.putInt(HEADER_VERSION);
+ header.putInt(mVersion);
header.putInt(totalSize);
header.putInt(HEADER_FLAGS);
header.putInt(bccOffset);
@@ -74,11 +83,18 @@
header.putInt(debugPolicyOffset);
header.putInt(debugPolicySize);
+ if (hasVmDtbo(mVersion)) {
+ // Add placeholder entry for VM DTBO.
+ // TODO(b/291191157): Add a real DTBO and test.
+ header.putInt(0);
+ header.putInt(0);
+ }
+
try (FileOutputStream pvmfw = new FileOutputStream(outFile)) {
appendFile(pvmfw, mPvmfwBinFile);
padTo(pvmfw, SIZE_4K);
pvmfw.write(header.array());
- padTo(pvmfw, HEADER_SIZE);
+ padTo(pvmfw, SIZE_8B);
appendFile(pvmfw, mBccFile);
if (mDebugPolicyFile != null) {
padTo(pvmfw, SIZE_8B);
@@ -110,6 +126,19 @@
}
}
+ private static int getHeaderSize(int version) {
+ if (version == getVersion(1, 0)) {
+ return Integer.BYTES * 8; // Header has 8 integers.
+ }
+ return Integer.BYTES * 10; // Default + VM DTBO (offset, size)
+ }
+
+ private static boolean hasVmDtbo(int version) {
+ int major = getMajorVersion(version);
+ int minor = getMinorVersion(version);
+ return major > 1 || (major == 1 && minor >= 1);
+ }
+
private static int alignTo(int x, int size) {
return (x + size - 1) & ~(size - 1);
}
@@ -118,15 +147,25 @@
return ((major & 0xFFFF) << 16) | (minor & 0xFFFF);
}
+ private static int getMajorVersion(int version) {
+ return (version >> 16) & 0xFFFF;
+ }
+
+ private static int getMinorVersion(int version) {
+ return version & 0xFFFF;
+ }
+
/** Builder for {@link Pvmfw}. */
public static final class Builder {
@NonNull private final File mPvmfwBinFile;
@NonNull private final File mBccFile;
@Nullable private File mDebugPolicyFile;
+ private int mVersion;
public Builder(@NonNull File pvmfwBinFile, @NonNull File bccFile) {
mPvmfwBinFile = Objects.requireNonNull(pvmfwBinFile);
mBccFile = Objects.requireNonNull(bccFile);
+ mVersion = HEADER_DEFAULT_VERSION;
}
@NonNull
@@ -136,8 +175,14 @@
}
@NonNull
+ public Builder setVersion(int major, int minor) {
+ mVersion = getVersion(major, minor);
+ return this;
+ }
+
+ @NonNull
public Pvmfw build() {
- return new Pvmfw(mPvmfwBinFile, mBccFile, mDebugPolicyFile);
+ return new Pvmfw(mPvmfwBinFile, mBccFile, mDebugPolicyFile, mVersion);
}
}
}
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 82d8571..21960b4 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -1065,6 +1065,27 @@
}
}
+ @Test
+ public void testDevcieAssignment() throws Exception {
+ assumeProtectedVmSupported();
+ assumeVfioPlatformSupported();
+
+ List<String> devices = getAssignableDevices();
+ assumeFalse("no assignable devices", devices.isEmpty());
+
+ final String configPath = "assets/vm_config.json";
+ mMicrodroidDevice =
+ MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
+ .debugLevel("full")
+ .memoryMib(minMemorySize())
+ .cpuTopology("match_host")
+ .protectedVm(true)
+ .addAssignableDevice(devices.get(0))
+ .build(getAndroidDevice());
+
+ mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
+ }
+
@Before
public void setUp() throws Exception {
assumeDeviceIsCapable(getDevice());
@@ -1110,6 +1131,31 @@
getAndroidDevice().supportsMicrodroid(false));
}
+ private void assumeVfioPlatformSupported() throws Exception {
+ TestDevice device = getAndroidDevice();
+ assumeTrue(
+ "Test skipped because VFIO platform is not supported.",
+ device.doesFileExist("/dev/vfio/vfio")
+ && device.doesFileExist("/sys/bus/platform/drivers/vfio-platform"));
+ }
+
+ private List<String> getAssignableDevices() throws Exception {
+ CommandRunner android = new CommandRunner(getDevice());
+ String result = android.run("/apex/com.android.virt/bin/vm", "info");
+ List<String> devices = new ArrayList<>();
+ for (String line : result.split("\n")) {
+ final String header = "Assignable devices: ";
+ if (!line.startsWith(header)) continue;
+
+ JSONArray jsonArray = new JSONArray(line.substring(header.length()));
+ for (int i = 0; i < jsonArray.length(); i++) {
+ devices.add(jsonArray.getString(i));
+ }
+ break;
+ }
+ return devices;
+ }
+
private TestDevice getAndroidDevice() {
TestDevice androidDevice = (TestDevice) getDevice();
assertThat(androidDevice).isNotNull();
diff --git a/tests/hostside/java/com/android/microdroid/test/PvmfwImgTest.java b/tests/hostside/java/com/android/microdroid/test/PvmfwImgTest.java
new file mode 100644
index 0000000..320b722
--- /dev/null
+++ b/tests/hostside/java/com/android/microdroid/test/PvmfwImgTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2023 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.
+ */
+
+package com.android.microdroid.test;
+
+import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assert.assertThrows;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
+import com.android.microdroid.test.host.Pvmfw;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.DeviceRuntimeException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.TestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.util.FileUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.Objects;
+
+/** Tests pvmfw.img and pvmfw */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PvmfwImgTest extends MicrodroidHostTestCaseBase {
+ @NonNull private static final String PVMFW_FILE_NAME = "pvmfw_test.bin";
+ @NonNull private static final String BCC_FILE_NAME = "bcc.dat";
+ @NonNull private static final String PACKAGE_FILE_NAME = "MicrodroidTestApp.apk";
+ @NonNull private static final String PACKAGE_NAME = "com.android.microdroid.test";
+ @NonNull private static final String MICRODROID_DEBUG_FULL = "full";
+ @NonNull private static final String MICRODROID_CONFIG_PATH = "assets/vm_config_apex.json";
+ private static final int BOOT_COMPLETE_TIMEOUT_MS = 30000; // 30 seconds
+ private static final int BOOT_FAILURE_WAIT_TIME_MS = 10000; // 10 seconds
+
+ @NonNull private static final String CUSTOM_PVMFW_FILE_PREFIX = "pvmfw";
+ @NonNull private static final String CUSTOM_PVMFW_FILE_SUFFIX = ".bin";
+ @NonNull private static final String CUSTOM_PVMFW_IMG_PATH = TEST_ROOT + PVMFW_FILE_NAME;
+ @NonNull private static final String CUSTOM_PVMFW_IMG_PATH_PROP = "hypervisor.pvmfw.path";
+
+ @Nullable private static File mPvmfwBinFileOnHost;
+ @Nullable private static File mBccFileOnHost;
+
+ @Nullable private TestDevice mAndroidDevice;
+ @Nullable private ITestDevice mMicrodroidDevice;
+ @Nullable private File mCustomPvmfwBinFileOnHost;
+
+ @Before
+ public void setUp() throws Exception {
+ mAndroidDevice = (TestDevice) Objects.requireNonNull(getDevice());
+
+ // Check device capabilities
+ assumeDeviceIsCapable(mAndroidDevice);
+ assumeTrue(
+ "Skip if protected VMs are not supported",
+ mAndroidDevice.supportsMicrodroid(/* protectedVm= */ true));
+ assumeFalse("Test requires setprop for using custom pvmfw and adb root", isUserBuild());
+
+ assumeTrue("Skip if adb root fails", mAndroidDevice.enableAdbRoot());
+
+ // tradefed copies the test artfacts under /tmp when running tests,
+ // so we should *find* the artifacts with the file name.
+ mPvmfwBinFileOnHost =
+ getTestInformation().getDependencyFile(PVMFW_FILE_NAME, /* targetFirst= */ false);
+ mBccFileOnHost =
+ getTestInformation().getDependencyFile(BCC_FILE_NAME, /* targetFirst= */ false);
+
+ // Prepare for system properties for custom pvmfw.img.
+ // File will be prepared later in individual test and then pushed to device
+ // when launching with launchProtectedVmAndWaitForBootCompleted().
+ mCustomPvmfwBinFileOnHost =
+ FileUtil.createTempFile(CUSTOM_PVMFW_FILE_PREFIX, CUSTOM_PVMFW_FILE_SUFFIX);
+ mAndroidDevice.setProperty(CUSTOM_PVMFW_IMG_PATH_PROP, CUSTOM_PVMFW_IMG_PATH);
+
+ // Prepare for launching microdroid
+ mAndroidDevice.installPackage(findTestFile(PACKAGE_FILE_NAME), /* reinstall */ false);
+ prepareVirtualizationTestSetup(mAndroidDevice);
+ mMicrodroidDevice = null;
+ }
+
+ @After
+ public void shutdown() throws Exception {
+ if (!mAndroidDevice.supportsMicrodroid(/* protectedVm= */ true)) {
+ return;
+ }
+ if (mMicrodroidDevice != null) {
+ mAndroidDevice.shutdownMicrodroid(mMicrodroidDevice);
+ mMicrodroidDevice = null;
+ }
+ mAndroidDevice.uninstallPackage(PACKAGE_NAME);
+
+ // Cleanup for custom pvmfw.img
+ mAndroidDevice.setProperty(CUSTOM_PVMFW_IMG_PATH_PROP, "");
+ FileUtil.deleteFile(mCustomPvmfwBinFileOnHost);
+
+ cleanUpVirtualizationTestSetup(mAndroidDevice);
+
+ mAndroidDevice.disableAdbRoot();
+ }
+
+ @Test
+ public void testConfigVersion1_0_boots() throws Exception {
+ Pvmfw pvmfw =
+ new Pvmfw.Builder(mPvmfwBinFileOnHost, mBccFileOnHost).setVersion(1, 0).build();
+ pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+
+ launchProtectedVmAndWaitForBootCompleted(BOOT_COMPLETE_TIMEOUT_MS);
+ }
+
+ @Test
+ public void testConfigVersion1_1_boots() throws Exception {
+ Pvmfw pvmfw =
+ new Pvmfw.Builder(mPvmfwBinFileOnHost, mBccFileOnHost).setVersion(1, 1).build();
+ pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+
+ launchProtectedVmAndWaitForBootCompleted(BOOT_COMPLETE_TIMEOUT_MS);
+ }
+
+ @Test
+ public void testInvalidConfigVersion_doesNotBoot() throws Exception {
+ // Disclaimer: Update versions when it becomes valid
+ Pvmfw pvmfw =
+ new Pvmfw.Builder(mPvmfwBinFileOnHost, mBccFileOnHost).setVersion(2, 0).build();
+ pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+
+ assertThrows(
+ "pvmfw shouldn't boot with invalid version",
+ DeviceRuntimeException.class,
+ () -> launchProtectedVmAndWaitForBootCompleted(BOOT_FAILURE_WAIT_TIME_MS));
+ }
+
+ private ITestDevice launchProtectedVmAndWaitForBootCompleted(long adbTimeoutMs)
+ throws DeviceNotAvailableException {
+ mMicrodroidDevice =
+ MicrodroidBuilder.fromDevicePath(
+ getPathForPackage(PACKAGE_NAME), MICRODROID_CONFIG_PATH)
+ .debugLevel(MICRODROID_DEBUG_FULL)
+ .protectedVm(true)
+ .addBootFile(mCustomPvmfwBinFileOnHost, PVMFW_FILE_NAME)
+ .setAdbConnectTimeoutMs(adbTimeoutMs)
+ .build(mAndroidDevice);
+ assertThat(mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT_MS)).isTrue();
+ return mMicrodroidDevice;
+ }
+}
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 8a31c21..526f240 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -2,6 +2,17 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+android_app_certificate {
+ name: "MicrodroidTestAppCert",
+
+ // The default app cert is the same as the default platform cert
+ // (on a test-keys build), which means we end up getting assigned
+ // the permissions via signature and can't reliably disclaim
+ // them. So instead we use our own custom cert. See b/290582742.
+ // Created via: development/tools/make_key microdroid_test_app '/CN=microdroid_test_app'
+ certificate: "microdroid_test_app",
+}
+
java_defaults {
name: "MicrodroidTestAppsDefaults",
test_suites: [
@@ -12,6 +23,7 @@
"com.android.microdroid.testservice-java",
"com.android.microdroid.test.vmshare_service-java",
],
+ certificate: ":MicrodroidTestAppCert",
sdk_version: "test_current",
jni_uses_platform_apis: true,
use_embedded_native_libs: true,
diff --git a/tests/testapk/microdroid_test_app.pk8 b/tests/testapk/microdroid_test_app.pk8
new file mode 100644
index 0000000..dc012bd
--- /dev/null
+++ b/tests/testapk/microdroid_test_app.pk8
Binary files differ
diff --git a/tests/testapk/microdroid_test_app.x509.pem b/tests/testapk/microdroid_test_app.x509.pem
new file mode 100644
index 0000000..9a0309c
--- /dev/null
+++ b/tests/testapk/microdroid_test_app.x509.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHzCCAgegAwIBAgIUNnOI4tOMieX67OtyD+6BjTsLm0IwDQYJKoZIhvcNAQEL
+BQAwHjEcMBoGA1UEAwwTbWljcm9kcm9pZF90ZXN0X2FwcDAgFw0yMzA4MTgxNDA4
+MDZaGA8yMDUxMDEwMzE0MDgwNlowHjEcMBoGA1UEAwwTbWljcm9kcm9pZF90ZXN0
+X2FwcDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK7B9xDTD2kS4xFQ
+gwQThRqnxKzOmckYqv2XznXq7tCuhU+RgXDrub7Aiq+QgA25Ouw8ORM5FkZAxD6j
+hCRSVo8cyXdNfPygRY/56umL6KqLMqB0tXLHPst3Lh8fl2su2S+jWL71lUwdOBmu
+nBIa1UqxI9PChR/uIqGyDxNRlUnqOA5/FgyX95P9wj8zmXEFe5No8rL/9hjpBvw1
+cOJCH4hea6JKDA15XYxDaTyj5pkmGb228ZbQb10XwOIhtS94CVxIvqmREzZHL7b0
+cjzCwFDDF6sQoVDi71eFYSWInxSNErDU6wv5h2t6+PV+9mGwTi/AJuxTmevSUoAp
+tGwq0NMCAwEAAaNTMFEwHQYDVR0OBBYEFI2m/0SoaNew99YPQlo6oYPJfh7lMB8G
+A1UdIwQYMBaAFI2m/0SoaNew99YPQlo6oYPJfh7lMA8GA1UdEwEB/wQFMAMBAf8w
+DQYJKoZIhvcNAQELBQADggEBABxIQ66ACIrSnDCiI/DqdPPwHf4vva2Y0bVJ5tXN
+ufFQN0Hr4UnttDzWPtfZHQTnrA478b9Z/g4Y0qg/tj2g5oZP50coF9a39mPe6v2k
+vazkMp2H/+ilG4c8L6QsC7UKXn7Lxxznn3ijlh1lYVJ3E6nMibGRKrfaVFpEwtvy
+zT0K8eK9KUZIyG5nf1v8On4Vfu7MnavuxNubKoUhfu0B8hSd5JKiGDuUkSk3MiFX
+uctYmJZEUD1xLI787SzqrhuYMGfuwmrrI0N46yvUgRgxpkVj2s6GNWqRD3F/fOG+
+qFbeenHjFoMJN9HIAZaz4OqzgGfhfMf596rn+HPAJnRMtsI=
+-----END CERTIFICATE-----
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 f6dc1b8..a928dcf 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -72,6 +72,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
@@ -1523,6 +1524,30 @@
}
@Test
+ @Ignore // Figure out how to run this conditionally
+ @CddTest(requirements = {"9.17/C-1-1"})
+ public void payloadIsNotRoot() throws Exception {
+ assumeSupportedDevice();
+
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+ .setMemoryBytes(minMemoryRequired())
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+ TestResults testResults =
+ runVmTestService(
+ TAG,
+ vm,
+ (ts, tr) -> {
+ tr.mUid = ts.getUid();
+ });
+ testResults.assertNoException();
+ assertThat(testResults.mUid).isNotEqualTo(0);
+ }
+
+ @Test
@CddTest(requirements = {"9.17/C-1-1"})
public void encryptedStorageIsPersistent() throws Exception {
assumeSupportedDevice();
@@ -1971,8 +1996,12 @@
| OsConstants.S_IROTH
| OsConstants.S_IWOTH
| OsConstants.S_IXOTH;
- assertThat(testResults.mFileMode & allPermissionsMask)
- .isEqualTo(OsConstants.S_IRUSR | OsConstants.S_IXUSR);
+ int expectedPermissions =
+ OsConstants.S_IRUSR
+ | OsConstants.S_IXUSR
+ | OsConstants.S_IRGRP
+ | OsConstants.S_IXGRP;
+ assertThat(testResults.mFileMode & allPermissionsMask).isEqualTo(expectedPermissions);
}
// Taken from bionic/libc/kernel/uapi/linux/mount.h
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 297b505..c9b5e3a 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -248,6 +248,11 @@
return ScopedAStatus::ok();
}
+ ScopedAStatus getUid(int* out) override {
+ *out = getuid();
+ return ScopedAStatus::ok();
+ }
+
ScopedAStatus runEchoReverseServer() override {
auto result = start_echo_reverse_server();
if (result.ok()) {
diff --git a/tests/testapk/test.keystore b/tests/testapk/test.keystore
deleted file mode 100644
index 2946641..0000000
--- a/tests/testapk/test.keystore
+++ /dev/null
Binary files differ
diff --git a/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
index 0ddf70b..dc8908b 100644
--- a/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
+++ b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
@@ -220,6 +220,11 @@
}
@Override
+ public int getUid() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
public void writeToFile(String content, String path) throws RemoteException {
throw new UnsupportedOperationException("Not supported");
}
diff --git a/virtualizationmanager/Android.bp b/virtualizationmanager/Android.bp
index 59e507f..de39aa2 100644
--- a/virtualizationmanager/Android.bp
+++ b/virtualizationmanager/Android.bp
@@ -27,6 +27,7 @@
"libandroid_logger",
"libanyhow",
"libapkverify",
+ "libavflog",
"libbase_rust",
"libbinder_rs",
"libclap",
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 91bd60b..97151d7 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -50,9 +50,11 @@
};
use anyhow::{anyhow, bail, Context, Result};
use apkverify::{HashAlgorithm, V4Signature};
+use avflog::LogResult;
use binder::{
self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor,
Status, StatusCode, Strong,
+ IntoBinderResult,
};
use disk::QcowFile;
use lazy_static::lazy_static;
@@ -179,7 +181,6 @@
Ok(())
}
}
-
impl IVirtualizationService for VirtualizationService {
/// Creates (but does not start) a new VM with the given configuration, assigning it the next
/// available CID.
@@ -212,27 +213,17 @@
partition_type: PartitionType,
) -> binder::Result<()> {
check_manage_access()?;
- let size_bytes = size_bytes.try_into().map_err(|e| {
- Status::new_exception_str(
- ExceptionCode::ILLEGAL_ARGUMENT,
- Some(format!("Invalid size {}: {:?}", size_bytes, e)),
- )
- })?;
+ let size_bytes = size_bytes
+ .try_into()
+ .with_context(|| format!("Invalid size: {}", size_bytes))
+ .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT)?;
let size_bytes = round_up(size_bytes, PARTITION_GRANULARITY_BYTES);
let image = clone_file(image_fd)?;
// initialize the file. Any data in the file will be erased.
- image.set_len(0).map_err(|e| {
- Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to reset a file: {:?}", e)),
- )
- })?;
- let mut part = QcowFile::new(image, size_bytes).map_err(|e| {
- Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to create QCOW2 image: {:?}", e)),
- )
- })?;
+ image.set_len(0).context("Failed to reset a file").or_service_specific_exception(-1)?;
+ let mut part = QcowFile::new(image, size_bytes)
+ .context("Failed to create QCOW2 image")
+ .or_service_specific_exception(-1)?;
match partition_type {
PartitionType::RAW => Ok(()),
@@ -243,12 +234,8 @@
format!("Unsupported partition type {:?}", partition_type),
)),
}
- .map_err(|e| {
- Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to initialize partition as {:?}: {:?}", partition_type, e)),
- )
- })?;
+ .with_context(|| format!("Failed to initialize partition as {:?}", partition_type))
+ .or_service_specific_exception(-1)?;
Ok(())
}
@@ -261,8 +248,7 @@
) -> binder::Result<()> {
check_manage_access()?;
- create_or_update_idsig_file(input_fd, idsig_fd)
- .map_err(|e| Status::new_service_specific_error_str(-1, Some(format!("{:?}", e))))?;
+ create_or_update_idsig_file(input_fd, idsig_fd).or_service_specific_exception(-1)?;
Ok(())
}
@@ -309,10 +295,8 @@
}
}
}
- Err(Status::new_service_specific_error_str(
- -1,
- Some("Too many attempts to create VM context failed."),
- ))
+ Err(anyhow!("Too many attempts to create VM context failed"))
+ .or_service_specific_exception(-1)
}
fn create_vm_internal(
@@ -381,12 +365,12 @@
let (is_app_config, config) = match config {
VirtualMachineConfig::RawConfig(config) => (false, BorrowedOrOwned::Borrowed(config)),
VirtualMachineConfig::AppConfig(config) => {
- let config =
- load_app_config(config, &debug_config, &temporary_directory).map_err(|e| {
+ let config = load_app_config(config, &debug_config, &temporary_directory)
+ .or_service_specific_exception_with(-1, |e| {
*is_protected = config.protectedVm;
let message = format!("Failed to load app config: {:?}", e);
error!("{}", message);
- Status::new_service_specific_error_str(-1, Some(message))
+ message
})?;
(true, BorrowedOrOwned::Owned(config))
}
@@ -410,26 +394,21 @@
}
})
.try_for_each(check_label_for_partition)
- .map_err(|e| Status::new_service_specific_error_str(-1, Some(format!("{:?}", e))))?;
+ .or_service_specific_exception(-1)?;
let kernel = maybe_clone_file(&config.kernel)?;
let initrd = maybe_clone_file(&config.initrd)?;
// In a protected VM, we require custom kernels to come from a trusted source (b/237054515).
if config.protectedVm {
- check_label_for_kernel_files(&kernel, &initrd).map_err(|e| {
- Status::new_service_specific_error_str(-1, Some(format!("{:?}", e)))
- })?;
+ check_label_for_kernel_files(&kernel, &initrd).or_service_specific_exception(-1)?;
}
let zero_filler_path = temporary_directory.join("zero.img");
- write_zero_filler(&zero_filler_path).map_err(|e| {
- error!("Failed to make composite image: {:?}", e);
- Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to make composite image: {:?}", e)),
- )
- })?;
+ write_zero_filler(&zero_filler_path)
+ .context("Failed to make composite image")
+ .with_log()
+ .or_service_specific_exception(-1)?;
// Assemble disk images if needed.
let disks = config
@@ -450,28 +429,21 @@
CpuTopology::MATCH_HOST => (None, true),
CpuTopology::ONE_CPU => (NonZeroU32::new(1), false),
val => {
- error!("Unexpected value of CPU topology: {:?}", val);
- return Err(Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to parse CPU topology value: {:?}", val)),
- ));
+ return Err(anyhow!("Failed to parse CPU topology value {:?}", val))
+ .with_log()
+ .or_service_specific_exception(-1);
}
};
let devices_dtbo = if !config.devices.is_empty() {
let mut set = HashSet::new();
for device in config.devices.iter() {
- let path = canonicalize(device).map_err(|e| {
- Status::new_exception_str(
- ExceptionCode::ILLEGAL_ARGUMENT,
- Some(format!("can't canonicalize {device}: {e:?}")),
- )
- })?;
+ let path = canonicalize(device)
+ .with_context(|| format!("can't canonicalize {device}"))
+ .or_service_specific_exception(-1)?;
if !set.insert(path) {
- return Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_ARGUMENT,
- Some(format!("duplicated device {device}")),
- ));
+ return Err(anyhow!("duplicated device {device}"))
+ .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
}
}
let dtbo_path = temporary_directory.join("dtbo");
@@ -533,13 +505,9 @@
requester_debug_pid,
vm_context,
)
- .map_err(|e| {
- error!("Failed to create VM with config {:?}: {:?}", config, e);
- Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to create VM: {:?}", e)),
- )
- })?,
+ .with_context(|| format!("Failed to create VM with config {:?}", config))
+ .with_log()
+ .or_service_specific_exception(-1)?,
);
state.add_vm(Arc::downgrade(&instance));
Ok(VirtualMachine::create(instance))
@@ -590,10 +558,8 @@
let image = if !disk.partitions.is_empty() {
if disk.image.is_some() {
warn!("DiskImage {:?} contains both image and partitions.", disk);
- return Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_ARGUMENT,
- Some("DiskImage contains both image and partitions."),
- ));
+ return Err(anyhow!("DiskImage contains both image and partitions"))
+ .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
}
let composite_image_filenames =
@@ -605,13 +571,9 @@
&composite_image_filenames.header,
&composite_image_filenames.footer,
)
- .map_err(|e| {
- error!("Failed to make composite image with config {:?}: {:?}", disk, e);
- Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to make composite image: {:?}", e)),
- )
- })?;
+ .with_context(|| format!("Failed to make composite disk image with config {:?}", disk))
+ .with_log()
+ .or_service_specific_exception(-1)?;
// Pass the file descriptors for the various partition files to crosvm when it
// is run.
@@ -622,10 +584,8 @@
clone_file(image)?
} else {
warn!("DiskImage {:?} didn't contain image or partitions.", disk);
- return Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_ARGUMENT,
- Some("DiskImage didn't contain image or partitions."),
- ));
+ return Err(anyhow!("DiskImage didn't contain image or partitions."))
+ .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
};
Ok(DiskFile { image, writable: disk.writable })
@@ -783,10 +743,8 @@
if perm_svc.checkPermission(perm, calling_pid, calling_uid as i32)? {
Ok(())
} else {
- Err(Status::new_exception_str(
- ExceptionCode::SECURITY,
- Some(format!("does not have the {} permission", perm)),
- ))
+ Err(anyhow!("does not have the {} permission", perm))
+ .or_binder_exception(ExceptionCode::SECURITY)
}
}
@@ -892,40 +850,41 @@
}
fn start(&self) -> binder::Result<()> {
- self.instance.start().map_err(|e| {
- error!("Error starting VM with CID {}: {:?}", self.instance.cid, e);
- Status::new_service_specific_error_str(-1, Some(e.to_string()))
- })
+ self.instance
+ .start()
+ .with_context(|| format!("Error starting VM with CID {}", self.instance.cid))
+ .with_log()
+ .or_service_specific_exception(-1)
}
fn stop(&self) -> binder::Result<()> {
- self.instance.kill().map_err(|e| {
- error!("Error stopping VM with CID {}: {:?}", self.instance.cid, e);
- Status::new_service_specific_error_str(-1, Some(e.to_string()))
- })
+ self.instance
+ .kill()
+ .with_context(|| format!("Error stopping VM with CID {}", self.instance.cid))
+ .with_log()
+ .or_service_specific_exception(-1)
}
fn onTrimMemory(&self, level: MemoryTrimLevel) -> binder::Result<()> {
- self.instance.trim_memory(level).map_err(|e| {
- error!("Error trimming VM with CID {}: {:?}", self.instance.cid, e);
- Status::new_service_specific_error_str(-1, Some(e.to_string()))
- })
+ self.instance
+ .trim_memory(level)
+ .with_context(|| format!("Error trimming VM with CID {}", self.instance.cid))
+ .with_log()
+ .or_service_specific_exception(-1)
}
fn connectVsock(&self, port: i32) -> binder::Result<ParcelFileDescriptor> {
if !matches!(&*self.instance.vm_state.lock().unwrap(), VmState::Running { .. }) {
- return Err(Status::new_service_specific_error_str(-1, Some("VM is not running")));
+ return Err(anyhow!("VM is not running")).or_service_specific_exception(-1);
}
let port = port as u32;
if port < 1024 {
- return Err(Status::new_service_specific_error_str(
- -1,
- Some(format!("Can't connect to privileged port {port}")),
- ));
+ return Err(anyhow!("Can't connect to privileged port {port}"))
+ .or_service_specific_exception(-1);
}
- let stream = VsockStream::connect_with_cid_port(self.instance.cid, port).map_err(|e| {
- Status::new_service_specific_error_str(-1, Some(format!("Failed to connect: {:?}", e)))
- })?;
+ let stream = VsockStream::connect_with_cid_port(self.instance.cid, port)
+ .context("Failed to connect")
+ .or_service_specific_exception(-1)?;
Ok(vsock_stream_to_pfd(stream))
}
}
@@ -1051,17 +1010,15 @@
}
/// Converts a `&ParcelFileDescriptor` to a `File` by cloning the file.
-pub fn clone_file(file: &ParcelFileDescriptor) -> Result<File, Status> {
- file.as_ref().try_clone().map_err(|e| {
- Status::new_exception_str(
- ExceptionCode::BAD_PARCELABLE,
- Some(format!("Failed to clone File from ParcelFileDescriptor: {:?}", e)),
- )
- })
+pub fn clone_file(file: &ParcelFileDescriptor) -> binder::Result<File> {
+ file.as_ref()
+ .try_clone()
+ .context("Failed to clone File from ParcelFileDescriptor")
+ .or_binder_exception(ExceptionCode::BAD_PARCELABLE)
}
/// Converts an `&Option<ParcelFileDescriptor>` to an `Option<File>` by cloning the file.
-fn maybe_clone_file(file: &Option<ParcelFileDescriptor>) -> Result<Option<File>, Status> {
+fn maybe_clone_file(file: &Option<ParcelFileDescriptor>) -> binder::Result<Option<File>> {
file.as_ref().map(clone_file).transpose()
}
@@ -1073,13 +1030,10 @@
}
/// Parses the platform version requirement string.
-fn parse_platform_version_req(s: &str) -> Result<VersionReq, Status> {
- VersionReq::parse(s).map_err(|e| {
- Status::new_exception_str(
- ExceptionCode::BAD_PARCELABLE,
- Some(format!("Invalid platform version requirement {}: {:?}", s, e)),
- )
- })
+fn parse_platform_version_req(s: &str) -> binder::Result<VersionReq> {
+ VersionReq::parse(s)
+ .with_context(|| format!("Invalid platform version requirement {}", s))
+ .or_binder_exception(ExceptionCode::BAD_PARCELABLE)
}
/// Create the empty ramdump file
@@ -1088,13 +1042,10 @@
// VM will emit ramdump to. `ramdump_read` will be sent back to the client (i.e. the VM
// owner) for readout.
let ramdump_path = temporary_directory.join("ramdump");
- let ramdump = File::create(ramdump_path).map_err(|e| {
- error!("Failed to prepare ramdump file: {:?}", e);
- Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to prepare ramdump file: {:?}", e)),
- )
- })?;
+ let ramdump = File::create(ramdump_path)
+ .context("Failed to prepare ramdump file")
+ .with_log()
+ .or_service_specific_exception(-1)?;
Ok(ramdump)
}
@@ -1107,20 +1058,16 @@
fn check_gdb_allowed(config: &VirtualMachineConfig) -> binder::Result<()> {
if is_protected(config) {
- return Err(Status::new_exception_str(
- ExceptionCode::SECURITY,
- Some("can't use gdb with protected VMs"),
- ));
+ return Err(anyhow!("Can't use gdb with protected VMs"))
+ .or_binder_exception(ExceptionCode::SECURITY);
}
match config {
VirtualMachineConfig::RawConfig(_) => Ok(()),
VirtualMachineConfig::AppConfig(config) => {
if config.debugLevel != DebugLevel::FULL {
- Err(Status::new_exception_str(
- ExceptionCode::SECURITY,
- Some("can't use gdb with non-debuggable VMs"),
- ))
+ Err(anyhow!("Can't use gdb with non-debuggable VMs"))
+ .or_binder_exception(ExceptionCode::SECURITY)
} else {
Ok(())
}
@@ -1150,9 +1097,8 @@
return Ok(None);
};
- let (raw_read_fd, raw_write_fd) = pipe().map_err(|e| {
- Status::new_service_specific_error_str(-1, Some(format!("Failed to create pipe: {:?}", e)))
- })?;
+ let (raw_read_fd, raw_write_fd) =
+ pipe().context("Failed to create pipe").or_service_specific_exception(-1)?;
// SAFETY: We are the sole owner of this FD as we just created it, and it is valid and open.
let mut reader = BufReader::new(unsafe { File::from_raw_fd(raw_read_fd) });
@@ -1212,9 +1158,8 @@
let cid = self.cid;
if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
info!("VM with CID {} started payload", cid);
- vm.update_payload_state(PayloadState::Started).map_err(|e| {
- Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
- })?;
+ vm.update_payload_state(PayloadState::Started)
+ .or_binder_exception(ExceptionCode::ILLEGAL_STATE)?;
vm.callbacks.notify_payload_started(cid);
let vm_start_timestamp = vm.vm_metric.lock().unwrap().start_timestamp;
@@ -1222,10 +1167,7 @@
Ok(())
} else {
error!("notifyPayloadStarted is called from an unknown CID {}", cid);
- Err(Status::new_service_specific_error_str(
- -1,
- Some(format!("cannot find a VM with CID {}", cid)),
- ))
+ Err(anyhow!("cannot find a VM with CID {}", cid)).or_service_specific_exception(-1)
}
}
@@ -1233,17 +1175,13 @@
let cid = self.cid;
if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
info!("VM with CID {} reported payload is ready", cid);
- vm.update_payload_state(PayloadState::Ready).map_err(|e| {
- Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
- })?;
+ vm.update_payload_state(PayloadState::Ready)
+ .or_binder_exception(ExceptionCode::ILLEGAL_STATE)?;
vm.callbacks.notify_payload_ready(cid);
Ok(())
} else {
error!("notifyPayloadReady is called from an unknown CID {}", cid);
- Err(Status::new_service_specific_error_str(
- -1,
- Some(format!("cannot find a VM with CID {}", cid)),
- ))
+ Err(anyhow!("cannot find a VM with CID {}", cid)).or_service_specific_exception(-1)
}
}
@@ -1251,17 +1189,13 @@
let cid = self.cid;
if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
info!("VM with CID {} finished payload", cid);
- vm.update_payload_state(PayloadState::Finished).map_err(|e| {
- Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
- })?;
+ vm.update_payload_state(PayloadState::Finished)
+ .or_binder_exception(ExceptionCode::ILLEGAL_STATE)?;
vm.callbacks.notify_payload_finished(cid, exit_code);
Ok(())
} else {
error!("notifyPayloadFinished is called from an unknown CID {}", cid);
- Err(Status::new_service_specific_error_str(
- -1,
- Some(format!("cannot find a VM with CID {}", cid)),
- ))
+ Err(anyhow!("cannot find a VM with CID {}", cid)).or_service_specific_exception(-1)
}
}
@@ -1269,17 +1203,13 @@
let cid = self.cid;
if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
info!("VM with CID {} encountered an error", cid);
- vm.update_payload_state(PayloadState::Finished).map_err(|e| {
- Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
- })?;
+ vm.update_payload_state(PayloadState::Finished)
+ .or_binder_exception(ExceptionCode::ILLEGAL_STATE)?;
vm.callbacks.notify_error(cid, error_code, message);
Ok(())
} else {
error!("notifyError is called from an unknown CID {}", cid);
- Err(Status::new_service_specific_error_str(
- -1,
- Some(format!("cannot find a VM with CID {}", cid)),
- ))
+ Err(anyhow!("cannot find a VM with CID {}", cid)).or_service_specific_exception(-1)
}
}
@@ -1287,10 +1217,8 @@
let cid = self.cid;
let Some(vm) = self.state.lock().unwrap().get_vm(cid) else {
error!("requestCertificate is called from an unknown CID {cid}");
- return Err(Status::new_service_specific_error_str(
- -1,
- Some(format!("cannot find a VM with CID {}", cid)),
- ));
+ return Err(anyhow!("cannot find a VM with CID {}", cid))
+ .or_service_specific_exception(-1);
};
let instance_img_path = vm.temporary_directory.join("rkpvm_instance.img");
let instance_img = OpenOptions::new()
@@ -1298,13 +1226,9 @@
.read(true)
.write(true)
.open(instance_img_path)
- .map_err(|e| {
- error!("Failed to create rkpvm_instance.img file: {:?}", e);
- Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to create rkpvm_instance.img file: {:?}", e)),
- )
- })?;
+ .context("Failed to create rkpvm_instance.img file")
+ .with_log()
+ .or_service_specific_exception(-1)?;
GLOBAL_SERVICE.requestCertificate(csr, &ParcelFileDescriptor::new(instance_img))
}
}
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 68cc7f2..6372fa8 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -529,8 +529,10 @@
MemoryTrimLevel::TRIM_MEMORY_RUNNING_MODERATE => 10,
_ => bail!("Invalid memory trim level {:?}", level),
};
- let command =
- BalloonControlCommand::Adjust { num_bytes: total_memory * pct / 100 };
+ let command = BalloonControlCommand::Adjust {
+ num_bytes: total_memory * pct / 100,
+ wait_for_success: false,
+ };
if let Err(e) = vm_control::client::handle_request(
&VmRequest::BalloonCommand(command),
&self.crosvm_control_socket_path,
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 6b39ff9..67890e2 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -27,12 +27,13 @@
"android.os.permissions_aidl-rust",
"libandroid_logger",
"libanyhow",
+ "libavflog",
"libbinder_rs",
- "libvmclient",
"liblibc",
"liblog_rust",
"libnix",
"librustutils",
+ "libvmclient",
"libstatslog_virtualization_rust",
"libtombstoned_client_rust",
"libvsock",
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 2e667d4..b2513d9 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -33,7 +33,8 @@
};
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::VM_TOMBSTONES_SERVICE_PORT;
use anyhow::{anyhow, ensure, Context, Result};
-use binder::{self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, Status, Strong};
+use avflog::LogResult;
+use binder::{self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, Status, Strong, IntoBinderResult};
use libc::VMADDR_CID_HOST;
use log::{error, info, warn};
use rustutils::system_properties;
@@ -42,7 +43,7 @@
use std::io::{Read, Write};
use std::os::unix::fs::PermissionsExt;
use std::os::unix::raw::{pid_t, uid_t};
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, Weak};
use tombstoned_client::{DebuggerdDumpType, TombstonedConnection};
use vsock::{VsockListener, VsockStream};
@@ -102,15 +103,10 @@
match ret {
0 => Ok(()),
- -1 => Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_STATE,
- Some(std::io::Error::last_os_error().to_string()),
- )),
- n => Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_STATE,
- Some(format!("Unexpected return value from prlimit(): {n}")),
- )),
+ -1 => Err(std::io::Error::last_os_error().into()),
+ n => Err(anyhow!("Unexpected return value from prlimit(): {n}")),
}
+ .or_binder_exception(ExceptionCode::ILLEGAL_STATE)
}
fn allocateGlobalVmContext(
@@ -122,9 +118,9 @@
let requester_uid = get_calling_uid();
let requester_debug_pid = requester_debug_pid as pid_t;
let state = &mut *self.state.lock().unwrap();
- state.allocate_vm_context(requester_uid, requester_debug_pid).map_err(|e| {
- Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
- })
+ state
+ .allocate_vm_context(requester_uid, requester_debug_pid)
+ .or_binder_exception(ExceptionCode::ILLEGAL_STATE)
}
fn atomVmBooted(&self, atom: &AtomVmBooted) -> Result<(), Status> {
@@ -167,20 +163,22 @@
) -> binder::Result<Vec<u8>> {
check_manage_access()?;
info!("Received csr. Getting certificate...");
- request_certificate(csr, instance_img_fd).map_err(|e| {
- error!("Failed to get certificate. Error: {e:?}");
- Status::new_exception_str(ExceptionCode::SERVICE_SPECIFIC, Some(e.to_string()))
- })
+ request_certificate(csr, instance_img_fd)
+ .context("Failed to get certificate")
+ .with_log()
+ .or_service_specific_exception(-1)
}
fn getAssignableDevices(&self) -> binder::Result<Vec<AssignableDevice>> {
check_use_custom_virtual_machine()?;
// TODO(b/291191362): read VM DTBO to find assignable devices.
- Ok(vec![AssignableDevice {
- kind: "eh".to_owned(),
- node: "/sys/bus/platform/devices/16d00000.eh".to_owned(),
- }])
+ let mut devices = Vec::new();
+ let eh_path = "/sys/bus/platform/devices/16d00000.eh";
+ if Path::new(eh_path).exists() {
+ devices.push(AssignableDevice { kind: "eh".to_owned(), node: eh_path.to_owned() });
+ }
+ Ok(devices)
}
fn bindDevicesToVfioDriver(
@@ -403,10 +401,8 @@
if perm_svc.checkPermission(perm, calling_pid, calling_uid as i32)? {
Ok(())
} else {
- Err(Status::new_exception_str(
- ExceptionCode::SECURITY,
- Some(format!("does not have the {} permission", perm)),
- ))
+ Err(anyhow!("does not have the {} permission", perm))
+ .or_binder_exception(ExceptionCode::SECURITY)
}
}
diff --git a/virtualizationservice/vfio_handler/Android.bp b/virtualizationservice/vfio_handler/Android.bp
index efbb7b5..66662d5 100644
--- a/virtualizationservice/vfio_handler/Android.bp
+++ b/virtualizationservice/vfio_handler/Android.bp
@@ -22,10 +22,13 @@
rustlibs: [
"android.system.virtualizationservice_internal-rust",
"libandroid_logger",
+ "libanyhow",
"libbinder_rs",
+ "liblazy_static",
"liblog_rust",
"libnix",
- "liblazy_static",
+ "librustutils",
+ "libzerocopy",
],
apex_available: ["com.android.virt"],
}
diff --git a/virtualizationservice/vfio_handler/src/aidl.rs b/virtualizationservice/vfio_handler/src/aidl.rs
index f082aba..bb9faf1 100644
--- a/virtualizationservice/vfio_handler/src/aidl.rs
+++ b/virtualizationservice/vfio_handler/src/aidl.rs
@@ -14,13 +14,20 @@
//! Implementation of the AIDL interface of the VirtualizationService.
+use anyhow::{anyhow, Context};
use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::IVfioHandler::IVfioHandler;
use android_system_virtualizationservice_internal::binder::ParcelFileDescriptor;
-use binder::{self, ExceptionCode, Interface, Status};
+use binder::{self, ExceptionCode, Interface, IntoBinderResult};
use lazy_static::lazy_static;
-use std::fs::{read_link, write};
-use std::io::Write;
-use std::path::Path;
+use std::fs::{read_link, write, File};
+use std::io::{Read, Seek, SeekFrom, Write};
+use std::mem::size_of;
+use std::path::{Path, PathBuf};
+use rustutils::system_properties;
+use zerocopy::{
+ byteorder::{BigEndian, U32},
+ FromBytes,
+};
#[derive(Debug, Default)]
pub struct VfioHandler {}
@@ -41,27 +48,13 @@
) -> binder::Result<()> {
// permission check is already done by IVirtualizationServiceInternal.
if !*IS_VFIO_SUPPORTED {
- return Err(Status::new_exception_str(
- ExceptionCode::UNSUPPORTED_OPERATION,
- Some("VFIO-platform not supported"),
- ));
+ return Err(anyhow!("VFIO-platform not supported"))
+ .or_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION);
}
-
devices.iter().try_for_each(|x| bind_device(Path::new(x)))?;
- let mut dtbo = dtbo.as_ref().try_clone().map_err(|e| {
- Status::new_exception_str(
- ExceptionCode::BAD_PARCELABLE,
- Some(format!("Failed to clone File from ParcelFileDescriptor: {e:?}")),
- )
- })?;
- // TODO(b/291191362): write DTBO for devices to dtbo.
- dtbo.write(b"\n").map_err(|e| {
- Status::new_exception_str(
- ExceptionCode::BAD_PARCELABLE,
- Some(format!("Can't write to ParcelFileDescriptor: {e:?}")),
- )
- })?;
+ write_dtbo(dtbo)?;
+
Ok(())
}
}
@@ -70,28 +63,63 @@
const SYSFS_PLATFORM_DEVICES_PATH: &str = "/sys/devices/platform/";
const VFIO_PLATFORM_DRIVER_PATH: &str = "/sys/bus/platform/drivers/vfio-platform";
const SYSFS_PLATFORM_DRIVERS_PROBE_PATH: &str = "/sys/bus/platform/drivers_probe";
+const DT_TABLE_MAGIC: u32 = 0xd7b7ab1e;
-lazy_static! {
- static ref IS_VFIO_SUPPORTED: bool = is_vfio_supported();
+/// The structure of DT table header in dtbo.img.
+/// https://source.android.com/docs/core/architecture/dto/partitions
+#[repr(C)]
+#[derive(Debug, FromBytes)]
+struct DtTableHeader {
+ /// DT_TABLE_MAGIC
+ magic: U32<BigEndian>,
+ /// includes dt_table_header + all dt_table_entry and all dtb/dtbo
+ _total_size: U32<BigEndian>,
+ /// sizeof(dt_table_header)
+ header_size: U32<BigEndian>,
+ /// sizeof(dt_table_entry)
+ dt_entry_size: U32<BigEndian>,
+ /// number of dt_table_entry
+ dt_entry_count: U32<BigEndian>,
+ /// offset to the first dt_table_entry from head of dt_table_header
+ dt_entries_offset: U32<BigEndian>,
+ /// flash page size we assume
+ _page_size: U32<BigEndian>,
+ /// DTBO image version, the current version is 0. The version will be
+ /// incremented when the dt_table_header struct is updated.
+ _version: U32<BigEndian>,
}
-fn is_vfio_supported() -> bool {
- Path::new(DEV_VFIO_PATH).exists() && Path::new(VFIO_PLATFORM_DRIVER_PATH).exists()
+/// The structure of each DT table entry (v0) in dtbo.img.
+/// https://source.android.com/docs/core/architecture/dto/partitions
+#[repr(C)]
+#[derive(Debug, FromBytes)]
+struct DtTableEntry {
+ /// size of each DT
+ dt_size: U32<BigEndian>,
+ /// offset from head of dt_table_header
+ dt_offset: U32<BigEndian>,
+ /// optional, must be zero if unused
+ _id: U32<BigEndian>,
+ /// optional, must be zero if unused
+ _rev: U32<BigEndian>,
+ /// optional, must be zero if unused
+ _custom: [U32<BigEndian>; 4],
+}
+
+lazy_static! {
+ static ref IS_VFIO_SUPPORTED: bool =
+ Path::new(DEV_VFIO_PATH).exists() && Path::new(VFIO_PLATFORM_DRIVER_PATH).exists();
}
fn check_platform_device(path: &Path) -> binder::Result<()> {
if !path.exists() {
- return Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_ARGUMENT,
- Some(format!("no such device {path:?}")),
- ));
+ return Err(anyhow!("no such device {path:?}"))
+ .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
}
if !path.starts_with(SYSFS_PLATFORM_DEVICES_PATH) {
- return Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_ARGUMENT,
- Some(format!("{path:?} is not a platform device")),
- ));
+ return Err(anyhow!("{path:?} is not a platform device"))
+ .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
}
Ok(())
@@ -121,65 +149,155 @@
// unbind
let Some(device) = path.file_name() else {
- return Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_ARGUMENT,
- Some(format!("can't get device name from {path:?}")),
- ));
+ return Err(anyhow!("can't get device name from {path:?}"))
+ .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
};
let Some(device_str) = device.to_str() else {
- return Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_ARGUMENT,
- Some(format!("invalid filename {device:?}")),
- ));
+ return Err(anyhow!("invalid filename {device:?}"))
+ .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
};
- write(path.join("driver/unbind"), device_str.as_bytes()).map_err(|e| {
- Status::new_exception_str(
- ExceptionCode::SERVICE_SPECIFIC,
- Some(format!("could not unbind {device_str}: {e:?}")),
- )
- })?;
+ let unbind_path = path.join("driver/unbind");
+ if unbind_path.exists() {
+ write(&unbind_path, device_str.as_bytes())
+ .with_context(|| format!("could not unbind {device_str}"))
+ .or_service_specific_exception(-1)?;
+ }
// bind to VFIO
- write(path.join("driver_override"), b"vfio-platform").map_err(|e| {
- Status::new_exception_str(
- ExceptionCode::SERVICE_SPECIFIC,
- Some(format!("could not bind {device_str} to vfio-platform: {e:?}")),
- )
- })?;
+ write(path.join("driver_override"), b"vfio-platform")
+ .with_context(|| format!("could not bind {device_str} to vfio-platform"))
+ .or_service_specific_exception(-1)?;
- write(SYSFS_PLATFORM_DRIVERS_PROBE_PATH, device_str.as_bytes()).map_err(|e| {
- Status::new_exception_str(
- ExceptionCode::SERVICE_SPECIFIC,
- Some(format!("could not write {device_str} to drivers-probe: {e:?}")),
- )
- })?;
+ write(SYSFS_PLATFORM_DRIVERS_PROBE_PATH, device_str.as_bytes())
+ .with_context(|| format!("could not write {device_str} to drivers-probe"))
+ .or_service_specific_exception(-1)?;
// final check
if !is_bound_to_vfio_driver(path) {
- return Err(Status::new_exception_str(
- ExceptionCode::SERVICE_SPECIFIC,
- Some(format!("{path:?} still not bound to vfio driver")),
- ));
+ return Err(anyhow!("{path:?} still not bound to vfio driver"))
+ .or_service_specific_exception(-1);
}
if get_device_iommu_group(path).is_none() {
- return Err(Status::new_exception_str(
- ExceptionCode::SERVICE_SPECIFIC,
- Some(format!("can't get iommu group for {path:?}")),
- ));
+ return Err(anyhow!("can't get iommu group for {path:?}"))
+ .or_service_specific_exception(-1);
}
Ok(())
}
fn bind_device(path: &Path) -> binder::Result<()> {
- let path = path.canonicalize().map_err(|e| {
- Status::new_exception_str(
- ExceptionCode::ILLEGAL_ARGUMENT,
- Some(format!("can't canonicalize {path:?}: {e:?}")),
- )
- })?;
+ let path = path
+ .canonicalize()
+ .with_context(|| format!("can't canonicalize {path:?}"))
+ .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT)?;
check_platform_device(&path)?;
bind_vfio_driver(&path)
}
+
+fn get_dtbo_img_path() -> binder::Result<PathBuf> {
+ let slot_suffix = system_properties::read("ro.boot.slot_suffix")
+ .context("Failed to read ro.boot.slot_suffix")
+ .or_service_specific_exception(-1)?
+ .ok_or_else(|| anyhow!("slot_suffix is none"))
+ .or_service_specific_exception(-1)?;
+ Ok(PathBuf::from(format!("/dev/block/by-name/dtbo{slot_suffix}")))
+}
+
+fn read_values(file: &mut File, size: usize, offset: u64) -> binder::Result<Vec<u8>> {
+ file.seek(SeekFrom::Start(offset))
+ .context("Cannot seek the offset")
+ .or_service_specific_exception(-1)?;
+ let mut buffer = vec![0_u8; size];
+ file.read_exact(&mut buffer)
+ .context("Failed to read buffer")
+ .or_service_specific_exception(-1)?;
+ Ok(buffer)
+}
+
+fn get_dt_table_header(file: &mut File) -> binder::Result<DtTableHeader> {
+ let values = read_values(file, size_of::<DtTableHeader>(), 0)?;
+ let dt_table_header = DtTableHeader::read_from(values.as_slice())
+ .context("DtTableHeader is invalid")
+ .or_service_specific_exception(-1)?;
+ if dt_table_header.magic.get() != DT_TABLE_MAGIC
+ || dt_table_header.header_size.get() as usize != size_of::<DtTableHeader>()
+ {
+ return Err(anyhow!("DtTableHeader is invalid")).or_service_specific_exception(-1)?;
+ }
+ Ok(dt_table_header)
+}
+
+fn get_dt_table_entry(
+ file: &mut File,
+ header: &DtTableHeader,
+ index: u32,
+) -> binder::Result<DtTableEntry> {
+ if index >= header.dt_entry_count.get() {
+ return Err(anyhow!("Invalid dtbo index {index}")).or_service_specific_exception(-1)?;
+ }
+ let Some(prev_dt_entry_total_size) = header.dt_entry_size.get().checked_mul(index) else {
+ return Err(anyhow!("Unexpected arithmetic result"))
+ .or_binder_exception(ExceptionCode::ILLEGAL_STATE);
+ };
+ let Some(dt_entry_offset) =
+ prev_dt_entry_total_size.checked_add(header.dt_entries_offset.get())
+ else {
+ return Err(anyhow!("Unexpected arithmetic result"))
+ .or_binder_exception(ExceptionCode::ILLEGAL_STATE);
+ };
+ let values = read_values(file, size_of::<DtTableEntry>(), dt_entry_offset.into())?;
+ let dt_table_entry = DtTableEntry::read_from(values.as_slice())
+ .with_context(|| format!("DtTableEntry at index {index} is invalid."))
+ .or_service_specific_exception(-1)?;
+ Ok(dt_table_entry)
+}
+
+fn filter_dtbo_from_img(
+ dtbo_img_file: &mut File,
+ entry: &DtTableEntry,
+ dtbo_fd: &ParcelFileDescriptor,
+) -> binder::Result<()> {
+ let dt_size = entry
+ .dt_size
+ .get()
+ .try_into()
+ .context("Failed to convert type")
+ .or_binder_exception(ExceptionCode::ILLEGAL_STATE)?;
+ let buffer = read_values(dtbo_img_file, dt_size, entry.dt_offset.get().into())?;
+
+ let mut dtbo_fd = dtbo_fd
+ .as_ref()
+ .try_clone()
+ .context("Failed to clone File from ParcelFileDescriptor")
+ .or_binder_exception(ExceptionCode::BAD_PARCELABLE)?;
+
+ // TODO(b/296796644): Filter dtbo.img, not writing all information.
+ dtbo_fd
+ .write_all(&buffer)
+ .context("Failed to write dtbo file")
+ .or_service_specific_exception(-1)?;
+ Ok(())
+}
+
+fn write_dtbo(dtbo_fd: &ParcelFileDescriptor) -> binder::Result<()> {
+ let dtbo_path = get_dtbo_img_path()?;
+ let mut dtbo_img = File::open(dtbo_path)
+ .context("Failed to open DTBO partition")
+ .or_service_specific_exception(-1)?;
+
+ let dt_table_header = get_dt_table_header(&mut dtbo_img)?;
+ let vm_dtbo_idx = system_properties::read("ro.boot.hypervisor.vm_dtbo_idx")
+ .context("Failed to read vm_dtbo_idx")
+ .or_service_specific_exception(-1)?
+ .ok_or_else(|| anyhow!("vm_dtbo_idx is none"))
+ .or_service_specific_exception(-1)?;
+ let vm_dtbo_idx = vm_dtbo_idx
+ .parse()
+ .context("vm_dtbo_idx is not an integer")
+ .or_service_specific_exception(-1)?;
+ let dt_table_entry = get_dt_table_entry(&mut dtbo_img, &dt_table_header, vm_dtbo_idx)?;
+ filter_dtbo_from_img(&mut dtbo_img, &dt_table_entry, dtbo_fd)?;
+ Ok(())
+}
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 64bcd02..4c44496 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -24,7 +24,7 @@
};
use anyhow::{Context, Error};
use binder::{ProcessState, Strong};
-use clap::Parser;
+use clap::{Args, Parser};
use create_idsig::command_create_idsig;
use create_partition::command_create_partition;
use run::{command_run, command_run_app, command_run_microdroid};
@@ -34,201 +34,165 @@
#[derive(Debug)]
struct Idsigs(Vec<PathBuf>);
+#[derive(Args)]
+/// Collection of flags that are at VM level and therefore applicable to all subcommands
+pub struct CommonConfig {
+ /// Name of VM
+ #[arg(long)]
+ name: Option<String>,
+
+ /// Run VM with vCPU topology matching that of the host. If unspecified, defaults to 1 vCPU.
+ #[arg(long, default_value = "one_cpu", value_parser = parse_cpu_topology)]
+ cpu_topology: CpuTopology,
+
+ /// Comma separated list of task profile names to apply to the VM
+ #[arg(long)]
+ task_profiles: Vec<String>,
+
+ /// Memory size (in MiB) of the VM. If unspecified, defaults to the value of `memory_mib`
+ /// in the VM config file.
+ #[arg(short, long)]
+ mem: Option<u32>,
+
+ /// Run VM in protected mode.
+ #[arg(short, long)]
+ protected: bool,
+}
+
+#[derive(Args)]
+/// Collection of flags for debugging
+pub struct DebugConfig {
+ /// Debug level of the VM. Supported values: "full" (default), and "none".
+ #[arg(long, default_value = "full", value_parser = parse_debug_level)]
+ debug: DebugLevel,
+
+ /// Path to file for VM console output.
+ #[arg(long)]
+ console: Option<PathBuf>,
+
+ /// Path to file for VM console input.
+ #[arg(long)]
+ console_in: Option<PathBuf>,
+
+ /// Path to file for VM log output.
+ #[arg(long)]
+ log: Option<PathBuf>,
+
+ /// Port at which crosvm will start a gdb server to debug guest kernel.
+ /// Note: this is only supported on Android kernels android14-5.15 and higher.
+ #[arg(long)]
+ gdb: Option<NonZeroU16>,
+}
+
+#[derive(Args)]
+/// Collection of flags that are Microdroid specific
+pub struct MicrodroidConfig {
+ /// Path to the file backing the storage.
+ /// Created if the option is used but the path does not exist in the device.
+ #[arg(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
+ #[arg(long)]
+ storage_size: Option<u64>,
+
+ /// Path to custom kernel image to use when booting Microdroid.
+ #[arg(long)]
+ kernel: Option<PathBuf>,
+
+ /// Path to disk image containing vendor-specific modules.
+ #[arg(long)]
+ vendor: Option<PathBuf>,
+
+ /// SysFS nodes of devices to assign to VM
+ #[arg(long)]
+ devices: Vec<PathBuf>,
+}
+
+#[derive(Args)]
+/// Flags for the run_app subcommand
+pub struct RunAppConfig {
+ #[command(flatten)]
+ common: CommonConfig,
+
+ #[command(flatten)]
+ debug: DebugConfig,
+
+ #[command(flatten)]
+ microdroid: MicrodroidConfig,
+
+ /// Path to VM Payload APK
+ apk: PathBuf,
+
+ /// Path to idsig of the APK
+ idsig: PathBuf,
+
+ /// Path to the instance image. Created if not exists.
+ instance: PathBuf,
+
+ /// Path to VM config JSON within APK (e.g. assets/vm_config.json)
+ #[arg(long)]
+ config_path: Option<String>,
+
+ /// Name of VM payload binary within APK (e.g. MicrodroidTestNativeLib.so)
+ #[arg(long)]
+ #[arg(alias = "payload_path")]
+ payload_binary_name: Option<String>,
+
+ /// Paths to extra idsig files.
+ #[arg(long = "extra-idsig")]
+ extra_idsigs: Vec<PathBuf>,
+}
+
+#[derive(Args)]
+/// Flags for the run_microdroid subcommand
+pub struct RunMicrodroidConfig {
+ #[command(flatten)]
+ common: CommonConfig,
+
+ #[command(flatten)]
+ debug: DebugConfig,
+
+ #[command(flatten)]
+ microdroid: MicrodroidConfig,
+
+ /// 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.
+ #[arg(long)]
+ work_dir: Option<PathBuf>,
+}
+
+#[derive(Args)]
+/// Flags for the run subcommand
+pub struct RunCustomVmConfig {
+ #[command(flatten)]
+ common: CommonConfig,
+
+ #[command(flatten)]
+ debug: DebugConfig,
+
+ /// Path to VM config JSON
+ config: PathBuf,
+}
+
#[derive(Parser)]
enum Opt {
/// Run a virtual machine with a config in APK
RunApp {
- /// Path to VM Payload APK
- apk: PathBuf,
-
- /// Path to idsig of the APK
- idsig: PathBuf,
-
- /// Path to the instance image. Created if not exists.
- instance: PathBuf,
-
- /// Path to VM config JSON within APK (e.g. assets/vm_config.json)
- #[clap(long)]
- config_path: Option<String>,
-
- /// Name of VM payload binary within APK (e.g. MicrodroidTestNativeLib.so)
- #[clap(long)]
- #[clap(alias = "payload_path")]
- payload_binary_name: Option<String>,
-
- /// Name of VM
- #[clap(long)]
- name: Option<String>,
-
- /// 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 console input.
- #[clap(long)]
- console_in: Option<PathBuf>,
-
- /// Path to file for VM log output.
- #[clap(long)]
- log: Option<PathBuf>,
-
- /// Debug level of the VM. Supported values: "full" (default), and "none".
- #[clap(long, default_value = "full", 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>,
-
- /// Run VM with vCPU topology matching that of the host. If unspecified, defaults to 1 vCPU.
- #[clap(long, default_value = "one_cpu", value_parser = parse_cpu_topology)]
- cpu_topology: CpuTopology,
-
- /// Comma separated list of task profile names to apply to the VM
- #[clap(long)]
- task_profiles: Vec<String>,
-
- /// Paths to extra idsig files.
- #[clap(long = "extra-idsig")]
- extra_idsigs: Vec<PathBuf>,
-
- /// Port at which crosvm will start a gdb server to debug guest kernel.
- /// Note: this is only supported on Android kernels android14-5.15 and higher.
- #[clap(long)]
- gdb: Option<NonZeroU16>,
-
- /// Path to custom kernel image to use when booting Microdroid.
- #[clap(long)]
- kernel: Option<PathBuf>,
-
- /// Path to disk image containing vendor-specific modules.
- #[clap(long)]
- vendor: Option<PathBuf>,
-
- /// SysFS nodes of devices to assign to VM
- #[clap(long)]
- devices: Vec<PathBuf>,
+ #[command(flatten)]
+ config: RunAppConfig,
},
/// 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>,
-
- /// 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 console input.
- #[clap(long)]
- console_in: Option<PathBuf>,
-
- /// Path to file for VM log output.
- #[clap(long)]
- log: Option<PathBuf>,
-
- /// Debug level of the VM. Supported values: "full" (default), and "none".
- #[clap(long, default_value = "full", 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>,
-
- /// Run VM with vCPU topology matching that of the host. If unspecified, defaults to 1 vCPU.
- #[clap(long, default_value = "one_cpu", value_parser = parse_cpu_topology)]
- cpu_topology: CpuTopology,
-
- /// Comma separated list of task profile names to apply to the VM
- #[clap(long)]
- task_profiles: Vec<String>,
-
- /// Port at which crosvm will start a gdb server to debug guest kernel.
- /// Note: this is only supported on Android kernels android14-5.15 and higher.
- #[clap(long)]
- gdb: Option<NonZeroU16>,
-
- /// Path to custom kernel image to use when booting Microdroid.
- #[clap(long)]
- kernel: Option<PathBuf>,
-
- /// Path to disk image containing vendor-specific modules.
- #[clap(long)]
- vendor: Option<PathBuf>,
-
- /// SysFS nodes of devices to assign to VM
- #[clap(long)]
- devices: Vec<PathBuf>,
+ #[command(flatten)]
+ config: RunMicrodroidConfig,
},
/// Run a virtual machine
Run {
- /// Path to VM config JSON
- config: PathBuf,
-
- /// Name of VM
- #[clap(long)]
- name: Option<String>,
-
- /// Run VM with vCPU topology matching that of the host. If unspecified, defaults to 1 vCPU.
- #[clap(long, default_value = "one_cpu", value_parser = parse_cpu_topology)]
- cpu_topology: CpuTopology,
-
- /// Comma separated list of task profile names to apply to the VM
- #[clap(long)]
- task_profiles: Vec<String>,
-
- /// Path to file for VM console output.
- #[clap(long)]
- console: Option<PathBuf>,
-
- /// Path to file for VM console input.
- #[clap(long)]
- console_in: Option<PathBuf>,
-
- /// Path to file for VM log output.
- #[clap(long)]
- log: Option<PathBuf>,
-
- /// Port at which crosvm will start a gdb server to debug guest kernel.
- /// Note: this is only supported on Android kernels android14-5.15 and higher.
- #[clap(long)]
- gdb: Option<NonZeroU16>,
+ #[command(flatten)]
+ config: RunCustomVmConfig,
},
/// List running virtual machines
List,
@@ -243,7 +207,7 @@
size: u64,
/// Type of the partition
- #[clap(short = 't', long = "type", default_value = "raw",
+ #[arg(short = 't', long = "type", default_value = "raw",
value_parser = parse_partition_type)]
partition_type: PartitionType,
},
@@ -295,102 +259,9 @@
ProcessState::start_thread_pool();
match opt {
- Opt::RunApp {
- name,
- apk,
- idsig,
- instance,
- storage,
- storage_size,
- config_path,
- payload_binary_name,
- console,
- console_in,
- log,
- debug,
- protected,
- mem,
- cpu_topology,
- task_profiles,
- extra_idsigs,
- gdb,
- kernel,
- vendor,
- devices,
- } => command_run_app(
- name,
- get_service()?.as_ref(),
- &apk,
- &idsig,
- &instance,
- storage.as_deref(),
- storage_size,
- config_path,
- payload_binary_name,
- console.as_deref(),
- console_in.as_deref(),
- log.as_deref(),
- debug,
- protected,
- mem,
- cpu_topology,
- task_profiles,
- &extra_idsigs,
- gdb,
- kernel.as_deref(),
- vendor.as_deref(),
- devices,
- ),
- Opt::RunMicrodroid {
- name,
- work_dir,
- storage,
- storage_size,
- console,
- console_in,
- log,
- debug,
- protected,
- mem,
- cpu_topology,
- task_profiles,
- gdb,
- kernel,
- vendor,
- devices,
- } => command_run_microdroid(
- name,
- get_service()?.as_ref(),
- work_dir,
- storage.as_deref(),
- storage_size,
- console.as_deref(),
- console_in.as_deref(),
- log.as_deref(),
- debug,
- protected,
- mem,
- cpu_topology,
- task_profiles,
- gdb,
- kernel.as_deref(),
- vendor.as_deref(),
- devices,
- ),
- Opt::Run { name, config, cpu_topology, task_profiles, console, console_in, log, gdb } => {
- command_run(
- name,
- get_service()?.as_ref(),
- &config,
- console.as_deref(),
- console_in.as_deref(),
- log.as_deref(),
- /* mem */ None,
- cpu_topology,
- task_profiles,
- gdb,
- )
- }
+ Opt::RunApp { config } => command_run_app(config),
+ Opt::RunMicrodroid { config } => command_run_microdroid(config),
+ Opt::Run { config } => command_run(config),
Opt::List => command_list(get_service()?.as_ref()),
Opt::Info => command_info(),
Opt::CreatePartition { path, size, partition_type } => {
@@ -444,6 +315,10 @@
println!("VFIO-platform is not supported.");
}
+ let devices = get_service()?.getAssignableDevices()?;
+ let devices = devices.into_iter().map(|x| x.node).collect::<Vec<_>>();
+ println!("Assignable devices: {}", serde_json::to_string(&devices)?);
+
Ok(())
}
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 250c56c..fc8d7e0 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -15,8 +15,8 @@
//! Command to run a VM.
use crate::create_partition::command_create_partition;
+use crate::{get_service, RunAppConfig, RunCustomVmConfig, RunMicrodroidConfig};
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
- CpuTopology::CpuTopology,
IVirtualizationService::IVirtualizationService,
PartitionType::PartitionType,
VirtualMachineAppConfig::{
@@ -35,7 +35,6 @@
use std::fs;
use std::fs::File;
use std::io;
-use std::num::NonZeroU16;
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::path::{Path, PathBuf};
use vmclient::{ErrorCode, VmInstance};
@@ -43,98 +42,78 @@
use zip::ZipArchive;
/// Run a VM from the given APK, idsig, and config.
-#[allow(clippy::too_many_arguments)]
-pub fn command_run_app(
- name: Option<String>,
- service: &dyn IVirtualizationService,
- apk: &Path,
- idsig: &Path,
- instance: &Path,
- storage: Option<&Path>,
- storage_size: Option<u64>,
- config_path: Option<String>,
- payload_binary_name: Option<String>,
- console_out_path: Option<&Path>,
- console_in_path: Option<&Path>,
- log_path: Option<&Path>,
- debug_level: DebugLevel,
- protected: bool,
- mem: Option<u32>,
- cpu_topology: CpuTopology,
- task_profiles: Vec<String>,
- extra_idsigs: &[PathBuf],
- gdb: Option<NonZeroU16>,
- kernel: Option<&Path>,
- vendor: Option<&Path>,
- devices: Vec<PathBuf>,
-) -> Result<(), Error> {
- let apk_file = File::open(apk).context("Failed to open APK file")?;
+pub fn command_run_app(config: RunAppConfig) -> Result<(), Error> {
+ let service = get_service()?;
+ let apk = File::open(&config.apk).context("Failed to open APK file")?;
- let extra_apks = match config_path.as_deref() {
- Some(path) => parse_extra_apk_list(apk, path)?,
+ let extra_apks = match config.config_path.as_deref() {
+ Some(path) => parse_extra_apk_list(&config.apk, path)?,
None => vec![],
};
- if extra_apks.len() != extra_idsigs.len() {
+ if extra_apks.len() != config.extra_idsigs.len() {
bail!(
"Found {} extra apks, but there are {} extra idsigs",
extra_apks.len(),
- extra_idsigs.len()
+ config.extra_idsigs.len()
)
}
- for i in 0..extra_apks.len() {
- let extra_apk_fd = ParcelFileDescriptor::new(File::open(&extra_apks[i])?);
- let extra_idsig_fd = ParcelFileDescriptor::new(File::create(&extra_idsigs[i])?);
+ for (i, extra_apk) in extra_apks.iter().enumerate() {
+ let extra_apk_fd = ParcelFileDescriptor::new(File::open(extra_apk)?);
+ let extra_idsig_fd = ParcelFileDescriptor::new(File::create(&config.extra_idsigs[i])?);
service.createOrUpdateIdsigFile(&extra_apk_fd, &extra_idsig_fd)?;
}
- let idsig_file = File::create(idsig).context("Failed to create idsig file")?;
+ let idsig = File::create(&config.idsig).context("Failed to create idsig file")?;
- let apk_fd = ParcelFileDescriptor::new(apk_file);
- let idsig_fd = ParcelFileDescriptor::new(idsig_file);
+ let apk_fd = ParcelFileDescriptor::new(apk);
+ let idsig_fd = ParcelFileDescriptor::new(idsig);
service.createOrUpdateIdsigFile(&apk_fd, &idsig_fd)?;
- let idsig_file = File::open(idsig).context("Failed to open idsig file")?;
- let idsig_fd = ParcelFileDescriptor::new(idsig_file);
+ let idsig = File::open(&config.idsig).context("Failed to open idsig file")?;
+ let idsig_fd = ParcelFileDescriptor::new(idsig);
- if !instance.exists() {
+ if !config.instance.exists() {
const INSTANCE_FILE_SIZE: u64 = 10 * 1024 * 1024;
command_create_partition(
- service,
- instance,
+ service.as_ref(),
+ &config.instance,
INSTANCE_FILE_SIZE,
PartitionType::ANDROID_VM_INSTANCE,
)?;
}
- let storage = if let Some(path) = storage {
+ let storage = if let Some(path) = config.microdroid.storage {
if !path.exists() {
command_create_partition(
- service,
- path,
- storage_size.unwrap_or(10 * 1024 * 1024),
+ service.as_ref(),
+ &path,
+ config.microdroid.storage_size.unwrap_or(10 * 1024 * 1024),
PartitionType::ENCRYPTEDSTORE,
)?;
}
- Some(open_parcel_file(path, true)?)
+ Some(open_parcel_file(&path, true)?)
} else {
None
};
- let kernel = kernel.map(|p| open_parcel_file(p, false)).transpose()?;
+ let kernel =
+ config.microdroid.kernel.as_ref().map(|p| open_parcel_file(p, false)).transpose()?;
- let vendor = vendor.map(|p| open_parcel_file(p, false)).transpose()?;
+ let vendor =
+ config.microdroid.vendor.as_ref().map(|p| open_parcel_file(p, false)).transpose()?;
- let extra_idsig_files: Result<Vec<File>, _> = extra_idsigs.iter().map(File::open).collect();
+ let extra_idsig_files: Result<Vec<File>, _> =
+ config.extra_idsigs.iter().map(File::open).collect();
let extra_idsig_fds = extra_idsig_files?.into_iter().map(ParcelFileDescriptor::new).collect();
- let payload = if let Some(config_path) = config_path {
- if payload_binary_name.is_some() {
+ let payload = if let Some(config_path) = config.config_path {
+ if config.payload_binary_name.is_some() {
bail!("Only one of --config-path or --payload-binary-name can be defined")
}
Payload::ConfigPath(config_path)
- } else if let Some(payload_binary_name) = payload_binary_name {
+ } else if let Some(payload_binary_name) = config.payload_binary_name {
Payload::PayloadConfig(VirtualMachinePayloadConfig {
payloadBinaryName: payload_binary_name,
})
@@ -142,14 +121,16 @@
bail!("Either --config-path or --payload-binary-name must be defined")
};
- let payload_config_str = format!("{:?}!{:?}", apk, payload);
+ let payload_config_str = format!("{:?}!{:?}", config.apk, payload);
let custom_config = CustomConfig {
customKernelImage: kernel,
- gdbPort: gdb.map(u16::from).unwrap_or(0) as i32, // 0 means no gdb
- taskProfiles: task_profiles,
+ gdbPort: config.debug.gdb.map(u16::from).unwrap_or(0) as i32, // 0 means no gdb
+ taskProfiles: config.common.task_profiles,
vendorImage: vendor,
- devices: devices
+ devices: config
+ .microdroid
+ .devices
.iter()
.map(|x| {
x.to_str().map(String::from).ok_or(anyhow!("Failed to convert {x:?} to String"))
@@ -157,21 +138,28 @@
.collect::<Result<_, _>>()?,
};
- let config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
- name: name.unwrap_or_else(|| String::from("VmRunApp")),
+ let vm_config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
+ name: config.common.name.unwrap_or_else(|| String::from("VmRunApp")),
apk: apk_fd.into(),
idsig: idsig_fd.into(),
extraIdsigs: extra_idsig_fds,
- instanceImage: open_parcel_file(instance, true /* writable */)?.into(),
+ instanceImage: open_parcel_file(&config.instance, true /* writable */)?.into(),
encryptedStorageImage: storage,
payload,
- debugLevel: debug_level,
- protectedVm: protected,
- memoryMib: mem.unwrap_or(0) as i32, // 0 means use the VM default
- cpuTopology: cpu_topology,
+ debugLevel: config.debug.debug,
+ protectedVm: config.common.protected,
+ memoryMib: config.common.mem.unwrap_or(0) as i32, // 0 means use the VM default
+ cpuTopology: config.common.cpu_topology,
customConfig: Some(custom_config),
});
- run(service, &config, &payload_config_str, console_out_path, console_in_path, log_path)
+ run(
+ service.as_ref(),
+ &vm_config,
+ &payload_config_str,
+ config.debug.console.as_ref().map(|p| p.as_ref()),
+ config.debug.console_in.as_ref().map(|p| p.as_ref()),
+ config.debug.log.as_ref().map(|p| p.as_ref()),
+ )
}
fn find_empty_payload_apk_path() -> Result<PathBuf, Error> {
@@ -197,100 +185,55 @@
}
/// 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>,
- console_out_path: Option<&Path>,
- console_in_path: Option<&Path>,
- log_path: Option<&Path>,
- debug_level: DebugLevel,
- protected: bool,
- mem: Option<u32>,
- cpu_topology: CpuTopology,
- task_profiles: Vec<String>,
- gdb: Option<NonZeroU16>,
- kernel: Option<&Path>,
- vendor: Option<&Path>,
- devices: Vec<PathBuf>,
-) -> Result<(), Error> {
+pub fn command_run_microdroid(config: RunMicrodroidConfig) -> Result<(), Error> {
let apk = find_empty_payload_apk_path()?;
println!("found path {}", apk.display());
- let work_dir = work_dir.unwrap_or(create_work_dir()?);
+ let work_dir = config.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_binary_name = "MicrodroidEmptyPayloadJniLib.so";
- let extra_sig = [];
- command_run_app(
- name,
- service,
- &apk,
- &idsig,
- &instance_img,
- storage,
- storage_size,
- /* config_path= */ None,
- Some(payload_binary_name.to_owned()),
- console_out_path,
- console_in_path,
- log_path,
- debug_level,
- protected,
- mem,
- cpu_topology,
- task_profiles,
- &extra_sig,
- gdb,
- kernel,
- vendor,
- devices,
- )
+ let app_config = RunAppConfig {
+ common: config.common,
+ debug: config.debug,
+ microdroid: config.microdroid,
+ apk,
+ idsig,
+ instance: instance_img,
+ config_path: None,
+ payload_binary_name: Some("MicrodroidEmptyPayloadJniLib.so".to_owned()),
+ extra_idsigs: [].to_vec(),
+ };
+ command_run_app(app_config)
}
/// Run a VM from the given configuration file.
-#[allow(clippy::too_many_arguments)]
-pub fn command_run(
- name: Option<String>,
- service: &dyn IVirtualizationService,
- config_path: &Path,
- console_out_path: Option<&Path>,
- console_in_path: Option<&Path>,
- log_path: Option<&Path>,
- mem: Option<u32>,
- cpu_topology: CpuTopology,
- task_profiles: Vec<String>,
- gdb: Option<NonZeroU16>,
-) -> Result<(), Error> {
- let config_file = File::open(config_path).context("Failed to open config file")?;
- let mut config =
+pub fn command_run(config: RunCustomVmConfig) -> Result<(), Error> {
+ let config_file = File::open(&config.config).context("Failed to open config file")?;
+ let mut vm_config =
VmConfig::load(&config_file).context("Failed to parse config file")?.to_parcelable()?;
- if let Some(mem) = mem {
- config.memoryMib = mem as i32;
+ if let Some(mem) = config.common.mem {
+ vm_config.memoryMib = mem as i32;
}
- if let Some(name) = name {
- config.name = name;
+ if let Some(name) = config.common.name {
+ vm_config.name = name;
} else {
- config.name = String::from("VmRun");
+ vm_config.name = String::from("VmRun");
}
- if let Some(gdb) = gdb {
- config.gdbPort = gdb.get() as i32;
+ if let Some(gdb) = config.debug.gdb {
+ vm_config.gdbPort = gdb.get() as i32;
}
- config.cpuTopology = cpu_topology;
- config.taskProfiles = task_profiles;
+ vm_config.cpuTopology = config.common.cpu_topology;
+ vm_config.taskProfiles = config.common.task_profiles;
run(
- service,
- &VirtualMachineConfig::RawConfig(config),
- &format!("{:?}", config_path),
- console_out_path,
- console_in_path,
- log_path,
+ get_service()?.as_ref(),
+ &VirtualMachineConfig::RawConfig(vm_config),
+ &format!("{:?}", &config.config),
+ config.debug.console.as_ref().map(|p| p.as_ref()),
+ config.debug.console_in.as_ref().map(|p| p.as_ref()),
+ config.debug.log.as_ref().map(|p| p.as_ref()),
)
}
diff --git a/vm_payload/Android.bp b/vm_payload/Android.bp
index ae0d1a6..49b7f5f 100644
--- a/vm_payload/Android.bp
+++ b/vm_payload/Android.bp
@@ -10,8 +10,6 @@
srcs: ["src/*.rs"],
include_dirs: ["include"],
prefer_rlib: true,
- // Require unsafe blocks for inside unsafe functions.
- flags: ["-Dunsafe_op_in_unsafe_fn"],
rustlibs: [
"android.system.virtualization.payload-rust",
"libandroid_logger",
diff --git a/vmbase/example/src/main.rs b/vmbase/example/src/main.rs
index a6f3bfa..ebd981c 100644
--- a/vmbase/example/src/main.rs
+++ b/vmbase/example/src/main.rs
@@ -16,8 +16,6 @@
#![no_main]
#![no_std]
-#![deny(unsafe_op_in_unsafe_fn)]
-#![deny(clippy::undocumented_unsafe_blocks)]
mod exceptions;
mod layout;
diff --git a/vmbase/src/lib.rs b/vmbase/src/lib.rs
index ca8756d..431e899 100644
--- a/vmbase/src/lib.rs
+++ b/vmbase/src/lib.rs
@@ -15,8 +15,6 @@
//! Basic functionality for bare-metal binaries to run in a VM under crosvm.
#![no_std]
-#![deny(unsafe_op_in_unsafe_fn)]
-#![deny(clippy::undocumented_unsafe_blocks)]
extern crate alloc;
diff --git a/zipfuse/src/inode.rs b/zipfuse/src/inode.rs
index ef48389..3175a30 100644
--- a/zipfuse/src/inode.rs
+++ b/zipfuse/src/inode.rs
@@ -31,10 +31,11 @@
const INVALID: Inode = 0;
const ROOT: Inode = 1;
-const DEFAULT_DIR_MODE: u32 = libc::S_IRUSR | libc::S_IXUSR;
+const DEFAULT_DIR_MODE: u32 = libc::S_IRUSR | libc::S_IXUSR | libc::S_IRGRP | libc::S_IXGRP;
// b/264668376 some files in APK don't have unix permissions specified. Default to 400
// otherwise those files won't be readable even by the owner.
-const DEFAULT_FILE_MODE: u32 = libc::S_IRUSR;
+const DEFAULT_FILE_MODE: u32 = libc::S_IRUSR | libc::S_IRGRP;
+const EXECUTABLE_FILE_MODE: u32 = DEFAULT_FILE_MODE | libc::S_IXUSR | libc::S_IXGRP;
/// `InodeData` represents an inode which has metadata about a file or a directory
#[derive(Debug)]
@@ -191,7 +192,7 @@
// additional binaries that they might want to execute.
// An example of such binary is measure_io one used in the authfs performance tests.
// More context available at b/265261525 and b/270955654.
- file_mode |= libc::S_IXUSR;
+ file_mode = EXECUTABLE_FILE_MODE;
}
while let Some(name) = iter.next() {