Merge "prng_seeder is a bootstrap process in microdroid"
diff --git a/apex/Android.bp b/apex/Android.bp
index d5f485b..dce8edd 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -57,6 +57,7 @@
"com.android.virt-bootclasspath-fragment",
],
jni_libs: [
+ "libvirtualizationservice_jni",
"libvirtualmachine_jni",
],
}
diff --git a/javalib/jni/Android.bp b/javalib/jni/Android.bp
index 2939db5..e82b2ce 100644
--- a/javalib/jni/Android.bp
+++ b/javalib/jni/Android.bp
@@ -3,11 +3,29 @@
}
cc_library_shared {
+ name: "libvirtualizationservice_jni",
+ srcs: [
+ "android_system_virtualmachine_VirtualizationService.cpp",
+ ],
+ apex_available: ["com.android.virt"],
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "libbinder_rpc_unstable",
+ "liblog",
+ "libnativehelper",
+ ],
+}
+
+cc_library_shared {
name: "libvirtualmachine_jni",
- srcs: ["android_system_virtualmachine_VirtualMachine.cpp"],
+ srcs: [
+ "android_system_virtualmachine_VirtualMachine.cpp",
+ ],
apex_available: ["com.android.virt"],
shared_libs: [
"android.system.virtualizationservice-ndk",
+ "libbase",
"libbinder_ndk",
"libbinder_rpc_unstable",
"liblog",
diff --git a/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
index a949d46..3230af4 100644
--- a/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
+++ b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
@@ -16,16 +16,16 @@
#define LOG_TAG "VirtualMachine"
-#include <tuple>
-
-#include <log/log.h>
-
#include <aidl/android/system/virtualizationservice/IVirtualMachine.h>
#include <android/binder_auto_utils.h>
#include <android/binder_ibinder_jni.h>
-#include <binder_rpc_unstable.hpp>
-
#include <jni.h>
+#include <log/log.h>
+
+#include <binder_rpc_unstable.hpp>
+#include <tuple>
+
+#include "common.h"
JNIEXPORT jobject JNICALL android_system_virtualmachine_VirtualMachine_connectToVsockServer(
JNIEnv* env, [[maybe_unused]] jclass clazz, jobject vmBinder, jint port) {
@@ -55,12 +55,9 @@
return ret;
};
- auto session = ARpcSession_new();
- auto client = ARpcSession_setupPreconnectedClient(session, requestFunc, &args);
- auto obj = AIBinder_toJavaBinder(env, client);
- // Free the NDK handle. The underlying RpcSession object remains alive.
- ARpcSession_free(session);
- return obj;
+ RpcSessionHandle session;
+ auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, &args);
+ return AIBinder_toJavaBinder(env, client);
}
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
diff --git a/javalib/jni/android_system_virtualmachine_VirtualizationService.cpp b/javalib/jni/android_system_virtualmachine_VirtualizationService.cpp
new file mode 100644
index 0000000..b9d2ca4
--- /dev/null
+++ b/javalib/jni/android_system_virtualmachine_VirtualizationService.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VirtualizationService"
+
+#include <android-base/unique_fd.h>
+#include <android/binder_ibinder_jni.h>
+#include <jni.h>
+#include <log/log.h>
+
+#include <string>
+
+#include "common.h"
+
+using namespace android::base;
+
+static constexpr const char VIRTMGR_PATH[] = "/apex/com.android.virt/bin/virtmgr";
+static constexpr size_t VIRTMGR_THREADS = 16;
+
+JNIEXPORT jint JNICALL android_system_virtualmachine_VirtualizationService_spawn(
+ JNIEnv* env, [[maybe_unused]] jclass clazz) {
+ unique_fd serverFd, clientFd;
+ if (!Socketpair(SOCK_STREAM, &serverFd, &clientFd)) {
+ env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
+ ("Failed to create socketpair: " + std::string(strerror(errno))).c_str());
+ return -1;
+ }
+
+ unique_fd waitFd, readyFd;
+ if (!Pipe(&waitFd, &readyFd, 0)) {
+ env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
+ ("Failed to create pipe: " + std::string(strerror(errno))).c_str());
+ return -1;
+ }
+
+ if (fork() == 0) {
+ // Close client's FDs.
+ clientFd.reset();
+ waitFd.reset();
+
+ auto strServerFd = std::to_string(serverFd.get());
+ auto strReadyFd = std::to_string(readyFd.get());
+
+ execl(VIRTMGR_PATH, VIRTMGR_PATH, "--rpc-server-fd", strServerFd.c_str(), "--ready-fd",
+ strReadyFd.c_str(), NULL);
+ }
+
+ // Close virtmgr's FDs.
+ serverFd.reset();
+ readyFd.reset();
+
+ // Wait for the server to signal its readiness by closing its end of the pipe.
+ char buf;
+ if (read(waitFd.get(), &buf, sizeof(buf)) < 0) {
+ env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
+ "Failed to wait for VirtualizationService to be ready");
+ return -1;
+ }
+
+ return clientFd.release();
+}
+
+JNIEXPORT jobject JNICALL android_system_virtualmachine_VirtualizationService_connect(
+ JNIEnv* env, [[maybe_unused]] jobject obj, int clientFd) {
+ RpcSessionHandle session;
+ ARpcSession_setFileDescriptorTransportMode(session.get(),
+ ARpcSession_FileDescriptorTransportMode::Unix);
+ ARpcSession_setMaxIncomingThreads(session.get(), VIRTMGR_THREADS);
+ ARpcSession_setMaxOutgoingThreads(session.get(), VIRTMGR_THREADS);
+ // SAFETY - ARpcSession_setupUnixDomainBootstrapClient does not take ownership of clientFd.
+ auto client = ARpcSession_setupUnixDomainBootstrapClient(session.get(), clientFd);
+ return AIBinder_toJavaBinder(env, client);
+}
+
+JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ ALOGE("%s: Failed to get the environment", __FUNCTION__);
+ return JNI_ERR;
+ }
+
+ jclass c = env->FindClass("android/system/virtualmachine/VirtualizationService");
+ if (c == nullptr) {
+ ALOGE("%s: Failed to find class android.system.virtualmachine.VirtualizationService",
+ __FUNCTION__);
+ return JNI_ERR;
+ }
+
+ // Register your class' native methods.
+ static const JNINativeMethod methods[] = {
+ {"nativeSpawn", "()I",
+ reinterpret_cast<void*>(android_system_virtualmachine_VirtualizationService_spawn)},
+ {"nativeConnect", "(I)Landroid/os/IBinder;",
+ reinterpret_cast<void*>(android_system_virtualmachine_VirtualizationService_connect)},
+ };
+ int rc = env->RegisterNatives(c, methods, sizeof(methods) / sizeof(JNINativeMethod));
+ if (rc != JNI_OK) {
+ ALOGE("%s: Failed to register natives", __FUNCTION__);
+ return rc;
+ }
+
+ return JNI_VERSION_1_6;
+}
diff --git a/javalib/jni/common.h b/javalib/jni/common.h
new file mode 100644
index 0000000..c70ba76
--- /dev/null
+++ b/javalib/jni/common.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder_rpc_unstable.hpp>
+
+// Wrapper around ARpcSession handle that automatically frees the handle when
+// it goes out of scope.
+class RpcSessionHandle {
+public:
+ RpcSessionHandle() : mHandle(ARpcSession_new()) {}
+ ~RpcSessionHandle() { ARpcSession_free(mHandle); }
+
+ ARpcSession* get() { return mHandle; }
+
+private:
+ ARpcSession* mHandle;
+};
diff --git a/javalib/src/android/system/virtualmachine/VirtualizationService.java b/javalib/src/android/system/virtualmachine/VirtualizationService.java
new file mode 100644
index 0000000..78d0c9c
--- /dev/null
+++ b/javalib/src/android/system/virtualmachine/VirtualizationService.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 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 android.system.virtualmachine;
+
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.system.virtualizationservice.IVirtualizationService;
+
+/** A running instance of virtmgr that is hosting a VirtualizationService AIDL service. */
+class VirtualizationService {
+ static {
+ System.loadLibrary("virtualizationservice_jni");
+ }
+
+ /*
+ * Client FD for UDS connection to virtmgr's RpcBinder server. Closing it
+ * will make virtmgr shut down.
+ */
+ private ParcelFileDescriptor mClientFd;
+
+ private static native int nativeSpawn();
+
+ private native IBinder nativeConnect(int clientFd);
+
+ /*
+ * Spawns a new virtmgr subprocess that will host a VirtualizationService
+ * AIDL service.
+ */
+ public VirtualizationService() throws VirtualMachineException {
+ int clientFd = nativeSpawn();
+ if (clientFd < 0) {
+ throw new VirtualMachineException("Could not spawn VirtualizationService");
+ }
+ mClientFd = ParcelFileDescriptor.adoptFd(clientFd);
+ }
+
+ /* Connects to the VirtualizationService AIDL service. */
+ public IVirtualizationService connect() throws VirtualMachineException {
+ IBinder binder = nativeConnect(mClientFd.getFd());
+ if (binder == null) {
+ throw new VirtualMachineException("Could not connect to VirtualizationService");
+ }
+ return IVirtualizationService.Stub.asInterface(binder);
+ }
+}
diff --git a/libs/capabilities/Android.bp b/libs/capabilities/Android.bp
new file mode 100644
index 0000000..db3f4d4
--- /dev/null
+++ b/libs/capabilities/Android.bp
@@ -0,0 +1,62 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_bindgen {
+ name: "libcap_bindgen",
+ edition: "2021",
+ wrapper_src: "bindgen/libcap.h",
+ crate_name: "cap_bindgen",
+ source_stem: "bindings",
+ shared_libs: ["libcap"],
+ bindgen_flags: [
+ "--default-enum-style rust",
+ ],
+ visibility: [
+ "//packages/modules/Virtualization:__subpackages__",
+ ],
+}
+
+rust_test {
+ name: "libcap_bindgen_test",
+ srcs: [":libcap_bindgen"],
+ crate_name: "cap_bindgen_test",
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ clippy_lints: "none",
+ lints: "none",
+}
+
+rust_defaults {
+ name: "libcap_rust.defaults",
+ crate_name: "cap",
+ srcs: ["src/caps.rs"],
+ rustlibs: [
+ "libanyhow",
+ "libcap_bindgen",
+ "liblibc",
+ "libnix",
+ "libscopeguard",
+ ],
+ edition: "2021",
+ prefer_rlib: true,
+ multilib: {
+ lib32: {
+ enabled: false,
+ },
+ },
+ shared_libs: [
+ "libcap",
+ ],
+}
+
+rust_library {
+ name: "libcap_rust",
+ defaults: ["libcap_rust.defaults"],
+}
+
+rust_test {
+ name: "libcap_rust.test",
+ defaults: ["libcap_rust.defaults"],
+ test_suites: ["general-tests"],
+}
diff --git a/libs/capabilities/bindgen/libcap.h b/libs/capabilities/bindgen/libcap.h
new file mode 100644
index 0000000..cbb90dc
--- /dev/null
+++ b/libs/capabilities/bindgen/libcap.h
@@ -0,0 +1,3 @@
+#pragma once
+
+#include <sys/capability.h>
diff --git a/libs/capabilities/src/caps.rs b/libs/capabilities/src/caps.rs
new file mode 100644
index 0000000..1f44a34
--- /dev/null
+++ b/libs/capabilities/src/caps.rs
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! A rust library wrapping the libcap functionality.
+
+use anyhow::{bail, Result};
+use cap_bindgen::{
+ cap_clear_flag, cap_drop_bound, cap_flag_t, cap_free, cap_get_proc, cap_set_proc, cap_value_t,
+ CAP_LAST_CAP,
+};
+use nix::errno::Errno;
+
+/// Removes inheritable capabilities set for this process.
+/// See: https://man7.org/linux/man-pages/man7/capabilities.7.html
+pub fn drop_inheritable_caps() -> Result<()> {
+ unsafe {
+ // SAFETY: we do not manipulate memory handled by libcap.
+ let caps = cap_get_proc();
+ scopeguard::defer! {
+ cap_free(caps as *mut std::os::raw::c_void);
+ }
+ if cap_clear_flag(caps, cap_flag_t::CAP_INHERITABLE) < 0 {
+ let e = Errno::last();
+ bail!("cap_clear_flag failed: {:?}", e)
+ }
+ if cap_set_proc(caps) < 0 {
+ let e = Errno::last();
+ bail!("cap_set_proc failed: {:?}", e)
+ }
+ }
+ Ok(())
+}
+
+/// Drop bounding set capabitilies for this process.
+/// See: https://man7.org/linux/man-pages/man7/capabilities.7.html
+pub fn drop_bounding_set() -> Result<()> {
+ let mut cap_id: cap_value_t = 0;
+ while cap_id <= CAP_LAST_CAP.try_into().unwrap() {
+ unsafe {
+ // SAFETY: we do not manipulate memory handled by libcap.
+ if cap_drop_bound(cap_id) == -1 {
+ let e = Errno::last();
+ bail!("cap_drop_bound failed for {}: {:?}", cap_id, e);
+ }
+ }
+ cap_id += 1;
+ }
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ // Basic test to verify that calling drop_inheritable_caps doesn't fail
+ #[test]
+ fn test_drop_inheritable_caps() {
+ let result = drop_inheritable_caps();
+ assert!(result.is_ok(), "failed with: {:?}", result)
+ }
+}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index fe538bd..ecaadf8 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -46,6 +46,7 @@
use_avb: true,
avb_private_key: ":microdroid_sign_key",
avb_algorithm: "SHA256_RSA4096",
+ avb_hash_algorithm: "sha256",
partition_name: "system",
deps: [
"init_second_stage",
@@ -215,6 +216,7 @@
},
avb_private_key: ":microdroid_sign_key",
avb_algorithm: "SHA256_RSA4096",
+ avb_hash_algorithm: "sha256",
file_contexts: ":microdroid_vendor_file_contexts.gen",
// For deterministic output, use fake_timestamp, hard-coded uuid
fake_timestamp: "1611569676",
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 44b4c01..383f371 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -18,6 +18,7 @@
"libapkverify",
"libbinder_rs",
"libbyteorder",
+ "libcap_rust",
"libdiced",
"libdiced_open_dice_cbor",
"libdiced_sample_inputs",
diff --git a/microdroid_manager/microdroid_manager.rc b/microdroid_manager/microdroid_manager.rc
index c41ee38..97d14b5 100644
--- a/microdroid_manager/microdroid_manager.rc
+++ b/microdroid_manager/microdroid_manager.rc
@@ -7,6 +7,8 @@
setenv RUST_LOG info
# TODO(jooyung) remove this when microdroid_manager becomes a daemon
oneshot
- # SYS_BOOT is required to exec kexecload from microdroid_manager
- capabilities AUDIT_CONTROL SYS_ADMIN SYS_BOOT
+ # CAP_SYS_BOOT is required to exec kexecload from microdroid_manager
+ # CAP_SETCAP is required to allow microdroid_manager to drop capabilities
+ # before executing the payload
+ capabilities AUDIT_CONTROL SYS_ADMIN SYS_BOOT SETPCAP
socket vm_payload_service stream 0666 system system
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 3fa7a2a..4018d00 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -55,6 +55,7 @@
use std::env;
use std::fs::{self, create_dir, OpenOptions};
use std::io::Write;
+use std::os::unix::process::CommandExt;
use std::os::unix::process::ExitStatusExt;
use std::path::Path;
use std::process::{Child, Command, Stdio};
@@ -796,6 +797,24 @@
command
}
};
+
+ unsafe {
+ // SAFETY: we are not accessing any resource of the parent process.
+ command.pre_exec(|| {
+ info!("dropping capabilities before executing payload");
+ // It is OK to continue with payload execution even if the calls below fail, since
+ // whether process can use a capability is controlled by the SELinux. Dropping the
+ // capabilities here is just another defense-in-depth layer.
+ if let Err(e) = cap::drop_inheritable_caps() {
+ error!("failed to drop inheritable capabilities: {:?}", e);
+ }
+ if let Err(e) = cap::drop_bounding_set() {
+ error!("failed to drop bounding set: {:?}", e);
+ }
+ Ok(())
+ });
+ }
+
command.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
info!("notifying payload started");
diff --git a/pvmfw/README.md b/pvmfw/README.md
new file mode 100644
index 0000000..e5ba88b
--- /dev/null
+++ b/pvmfw/README.md
@@ -0,0 +1,72 @@
+# Protected Virtual Machine Firmware
+
+## Configuration Data Format
+
+pvmfw will expect a [header] to have been appended to its loaded binary image
+at the next 4KiB boundary. It describes the configuration data entries that
+pvmfw will use and, being loaded by the pvmfw loader, is necessarily trusted.
+
+The layout of the configuration data is as follows:
+
+```
++===============================+
+| pvmfw.bin |
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
+| (Padding to 4KiB alignment) |
++===============================+ <-- HEAD
+| Magic (= 0x666d7670) |
++-------------------------------+
+| Version |
++-------------------------------+
+| Total Size = (TAIL - HEAD) |
++-------------------------------+
+| Flags |
++-------------------------------+
+| [Entry 0] |
+| offset = (FIRST - HEAD) |
+| size = (FIRST_END - FIRST) |
++-------------------------------+
+| [Entry 1] |
+| offset = (SECOND - HEAD) |
+| size = (SECOND_END - SECOND) |
++-------------------------------+
+| ... |
++-------------------------------+
+| [Entry n] |
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
+| (Padding to 8-byte alignment) |
++===============================+ <-- FIRST
+| {First blob: BCC} |
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ <-- FIRST_END
+| (Padding to 8-byte alignment) |
++===============================+ <-- SECOND
+| {Second blob: DP} |
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ <-- SECOND_END
+| (Padding to 8-byte alignment) |
++===============================+
+| ... |
++===============================+ <-- TAIL
+```
+
+Where the version number is encoded using a "`major.minor`" as follows
+
+```
+((major << 16) | (minor & 0xffff))
+```
+
+and defines the format of the header (which may change between major versions),
+its size and, in particular, the expected number of appended blobs. Each blob is
+referred to by its offset in the entry array and may be mandatory or optional
+(as defined by this specification), where missing entries are denoted by a zero
+size. It is therefore not allowed to trim missing optional entries from the end
+of the array. The header uses the endianness of the virtual machine.
+
+The header format itself is agnostic of the internal format of the individual
+blos it refers to. In version 1.0, it describes two blobs:
+
+- entry 0 must point to a valid [BCC Handover]
+- entry 1 may point to a [DTBO] to be applied to the pVM device tree
+
+[header]: src/config.rs
+[BCC Handover]: https://pigweed.googlesource.com/open-dice/+/825e3beb6c6efcd8c35506d818c18d1e73b9834a/src/android/bcc.c#260
+[DTBO]: https://android.googlesource.com/platform/external/dtc/+/refs/heads/master/Documentation/dt-object-internal.txt
diff --git a/pvmfw/src/helpers.rs b/pvmfw/src/helpers.rs
index d1b828a..e8a20a8 100644
--- a/pvmfw/src/helpers.rs
+++ b/pvmfw/src/helpers.rs
@@ -45,6 +45,17 @@
}
}
+/// Aligns the given address to the given alignment, if it is a power of two.
+///
+/// Returns `None` if the alignment isn't a power of two.
+pub const fn align_down(addr: usize, alignment: usize) -> Option<usize> {
+ if !alignment.is_power_of_two() {
+ None
+ } else {
+ Some(unchecked_align_down(addr, alignment))
+ }
+}
+
/// Computes the address of the 4KiB page containing a given address.
pub const fn page_4kb_of(addr: usize) -> usize {
unchecked_align_down(addr, SIZE_4KB)
diff --git a/pvmfw/src/hvc.rs b/pvmfw/src/hvc.rs
index 66f7977..dc99303 100644
--- a/pvmfw/src/hvc.rs
+++ b/pvmfw/src/hvc.rs
@@ -17,11 +17,41 @@
use crate::smccc::{self, checked_hvc64, checked_hvc64_expect_zero};
use log::info;
+const ARM_SMCCC_KVM_FUNC_HYP_MEMINFO: u32 = 0xc6000002;
+const ARM_SMCCC_KVM_FUNC_MEM_SHARE: u32 = 0xc6000003;
+const ARM_SMCCC_KVM_FUNC_MEM_UNSHARE: u32 = 0xc6000004;
const VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID: u32 = 0xc6000005;
const VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID: u32 = 0xc6000006;
const VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID: u32 = 0xc6000007;
const VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID: u32 = 0xc6000008;
+/// Queries the memory protection parameters for a protected virtual machine.
+///
+/// Returns the memory protection granule size in bytes.
+pub fn hyp_meminfo() -> smccc::Result<u64> {
+ let args = [0u64; 17];
+ checked_hvc64(ARM_SMCCC_KVM_FUNC_HYP_MEMINFO, args)
+}
+
+/// Shares a region of memory with the KVM host, granting it read, write and execute permissions.
+/// The size of the region is equal to the memory protection granule returned by [`hyp_meminfo`].
+pub fn mem_share(base_ipa: u64) -> smccc::Result<()> {
+ let mut args = [0u64; 17];
+ args[0] = base_ipa;
+
+ checked_hvc64_expect_zero(ARM_SMCCC_KVM_FUNC_MEM_SHARE, args)
+}
+
+/// Revokes access permission from the KVM host to a memory region previously shared with
+/// [`mem_share`]. The size of the region is equal to the memory protection granule returned by
+/// [`hyp_meminfo`].
+pub fn mem_unshare(base_ipa: u64) -> smccc::Result<()> {
+ let mut args = [0u64; 17];
+ args[0] = base_ipa;
+
+ checked_hvc64_expect_zero(ARM_SMCCC_KVM_FUNC_MEM_UNSHARE, args)
+}
+
pub fn mmio_guard_info() -> smccc::Result<u64> {
let args = [0u64; 17];
diff --git a/pvmfw/src/memory.rs b/pvmfw/src/memory.rs
index 892089e..5e4874f 100644
--- a/pvmfw/src/memory.rs
+++ b/pvmfw/src/memory.rs
@@ -14,9 +14,11 @@
//! Low-level allocation and tracking of main memory.
-use crate::helpers::{self, page_4kb_of, SIZE_4KB};
+use crate::helpers::{self, align_down, page_4kb_of, SIZE_4KB};
+use crate::hvc::{hyp_meminfo, mem_share, mem_unshare};
use crate::mmio_guard;
use crate::mmu;
+use crate::smccc;
use core::cmp::max;
use core::cmp::min;
use core::fmt;
@@ -267,6 +269,36 @@
}
}
+/// Gives the KVM host read, write and execute permissions on the given memory range. If the range
+/// is not aligned with the memory protection granule then it will be extended on either end to
+/// align.
+#[allow(unused)]
+pub fn share_range(range: &MemoryRange) -> smccc::Result<()> {
+ let granule = hyp_meminfo()? as usize;
+ for base in (align_down(range.start, granule)
+ .expect("Memory protection granule was not a power of two")..range.end)
+ .step_by(granule)
+ {
+ mem_share(base as u64)?;
+ }
+ Ok(())
+}
+
+/// Removes permission from the KVM host to access the given memory range which was previously
+/// shared. If the range is not aligned with the memory protection granule then it will be extended
+/// on either end to align.
+#[allow(unused)]
+pub fn unshare_range(range: &MemoryRange) -> smccc::Result<()> {
+ let granule = hyp_meminfo()? as usize;
+ for base in (align_down(range.start, granule)
+ .expect("Memory protection granule was not a power of two")..range.end)
+ .step_by(granule)
+ {
+ mem_unshare(base as u64)?;
+ }
+ Ok(())
+}
+
/// Returns an iterator which yields the base address of each 4 KiB page within the given range.
fn page_iterator(range: &MemoryRange) -> impl Iterator<Item = usize> {
(page_4kb_of(range.start)..range.end).step_by(SIZE_4KB)
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index a4ecc45..c936e1b 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -46,4 +46,7 @@
* each line reverse.
*/
void runEchoReverseServer();
+
+ /** Returns a mask of effective capabilities that the process running the payload binary has. */
+ String[] getEffectiveCapabilities();
}
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index edb4759..e3c9961 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -40,6 +40,7 @@
header_libs: ["vm_payload_restricted_headers"],
shared_libs: [
"libbinder_ndk",
+ "libcap",
"MicrodroidTestNativeLibSub",
"libvm_payload#current",
],
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 eb756d8..f3bbbf1 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -1117,6 +1117,25 @@
assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
}
+ @Test
+ @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ public void microdroidLauncherHasEmptyCapabilities() throws Exception {
+ assumeSupportedKernel();
+
+ final VirtualMachineConfig vmConfig =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setMemoryMib(minMemoryRequired())
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+ final VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_caps", vmConfig);
+
+ final TestResults testResults = runVmTestService(vm);
+
+ assertThat(testResults.mException).isNull();
+ assertThat(testResults.mEffectiveCapabilities).isEmpty();
+ }
+
private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
throws IOException {
File file1 = getVmFile(vmName1, fileName);
@@ -1171,6 +1190,7 @@
String mExtraApkTestProp;
String mApkContentsPath;
String mEncryptedStoragePath;
+ String[] mEffectiveCapabilities;
}
private TestResults runVmTestService(VirtualMachine vm) throws Exception {
@@ -1194,6 +1214,8 @@
testResults.mApkContentsPath = testService.getApkContentsPath();
testResults.mEncryptedStoragePath =
testService.getEncryptedStoragePath();
+ testResults.mEffectiveCapabilities =
+ testService.getEffectiveCapabilities();
} catch (Exception e) {
testResults.mException = e;
}
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index b6a7aa2..da408e4 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -18,12 +18,14 @@
#include <android-base/file.h>
#include <android-base/properties.h>
#include <android-base/result.h>
+#include <android-base/scopeguard.h>
#include <android/log.h>
#include <fcntl.h>
#include <fsverity_digests.pb.h>
#include <linux/vm_sockets.h>
#include <stdint.h>
#include <stdio.h>
+#include <sys/capability.h>
#include <sys/system_properties.h>
#include <unistd.h>
#include <vm_main.h>
@@ -35,6 +37,7 @@
using android::base::borrowed_fd;
using android::base::ErrnoError;
using android::base::Error;
+using android::base::make_scope_guard;
using android::base::Result;
using android::base::unique_fd;
@@ -197,6 +200,29 @@
return ScopedAStatus::ok();
}
+ ScopedAStatus getEffectiveCapabilities(std::vector<std::string>* out) override {
+ if (out == nullptr) {
+ return ScopedAStatus::ok();
+ }
+ cap_t cap = cap_get_proc();
+ auto guard = make_scope_guard([&cap]() { cap_free(cap); });
+ for (cap_value_t cap_id = 0; cap_id < CAP_LAST_CAP + 1; cap_id++) {
+ cap_flag_value_t value;
+ if (cap_get_flag(cap, cap_id, CAP_EFFECTIVE, &value) != 0) {
+ return ScopedAStatus::
+ fromServiceSpecificErrorWithMessage(0, "cap_get_flag failed");
+ }
+ if (value == CAP_SET) {
+ // Ideally we would just send back the cap_ids, but I wasn't able to find java
+ // APIs for linux capabilities, hence we transform to the human readable name
+ // here.
+ char* name = cap_to_name(cap_id);
+ out->push_back(std::string(name) + "(" + std::to_string(cap_id) + ")");
+ }
+ }
+ return ScopedAStatus::ok();
+ }
+
virtual ::ScopedAStatus runEchoReverseServer() override {
auto result = start_echo_reverse_server();
if (result.ok()) {
diff --git a/vmclient/Android.bp b/vmclient/Android.bp
index 88b0c9a..0a2e692 100644
--- a/vmclient/Android.bp
+++ b/vmclient/Android.bp
@@ -11,8 +11,11 @@
"android.system.virtualizationcommon-rust",
"android.system.virtualizationservice-rust",
"libbinder_rs",
+ "libcommand_fds",
"liblog_rust",
+ "libnix",
"librpcbinder_rs",
+ "libshared_child",
"libthiserror",
],
shared_libs: [
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index a9c2619..0e3d140 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -39,12 +39,16 @@
ParcelFileDescriptor, Result as BinderResult, StatusCode, Strong,
},
};
+use command_fds::CommandFdExt;
use log::warn;
-use rpcbinder::RpcSession;
+use rpcbinder::{FileDescriptorTransportMode, RpcSession};
+use shared_child::SharedChild;
+use std::io::{self, Read};
+use std::process::Command;
use std::{
fmt::{self, Debug, Formatter},
fs::File,
- os::unix::io::IntoRawFd,
+ os::unix::io::{AsFd, AsRawFd, FromRawFd, IntoRawFd, OwnedFd},
sync::Arc,
time::Duration,
};
@@ -52,6 +56,79 @@
const VIRTUALIZATION_SERVICE_BINDER_SERVICE_IDENTIFIER: &str =
"android.system.virtualizationservice";
+const VIRTMGR_PATH: &str = "/apex/com.android.virt/bin/virtmgr";
+const VIRTMGR_THREADS: usize = 16;
+
+fn posix_pipe() -> Result<(OwnedFd, OwnedFd), io::Error> {
+ use nix::fcntl::OFlag;
+ use nix::unistd::pipe2;
+
+ // Create new POSIX pipe. Make it O_CLOEXEC to align with how Rust creates
+ // file descriptors (expected by SharedChild).
+ let (raw1, raw2) = pipe2(OFlag::O_CLOEXEC)?;
+
+ // SAFETY - Taking ownership of brand new FDs.
+ unsafe { Ok((OwnedFd::from_raw_fd(raw1), OwnedFd::from_raw_fd(raw2))) }
+}
+
+fn posix_socketpair() -> Result<(OwnedFd, OwnedFd), io::Error> {
+ use nix::sys::socket::{socketpair, AddressFamily, SockFlag, SockType};
+
+ // Create new POSIX socketpair, suitable for use with RpcBinder UDS bootstrap
+ // transport. Make it O_CLOEXEC to align with how Rust creates file
+ // descriptors (expected by SharedChild).
+ let (raw1, raw2) =
+ socketpair(AddressFamily::Unix, SockType::Stream, None, SockFlag::SOCK_CLOEXEC)?;
+
+ // SAFETY - Taking ownership of brand new FDs.
+ unsafe { Ok((OwnedFd::from_raw_fd(raw1), OwnedFd::from_raw_fd(raw2))) }
+}
+
+/// A running instance of virtmgr which is hosting a VirtualizationService
+/// RpcBinder server.
+pub struct VirtualizationService {
+ /// Client FD for UDS connection to virtmgr's RpcBinder server. Closing it
+ /// will make virtmgr shut down.
+ client_fd: OwnedFd,
+}
+
+impl VirtualizationService {
+ /// Spawns a new instance of virtmgr, a child process that will host
+ /// the VirtualizationService AIDL service.
+ pub fn new() -> Result<VirtualizationService, io::Error> {
+ let (wait_fd, ready_fd) = posix_pipe()?;
+ let (client_fd, server_fd) = posix_socketpair()?;
+
+ let mut command = Command::new(VIRTMGR_PATH);
+ command.arg("--rpc-server-fd").arg(format!("{}", server_fd.as_raw_fd()));
+ command.arg("--ready-fd").arg(format!("{}", ready_fd.as_raw_fd()));
+ command.preserved_fds(vec![server_fd.as_raw_fd(), ready_fd.as_raw_fd()]);
+
+ SharedChild::spawn(&mut command)?;
+
+ // Drop FDs that belong to virtmgr.
+ drop(server_fd);
+ drop(ready_fd);
+
+ // Wait for the child to signal that the RpcBinder server is ready
+ // by closing its end of the pipe.
+ let _ = File::from(wait_fd).read(&mut [0]);
+
+ Ok(VirtualizationService { client_fd })
+ }
+
+ /// Connects to the VirtualizationService AIDL service.
+ pub fn connect(&self) -> Result<Strong<dyn IVirtualizationService>, io::Error> {
+ let session = RpcSession::new();
+ session.set_file_descriptor_transport_mode(FileDescriptorTransportMode::Unix);
+ session.set_max_incoming_threads(VIRTMGR_THREADS);
+ session.set_max_outgoing_threads(VIRTMGR_THREADS);
+ session
+ .setup_unix_domain_bootstrap_client(self.client_fd.as_fd())
+ .map_err(|_| io::Error::from(io::ErrorKind::ConnectionRefused))
+ }
+}
+
/// Connects to the VirtualizationService AIDL service.
pub fn connect() -> Result<Strong<dyn IVirtualizationService>, StatusCode> {
wait_for_interface(VIRTUALIZATION_SERVICE_BINDER_SERVICE_IDENTIFIER)