Merge "Set avb_hash_algorithm=sha256 for system & vendor img"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index dd81738..321dbf4 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -58,6 +58,9 @@
"path": "packages/modules/Virtualization/authfs"
},
{
+ "path": "packages/modules/Virtualization/pvmfw"
+ },
+ {
"path": "packages/modules/Virtualization/rialto"
},
{
diff --git a/apex/Android.bp b/apex/Android.bp
index 579d7c7..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",
],
}
@@ -73,6 +74,7 @@
arm64: {
binaries: [
"crosvm",
+ "virtmgr",
"virtualizationservice",
],
filesystems: microdroid_filesystem_images,
@@ -80,6 +82,7 @@
x86_64: {
binaries: [
"crosvm",
+ "virtmgr",
"virtualizationservice",
],
filesystems: microdroid_filesystem_images,
diff --git a/authfs/src/file.rs b/authfs/src/file.rs
index aff47c5..55c783b 100644
--- a/authfs/src/file.rs
+++ b/authfs/src/file.rs
@@ -9,7 +9,7 @@
use crate::common::{divide_roundup, CHUNK_SIZE};
use authfs_aidl_interface::aidl::com::android::virt::fs::IVirtFdService::IVirtFdService;
use binder::{Status, StatusCode, Strong};
-use rpcbinder::get_vsock_rpc_interface;
+use rpcbinder::RpcSession;
use std::convert::TryFrom;
use std::io;
use std::path::{Path, MAIN_SEPARATOR};
@@ -22,7 +22,7 @@
pub const RPC_SERVICE_PORT: u32 = 3264;
pub fn get_rpc_binder_service(cid: u32) -> io::Result<VirtFdService> {
- get_vsock_rpc_interface(cid, RPC_SERVICE_PORT).map_err(|e| match e {
+ RpcSession::new().setup_vsock_client(cid, RPC_SERVICE_PORT).map_err(|e| match e {
StatusCode::BAD_VALUE => {
io::Error::new(io::ErrorKind::InvalidInput, "Invalid raw AIBinder")
}
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 40d14d8..8febd52 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -39,7 +39,7 @@
};
use compos_common::binder::to_binder_result;
use compos_common::odrefresh::{is_system_property_interesting, ODREFRESH_PATH};
-use rpcbinder::get_unix_domain_rpc_interface;
+use rpcbinder::RpcSession;
/// Constructs a binder object that implements ICompOsService.
pub fn new_binder() -> Result<Strong<dyn ICompOsService>> {
@@ -130,9 +130,9 @@
impl CompOsService {
fn do_odrefresh(&self, args: &OdrefreshArgs) -> Result<i8> {
log::debug!("Prepare to connect to {}", AUTHFS_SERVICE_SOCKET_NAME);
- let authfs_service: Strong<dyn IAuthFsService> =
- get_unix_domain_rpc_interface(AUTHFS_SERVICE_SOCKET_NAME)
- .with_context(|| format!("Failed to connect to {}", AUTHFS_SERVICE_SOCKET_NAME))?;
+ let authfs_service: Strong<dyn IAuthFsService> = RpcSession::new()
+ .setup_unix_domain_client(AUTHFS_SERVICE_SOCKET_NAME)
+ .with_context(|| format!("Failed to connect to {}", AUTHFS_SERVICE_SOCKET_NAME))?;
let exit_code = odrefresh(&self.odrefresh_path, args, authfs_service, |output_dir| {
// authfs only shows us the files we created, so it's ok to just sign everything
// under the output directory.
diff --git a/javalib/api/system-current.txt b/javalib/api/system-current.txt
index 821c47e..592a751 100644
--- a/javalib/api/system-current.txt
+++ b/javalib/api/system-current.txt
@@ -4,8 +4,8 @@
public class VirtualMachine implements java.lang.AutoCloseable {
method public void clearCallback();
method public void close();
- method @NonNull public android.os.IBinder connectToVsockServer(int) throws android.system.virtualmachine.VirtualMachineException;
- method @NonNull public android.os.ParcelFileDescriptor connectVsock(int) throws android.system.virtualmachine.VirtualMachineException;
+ method @NonNull public android.os.IBinder connectToVsockServer(@IntRange(from=android.system.virtualmachine.VirtualMachine.MIN_VSOCK_PORT, to=android.system.virtualmachine.VirtualMachine.MAX_VSOCK_PORT) long) throws android.system.virtualmachine.VirtualMachineException;
+ method @NonNull public android.os.ParcelFileDescriptor connectVsock(@IntRange(from=android.system.virtualmachine.VirtualMachine.MIN_VSOCK_PORT, to=android.system.virtualmachine.VirtualMachine.MAX_VSOCK_PORT) long) throws android.system.virtualmachine.VirtualMachineException;
method @NonNull public android.system.virtualmachine.VirtualMachineConfig getConfig();
method @NonNull public java.io.InputStream getConsoleOutput() throws android.system.virtualmachine.VirtualMachineException;
method @NonNull public java.io.InputStream getLogOutput() throws android.system.virtualmachine.VirtualMachineException;
@@ -17,6 +17,8 @@
method public void stop() throws android.system.virtualmachine.VirtualMachineException;
method @NonNull public android.system.virtualmachine.VirtualMachineDescriptor toDescriptor() throws android.system.virtualmachine.VirtualMachineException;
field public static final String MANAGE_VIRTUAL_MACHINE_PERMISSION = "android.permission.MANAGE_VIRTUAL_MACHINE";
+ field public static final long MAX_VSOCK_PORT = 4294967295L; // 0xffffffffL
+ field public static final long MIN_VSOCK_PORT = 1024L; // 0x400L
field public static final int STATUS_DELETED = 2; // 0x2
field public static final int STATUS_RUNNING = 1; // 0x1
field public static final int STATUS_STOPPED = 0; // 0x0
@@ -86,9 +88,6 @@
}
public class VirtualMachineException extends java.lang.Exception {
- ctor public VirtualMachineException(@Nullable String);
- ctor public VirtualMachineException(@Nullable String, @Nullable Throwable);
- ctor public VirtualMachineException(@Nullable Throwable);
}
public class VirtualMachineManager {
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 7234dad..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,7 +55,9 @@
return ret;
};
- return AIBinder_toJavaBinder(env, RpcPreconnectedClient(requestFunc, &args));
+ 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/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 49465aa..db5b4d1 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -45,9 +45,11 @@
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.ComponentCallbacks2;
@@ -59,8 +61,8 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
+import android.system.virtualizationcommon.DeathReason;
import android.system.virtualizationcommon.ErrorCode;
-import android.system.virtualizationservice.DeathReason;
import android.system.virtualizationservice.IVirtualMachine;
import android.system.virtualizationservice.IVirtualMachineCallback;
import android.system.virtualizationservice.IVirtualizationService;
@@ -121,6 +123,24 @@
"android.permission.USE_CUSTOM_VIRTUAL_MACHINE";
/**
+ * The lowest port number that can be used to communicate with the virtual machine payload.
+ *
+ * @see #connectToVsockServer
+ * @see #connectVsock
+ */
+ @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock.
+ public static final long MIN_VSOCK_PORT = 1024;
+
+ /**
+ * The highest port number that can be used to communicate with the virtual machine payload.
+ *
+ * @see #connectToVsockServer
+ * @see #connectVsock
+ */
+ @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock.
+ public static final long MAX_VSOCK_PORT = (1L << 32) - 1;
+
+ /**
* Status of a virtual machine
*
* @hide
@@ -993,9 +1013,13 @@
*/
@SystemApi
@NonNull
- public IBinder connectToVsockServer(int port) throws VirtualMachineException {
+ public IBinder connectToVsockServer(
+ @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
+ throws VirtualMachineException {
+
synchronized (mLock) {
- IBinder iBinder = nativeConnectToVsockServer(getRunningVm().asBinder(), port);
+ IBinder iBinder =
+ nativeConnectToVsockServer(getRunningVm().asBinder(), validatePort(port));
if (iBinder == null) {
throw new VirtualMachineException("Failed to connect to vsock server");
}
@@ -1011,10 +1035,12 @@
*/
@SystemApi
@NonNull
- public ParcelFileDescriptor connectVsock(int port) throws VirtualMachineException {
+ public ParcelFileDescriptor connectVsock(
+ @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
+ throws VirtualMachineException {
synchronized (mLock) {
try {
- return getRunningVm().connectVsock(port);
+ return getRunningVm().connectVsock(validatePort(port));
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} catch (ServiceSpecificException e) {
@@ -1023,6 +1049,16 @@
}
}
+ private int validatePort(long port) {
+ // Ports below 1024 are "privileged" (payload code can't bind to these), and port numbers
+ // are 32-bit unsigned numbers at the OS level, even though we pass them as 32-bit signed
+ // numbers internally.
+ if (port < MIN_VSOCK_PORT || port > MAX_VSOCK_PORT) {
+ throw new IllegalArgumentException("Bad port " + port);
+ }
+ return (int) port;
+ }
+
/**
* Returns the root directory where all files related to this {@link VirtualMachine} (e.g.
* {@code instance.img}, {@code apk.idsig}, etc) are stored.
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 602f8b8..75e5414 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -123,7 +123,7 @@
*/
@Nullable private final String mPayloadBinaryPath;
- /** The size of storage in KB. 0 indicates that encryptedStorage is not required */
+ /** The size of storage in KiB. 0 indicates that encryptedStorage is not required */
private final long mEncryptedStorageKib;
private VirtualMachineConfig(
@@ -336,7 +336,7 @@
}
/**
- * Returns the size of encrypted storage (in KB) available in the VM, or 0 if encrypted storage
+ * Returns the size of encrypted storage (in KiB) available in the VM, or 0 if encrypted storage
* is not enabled
*
* @hide
@@ -608,7 +608,7 @@
}
/**
- * Sets the size (in KB) of encrypted storage available to the VM. If not set, no encrypted
+ * Sets the size (in KiB) of encrypted storage available to the VM. If not set, no encrypted
* storage is provided.
*
* <p>The storage is encrypted with a key deterministically derived from the VM identity
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineException.java b/javalib/src/android/system/virtualmachine/VirtualMachineException.java
index 985eb70..9948fda 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineException.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineException.java
@@ -26,15 +26,15 @@
*/
@SystemApi
public class VirtualMachineException extends Exception {
- public VirtualMachineException(@Nullable String message) {
+ VirtualMachineException(@Nullable String message) {
super(message);
}
- public VirtualMachineException(@Nullable String message, @Nullable Throwable cause) {
+ VirtualMachineException(@Nullable String message, @Nullable Throwable cause) {
super(message, cause);
}
- public VirtualMachineException(@Nullable Throwable cause) {
+ VirtualMachineException(@Nullable Throwable cause) {
super(cause);
}
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index ea0a305..5b30617 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -150,6 +150,11 @@
* Returns an existing {@link VirtualMachine} with the given name. Returns null if there is no
* such virtual machine.
*
+ * <p>There is at most one {@code VirtualMachine} object corresponding to a given virtual
+ * machine instance. Multiple calls to get() passing the same name will get the same object
+ * returned, until the virtual machine is deleted (via {@link #delete}) and then recreated.
+ *
+ * @see #getOrCreate
* @throws VirtualMachineException if the virtual machine exists but could not be successfully
* retrieved.
* @hide
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/avb/Android.bp b/libs/avb/Android.bp
index a19a538..436f672 100644
--- a/libs/avb/Android.bp
+++ b/libs/avb/Android.bp
@@ -12,6 +12,7 @@
source_stem: "bindings",
bindgen_flags: [
"--size_t-is-usize",
+ "--default-enum-style rust",
"--allowlist-function=.*",
"--use-core",
"--raw-line=#![no_std]",
@@ -36,18 +37,3 @@
clippy_lints: "none",
lints: "none",
}
-
-rust_library_rlib {
- name: "libavb_nostd",
- crate_name: "avb",
- srcs: ["src/lib.rs"],
- no_stdlibs: true,
- prefer_rlib: true,
- stdlibs: [
- "libcore.rust_sysroot",
- ],
- rustlibs: [
- "libavb_bindgen",
- "liblog_rust_nostd",
- ],
-}
diff --git a/libs/avb/src/ops.rs b/libs/avb/src/ops.rs
deleted file mode 100644
index 429c980..0000000
--- a/libs/avb/src/ops.rs
+++ /dev/null
@@ -1,151 +0,0 @@
-// 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.
-
-//! This module regroups methods related to AvbOps.
-
-#![warn(unsafe_op_in_unsafe_fn)]
-// TODO(b/256148034): Remove this when the feature is code complete.
-#![allow(dead_code)]
-#![allow(unused_imports)]
-
-use alloc::ffi::CString;
-use avb_bindgen::{
- avb_slot_verify, AvbHashtreeErrorMode_AVB_HASHTREE_ERROR_MODE_EIO,
- AvbSlotVerifyFlags_AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION,
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT,
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA,
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_IO,
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_OOM,
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED,
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX,
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION,
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_OK,
-};
-use core::fmt;
-use log::debug;
-
-/// Error code from AVB image verification.
-#[derive(Clone, Copy, Debug)]
-pub enum AvbImageVerifyError {
- /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT
- InvalidArgument,
- /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA
- InvalidMetadata,
- /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_IO
- Io,
- /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_OOM
- Oom,
- /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED
- PublicKeyRejected,
- /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX
- RollbackIndex,
- /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION
- UnsupportedVersion,
- /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION
- Verification,
- /// Unknown error.
- Unknown(u32),
-}
-
-fn to_avb_verify_result(result: u32) -> Result<(), AvbImageVerifyError> {
- #[allow(non_upper_case_globals)]
- match result {
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_OK => Ok(()),
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT => {
- Err(AvbImageVerifyError::InvalidArgument)
- }
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA => {
- Err(AvbImageVerifyError::InvalidMetadata)
- }
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_IO => Err(AvbImageVerifyError::Io),
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_OOM => Err(AvbImageVerifyError::Oom),
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED => {
- Err(AvbImageVerifyError::PublicKeyRejected)
- }
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX => {
- Err(AvbImageVerifyError::RollbackIndex)
- }
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION => {
- Err(AvbImageVerifyError::UnsupportedVersion)
- }
- AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION => {
- Err(AvbImageVerifyError::Verification)
- }
- _ => Err(AvbImageVerifyError::Unknown(result)),
- }
-}
-
-impl fmt::Display for AvbImageVerifyError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Self::InvalidArgument => write!(f, "Invalid parameters."),
- Self::InvalidMetadata => write!(f, "Invalid metadata."),
- Self::Io => write!(f, "I/O error while trying to load data or get a rollback index."),
- Self::Oom => write!(f, "Unable to allocate memory."),
- Self::PublicKeyRejected => write!(
- f,
- "Everything is verified correctly out but the public key is not accepted. \
- This includes the case where integrity data is not signed."
- ),
- Self::RollbackIndex => write!(f, "Rollback index is less than its stored value."),
- Self::UnsupportedVersion => write!(
- f,
- "Some of the metadata requires a newer version of libavb than what is in use."
- ),
- Self::Verification => write!(f, "Data does not verify."),
- Self::Unknown(e) => write!(f, "Unknown avb_slot_verify error '{e}'"),
- }
- }
-}
-
-/// Verifies that for the given image:
-/// - The given public key is acceptable.
-/// - The VBMeta struct is valid.
-/// - The partitions of the image match the descriptors of the verified VBMeta struct.
-/// Returns Ok if everything is verified correctly and the public key is accepted.
-pub fn verify_image(image: &[u8], public_key: &[u8]) -> Result<(), AvbImageVerifyError> {
- AvbOps::new().verify_image(image, public_key)
-}
-
-/// TODO(b/256148034): Make AvbOps a rust wrapper of avb_bindgen::AvbOps using foreign_types.
-struct AvbOps {}
-
-impl AvbOps {
- fn new() -> Self {
- AvbOps {}
- }
-
- fn verify_image(&self, image: &[u8], public_key: &[u8]) -> Result<(), AvbImageVerifyError> {
- debug!("AVB image: addr={:?}, size={:#x} ({1})", image.as_ptr(), image.len());
- debug!(
- "AVB public key: addr={:?}, size={:#x} ({1})",
- public_key.as_ptr(),
- public_key.len()
- );
- // TODO(b/256148034): Verify the kernel image with avb_slot_verify()
- // let result = unsafe {
- // avb_slot_verify(
- // self.as_ptr(),
- // requested_partitions.as_ptr(),
- // ab_suffix.as_ptr(),
- // AvbSlotVerifyFlags_AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION,
- // AvbHashtreeErrorMode_AVB_HASHTREE_ERROR_MODE_EIO,
- // &image.as_ptr(),
- // )
- // };
- let result = AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_OK;
- to_avb_verify_result(result)
- }
-}
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/libs/vbmeta/src/lib.rs b/libs/vbmeta/src/lib.rs
index c273973..1a40e45 100644
--- a/libs/vbmeta/src/lib.rs
+++ b/libs/vbmeta/src/lib.rs
@@ -18,18 +18,12 @@
use avb_bindgen::{
avb_footer_validate_and_byteswap, avb_vbmeta_image_header_to_host_byte_order,
- avb_vbmeta_image_verify, AvbAlgorithmType_AVB_ALGORITHM_TYPE_NONE, AvbFooter,
- AvbVBMetaImageHeader, AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH,
- AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
- AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK,
- AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED,
- AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH,
- AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION,
+ avb_vbmeta_image_verify, AvbAlgorithmType, AvbFooter, AvbVBMetaImageHeader,
+ AvbVBMetaVerifyResult,
};
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom};
use std::mem::{size_of, transmute, MaybeUninit};
-use std::os::raw::c_uint;
use std::path::Path;
use std::ptr::null_mut;
use thiserror::Error;
@@ -68,9 +62,6 @@
/// The VBMeta image signature did not validate.
#[error("Signature mismatch")]
SignatureMismatch,
- /// An unexpected libavb error code was returned.
- #[error("Unknown libavb error: {0}")]
- UnknownLibavbError(c_uint),
}
/// A VBMeta Image.
@@ -130,7 +121,7 @@
/// Get the public key that verified the VBMeta image. If the image was not signed, there
/// is no such public key.
pub fn public_key(&self) -> Option<&[u8]> {
- if self.header.algorithm_type == AvbAlgorithmType_AVB_ALGORITHM_TYPE_NONE {
+ if self.header.algorithm_type == AvbAlgorithmType::AVB_ALGORITHM_TYPE_NONE as u32 {
return None;
}
let begin = size_of::<AvbVBMetaImageHeader>()
@@ -144,7 +135,7 @@
/// image was not signed, there might not be a hash and, if there is, it's not known to be
/// correct.
pub fn hash(&self) -> Option<&[u8]> {
- if self.header.algorithm_type == AvbAlgorithmType_AVB_ALGORITHM_TYPE_NONE {
+ if self.header.algorithm_type == AvbAlgorithmType::AVB_ALGORITHM_TYPE_NONE as u32 {
return None;
}
let begin = size_of::<AvbVBMetaImageHeader>() + self.header.hash_offset as usize;
@@ -168,23 +159,21 @@
// SAFETY: the function only reads from the provided data and the NULL pointers disable the
// output arguments.
let res = unsafe { avb_vbmeta_image_verify(data.as_ptr(), data.len(), null_mut(), null_mut()) };
- #[allow(non_upper_case_globals)]
match res {
- AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK
- | AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED => Ok(()),
- AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER => {
+ AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK
+ | AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED => Ok(()),
+ AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER => {
Err(VbMetaImageParseError::InvalidHeader.into())
}
- AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION => {
+ AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION => {
Err(VbMetaImageParseError::UnsupportedVersion.into())
}
- AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH => {
+ AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH => {
Err(VbMetaImageVerificationError::HashMismatch)
}
- AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH => {
+ AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH => {
Err(VbMetaImageVerificationError::SignatureMismatch)
}
- err => Err(VbMetaImageVerificationError::UnknownLibavbError(err)),
}
}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 3141741..99f91ac 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -90,6 +90,8 @@
"liblzma", // used by init_second_stage
"libvm_payload", // used by payload to interact with microdroid manager
+
+ "prng_seeder",
] + microdroid_shell_and_utilities,
multilib: {
common: {
diff --git a/microdroid/ueventd.rc b/microdroid/ueventd.rc
index 268d3a2..0c5fbfc 100644
--- a/microdroid/ueventd.rc
+++ b/microdroid/ueventd.rc
@@ -29,3 +29,6 @@
/dev/hvc2 0666 system system
/dev/open-dice0 0660 root root
+
+# Aside from kernel threads, only prng_seeder needs access to HW RNG
+/dev/hw_random 0400 prng_seeder prng_seeder
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 6a37b88..4018d00 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -46,7 +46,7 @@
use openssl::sha::Sha512;
use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
use rand::Fill;
-use rpcbinder::get_vsock_rpc_interface;
+use rpcbinder::RpcSession;
use rustutils::sockets::android_get_control_socket;
use rustutils::system_properties;
use rustutils::system_properties::PropertyWatcher;
@@ -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};
@@ -160,7 +161,8 @@
// The host is running a VirtualMachineService for this VM on a port equal
// to the CID of this VM.
let port = vsock::get_local_cid().context("Could not determine local CID")?;
- get_vsock_rpc_interface(VMADDR_CID_HOST, port)
+ RpcSession::new()
+ .setup_vsock_client(VMADDR_CID_HOST, port)
.context("Could not connect to IVirtualMachineService")
}
@@ -795,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/Android.bp b/pvmfw/Android.bp
index 4218fae..2912c91 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -13,17 +13,17 @@
],
rustlibs: [
"libaarch64_paging",
- "libavb_nostd",
"libbuddy_system_allocator",
"libdice_nostd",
"liblibfdt",
"liblog_rust_nostd",
+ "libpvmfw_avb_nostd",
"libpvmfw_embedded_key",
"libtinyvec_nostd",
"libvirtio_drivers",
"libvmbase",
+ "libzeroize_nostd",
],
- apex_available: ["com.android.virt"],
}
cc_binary {
@@ -39,7 +39,15 @@
"image.ld",
":vmbase_sections",
],
- apex_available: ["com.android.virt"],
+ // `installable: false` is inherited from vmbase_elf_defaults, and that
+ // hides this module from Make, which makes it impossible for the Make world
+ // to place the unstripped binary to the symbols directory. Marking back as
+ // installable exposes this module to the Make world again. Note that this
+ // module (pvmfw) still is NOT installed to any of the filesystem images. It
+ // is fed into pvmfw_bin and then into pvmfw_img to become a standalone
+ // partition image. This is just to package the unstripped file into the
+ // symbols zip file for debugging purpose.
+ installable: true,
}
raw_binary {
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/TEST_MAPPING b/pvmfw/TEST_MAPPING
new file mode 100644
index 0000000..8a2d352
--- /dev/null
+++ b/pvmfw/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "avf-presubmit" : [
+ {
+ "name" : "libpvmfw_avb.test"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/pvmfw/avb/Android.bp b/pvmfw/avb/Android.bp
new file mode 100644
index 0000000..65259a5
--- /dev/null
+++ b/pvmfw/avb/Android.bp
@@ -0,0 +1,28 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "libpvmfw_avb_nostd_defaults",
+ crate_name: "pvmfw_avb",
+ srcs: ["src/lib.rs"],
+ prefer_rlib: true,
+ rustlibs: [
+ "libavb_bindgen",
+ ],
+}
+
+rust_library_rlib {
+ name: "libpvmfw_avb_nostd",
+ defaults: ["libpvmfw_avb_nostd_defaults"],
+ no_stdlibs: true,
+ stdlibs: [
+ "libcore.rust_sysroot",
+ ],
+}
+
+rust_test {
+ name: "libpvmfw_avb.test",
+ defaults: ["libpvmfw_avb_nostd_defaults"],
+ test_suites: ["general-tests"],
+}
diff --git a/libs/avb/src/lib.rs b/pvmfw/avb/src/lib.rs
similarity index 77%
rename from libs/avb/src/lib.rs
rename to pvmfw/avb/src/lib.rs
index 21b7d2a..eb1f918 100644
--- a/libs/avb/src/lib.rs
+++ b/pvmfw/avb/src/lib.rs
@@ -12,12 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//! This module regroups the rust API for libavb.
+//! A library implementing the payload verification for pvmfw with libavb
-#![no_std]
+#![cfg_attr(not(test), no_std)]
-extern crate alloc;
+mod verify;
-mod ops;
-
-pub use ops::{verify_image, AvbImageVerifyError};
+pub use verify::{verify_payload, AvbImageVerifyError};
diff --git a/pvmfw/avb/src/verify.rs b/pvmfw/avb/src/verify.rs
new file mode 100644
index 0000000..7f3ba3d
--- /dev/null
+++ b/pvmfw/avb/src/verify.rs
@@ -0,0 +1,112 @@
+// 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.
+
+//! This module handles the pvmfw payload verification.
+
+use avb_bindgen::AvbSlotVerifyResult;
+use core::fmt;
+
+/// Error code from AVB image verification.
+#[derive(Clone, Debug)]
+pub enum AvbImageVerifyError {
+ /// AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT
+ InvalidArgument,
+ /// AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA
+ InvalidMetadata,
+ /// AVB_SLOT_VERIFY_RESULT_ERROR_IO
+ Io,
+ /// AVB_SLOT_VERIFY_RESULT_ERROR_OOM
+ Oom,
+ /// AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED
+ PublicKeyRejected,
+ /// AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX
+ RollbackIndex,
+ /// AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION
+ UnsupportedVersion,
+ /// AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION
+ Verification,
+}
+
+impl fmt::Display for AvbImageVerifyError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::InvalidArgument => write!(f, "Invalid parameters."),
+ Self::InvalidMetadata => write!(f, "Invalid metadata."),
+ Self::Io => write!(f, "I/O error while trying to load data or get a rollback index."),
+ Self::Oom => write!(f, "Unable to allocate memory."),
+ Self::PublicKeyRejected => write!(f, "Public key rejected or data not signed."),
+ Self::RollbackIndex => write!(f, "Rollback index is less than its stored value."),
+ Self::UnsupportedVersion => write!(
+ f,
+ "Some of the metadata requires a newer version of libavb than what is in use."
+ ),
+ Self::Verification => write!(f, "Data does not verify."),
+ }
+ }
+}
+
+fn to_avb_verify_result(result: AvbSlotVerifyResult) -> Result<(), AvbImageVerifyError> {
+ match result {
+ AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_OK => Ok(()),
+ AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT => {
+ Err(AvbImageVerifyError::InvalidArgument)
+ }
+ AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA => {
+ Err(AvbImageVerifyError::InvalidMetadata)
+ }
+ AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_IO => Err(AvbImageVerifyError::Io),
+ AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_OOM => Err(AvbImageVerifyError::Oom),
+ AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED => {
+ Err(AvbImageVerifyError::PublicKeyRejected)
+ }
+ AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX => {
+ Err(AvbImageVerifyError::RollbackIndex)
+ }
+ AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION => {
+ Err(AvbImageVerifyError::UnsupportedVersion)
+ }
+ AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION => {
+ Err(AvbImageVerifyError::Verification)
+ }
+ }
+}
+
+/// Verifies the payload (signed kernel + initrd) against the trusted public key.
+pub fn verify_payload(_public_key: &[u8]) -> Result<(), AvbImageVerifyError> {
+ // TODO(b/256148034): Verify the kernel image with avb_slot_verify()
+ // let result = unsafe {
+ // avb_slot_verify(
+ // &mut avb_ops,
+ // requested_partitions.as_ptr(),
+ // ab_suffix.as_ptr(),
+ // flags,
+ // hashtree_error_mode,
+ // null_mut(),
+ // )
+ // };
+ let result = AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_OK;
+ to_avb_verify_result(result)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ // TODO(b/256148034): Test verification succeeds with valid payload later.
+ #[test]
+ fn verification_succeeds_with_placeholder_input() {
+ let fake_public_key = [0u8; 2];
+ assert!(verify_payload(&fake_public_key).is_ok());
+ }
+}
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
index b633745..f209784 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -20,13 +20,19 @@
use core::ops::Range;
use core::result;
+/// Configuration data header.
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
struct Header {
+ /// Magic number; must be `Header::MAGIC`.
magic: u32,
+ /// Version of the header format.
version: u32,
+ /// 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],
}
@@ -90,6 +96,7 @@
impl Header {
const MAGIC: u32 = u32::from_ne_bytes(*b"pvmf");
+ const VERSION_1_0: u32 = Self::version(1, 0);
const PADDED_SIZE: usize =
helpers::unchecked_align_up(mem::size_of::<Self>(), mem::size_of::<u64>());
@@ -186,7 +193,7 @@
return Err(Error::InvalidMagic);
}
- if header.version != Header::version(1, 0) {
+ if header.version != Header::VERSION_1_0 {
let (major, minor) = header.version_tuple();
return Err(Error::UnsupportedVersion(major, minor));
}
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 2763e80..45a8459 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -249,7 +249,7 @@
// This wrapper allows main() to be blissfully ignorant of platform details.
crate::main(slices.fdt, slices.kernel, slices.ramdisk, &bcc, &mut memory)?;
- // TODO: Overwrite BCC before jumping to payload to avoid leaking our sealing key.
+ helpers::flushed_zeroize(bcc_slice);
info!("Expecting a bug making MMIO_GUARD_UNMAP return NOT_SUPPORTED on success");
memory.mmio_unmap_all().map_err(|e| {
diff --git a/pvmfw/src/helpers.rs b/pvmfw/src/helpers.rs
index f1ff36d..e8a20a8 100644
--- a/pvmfw/src/helpers.rs
+++ b/pvmfw/src/helpers.rs
@@ -15,6 +15,7 @@
//! Miscellaneous helper functions.
use core::arch::asm;
+use zeroize::Zeroize;
pub const SIZE_4KB: usize = 4 << 10;
pub const SIZE_2MB: usize = 2 << 20;
@@ -44,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)
@@ -75,3 +87,10 @@
unsafe { asm!("dc cvau, {x}", x = in(reg) line) }
}
}
+
+#[inline]
+/// Overwrites the slice with zeroes, to the point of unification.
+pub fn flushed_zeroize(reg: &mut [u8]) {
+ reg.zeroize();
+ flush_region(reg.as_ptr() as usize, reg.len())
+}
diff --git a/pvmfw/src/hvc.rs b/pvmfw/src/hvc.rs
new file mode 100644
index 0000000..dc99303
--- /dev/null
+++ b/pvmfw/src/hvc.rs
@@ -0,0 +1,96 @@
+// 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.
+
+//! Wrappers around calls to the hypervisor.
+
+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];
+
+ checked_hvc64(VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID, args)
+}
+
+pub fn mmio_guard_enroll() -> smccc::Result<()> {
+ let args = [0u64; 17];
+
+ checked_hvc64_expect_zero(VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID, args)
+}
+
+pub fn mmio_guard_map(ipa: u64) -> smccc::Result<()> {
+ let mut args = [0u64; 17];
+ args[0] = ipa;
+
+ // TODO(b/253586500): pKVM currently returns a i32 instead of a i64.
+ let is_i32_error_code = |n| u32::try_from(n).ok().filter(|v| (*v as i32) < 0).is_some();
+ match checked_hvc64_expect_zero(VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID, args) {
+ Err(smccc::Error::Unexpected(e)) if is_i32_error_code(e) => {
+ info!("Handled a pKVM bug by interpreting the MMIO_GUARD_MAP return value as i32");
+ match e as u32 as i32 {
+ -1 => Err(smccc::Error::NotSupported),
+ -2 => Err(smccc::Error::NotRequired),
+ -3 => Err(smccc::Error::InvalidParameter),
+ ret => Err(smccc::Error::Unknown(ret as i64)),
+ }
+ }
+ res => res,
+ }
+}
+
+pub fn mmio_guard_unmap(ipa: u64) -> smccc::Result<()> {
+ let mut args = [0u64; 17];
+ args[0] = ipa;
+
+ // TODO(b/251426790): pKVM currently returns NOT_SUPPORTED for SUCCESS.
+ match checked_hvc64_expect_zero(VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID, args) {
+ Err(smccc::Error::NotSupported) | Ok(_) => Ok(()),
+ x => x,
+ }
+}
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 79b6f57..59efca0 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -26,6 +26,7 @@
mod fdt;
mod heap;
mod helpers;
+mod hvc;
mod memory;
mod mmio_guard;
mod mmu;
@@ -38,10 +39,10 @@
memory::MemoryTracker,
pci::{allocate_all_virtio_bars, PciError, PciInfo, PciMemory32Allocator},
};
-use ::avb::verify_image;
use dice::bcc;
use libfdt::Fdt;
use log::{debug, error, info, trace};
+use pvmfw_avb::verify_payload;
fn main(
fdt: &Fdt,
@@ -71,7 +72,7 @@
let mut pci_root = unsafe { pci_info.make_pci_root() };
allocate_all_virtio_bars(&mut pci_root, &mut bar_allocator).map_err(handle_pci_error)?;
- verify_image(signed_kernel, PUBLIC_KEY).map_err(|e| {
+ verify_payload(PUBLIC_KEY).map_err(|e| {
error!("Failed to verify the payload: {e}");
RebootReason::PayloadVerificationError
})?;
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/pvmfw/src/mmio_guard.rs b/pvmfw/src/mmio_guard.rs
index 28f928f..e5f376e 100644
--- a/pvmfw/src/mmio_guard.rs
+++ b/pvmfw/src/mmio_guard.rs
@@ -15,9 +15,9 @@
//! Safe MMIO_GUARD support.
use crate::helpers;
+use crate::hvc::{mmio_guard_enroll, mmio_guard_info, mmio_guard_map, mmio_guard_unmap};
use crate::smccc;
use core::{fmt, result};
-use log::info;
#[derive(Debug, Clone)]
pub enum Error {
@@ -63,50 +63,3 @@
pub fn unmap(addr: usize) -> Result<()> {
mmio_guard_unmap(helpers::page_4kb_of(addr) as u64).map_err(Error::UnmapFailed)
}
-
-fn mmio_guard_info() -> smccc::Result<u64> {
- const VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID: u32 = 0xc6000005;
- let args = [0u64; 17];
-
- smccc::checked_hvc64(VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID, args)
-}
-
-fn mmio_guard_enroll() -> smccc::Result<()> {
- const VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID: u32 = 0xc6000006;
- let args = [0u64; 17];
-
- smccc::checked_hvc64_expect_zero(VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID, args)
-}
-
-fn mmio_guard_map(ipa: u64) -> smccc::Result<()> {
- const VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID: u32 = 0xc6000007;
- let mut args = [0u64; 17];
- args[0] = ipa;
-
- // TODO(b/253586500): pKVM currently returns a i32 instead of a i64.
- let is_i32_error_code = |n| u32::try_from(n).ok().filter(|v| (*v as i32) < 0).is_some();
- match smccc::checked_hvc64_expect_zero(VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID, args) {
- Err(smccc::Error::Unexpected(e)) if is_i32_error_code(e) => {
- info!("Handled a pKVM bug by interpreting the MMIO_GUARD_MAP return value as i32");
- match e as u32 as i32 {
- -1 => Err(smccc::Error::NotSupported),
- -2 => Err(smccc::Error::NotRequired),
- -3 => Err(smccc::Error::InvalidParameter),
- ret => Err(smccc::Error::Unknown(ret as i64)),
- }
- }
- res => res,
- }
-}
-
-fn mmio_guard_unmap(ipa: u64) -> smccc::Result<()> {
- const VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID: u32 = 0xc6000008;
- let mut args = [0u64; 17];
- args[0] = ipa;
-
- // TODO(b/251426790): pKVM currently returns NOT_SUPPORTED for SUCCESS.
- match smccc::checked_hvc64_expect_zero(VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID, args) {
- Err(smccc::Error::NotSupported) | Ok(_) => Ok(()),
- x => x,
- }
-}
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index 077c74f..c936e1b 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -17,9 +17,9 @@
/** {@hide} */
interface ITestService {
- const int SERVICE_PORT = 5678;
+ const long SERVICE_PORT = 5678;
- const int ECHO_REVERSE_PORT = 6789;
+ const long ECHO_REVERSE_PORT = 0x80000001L; // Deliberately chosen to be > 2^31, < 2^32
/* add two integers. */
int addInteger(int a, int b);
@@ -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 25f2310..f3bbbf1 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -53,6 +53,7 @@
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.function.ThrowingRunnable;
import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -212,6 +213,55 @@
}
@Test
+ @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ public void vmLifecycleChecks() throws Exception {
+ assumeSupportedKernel();
+
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setMemoryMib(minMemoryRequired())
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+
+ VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+ assertThat(vm.getStatus()).isEqualTo(STATUS_STOPPED);
+
+ // These methods require a running VM
+ assertThrowsVmExceptionContaining(
+ () -> vm.connectVsock(VirtualMachine.MIN_VSOCK_PORT), "not in running state");
+ assertThrowsVmExceptionContaining(
+ () -> vm.connectToVsockServer(VirtualMachine.MIN_VSOCK_PORT),
+ "not in running state");
+
+ vm.run();
+ assertThat(vm.getStatus()).isEqualTo(STATUS_RUNNING);
+
+ // These methods require a stopped VM
+ assertThrowsVmExceptionContaining(() -> vm.run(), "not in stopped state");
+ assertThrowsVmExceptionContaining(() -> vm.setConfig(config), "not in stopped state");
+ assertThrowsVmExceptionContaining(() -> vm.toDescriptor(), "not in stopped state");
+ assertThrowsVmExceptionContaining(
+ () -> getVirtualMachineManager().delete("test_vm"), "not in stopped state");
+
+ vm.stop();
+ getVirtualMachineManager().delete("test_vm");
+ assertThat(vm.getStatus()).isEqualTo(STATUS_DELETED);
+
+ // None of these should work for a deleted VM
+ assertThrowsVmExceptionContaining(
+ () -> vm.connectVsock(VirtualMachine.MIN_VSOCK_PORT), "deleted");
+ assertThrowsVmExceptionContaining(
+ () -> vm.connectToVsockServer(VirtualMachine.MIN_VSOCK_PORT), "deleted");
+ assertThrowsVmExceptionContaining(() -> vm.run(), "deleted");
+ assertThrowsVmExceptionContaining(() -> vm.setConfig(config), "deleted");
+ assertThrowsVmExceptionContaining(() -> vm.toDescriptor(), "deleted");
+ // This is indistinguishable from the VM having never existed, so the message
+ // is non-specific.
+ assertThrowsVmException(() -> getVirtualMachineManager().delete("test_vm"));
+ }
+
+ @Test
@CddTest(requirements = {"9.17/C-1-1"})
public void connectVsock() throws Exception {
assumeSupportedKernel();
@@ -313,6 +363,37 @@
@Test
@CddTest(requirements = {"9.17/C-1-1"})
+ public void vmConfigBuilderUnitTests() {
+ VirtualMachineConfig.Builder builder = newVmConfigBuilder();
+
+ // All your null are belong to me.
+ assertThrows(NullPointerException.class, () -> new VirtualMachineConfig.Builder(null));
+ assertThrows(NullPointerException.class, () -> builder.setApkPath(null));
+ assertThrows(NullPointerException.class, () -> builder.setPayloadConfigPath(null));
+ assertThrows(NullPointerException.class, () -> builder.setPayloadBinaryPath(null));
+ assertThrows(NullPointerException.class, () -> builder.setPayloadConfigPath(null));
+
+ // Individual property checks.
+ assertThrows(
+ IllegalArgumentException.class, () -> builder.setApkPath("relative/path/to.apk"));
+ assertThrows(IllegalArgumentException.class, () -> builder.setDebugLevel(-1));
+ assertThrows(IllegalArgumentException.class, () -> builder.setMemoryMib(0));
+ assertThrows(IllegalArgumentException.class, () -> builder.setNumCpus(0));
+ assertThrows(IllegalArgumentException.class, () -> builder.setEncryptedStorageKib(0));
+
+ // Consistency checks enforced at build time.
+ Exception e;
+ e = assertThrows(IllegalStateException.class, () -> builder.build());
+ assertThat(e).hasMessageThat().contains("setPayloadBinaryPath must be called");
+
+ VirtualMachineConfig.Builder protectedNotSet =
+ new VirtualMachineConfig.Builder(getContext()).setPayloadBinaryPath("binary/path");
+ e = assertThrows(IllegalStateException.class, () -> protectedNotSet.build());
+ assertThat(e).hasMessageThat().contains("setProtectedVm must be called");
+ }
+
+ @Test
+ @CddTest(requirements = {"9.17/C-1-1"})
public void vmUnitTests() throws Exception {
VirtualMachineConfig.Builder builder =
newVmConfigBuilder().setPayloadBinaryPath("binary/path");
@@ -335,6 +416,48 @@
@Test
@CddTest(requirements = {"9.17/C-1-1"})
+ public void vmmGetAndCreate() throws Exception {
+ assumeSupportedKernel();
+
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setMemoryMib(minMemoryRequired())
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+
+ VirtualMachineManager vmm = getVirtualMachineManager();
+ String vmName = "vmName";
+
+ // VM does not yet exist
+ assertThat(vmm.get(vmName)).isNull();
+
+ VirtualMachine vm1 = vmm.create(vmName, config);
+
+ // Now it does, and we should get the same instance back
+ assertThat(vmm.get(vmName)).isSameInstanceAs(vm1);
+ assertThat(vmm.getOrCreate(vmName, config)).isSameInstanceAs(vm1);
+
+ // Can't recreate it though
+ assertThrowsVmException(() -> vmm.create(vmName, config));
+
+ vmm.delete(vmName);
+ assertThat(vmm.get(vmName)).isNull();
+
+ // Now that we deleted the old one, this should create rather than get, and it should be a
+ // new instance.
+ VirtualMachine vm2 = vmm.getOrCreate(vmName, config);
+ assertThat(vm2).isNotSameInstanceAs(vm1);
+
+ // Subsequent gets should return this new one.
+ assertThat(vmm.get(vmName)).isSameInstanceAs(vm2);
+ assertThat(vmm.getOrCreate(vmName, config)).isSameInstanceAs(vm2);
+
+ vmm.delete(vmName);
+ }
+
+ @Test
+ @CddTest(requirements = {"9.17/C-1-1"})
public void vmFilesStoredInDeDirWhenCreatedFromDEContext() throws Exception {
final Context ctx = getContext().createDeviceProtectedStorageContext();
final int userId = ctx.getUserId();
@@ -423,10 +546,10 @@
assertThat(vmm.get("test_vm_delete")).isNull();
// Can't start the VM even with an existing reference
- assertThrows(VirtualMachineException.class, vm::run);
+ assertThrowsVmException(vm::run);
// Can't delete the VM since it no longer exists
- assertThrows(VirtualMachineException.class, () -> vmm.delete("test_vm_delete"));
+ assertThrowsVmException(() -> vmm.delete("test_vm_delete"));
}
@Test
@@ -451,20 +574,6 @@
}
@Test
- @CddTest(requirements = {
- "9.17/C-1-1",
- })
- public void invalidApkPathIsRejected() {
- VirtualMachineConfig.Builder builder =
- newVmConfigBuilder()
- .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
- .setDebugLevel(DEBUG_LEVEL_FULL)
- .setMemoryMib(minMemoryRequired());
- assertThrows(
- IllegalArgumentException.class, () -> builder.setApkPath("relative/path/to.apk"));
- }
-
- @Test
@CddTest(requirements = {"9.17/C-1-1"})
public void invalidVmNameIsRejected() {
VirtualMachineManager vmm = getVirtualMachineManager();
@@ -1008,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);
@@ -1024,6 +1152,16 @@
return filePath.toFile();
}
+ private void assertThrowsVmException(ThrowingRunnable runnable) {
+ assertThrows(VirtualMachineException.class, runnable);
+ }
+
+ private void assertThrowsVmExceptionContaining(
+ ThrowingRunnable runnable, String expectedContents) {
+ Exception e = assertThrows(VirtualMachineException.class, runnable);
+ assertThat(e).hasMessageThat().contains(expectedContents);
+ }
+
private int minMemoryRequired() {
if (Build.SUPPORTED_ABIS.length > 0) {
String primaryAbi = Build.SUPPORTED_ABIS[0];
@@ -1052,6 +1190,7 @@
String mExtraApkTestProp;
String mApkContentsPath;
String mEncryptedStoragePath;
+ String[] mEffectiveCapabilities;
}
private TestResults runVmTestService(VirtualMachine vm) throws Exception {
@@ -1075,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 8a0019d..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;
@@ -112,7 +115,7 @@
}
struct sockaddr_vm server_sa = (struct sockaddr_vm){
.svm_family = AF_VSOCK,
- .svm_port = BnTestService::ECHO_REVERSE_PORT,
+ .svm_port = static_cast<uint32_t>(BnTestService::ECHO_REVERSE_PORT),
.svm_cid = VMADDR_CID_ANY,
};
int ret = TEMP_FAILURE_RETRY(bind(server_fd, (struct sockaddr*)&server_sa, sizeof(server_sa)));
@@ -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/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index da56f76..d0dde42 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -5,7 +5,6 @@
rust_defaults {
name: "virtualizationservice_defaults",
crate_name: "virtualizationservice",
- srcs: ["src/main.rs"],
edition: "2021",
// Only build on targets which crosvm builds on.
enabled: false,
@@ -67,11 +66,23 @@
rust_binary {
name: "virtualizationservice",
defaults: ["virtualizationservice_defaults"],
+ srcs: ["src/main.rs"],
+ apex_available: ["com.android.virt"],
+}
+
+rust_binary {
+ name: "virtmgr",
+ defaults: ["virtualizationservice_defaults"],
+ srcs: ["src/virtmgr.rs"],
+ rustlibs: [
+ "libclap",
+ ],
apex_available: ["com.android.virt"],
}
rust_test {
name: "virtualizationservice_device_test",
+ srcs: ["src/main.rs"],
defaults: ["virtualizationservice_defaults"],
rustlibs: [
"libtempfile",
diff --git a/virtualizationservice/aidl/Android.bp b/virtualizationservice/aidl/Android.bp
index a0bbc00..f028c0f 100644
--- a/virtualizationservice/aidl/Android.bp
+++ b/virtualizationservice/aidl/Android.bp
@@ -36,6 +36,7 @@
aidl_interface {
name: "android.system.virtualizationservice_internal",
srcs: ["android/system/virtualizationservice_internal/**/*.aidl"],
+ imports: ["android.system.virtualizationcommon"],
unstable: true,
backend: {
java: {
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/DeathReason.aidl b/virtualizationservice/aidl/android/system/virtualizationcommon/DeathReason.aidl
similarity index 97%
rename from virtualizationservice/aidl/android/system/virtualizationservice/DeathReason.aidl
rename to virtualizationservice/aidl/android/system/virtualizationcommon/DeathReason.aidl
index 0980630..3f47002 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/DeathReason.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationcommon/DeathReason.aidl
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.system.virtualizationservice;
+package android.system.virtualizationcommon;
/**
* The reason why a VM died.
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
index 34b6fa5..40e4c58 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
@@ -15,8 +15,8 @@
*/
package android.system.virtualizationservice;
+import android.system.virtualizationcommon.DeathReason;
import android.system.virtualizationcommon.ErrorCode;
-import android.system.virtualizationservice.DeathReason;
/**
* An object which a client may register with the VirtualizationService to get callbacks about the
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmBooted.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmBooted.aidl
new file mode 100644
index 0000000..2d9e9bf
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmBooted.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+package android.system.virtualizationservice_internal;
+
+parcelable AtomVmBooted {
+ int uid;
+ @utf8InCpp String vmIdentifier;
+ long elapsedTimeMillis;
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmCreationRequested.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmCreationRequested.aidl
new file mode 100644
index 0000000..ec2d206
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmCreationRequested.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package android.system.virtualizationservice_internal;
+
+parcelable AtomVmCreationRequested {
+ int uid;
+ @utf8InCpp String vmIdentifier;
+ boolean isProtected;
+ boolean creationSucceeded;
+ int binderExceptionCode;
+ int configType;
+ int numCpus;
+ int memoryMib;
+ @utf8InCpp String apexes;
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmExited.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmExited.aidl
new file mode 100644
index 0000000..60b902d
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmExited.aidl
@@ -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.
+ */
+
+package android.system.virtualizationservice_internal;
+
+import android.system.virtualizationcommon.DeathReason;
+
+parcelable AtomVmExited {
+ int uid;
+ String vmIdentifier;
+ DeathReason deathReason;
+ int exitSignal;
+ long elapsedTimeMillis;
+ long guestTimeMillis;
+ long rssVmKb;
+ long rssCrosvmKb;
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index 851ddf4..ff56b68 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -15,6 +15,9 @@
*/
package android.system.virtualizationservice_internal;
+import android.system.virtualizationservice_internal.AtomVmBooted;
+import android.system.virtualizationservice_internal.AtomVmCreationRequested;
+import android.system.virtualizationservice_internal.AtomVmExited;
import android.system.virtualizationservice_internal.IGlobalVmContext;
interface IVirtualizationServiceInternal {
@@ -26,4 +29,13 @@
* to the returned object.
*/
IGlobalVmContext allocateGlobalVmContext();
+
+ /** Forwards a VmBooted atom to statsd. */
+ void atomVmBooted(in AtomVmBooted atom);
+
+ /** Forwards a VmCreationRequested atom to statsd. */
+ void atomVmCreationRequested(in AtomVmCreationRequested atom);
+
+ /** Forwards a VmExited atom to statsd. */
+ void atomVmExited(in AtomVmExited atom);
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 4c6b961..d7c7125 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -14,15 +14,20 @@
//! Implementation of the AIDL interface of the VirtualizationService.
-use crate::atom::{write_vm_booted_stats, write_vm_creation_stats};
+use crate::{get_calling_pid, get_calling_uid};
+use crate::atom::{
+ forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom,
+ write_vm_booted_stats, write_vm_creation_stats};
use crate::composite::make_composite_image;
use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmContext, VmInstance, VmState};
use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images};
use crate::selinux::{getfilecon, SeContext};
use android_os_permissions_aidl::aidl::android::os::IPermissionController;
-use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
-use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::{
DeathReason::DeathReason,
+ ErrorCode::ErrorCode,
+};
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
DiskImage::DiskImage,
IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
IVirtualMachineCallback::IVirtualMachineCallback,
@@ -38,6 +43,9 @@
VirtualMachineState::VirtualMachineState,
};
use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::{
+ AtomVmBooted::AtomVmBooted,
+ AtomVmCreationRequested::AtomVmCreationRequested,
+ AtomVmExited::AtomVmExited,
IGlobalVmContext::{BnGlobalVmContext, IGlobalVmContext},
IVirtualizationServiceInternal::{BnVirtualizationServiceInternal, IVirtualizationServiceInternal},
};
@@ -48,9 +56,10 @@
use apkverify::{HashAlgorithm, V4Signature};
use binder::{
self, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, ParcelFileDescriptor,
- Status, StatusCode, Strong, ThreadState,
+ Status, StatusCode, Strong,
};
use disk::QcowFile;
+use lazy_static::lazy_static;
use libc::VMADDR_CID_HOST;
use log::{debug, error, info, warn};
use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
@@ -74,8 +83,6 @@
/// The unique ID of a VM used (together with a port number) for vsock communication.
pub type Cid = u32;
-pub const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
-
/// Directory in which to write disk image files used while running VMs.
pub const TEMPORARY_DIRECTORY: &str = "/data/misc/virtualizationservice";
@@ -102,6 +109,13 @@
const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
+lazy_static! {
+ pub static ref GLOBAL_SERVICE: Strong<dyn IVirtualizationServiceInternal> = {
+ let service = VirtualizationServiceInternal::init();
+ BnVirtualizationServiceInternal::new_binder(service, BinderFeatures::default())
+ };
+}
+
fn is_valid_guest_cid(cid: Cid) -> bool {
(GUEST_CID_MIN..=GUEST_CID_MAX).contains(&cid)
}
@@ -164,6 +178,21 @@
})?;
Ok(GlobalVmContext::create(cid))
}
+
+ fn atomVmBooted(&self, atom: &AtomVmBooted) -> Result<(), Status> {
+ forward_vm_booted_atom(atom);
+ Ok(())
+ }
+
+ fn atomVmCreationRequested(&self, atom: &AtomVmCreationRequested) -> Result<(), Status> {
+ forward_vm_creation_atom(atom);
+ Ok(())
+ }
+
+ fn atomVmExited(&self, atom: &AtomVmExited) -> Result<(), Status> {
+ forward_vm_exited_atom(atom);
+ Ok(())
+ }
}
/// The mutable state of the VirtualizationServiceInternal. There should only be one instance
@@ -257,10 +286,9 @@
}
/// Implementation of `IVirtualizationService`, the entry point of the AIDL service.
-#[derive(Debug)]
+#[derive(Debug, Default)]
pub struct VirtualizationService {
state: Arc<Mutex<State>>,
- global_service: Strong<dyn IVirtualizationServiceInternal>,
}
impl Interface for VirtualizationService {
@@ -461,18 +489,14 @@
impl VirtualizationService {
pub fn init() -> VirtualizationService {
- let global_service = VirtualizationServiceInternal::init();
- let global_service =
- BnVirtualizationServiceInternal::new_binder(global_service, BinderFeatures::default());
-
- VirtualizationService { global_service, state: Default::default() }
+ VirtualizationService::default()
}
fn create_vm_context(&self) -> Result<(VmContext, Cid)> {
const NUM_ATTEMPTS: usize = 5;
for _ in 0..NUM_ATTEMPTS {
- let global_context = self.global_service.allocateGlobalVmContext()?;
+ let global_context = GLOBAL_SERVICE.allocateGlobalVmContext()?;
let cid = global_context.getCid()? as Cid;
let service = VirtualMachineService::new_binder(self.state.clone(), cid).as_binder();
@@ -525,8 +549,8 @@
let state = &mut *self.state.lock().unwrap();
let console_fd = console_fd.map(clone_file).transpose()?;
let log_fd = log_fd.map(clone_file).transpose()?;
- let requester_uid = ThreadState::get_calling_uid();
- let requester_debug_pid = ThreadState::get_calling_pid();
+ let requester_uid = get_calling_uid();
+ let requester_debug_pid = get_calling_pid();
// Counter to generate unique IDs for temporary image files.
let mut next_temporary_image_id = 0;
@@ -852,8 +876,8 @@
/// Checks whether the caller has a specific permission
fn check_permission(perm: &str) -> binder::Result<()> {
- let calling_pid = ThreadState::get_calling_pid();
- let calling_uid = ThreadState::get_calling_uid();
+ let calling_pid = get_calling_pid();
+ let calling_uid = get_calling_uid();
// Root can do anything
if calling_uid == 0 {
return Ok(());
@@ -987,13 +1011,16 @@
if !matches!(&*self.instance.vm_state.lock().unwrap(), VmState::Running { .. }) {
return Err(Status::new_service_specific_error_str(-1, Some("VM is not running")));
}
- let stream =
- VsockStream::connect_with_cid_port(self.instance.cid, port as u32).map_err(|e| {
- Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to connect: {:?}", e)),
- )
- })?;
+ 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}")),
+ ));
+ }
+ 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)))
+ })?;
Ok(vsock_stream_to_pfd(stream))
}
}
diff --git a/virtualizationservice/src/atom.rs b/virtualizationservice/src/atom.rs
index a880d60..e430c74 100644
--- a/virtualizationservice/src/atom.rs
+++ b/virtualizationservice/src/atom.rs
@@ -14,17 +14,23 @@
//! Functions for creating and collecting atoms.
-use crate::aidl::clone_file;
+use crate::aidl::{clone_file, GLOBAL_SERVICE};
use crate::crosvm::VmMetric;
+use crate::get_calling_uid;
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::DeathReason::DeathReason;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
- DeathReason::DeathReason,
IVirtualMachine::IVirtualMachine,
VirtualMachineAppConfig::{Payload::Payload, VirtualMachineAppConfig},
VirtualMachineConfig::VirtualMachineConfig,
};
use android_system_virtualizationservice::binder::{Status, Strong};
+use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::{
+ AtomVmBooted::AtomVmBooted,
+ AtomVmCreationRequested::AtomVmCreationRequested,
+ AtomVmExited::AtomVmExited,
+};
use anyhow::{anyhow, Result};
-use binder::{ParcelFileDescriptor, ThreadState};
+use binder::ParcelFileDescriptor;
use log::{trace, warn};
use microdroid_payload_config::VmPayloadConfig;
use rustutils::system_properties;
@@ -106,34 +112,58 @@
),
};
- let uid = ThreadState::get_calling_uid() as i32;
- thread::spawn(move || {
- let vm_creation_requested = vm_creation_requested::VmCreationRequested {
- uid,
- vm_identifier: &vm_identifier,
- hypervisor: vm_creation_requested::Hypervisor::Pkvm,
- is_protected,
- creation_succeeded,
- binder_exception_code,
- config_type,
- num_cpus,
- cpu_affinity: "", // deprecated
- memory_mib,
- apexes: &apexes,
- // TODO(seungjaeyoo) Fill information about task_profile
- // TODO(seungjaeyoo) Fill information about disk_image for raw config
- };
+ let atom = AtomVmCreationRequested {
+ uid: get_calling_uid() as i32,
+ vmIdentifier: vm_identifier,
+ isProtected: is_protected,
+ creationSucceeded: creation_succeeded,
+ binderExceptionCode: binder_exception_code,
+ configType: config_type as i32,
+ numCpus: num_cpus,
+ memoryMib: memory_mib,
+ apexes,
+ };
- wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
- match vm_creation_requested.stats_write() {
- Err(e) => {
- warn!("statslog_rust failed with error: {}", e);
- }
- Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
- }
+ thread::spawn(move || {
+ GLOBAL_SERVICE.atomVmCreationRequested(&atom).unwrap_or_else(|e| {
+ warn!("Failed to write VmCreationRequested atom: {e}");
+ });
});
}
+pub fn forward_vm_creation_atom(atom: &AtomVmCreationRequested) {
+ let config_type = match atom.configType {
+ x if x == vm_creation_requested::ConfigType::VirtualMachineAppConfig as i32 => {
+ vm_creation_requested::ConfigType::VirtualMachineAppConfig
+ }
+ x if x == vm_creation_requested::ConfigType::VirtualMachineRawConfig as i32 => {
+ vm_creation_requested::ConfigType::VirtualMachineRawConfig
+ }
+ _ => vm_creation_requested::ConfigType::UnknownConfig,
+ };
+ let vm_creation_requested = vm_creation_requested::VmCreationRequested {
+ uid: atom.uid,
+ vm_identifier: &atom.vmIdentifier,
+ hypervisor: vm_creation_requested::Hypervisor::Pkvm,
+ is_protected: atom.isProtected,
+ creation_succeeded: atom.creationSucceeded,
+ binder_exception_code: atom.binderExceptionCode,
+ config_type,
+ num_cpus: atom.numCpus,
+ cpu_affinity: "", // deprecated
+ memory_mib: atom.memoryMib,
+ apexes: &atom.apexes,
+ // TODO(seungjaeyoo) Fill information about task_profile
+ // TODO(seungjaeyoo) Fill information about disk_image for raw config
+ };
+
+ wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
+ match vm_creation_requested.stats_write() {
+ Err(e) => warn!("statslog_rust failed with error: {}", e),
+ Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
+ }
+}
+
/// Write the stats of VM boot to statsd
/// The function creates a separate thread which waits fro statsd to start to push atom
pub fn write_vm_booted_stats(
@@ -143,22 +173,34 @@
) {
let vm_identifier = vm_identifier.to_owned();
let duration = get_duration(vm_start_timestamp);
+
+ let atom = AtomVmBooted {
+ uid,
+ vmIdentifier: vm_identifier,
+ elapsedTimeMillis: duration.as_millis() as i64,
+ };
+
thread::spawn(move || {
- let vm_booted = vm_booted::VmBooted {
- uid,
- vm_identifier: &vm_identifier,
- elapsed_time_millis: duration.as_millis() as i64,
- };
- wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
- match vm_booted.stats_write() {
- Err(e) => {
- warn!("statslog_rust failed with error: {}", e);
- }
- Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
- }
+ GLOBAL_SERVICE.atomVmBooted(&atom).unwrap_or_else(|e| {
+ warn!("Failed to write VmCreationRequested atom: {e}");
+ });
});
}
+pub fn forward_vm_booted_atom(atom: &AtomVmBooted) {
+ let vm_booted = vm_booted::VmBooted {
+ uid: atom.uid,
+ vm_identifier: &atom.vmIdentifier,
+ elapsed_time_millis: atom.elapsedTimeMillis,
+ };
+
+ wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
+ match vm_booted.stats_write() {
+ Err(e) => warn!("statslog_rust failed with error: {}", e),
+ Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
+ }
+}
+
/// Write the stats of VM exit to statsd
/// The function creates a separate thread which waits fro statsd to start to push atom
pub fn write_vm_exited_stats(
@@ -173,64 +215,82 @@
let guest_time_millis = vm_metric.cpu_guest_time.unwrap_or_default();
let rss = vm_metric.rss.unwrap_or_default();
+ let atom = AtomVmExited {
+ uid,
+ vmIdentifier: vm_identifier,
+ elapsedTimeMillis: elapsed_time_millis,
+ deathReason: reason,
+ guestTimeMillis: guest_time_millis,
+ rssVmKb: rss.vm,
+ rssCrosvmKb: rss.crosvm,
+ exitSignal: exit_signal.unwrap_or_default(),
+ };
+
thread::spawn(move || {
- let vm_exited = vm_exited::VmExited {
- uid,
- vm_identifier: &vm_identifier,
- elapsed_time_millis,
- death_reason: match reason {
- DeathReason::INFRASTRUCTURE_ERROR => vm_exited::DeathReason::InfrastructureError,
- DeathReason::KILLED => vm_exited::DeathReason::Killed,
- DeathReason::UNKNOWN => vm_exited::DeathReason::Unknown,
- DeathReason::SHUTDOWN => vm_exited::DeathReason::Shutdown,
- DeathReason::START_FAILED => vm_exited::DeathReason::Error,
- DeathReason::REBOOT => vm_exited::DeathReason::Reboot,
- DeathReason::CRASH => vm_exited::DeathReason::Crash,
- DeathReason::PVM_FIRMWARE_PUBLIC_KEY_MISMATCH => {
- vm_exited::DeathReason::PvmFirmwarePublicKeyMismatch
- }
- DeathReason::PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED => {
- vm_exited::DeathReason::PvmFirmwareInstanceImageChanged
- }
- DeathReason::BOOTLOADER_PUBLIC_KEY_MISMATCH => {
- vm_exited::DeathReason::BootloaderPublicKeyMismatch
- }
- DeathReason::BOOTLOADER_INSTANCE_IMAGE_CHANGED => {
- vm_exited::DeathReason::BootloaderInstanceImageChanged
- }
- DeathReason::MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE => {
- vm_exited::DeathReason::MicrodroidFailedToConnectToVirtualizationService
- }
- DeathReason::MICRODROID_PAYLOAD_HAS_CHANGED => {
- vm_exited::DeathReason::MicrodroidPayloadHasChanged
- }
- DeathReason::MICRODROID_PAYLOAD_VERIFICATION_FAILED => {
- vm_exited::DeathReason::MicrodroidPayloadVerificationFailed
- }
- DeathReason::MICRODROID_INVALID_PAYLOAD_CONFIG => {
- vm_exited::DeathReason::MicrodroidInvalidPayloadConfig
- }
- DeathReason::MICRODROID_UNKNOWN_RUNTIME_ERROR => {
- vm_exited::DeathReason::MicrodroidUnknownRuntimeError
- }
- DeathReason::HANGUP => vm_exited::DeathReason::Hangup,
- _ => vm_exited::DeathReason::Unknown,
- },
- guest_time_millis,
- rss_vm_kb: rss.vm,
- rss_crosvm_kb: rss.crosvm,
- exit_signal: exit_signal.unwrap_or_default(),
- };
- wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
- match vm_exited.stats_write() {
- Err(e) => {
- warn!("statslog_rust failed with error: {}", e);
- }
- Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
- }
+ GLOBAL_SERVICE.atomVmExited(&atom).unwrap_or_else(|e| {
+ warn!("Failed to write VmExited atom: {e}");
+ });
});
}
+pub fn forward_vm_exited_atom(atom: &AtomVmExited) {
+ let death_reason = match atom.deathReason {
+ DeathReason::INFRASTRUCTURE_ERROR => vm_exited::DeathReason::InfrastructureError,
+ DeathReason::KILLED => vm_exited::DeathReason::Killed,
+ DeathReason::UNKNOWN => vm_exited::DeathReason::Unknown,
+ DeathReason::SHUTDOWN => vm_exited::DeathReason::Shutdown,
+ DeathReason::START_FAILED => vm_exited::DeathReason::Error,
+ DeathReason::REBOOT => vm_exited::DeathReason::Reboot,
+ DeathReason::CRASH => vm_exited::DeathReason::Crash,
+ DeathReason::PVM_FIRMWARE_PUBLIC_KEY_MISMATCH => {
+ vm_exited::DeathReason::PvmFirmwarePublicKeyMismatch
+ }
+ DeathReason::PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED => {
+ vm_exited::DeathReason::PvmFirmwareInstanceImageChanged
+ }
+ DeathReason::BOOTLOADER_PUBLIC_KEY_MISMATCH => {
+ vm_exited::DeathReason::BootloaderPublicKeyMismatch
+ }
+ DeathReason::BOOTLOADER_INSTANCE_IMAGE_CHANGED => {
+ vm_exited::DeathReason::BootloaderInstanceImageChanged
+ }
+ DeathReason::MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE => {
+ vm_exited::DeathReason::MicrodroidFailedToConnectToVirtualizationService
+ }
+ DeathReason::MICRODROID_PAYLOAD_HAS_CHANGED => {
+ vm_exited::DeathReason::MicrodroidPayloadHasChanged
+ }
+ DeathReason::MICRODROID_PAYLOAD_VERIFICATION_FAILED => {
+ vm_exited::DeathReason::MicrodroidPayloadVerificationFailed
+ }
+ DeathReason::MICRODROID_INVALID_PAYLOAD_CONFIG => {
+ vm_exited::DeathReason::MicrodroidInvalidPayloadConfig
+ }
+ DeathReason::MICRODROID_UNKNOWN_RUNTIME_ERROR => {
+ vm_exited::DeathReason::MicrodroidUnknownRuntimeError
+ }
+ DeathReason::HANGUP => vm_exited::DeathReason::Hangup,
+ _ => vm_exited::DeathReason::Unknown,
+ };
+
+ let vm_exited = vm_exited::VmExited {
+ uid: atom.uid,
+ vm_identifier: &atom.vmIdentifier,
+ elapsed_time_millis: atom.elapsedTimeMillis,
+ death_reason,
+ guest_time_millis: atom.guestTimeMillis,
+ rss_vm_kb: atom.rssVmKb,
+ rss_crosvm_kb: atom.rssCrosvmKb,
+ exit_signal: atom.exitSignal,
+ };
+
+ wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
+ match vm_exited.stats_write() {
+ Err(e) => warn!("statslog_rust failed with error: {}", e),
+ Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
+ }
+}
+
fn wait_for_statsd() -> Result<()> {
let mut prop = system_properties::PropertyWatcher::new("init.svc.statsd")?;
loop {
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 5125f19..94248f8 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -39,11 +39,9 @@
use std::process::{Command, ExitStatus};
use std::sync::{Arc, Condvar, Mutex};
use std::time::{Duration, SystemTime};
-use std::thread;
-use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
- DeathReason::DeathReason,
- MemoryTrimLevel::MemoryTrimLevel,
-};
+use std::thread::{self, JoinHandle};
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::DeathReason::DeathReason;
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::MemoryTrimLevel::MemoryTrimLevel;
use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::IGlobalVmContext::IGlobalVmContext;
use binder::Strong;
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
@@ -140,6 +138,8 @@
Running {
/// The crosvm child process.
child: Arc<SharedChild>,
+ /// The thread waiting for crosvm to finish.
+ monitor_vm_exit_thread: Option<JoinHandle<()>>,
},
/// The VM died or was killed.
Dead,
@@ -187,9 +187,9 @@
let child_clone = child.clone();
let instance_clone = instance.clone();
- thread::spawn(move || {
+ let monitor_vm_exit_thread = Some(thread::spawn(move || {
instance_clone.monitor_vm_exit(child_clone, failure_pipe_read);
- });
+ }));
if detect_hangup {
let child_clone = child.clone();
@@ -199,7 +199,7 @@
}
// If it started correctly, update the state.
- *self = VmState::Running { child };
+ *self = VmState::Running { child, monitor_vm_exit_thread };
Ok(())
} else {
*self = state;
@@ -465,19 +465,24 @@
/// Kills the crosvm instance, if it is running.
pub fn kill(&self) -> Result<(), Error> {
- let vm_state = &*self.vm_state.lock().unwrap();
- if let VmState::Running { child } = vm_state {
- let id = child.id();
- debug!("Killing crosvm({})", id);
- // TODO: Talk to crosvm to shutdown cleanly.
- if let Err(e) = child.kill() {
- bail!("Error killing crosvm({}) instance: {}", id, e);
+ let monitor_vm_exit_thread = {
+ let vm_state = &mut *self.vm_state.lock().unwrap();
+ if let VmState::Running { child, monitor_vm_exit_thread } = vm_state {
+ let id = child.id();
+ debug!("Killing crosvm({})", id);
+ // TODO: Talk to crosvm to shutdown cleanly.
+ child.kill().with_context(|| format!("Error killing crosvm({id}) instance"))?;
+ monitor_vm_exit_thread.take()
} else {
- Ok(())
+ bail!("VM is not running")
}
- } else {
- bail!("VM is not running")
- }
+ };
+
+ // Wait for monitor_vm_exit() to finish. Must release vm_state lock
+ // first, as monitor_vm_exit() takes it as well.
+ monitor_vm_exit_thread.map(JoinHandle::join);
+
+ Ok(())
}
/// Responds to memory-trimming notifications by inflating the virtio
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index 714bcfd..9272e65 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -21,16 +21,27 @@
mod payload;
mod selinux;
-use crate::aidl::{VirtualizationService, BINDER_SERVICE_IDENTIFIER, TEMPORARY_DIRECTORY};
+use crate::aidl::{VirtualizationService, TEMPORARY_DIRECTORY};
use android_logger::{Config, FilterBuilder};
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::BnVirtualizationService;
use anyhow::{bail, Context, Error};
-use binder::{register_lazy_service, BinderFeatures, ProcessState};
+use binder::{register_lazy_service, BinderFeatures, ProcessState, ThreadState};
use log::{info, Level};
use std::fs::{remove_dir_all, remove_file, read_dir};
+use std::os::unix::raw::{pid_t, uid_t};
const LOG_TAG: &str = "VirtualizationService";
+const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
+
+fn get_calling_pid() -> pid_t {
+ ThreadState::get_calling_pid()
+}
+
+fn get_calling_uid() -> uid_t {
+ ThreadState::get_calling_uid()
+}
+
fn main() {
android_logger::init_once(
Config::default()
diff --git a/virtualizationservice/src/virtmgr.rs b/virtualizationservice/src/virtmgr.rs
new file mode 100644
index 0000000..1aa3df9
--- /dev/null
+++ b/virtualizationservice/src/virtmgr.rs
@@ -0,0 +1,120 @@
+// 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.
+
+//! Android Virtualization Manager
+
+mod aidl;
+mod atom;
+mod composite;
+mod crosvm;
+mod payload;
+mod selinux;
+
+use crate::aidl::VirtualizationService;
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::BnVirtualizationService;
+use anyhow::{bail, Context};
+use binder::BinderFeatures;
+use lazy_static::lazy_static;
+use log::{info, Level};
+use rpcbinder::{FileDescriptorTransportMode, RpcServer};
+use std::os::unix::io::{FromRawFd, OwnedFd, RawFd};
+use clap::Parser;
+use nix::unistd::{Pid, Uid};
+use std::os::unix::raw::{pid_t, uid_t};
+
+const LOG_TAG: &str = "virtmgr";
+
+lazy_static! {
+ static ref PID_PARENT: Pid = Pid::parent();
+ static ref UID_CURRENT: Uid = Uid::current();
+}
+
+fn get_calling_pid() -> pid_t {
+ // The caller is the parent of this process.
+ PID_PARENT.as_raw()
+}
+
+fn get_calling_uid() -> uid_t {
+ // The caller and this process share the same UID.
+ UID_CURRENT.as_raw()
+}
+
+#[derive(Parser)]
+struct Args {
+ /// File descriptor inherited from the caller to run RpcBinder server on.
+ /// This should be one end of a socketpair() compatible with RpcBinder's
+ /// UDS bootstrap transport.
+ #[clap(long)]
+ rpc_server_fd: RawFd,
+ /// File descriptor inherited from the caller to signal RpcBinder server
+ /// readiness. This should be one end of pipe() and the caller should be
+ /// waiting for HUP on the other end.
+ #[clap(long)]
+ ready_fd: RawFd,
+}
+
+fn take_fd_ownership(raw_fd: RawFd, owned_fds: &mut Vec<RawFd>) -> Result<OwnedFd, anyhow::Error> {
+ // Basic check that the integer value does correspond to a file descriptor.
+ nix::fcntl::fcntl(raw_fd, nix::fcntl::F_GETFD)
+ .with_context(|| format!("Invalid file descriptor {raw_fd}"))?;
+
+ // Creating OwnedFd for stdio FDs is not safe.
+ if [libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO].contains(&raw_fd) {
+ bail!("File descriptor {raw_fd} is standard I/O descriptor");
+ }
+
+ // Reject RawFds that already have a corresponding OwnedFd.
+ if owned_fds.contains(&raw_fd) {
+ bail!("File descriptor {raw_fd} already owned");
+ }
+ owned_fds.push(raw_fd);
+
+ // SAFETY - Initializing OwnedFd for a RawFd provided in cmdline arguments.
+ // We checked that the integer value corresponds to a valid FD and that this
+ // is the first argument to claim its ownership.
+ Ok(unsafe { OwnedFd::from_raw_fd(raw_fd) })
+}
+
+fn main() {
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag(LOG_TAG)
+ .with_min_level(Level::Info)
+ .with_log_id(android_logger::LogId::System),
+ );
+
+ let args = Args::parse();
+
+ let mut owned_fds = vec![];
+ let rpc_server_fd = take_fd_ownership(args.rpc_server_fd, &mut owned_fds)
+ .expect("Failed to take ownership of rpc_server_fd");
+ let ready_fd = take_fd_ownership(args.ready_fd, &mut owned_fds)
+ .expect("Failed to take ownership of ready_fd");
+
+ let service = VirtualizationService::init();
+ let service =
+ BnVirtualizationService::new_binder(service, BinderFeatures::default()).as_binder();
+
+ let server = RpcServer::new_unix_domain_bootstrap(service, rpc_server_fd)
+ .expect("Failed to start RpcServer");
+ server.set_supported_file_descriptor_transport_modes(&[FileDescriptorTransportMode::Unix]);
+
+ info!("Started VirtualizationService RpcServer. Ready to accept connections");
+
+ // Signal readiness to the caller by closing our end of the pipe.
+ drop(ready_fd);
+
+ server.join();
+ info!("Shutting down VirtualizationService RpcServer");
+}
diff --git a/vm_payload/include-restricted/vm_payload_restricted.h b/vm_payload/include-restricted/vm_payload_restricted.h
index 0b78541..7f17cde 100644
--- a/vm_payload/include-restricted/vm_payload_restricted.h
+++ b/vm_payload/include-restricted/vm_payload_restricted.h
@@ -34,21 +34,21 @@
/**
* Get the VM's DICE attestation chain.
*
- * \param data pointer to size bytes where the chain is written.
+ * \param data pointer to size bytes where the chain is written (may be null if size is 0).
* \param size number of bytes that can be written to data.
*
* \return the total size of the chain
*/
-size_t AVmPayload_getDiceAttestationChain(void *data, size_t size);
+size_t AVmPayload_getDiceAttestationChain(void* _Nullable data, size_t size);
/**
* Get the VM's DICE attestation CDI.
*
- * \param data pointer to size bytes where the CDI is written.
+ * \param data pointer to size bytes where the CDI is written (may be null if size is 0).
* \param size number of bytes that can be written to data.
*
* \return the total size of the CDI
*/
-size_t AVmPayload_getDiceAttestationCdi(void *data, size_t size);
+size_t AVmPayload_getDiceAttestationCdi(void* _Nullable data, size_t size);
__END_DECLS
diff --git a/vm_payload/include/vm_payload.h b/vm_payload/include/vm_payload.h
index 7c224f6..e0c2613 100644
--- a/vm_payload/include/vm_payload.h
+++ b/vm_payload/include/vm_payload.h
@@ -18,6 +18,7 @@
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <stdnoreturn.h>
#include <sys/cdefs.h>
@@ -52,12 +53,13 @@
*
* \param service the service to bind to the given port.
* \param port vsock port.
- * \param on_ready the callback to execute once the server is ready for connections. The callback
- * will be called at most once.
- * \param param param for the `on_ready` callback.
+ * \param on_ready the callback to execute once the server is ready for connections. If not null the
+ * callback will be called at most once.
+ * \param param parameter to be passed to the `on_ready` callback.
*/
-noreturn void AVmPayload_runVsockRpcServer(AIBinder *service, unsigned int port,
- void (*on_ready)(void *param), void *param);
+noreturn void AVmPayload_runVsockRpcServer(AIBinder* _Nonnull service, uint32_t port,
+ void (*_Nullable on_ready)(void* _Nullable param),
+ void* _Nullable param);
/**
* Get a secret that is uniquely bound to this VM instance. The secrets are
@@ -69,8 +71,8 @@
* \param secret pointer to size bytes where the secret is written.
* \param size number of bytes of the secret to get, <= 32.
*/
-void AVmPayload_getVmInstanceSecret(const void *identifier, size_t identifier_size, void *secret,
- size_t size);
+void AVmPayload_getVmInstanceSecret(const void* _Nonnull identifier, size_t identifier_size,
+ void* _Nonnull secret, size_t size);
/**
* Gets the path to the APK contents. It is a directory, under which are
@@ -81,7 +83,7 @@
* deleted or freed by the application. The string remains valid for the
* lifetime of the VM.
*/
-const char *AVmPayload_getApkContentsPath(void);
+const char* _Nonnull AVmPayload_getApkContentsPath(void);
/**
* Gets the path to the encrypted persistent storage for the VM, if any. This is
@@ -94,6 +96,6 @@
* be deleted or freed by the application and remains valid for the lifetime of
* the VM.
*/
-const char *AVmPayload_getEncryptedStoragePath(void);
+const char* _Nullable AVmPayload_getEncryptedStoragePath(void);
__END_DECLS
diff --git a/vm_payload/src/api.rs b/vm_payload/src/api.rs
index 66c8ef7..4b565e0 100644
--- a/vm_payload/src/api.rs
+++ b/vm_payload/src/api.rs
@@ -23,7 +23,7 @@
use binder::{Strong, unstable_api::{AIBinder, new_spibinder}};
use lazy_static::lazy_static;
use log::{error, info, Level};
-use rpcbinder::{get_unix_domain_rpc_interface, RpcServer};
+use rpcbinder::{RpcSession, RpcServer};
use std::convert::Infallible;
use std::ffi::CString;
use std::fmt::Debug;
@@ -49,10 +49,9 @@
if let Some(strong) = &*connection {
Ok(strong.clone())
} else {
- let new_connection: Strong<dyn IVmPayloadService> = get_unix_domain_rpc_interface(
- VM_PAYLOAD_SERVICE_SOCKET_NAME,
- )
- .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_SOCKET_NAME))?;
+ let new_connection: Strong<dyn IVmPayloadService> = RpcSession::new()
+ .setup_unix_domain_client(VM_PAYLOAD_SERVICE_SOCKET_NAME)
+ .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_SOCKET_NAME))?;
*connection = Some(new_connection.clone());
Ok(new_connection)
}
@@ -206,7 +205,7 @@
///
/// Behavior is undefined if any of the following conditions are violated:
///
-/// * `data` must be [valid] for writes of `size` bytes.
+/// * `data` must be [valid] for writes of `size` bytes, if size > 0.
///
/// [valid]: ptr#safety
#[no_mangle]
@@ -214,9 +213,13 @@
initialize_logging();
let chain = unwrap_or_abort(try_get_dice_attestation_chain());
- // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
- // the length of either buffer, and `chain` cannot overlap `data` because we just allocated it.
- unsafe { ptr::copy_nonoverlapping(chain.as_ptr(), data, std::cmp::min(chain.len(), size)) };
+ if size != 0 {
+ // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
+ // the length of either buffer, and `chain` cannot overlap `data` because we just allocated
+ // it. We allow data to be null, which is never valid, but only if size == 0 which is
+ // checked above.
+ unsafe { ptr::copy_nonoverlapping(chain.as_ptr(), data, std::cmp::min(chain.len(), size)) };
+ }
chain.len()
}
@@ -231,7 +234,7 @@
///
/// Behavior is undefined if any of the following conditions are violated:
///
-/// * `data` must be [valid] for writes of `size` bytes.
+/// * `data` must be [valid] for writes of `size` bytes, if size > 0.
///
/// [valid]: ptr#safety
#[no_mangle]
@@ -239,9 +242,13 @@
initialize_logging();
let cdi = unwrap_or_abort(try_get_dice_attestation_cdi());
- // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
- // the length of either buffer, and `cdi` cannot overlap `data` because we just allocated it.
- unsafe { ptr::copy_nonoverlapping(cdi.as_ptr(), data, std::cmp::min(cdi.len(), size)) };
+ if size != 0 {
+ // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
+ // the length of either buffer, and `cdi` cannot overlap `data` because we just allocated
+ // it. We allow data to be null, which is never valid, but only if size == 0 which is
+ // checked above.
+ unsafe { ptr::copy_nonoverlapping(cdi.as_ptr(), data, std::cmp::min(cdi.len(), size)) };
+ }
cdi.len()
}
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/death_reason.rs b/vmclient/src/death_reason.rs
index 34d89fc..c417a7c 100644
--- a/vmclient/src/death_reason.rs
+++ b/vmclient/src/death_reason.rs
@@ -12,9 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use android_system_virtualizationservice::{
- aidl::android::system::virtualizationservice::{
- DeathReason::DeathReason as AidlDeathReason}};
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::DeathReason::DeathReason as AidlDeathReason;
/// The reason why a VM died.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index 7c05545..0e3d140 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -23,10 +23,11 @@
pub use crate::error_code::ErrorCode;
pub use crate::errors::VmWaitError;
use crate::sync::Monitor;
-use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode as AidlErrorCode;
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::{
+ DeathReason::DeathReason as AidlDeathReason, ErrorCode::ErrorCode as AidlErrorCode,
+};
use android_system_virtualizationservice::{
aidl::android::system::virtualizationservice::{
- DeathReason::DeathReason as AidlDeathReason,
IVirtualMachine::IVirtualMachine,
IVirtualMachineCallback::{BnVirtualMachineCallback, IVirtualMachineCallback},
IVirtualizationService::IVirtualizationService,
@@ -38,12 +39,16 @@
ParcelFileDescriptor, Result as BinderResult, StatusCode, Strong,
},
};
+use command_fds::CommandFdExt;
use log::warn;
-use rpcbinder::get_preconnected_rpc_interface;
+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,
};
@@ -51,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)
@@ -177,7 +255,7 @@
&self,
port: u32,
) -> Result<Strong<T>, StatusCode> {
- get_preconnected_rpc_interface(|| {
+ RpcSession::new().setup_preconnected_client(|| {
match self.vm.connectVsock(port as i32) {
Ok(vsock) => {
// Ownership of the fd is transferred to binder