Merge "No need to allocate PCI BARs after all."
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/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index 4d83c5f..557c8aa 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -239,14 +239,15 @@
image_size = ReadBytesSize(info['Image size'])
algorithm = info['Algorithm']
partition_name = descriptor['Partition Name']
+ hash_algorithm = descriptor['Hash Algorithm']
partition_size = str(image_size)
-
cmd = ['avbtool', 'add_hashtree_footer',
'--key', key,
'--algorithm', algorithm,
'--partition_name', partition_name,
'--partition_size', partition_size,
'--do_not_generate_fec',
+ '--hash_algorithm', hash_algorithm,
'--image', image_path]
if args.signing_args:
cmd.extend(shlex.split(args.signing_args))
diff --git a/authfs/fd_server/src/main.rs b/authfs/fd_server/src/main.rs
index 21d0e64..9d97423 100644
--- a/authfs/fd_server/src/main.rs
+++ b/authfs/fd_server/src/main.rs
@@ -37,7 +37,8 @@
use aidl::{FdConfig, FdService};
use authfs_fsverity_metadata::parse_fsverity_metadata;
-const RPC_SERVICE_PORT: u32 = 3264; // TODO: support dynamic port for multiple fd_server instances
+// TODO(b/259920193): support dynamic port for multiple fd_server instances
+const RPC_SERVICE_PORT: u32 = 3264;
fn is_fd_valid(fd: i32) -> bool {
// SAFETY: a query-only syscall
@@ -137,7 +138,8 @@
debug!("fd_server is starting as a rpc service.");
let service = FdService::new_binder(fd_pool).as_binder();
- let server = RpcServer::new_vsock(service, RPC_SERVICE_PORT)?;
+ // TODO(b/259920193): Only accept connections from the intended guest VM.
+ let server = RpcServer::new_vsock(service, libc::VMADDR_CID_ANY, RPC_SERVICE_PORT)?;
debug!("fd_server is ready");
// Close the ready-fd if we were given one to signal our readiness.
diff --git a/authfs/service/authfs_service.rc b/authfs/service/authfs_service.rc
index 7edb1ca..409e91c 100644
--- a/authfs/service/authfs_service.rc
+++ b/authfs/service/authfs_service.rc
@@ -1,3 +1,5 @@
service authfs_service /system/bin/authfs_service
disabled
socket authfs_service stream 0666 root system
+ # SYS_ADMIN capability allows to mount FUSE filesystem
+ capabilities SYS_ADMIN
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/authfs/tests/benchmarks/src/measure_io.cpp b/authfs/tests/benchmarks/src/measure_io.cpp
index e1f2fb8..e766664 100644
--- a/authfs/tests/benchmarks/src/measure_io.cpp
+++ b/authfs/tests/benchmarks/src/measure_io.cpp
@@ -55,7 +55,10 @@
}
char buf[kBlockSizeBytes];
- clock_t start = clock();
+ struct timespec start;
+ if (clock_gettime(CLOCK_MONOTONIC, &start) == -1) {
+ err(EXIT_FAILURE, "failed to clock_gettime");
+ }
for (auto i = 0; i < block_count; ++i) {
auto bytes = is_read ? pread(fd, buf, kBlockSizeBytes, offsets[i])
: pwrite(fd, buf, kBlockSizeBytes, offsets[i]);
@@ -69,7 +72,11 @@
// Writes all the buffered modifications to the open file.
assert(syncfs(fd) == 0);
}
- double elapsed_seconds = ((double)clock() - start) / CLOCKS_PER_SEC;
+ struct timespec finish;
+ if (clock_gettime(CLOCK_MONOTONIC, &finish) == -1) {
+ err(EXIT_FAILURE, "failed to clock_gettime");
+ }
+ double elapsed_seconds = finish.tv_sec - start.tv_sec + (finish.tv_nsec - start.tv_nsec) / 1e9;
double rate = (double)file_size_mb / elapsed_seconds;
std::cout << std::setprecision(12) << rate << std::endl;
diff --git a/compos/apex/composd.rc b/compos/apex/composd.rc
index 3e2efb1..df04642 100644
--- a/compos/apex/composd.rc
+++ b/compos/apex/composd.rc
@@ -19,3 +19,10 @@
interface aidl android.system.composd
disabled
oneshot
+ # Explicitly specify empty capabilities, otherwise composd will inherit all
+ # the capabilities from init.
+ # Note: whether a process can use capabilities is controlled by SELinux, so
+ # inheriting all the capabilities from init is not a security issue.
+ # However, for defense-in-depth and just for the sake of bookkeeping it's
+ # better to explicitly state that composd doesn't need any capabilities.
+ capabilities
diff --git a/compos/common/lib.rs b/compos/common/lib.rs
index c9555d5..8d49ff0 100644
--- a/compos/common/lib.rs
+++ b/compos/common/lib.rs
@@ -21,9 +21,6 @@
pub mod odrefresh;
pub mod timeouts;
-/// Special CID indicating "any".
-pub const VMADDR_CID_ANY: u32 = -1i32 as u32;
-
/// VSock port that the CompOS server listens on for RPC binder connections. This should be out of
/// future port range (if happens) that microdroid may reserve for system components.
pub const COMPOS_VSOCK_PORT: u32 = 6432;
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/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index 77f2ee7..54d7420 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -238,13 +238,6 @@
mService.shutdownNow();
mStatus.postValue(VirtualMachine.STATUS_STOPPED);
}
-
- @Override
- public void onRamdump(VirtualMachine vm, ParcelFileDescriptor ramdump) {
- if (!mService.isShutdown()) {
- mPayloadOutput.postValue("(Kernel panic. Ramdump created)");
- }
- }
};
try {
diff --git a/docs/debug/ramdump.md b/docs/debug/ramdump.md
index a0d9bf2..771c608 100644
--- a/docs/debug/ramdump.md
+++ b/docs/debug/ramdump.md
@@ -1,6 +1,6 @@
# Doing RAM dump of a Microdroid VM and analyzing it
-A Microdroid VM creates a RAM dump of itself when the kernel panics. This
+A debuggable Microdroid VM creates a RAM dump of itself when the kernel panics. This
document explains how the dump can be obtained and analyzed.
## Force triggering a RAM dump
@@ -49,7 +49,7 @@
## Obtaining the RAM dump
-By default, RAM dumps are sent to tombstone. To see which tombstone file is for
+RAM dumps are sent to tombstone. To see which tombstone file is for
the RAM dump, look into the log.
```shell
@@ -64,15 +64,6 @@
$ adb root && adb pull /data/tombstones/tombstone_47 ramdump && adb unroot
```
-Alternatively, you can specify the path to where RAM dump is stored when
-launching the VM using the `--ramdump` option of the `vm` tool.
-
-```shell
-$ adb shelll /apex/com.android.virt/bin/vm run-app --ramdump /data/local/tmp/virt/ramdump ...
-```
-
-In the above example, the RAM dump is saved to `/data/local/tmp/virt/ramdump`.
-
## Analyzing the RAM dump
### Building the crash(8) tool
@@ -151,9 +142,3 @@
actually triggered a crash in the kernel.
For more commands of crash(8), refer to the man page, or embedded `help` command.
-
-
-
-
-
-
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
index 9c8311d..7140ae2 100644
--- a/encryptedstore/src/main.rs
+++ b/encryptedstore/src/main.rs
@@ -137,7 +137,10 @@
fn mount(source: &Path, mountpoint: &Path) -> Result<()> {
create_dir_all(mountpoint).context(format!("Failed to create {:?}", &mountpoint))?;
- let mount_options = CString::new("").unwrap();
+ let mount_options = CString::new(
+ "fscontext=u:object_r:encryptedstore_fs:s0,context=u:object_r:encryptedstore_file:s0",
+ )
+ .unwrap();
let source = CString::new(source.as_os_str().as_bytes())?;
let mountpoint = CString::new(mountpoint.as_os_str().as_bytes())?;
let fstype = CString::new("ext4").unwrap();
diff --git a/javalib/api/system-current.txt b/javalib/api/system-current.txt
index fb7c98c..592a751 100644
--- a/javalib/api/system-current.txt
+++ b/javalib/api/system-current.txt
@@ -4,9 +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 public int getCid() 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;
@@ -18,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
@@ -29,7 +30,6 @@
method public void onPayloadFinished(@NonNull android.system.virtualmachine.VirtualMachine, int);
method public void onPayloadReady(@NonNull android.system.virtualmachine.VirtualMachine);
method public void onPayloadStarted(@NonNull android.system.virtualmachine.VirtualMachine);
- method public void onRamdump(@NonNull android.system.virtualmachine.VirtualMachine, @NonNull android.os.ParcelFileDescriptor);
method public void onStopped(@NonNull android.system.virtualmachine.VirtualMachine, int);
field public static final int ERROR_PAYLOAD_CHANGED = 2; // 0x2
field public static final int ERROR_PAYLOAD_INVALID_CONFIG = 3; // 0x3
@@ -58,10 +58,12 @@
public final class VirtualMachineConfig {
method @NonNull public String getApkPath();
method @NonNull public int getDebugLevel();
+ method @IntRange(from=0) public long getEncryptedStorageKib();
method @IntRange(from=0) public int getMemoryMib();
method @IntRange(from=1) public int getNumCpus();
method @Nullable public String getPayloadBinaryPath();
method public boolean isCompatibleWith(@NonNull android.system.virtualmachine.VirtualMachineConfig);
+ method public boolean isEncryptedStorageEnabled();
method public boolean isProtectedVm();
field public static final int DEBUG_LEVEL_FULL = 1; // 0x1
field public static final int DEBUG_LEVEL_NONE = 0; // 0x0
@@ -72,7 +74,8 @@
method @NonNull public android.system.virtualmachine.VirtualMachineConfig build();
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setApkPath(@NonNull String);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setDebugLevel(int);
- method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setMemoryMib(@IntRange(from=0) int);
+ method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setEncryptedStorageKib(@IntRange(from=1) long);
+ method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setMemoryMib(@IntRange(from=1) int);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setNumCpus(@IntRange(from=1) int);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadBinaryPath(@NonNull String);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setProtectedVm(boolean);
@@ -85,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 b8be703..b040cc0 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
@@ -169,6 +189,9 @@
/** Size of the instance image. 10 MB. */
private static final long INSTANCE_FILE_SIZE = 10 * 1024 * 1024;
+ /** Name of the file backing the encrypted storage */
+ private static final String ENCRYPTED_STORE_FILE = "storage.img";
+
/** The package which owns this VM. */
@NonNull private final String mPackageName;
@@ -191,6 +214,9 @@
/** Path to the idsig file for this VM. */
@NonNull private final File mIdsigFilePath;
+ /** File that backs the encrypted storage - Will be null if not enabled. */
+ @Nullable private final File mEncryptedStoreFilePath;
+
/**
* Unmodifiable list of extra apks. Apks are specified by the vm config, and corresponding
* idsigs are to be generated.
@@ -285,6 +311,9 @@
@Nullable
private ParcelFileDescriptor mLogWriter;
+ @GuardedBy("mLock")
+ private boolean mWasDeleted = false;
+
/** The registered callback */
@GuardedBy("mCallbackLock")
@Nullable
@@ -324,6 +353,10 @@
mExtraApks = setupExtraApks(context, config, thisVmDir);
mMemoryManagementCallbacks = new MemoryManagementCallbacks();
mContext = context;
+ mEncryptedStoreFilePath =
+ (config.isEncryptedStorageEnabled())
+ ? new File(thisVmDir, ENCRYPTED_STORE_FILE)
+ : null;
}
/**
@@ -354,6 +387,16 @@
throw new VirtualMachineException("failed to create instance image", e);
}
vm.importInstanceFrom(vmDescriptor.getInstanceImgFd());
+
+ if (vmDescriptor.getEncryptedStoreFd() != null) {
+ try {
+ vm.mEncryptedStoreFilePath.createNewFile();
+ } catch (IOException e) {
+ throw new VirtualMachineException(
+ "failed to create encrypted storage image", e);
+ }
+ vm.importEncryptedStoreFrom(vmDescriptor.getEncryptedStoreFd());
+ }
return vm;
} catch (VirtualMachineException | RuntimeException e) {
// If anything goes wrong, delete any files created so far and the VM's directory
@@ -386,6 +429,14 @@
} catch (IOException e) {
throw new VirtualMachineException("failed to create instance image", e);
}
+ if (config.isEncryptedStorageEnabled()) {
+ try {
+ vm.mEncryptedStoreFilePath.createNewFile();
+ } catch (IOException e) {
+ throw new VirtualMachineException(
+ "failed to create encrypted storage image", e);
+ }
+ }
IVirtualizationService service =
IVirtualizationService.Stub.asInterface(
@@ -403,6 +454,22 @@
} catch (ServiceSpecificException | IllegalArgumentException e) {
throw new VirtualMachineException("failed to create instance partition", e);
}
+
+ if (config.isEncryptedStorageEnabled()) {
+ try {
+ service.initializeWritablePartition(
+ ParcelFileDescriptor.open(vm.mEncryptedStoreFilePath, MODE_READ_WRITE),
+ config.getEncryptedStorageKib() * 1024L,
+ PartitionType.ENCRYPTEDSTORE);
+ } catch (FileNotFoundException e) {
+ throw new VirtualMachineException("encrypted storage image missing", e);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (ServiceSpecificException | IllegalArgumentException e) {
+ throw new VirtualMachineException(
+ "failed to create encrypted storage partition", e);
+ }
+ }
return vm;
} catch (VirtualMachineException | RuntimeException e) {
// If anything goes wrong, delete any files created so far and the VM's directory
@@ -432,7 +499,9 @@
if (!vm.mInstanceFilePath.exists()) {
throw new VirtualMachineException("instance image missing");
}
-
+ if (config.isEncryptedStorageEnabled() && !vm.mEncryptedStoreFilePath.exists()) {
+ throw new VirtualMachineException("Storage image missing");
+ }
return vm;
}
@@ -440,6 +509,9 @@
void delete(Context context, String name) throws VirtualMachineException {
synchronized (mLock) {
checkStopped();
+ // Once we explicitly delete a VM it must remain permanently in the deleted state;
+ // if a new VM is created with the same name (and files) that's unrelated.
+ mWasDeleted = true;
deleteVmDirectory(context, name);
}
}
@@ -521,6 +593,9 @@
public int getStatus() {
IVirtualMachine virtualMachine;
synchronized (mLock) {
+ if (mWasDeleted) {
+ return STATUS_DELETED;
+ }
virtualMachine = mVirtualMachine;
}
if (virtualMachine == null) {
@@ -551,7 +626,7 @@
// Throw an appropriate exception if we have a running VM, or the VM has been deleted.
@GuardedBy("mLock")
private void checkStopped() throws VirtualMachineException {
- if (!mVmRootPath.exists()) {
+ if (mWasDeleted || !mVmRootPath.exists()) {
throw new VirtualMachineException("VM has been deleted");
}
if (mVirtualMachine == null) {
@@ -564,9 +639,22 @@
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
+ // It's stopped, but we still have a reference to it - we can fix that.
+ dropVm();
}
- // If we have an IVirtualMachine in the running state return it, otherwise throw.
+ /**
+ * This should only be called when we know our VM has stopped; we no longer need to hold a
+ * reference to it (this allows resources to be GC'd) and we no longer need to be informed of
+ * memory pressure.
+ */
+ @GuardedBy("mLock")
+ private void dropVm() {
+ mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks);
+ mVirtualMachine = null;
+ }
+
+ /** If we have an IVirtualMachine in the running state return it, otherwise throw. */
@GuardedBy("mLock")
private IVirtualMachine getRunningVm() throws VirtualMachineException {
try {
@@ -574,7 +662,7 @@
&& stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) {
return mVirtualMachine;
} else {
- if (!mVmRootPath.exists()) {
+ if (mWasDeleted || !mVmRootPath.exists()) {
throw new VirtualMachineException("VM has been deleted");
} else {
throw new VirtualMachineException("VM is not in running state");
@@ -681,8 +769,12 @@
// Re-open idsig file in read-only mode
appConfig.idsig = ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_ONLY);
- appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath,
- MODE_READ_WRITE);
+ appConfig.instanceImage =
+ ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE);
+ if (mEncryptedStoreFilePath != null) {
+ appConfig.encryptedStorageImage =
+ ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_WRITE);
+ }
List<ParcelFileDescriptor> extraIdsigs = new ArrayList<>();
for (ExtraApkSpec extraApk : mExtraApks) {
extraIdsigs.add(ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_ONLY));
@@ -746,11 +838,6 @@
VirtualMachine.this, translatedReason));
}
}
-
- @Override
- public void onRamdump(int cid, ParcelFileDescriptor ramdump) {
- executeCallback((cb) -> cb.onRamdump(VirtualMachine.this, ramdump));
- }
});
mContext.registerComponentCallbacks(mMemoryManagementCallbacks);
service.asBinder().linkToDeath(deathRecipient, 0);
@@ -829,8 +916,7 @@
}
try {
mVirtualMachine.stop();
- mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks);
- mVirtualMachine = null;
+ dropVm();
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} catch (ServiceSpecificException e) {
@@ -855,8 +941,7 @@
try {
if (stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) {
mVirtualMachine.stop();
- mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks);
- mVirtualMachine = null;
+ dropVm();
}
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
@@ -890,23 +975,6 @@
}
/**
- * Returns the CID of this virtual machine, if it is running.
- *
- * @throws VirtualMachineException if the virtual machine is not running.
- * @hide
- */
- @SystemApi
- public int getCid() throws VirtualMachineException {
- synchronized (mLock) {
- try {
- return getRunningVm().getCid();
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
- }
-
- /**
* Changes the config of this virtual machine to a new one. This can be used to adjust things
* like the number of CPU and size of the RAM, depending on the situation (e.g. the size of the
* application to run on the virtual machine, etc.)
@@ -954,9 +1022,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");
}
@@ -972,10 +1044,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) {
@@ -984,6 +1058,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.
@@ -1017,7 +1101,10 @@
try {
return new VirtualMachineDescriptor(
ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY),
- ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY));
+ ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY),
+ mEncryptedStoreFilePath != null
+ ? ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_ONLY)
+ : null);
} catch (IOException e) {
throw new VirtualMachineException(e);
}
@@ -1183,4 +1270,14 @@
throw new VirtualMachineException("failed to transfer instance image", e);
}
}
+
+ private void importEncryptedStoreFrom(@NonNull ParcelFileDescriptor encryptedStoreFd)
+ throws VirtualMachineException {
+ try (FileChannel storeOutput = new FileOutputStream(mEncryptedStoreFilePath).getChannel();
+ FileChannel storeInput = new AutoCloseInputStream(encryptedStoreFd).getChannel()) {
+ storeOutput.transferFrom(storeInput, /*position=*/ 0, storeInput.size());
+ } catch (IOException e) {
+ throw new VirtualMachineException("failed to transfer encryptedstore image", e);
+ }
+ }
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
index fad2fa9..9aaecf0 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.os.ParcelFileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -155,7 +154,4 @@
/** Called when the VM has stopped. */
void onStopped(@NonNull VirtualMachine vm, @StopReason int reason);
-
- /** Called when kernel panic occurs and as a result ramdump is generated from the VM. */
- void onRamdump(@NonNull VirtualMachine vm, @NonNull ParcelFileDescriptor ramdump);
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index a9e062a..75e5414 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -55,6 +55,8 @@
*/
@SystemApi
public final class VirtualMachineConfig {
+ private static final String[] EMPTY_STRING_ARRAY = {};
+
// These define the schema of the config file persisted on disk.
private static final int VERSION = 2;
private static final String KEY_VERSION = "version";
@@ -65,6 +67,7 @@
private static final String KEY_PROTECTED_VM = "protectedVm";
private static final String KEY_MEMORY_MIB = "memoryMib";
private static final String KEY_NUM_CPUS = "numCpus";
+ private static final String KEY_ENCRYPTED_STORAGE_KIB = "encryptedStorageKib";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -120,6 +123,9 @@
*/
@Nullable private final String mPayloadBinaryPath;
+ /** The size of storage in KiB. 0 indicates that encryptedStorage is not required */
+ private final long mEncryptedStorageKib;
+
private VirtualMachineConfig(
@NonNull String apkPath,
@Nullable String payloadConfigPath,
@@ -127,47 +133,9 @@
@DebugLevel int debugLevel,
boolean protectedVm,
int memoryMib,
- int numCpus) {
- requireNonNull(apkPath);
- if (!apkPath.startsWith("/")) {
- throw new IllegalArgumentException("APK path must be an absolute path");
- }
-
- if (memoryMib < 0) {
- throw new IllegalArgumentException("Memory size cannot be negative");
- }
-
- int availableCpus = Runtime.getRuntime().availableProcessors();
- if (numCpus < 1 || numCpus > availableCpus) {
- throw new IllegalArgumentException("Number of vCPUs (" + numCpus + ") is out of "
- + "range [1, " + availableCpus + "]");
- }
-
- if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) {
- throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel);
- }
-
- if (payloadBinaryPath == null) {
- if (payloadConfigPath == null) {
- throw new IllegalStateException("setPayloadBinaryPath must be called");
- }
- } else {
- if (payloadConfigPath != null) {
- throw new IllegalStateException(
- "setPayloadBinaryPath and setPayloadConfigPath may not both be called");
- }
- }
-
- if (protectedVm
- && !HypervisorProperties.hypervisor_protected_vm_supported().orElse(false)) {
- throw new UnsupportedOperationException(
- "Protected VMs are not supported on this device.");
- }
- if (!protectedVm && !HypervisorProperties.hypervisor_vm_supported().orElse(false)) {
- throw new UnsupportedOperationException(
- "Unprotected VMs are not supported on this device.");
- }
-
+ int numCpus,
+ long encryptedStorageKib) {
+ // This is only called from Builder.build(); the builder handles parameter validation.
mApkPath = apkPath;
mPayloadConfigPath = payloadConfigPath;
mPayloadBinaryPath = payloadBinaryPath;
@@ -175,6 +143,7 @@
mProtectedVm = protectedVm;
mMemoryMib = memoryMib;
mNumCpus = numCpus;
+ mEncryptedStorageKib = encryptedStorageKib;
}
/** Loads a config from a file. */
@@ -203,32 +172,48 @@
private static VirtualMachineConfig fromInputStream(@NonNull InputStream input)
throws IOException, VirtualMachineException {
PersistableBundle b = PersistableBundle.readFromStream(input);
+ try {
+ return fromPersistableBundle(b);
+ } catch (NullPointerException | IllegalArgumentException | IllegalStateException e) {
+ throw new VirtualMachineException("Persisted VM config is invalid", e);
+ }
+ }
+
+ @NonNull
+ private static VirtualMachineConfig fromPersistableBundle(PersistableBundle b) {
int version = b.getInt(KEY_VERSION);
if (version > VERSION) {
- throw new VirtualMachineException("Version too high");
+ throw new IllegalArgumentException(
+ "Version " + version + " too high; current is " + VERSION);
}
- String apkPath = b.getString(KEY_APKPATH);
- if (apkPath == null) {
- throw new VirtualMachineException("No apkPath");
+
+ Builder builder = new Builder();
+ builder.setApkPath(b.getString(KEY_APKPATH));
+
+ String payloadConfigPath = b.getString(KEY_PAYLOADCONFIGPATH);
+ if (payloadConfigPath == null) {
+ builder.setPayloadBinaryPath(b.getString(KEY_PAYLOADBINARYPATH));
+ } else {
+ builder.setPayloadConfigPath(payloadConfigPath);
}
- String payloadBinaryPath = b.getString(KEY_PAYLOADBINARYPATH);
- String payloadConfigPath = null;
- if (payloadBinaryPath == null) {
- payloadConfigPath = b.getString(KEY_PAYLOADCONFIGPATH);
- if (payloadConfigPath == null) {
- throw new VirtualMachineException("No payloadBinaryPath");
- }
- }
+
@DebugLevel int debugLevel = b.getInt(KEY_DEBUGLEVEL);
if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) {
- throw new VirtualMachineException("Invalid debugLevel: " + debugLevel);
+ throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel);
}
- boolean protectedVm = b.getBoolean(KEY_PROTECTED_VM);
+ builder.setDebugLevel(debugLevel);
+ builder.setProtectedVm(b.getBoolean(KEY_PROTECTED_VM));
int memoryMib = b.getInt(KEY_MEMORY_MIB);
- int numCpus = b.getInt(KEY_NUM_CPUS);
+ if (memoryMib != 0) {
+ builder.setMemoryMib(memoryMib);
+ }
+ builder.setNumCpus(b.getInt(KEY_NUM_CPUS));
+ long encryptedStorageKib = b.getLong(KEY_ENCRYPTED_STORAGE_KIB);
+ if (encryptedStorageKib != 0) {
+ builder.setEncryptedStorageKib(encryptedStorageKib);
+ }
- return new VirtualMachineConfig(apkPath, payloadConfigPath, payloadBinaryPath, debugLevel,
- protectedVm, memoryMib, numCpus);
+ return builder.build();
}
/** Persists this config to a file. */
@@ -253,6 +238,9 @@
if (mMemoryMib > 0) {
b.putInt(KEY_MEMORY_MIB, mMemoryMib);
}
+ if (mEncryptedStorageKib > 0) {
+ b.putLong(KEY_ENCRYPTED_STORAGE_KIB, mEncryptedStorageKib);
+ }
b.writeToStream(output);
}
@@ -315,7 +303,8 @@
}
/**
- * Returns the amount of RAM that will be made available to the VM.
+ * Returns the amount of RAM that will be made available to the VM, or 0 if the default size
+ * will be used.
*
* @hide
*/
@@ -337,6 +326,28 @@
}
/**
+ * Returns whether encrypted storage is enabled or not.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isEncryptedStorageEnabled() {
+ return mEncryptedStorageKib > 0;
+ }
+
+ /**
+ * Returns the size of encrypted storage (in KiB) available in the VM, or 0 if encrypted storage
+ * is not enabled
+ *
+ * @hide
+ */
+ @SystemApi
+ @IntRange(from = 0)
+ public long getEncryptedStorageKib() {
+ return mEncryptedStorageKib;
+ }
+
+ /**
* Tests if this config is compatible with other config. Being compatible means that the configs
* can be interchangeably used for the same virtual machine. Compatible changes includes the
* number of CPUs and the size of the RAM. All other changes (e.g. using a different payload,
@@ -348,6 +359,7 @@
public boolean isCompatibleWith(@NonNull VirtualMachineConfig other) {
return this.mDebugLevel == other.mDebugLevel
&& this.mProtectedVm == other.mProtectedVm
+ && this.mEncryptedStorageKib == other.mEncryptedStorageKib
&& Objects.equals(this.mPayloadConfigPath, other.mPayloadConfigPath)
&& Objects.equals(this.mPayloadBinaryPath, other.mPayloadBinaryPath)
&& this.mApkPath.equals(other.mApkPath);
@@ -383,9 +395,8 @@
vsConfig.protectedVm = mProtectedVm;
vsConfig.memoryMib = mMemoryMib;
vsConfig.numCpus = mNumCpus;
- // Don't allow apps to set task profiles ... at last for now. Also, don't forget to
- // validate the string because these are appended to the cmdline argument.
- vsConfig.taskProfiles = new String[0];
+ // Don't allow apps to set task profiles ... at least for now.
+ vsConfig.taskProfiles = EMPTY_STRING_ARRAY;
return vsConfig;
}
@@ -396,15 +407,16 @@
*/
@SystemApi
public static final class Builder {
- private final Context mContext;
+ @Nullable private final Context mContext;
@Nullable private String mApkPath;
@Nullable private String mPayloadConfigPath;
@Nullable private String mPayloadBinaryPath;
- @DebugLevel private int mDebugLevel;
+ @DebugLevel private int mDebugLevel = DEBUG_LEVEL_NONE;
private boolean mProtectedVm;
private boolean mProtectedVmSet;
private int mMemoryMib;
- private int mNumCpus;
+ private int mNumCpus = 1;
+ private long mEncryptedStorageKib;
/**
* Creates a builder for the given context.
@@ -414,8 +426,14 @@
@SystemApi
public Builder(@NonNull Context context) {
mContext = requireNonNull(context, "context must not be null");
- mDebugLevel = DEBUG_LEVEL_NONE;
- mNumCpus = 1;
+ }
+
+ /**
+ * Creates a builder with no associated context; {@link #setApkPath} must be called to
+ * specify which APK contains the payload.
+ */
+ private Builder() {
+ mContext = null;
}
/**
@@ -426,15 +444,40 @@
@SystemApi
@NonNull
public VirtualMachineConfig build() {
- String apkPath = (mApkPath == null) ? mContext.getPackageCodePath() : mApkPath;
+ String apkPath;
+ if (mApkPath == null) {
+ if (mContext == null) {
+ throw new IllegalStateException("apkPath must be specified");
+ }
+ apkPath = mContext.getPackageCodePath();
+ } else {
+ apkPath = mApkPath;
+ }
+
+ if (mPayloadBinaryPath == null) {
+ if (mPayloadConfigPath == null) {
+ throw new IllegalStateException("setPayloadBinaryPath must be called");
+ }
+ } else {
+ if (mPayloadConfigPath != null) {
+ throw new IllegalStateException(
+ "setPayloadBinaryPath and setPayloadConfigPath may not both be called");
+ }
+ }
if (!mProtectedVmSet) {
throw new IllegalStateException("setProtectedVm must be called explicitly");
}
return new VirtualMachineConfig(
- apkPath, mPayloadConfigPath, mPayloadBinaryPath, mDebugLevel, mProtectedVm,
- mMemoryMib, mNumCpus);
+ apkPath,
+ mPayloadConfigPath,
+ mPayloadBinaryPath,
+ mDebugLevel,
+ mProtectedVm,
+ mMemoryMib,
+ mNumCpus,
+ mEncryptedStorageKib);
}
/**
@@ -446,7 +489,11 @@
@SystemApi
@NonNull
public Builder setApkPath(@NonNull String apkPath) {
- mApkPath = requireNonNull(apkPath);
+ requireNonNull(apkPath, "apkPath must not be null");
+ if (!apkPath.startsWith("/")) {
+ throw new IllegalArgumentException("APK path must be an absolute path");
+ }
+ mApkPath = apkPath;
return this;
}
@@ -461,7 +508,8 @@
@TestApi
@NonNull
public Builder setPayloadConfigPath(@NonNull String payloadConfigPath) {
- mPayloadConfigPath = requireNonNull(payloadConfigPath);
+ mPayloadConfigPath =
+ requireNonNull(payloadConfigPath, "payloadConfigPath must not be null");
return this;
}
@@ -474,7 +522,8 @@
@SystemApi
@NonNull
public Builder setPayloadBinaryPath(@NonNull String payloadBinaryPath) {
- mPayloadBinaryPath = requireNonNull(payloadBinaryPath);
+ mPayloadBinaryPath =
+ requireNonNull(payloadBinaryPath, "payloadBinaryPath must not be null");
return this;
}
@@ -486,6 +535,9 @@
@SystemApi
@NonNull
public Builder setDebugLevel(@DebugLevel int debugLevel) {
+ if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) {
+ throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel);
+ }
mDebugLevel = debugLevel;
return this;
}
@@ -500,20 +552,34 @@
@SystemApi
@NonNull
public Builder setProtectedVm(boolean protectedVm) {
+ if (protectedVm) {
+ if (!HypervisorProperties.hypervisor_protected_vm_supported().orElse(false)) {
+ throw new UnsupportedOperationException(
+ "Protected VMs are not supported on this device.");
+ }
+ } else {
+ if (!HypervisorProperties.hypervisor_vm_supported().orElse(false)) {
+ throw new UnsupportedOperationException(
+ "Unprotected VMs are not supported on this device.");
+ }
+ }
mProtectedVm = protectedVm;
mProtectedVmSet = true;
return this;
}
/**
- * Sets the amount of RAM to give the VM, in mebibytes. If zero or not explicitly set then a
- * default size will be used.
+ * Sets the amount of RAM to give the VM, in mebibytes. If not explicitly set then a default
+ * size will be used.
*
* @hide
*/
@SystemApi
@NonNull
- public Builder setMemoryMib(@IntRange(from = 0) int memoryMib) {
+ public Builder setMemoryMib(@IntRange(from = 1) int memoryMib) {
+ if (memoryMib <= 0) {
+ throw new IllegalArgumentException("Memory size must be positive");
+ }
mMemoryMib = memoryMib;
return this;
}
@@ -526,8 +592,44 @@
*/
@SystemApi
@NonNull
- public Builder setNumCpus(@IntRange(from = 1) int num) {
- mNumCpus = num;
+ public Builder setNumCpus(@IntRange(from = 1) int numCpus) {
+ int availableCpus = Runtime.getRuntime().availableProcessors();
+ if (numCpus < 1 || numCpus > availableCpus) {
+ throw new IllegalArgumentException(
+ "Number of vCPUs ("
+ + numCpus
+ + ") is out of "
+ + "range [1, "
+ + availableCpus
+ + "]");
+ }
+ mNumCpus = numCpus;
+ return this;
+ }
+
+ /**
+ * 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
+ *
+ * <p>The encrypted storage is persistent across VM reboots as well as device reboots. The
+ * backing file (containing encrypted data) is stored in the app's private data directory.
+ *
+ * <p>Note - There is no integrity guarantee or rollback protection on the storage in case
+ * the encrypted data is modified.
+ *
+ * <p>Deleting the VM will delete the encrypted data - there is no way to recover that data.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public Builder setEncryptedStorageKib(@IntRange(from = 1) long encryptedStorageKib) {
+ if (encryptedStorageKib <= 0) {
+ throw new IllegalArgumentException("Encrypted Storage size must be positive");
+ }
+ mEncryptedStorageKib = encryptedStorageKib;
return this;
}
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
index edaf5b4..c9718aa 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
@@ -19,6 +19,7 @@
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
@@ -37,7 +38,9 @@
public final class VirtualMachineDescriptor implements Parcelable {
@NonNull private final ParcelFileDescriptor mConfigFd;
@NonNull private final ParcelFileDescriptor mInstanceImgFd;
- // TODO(b/243129654): Add trusted storage fd once it is available.
+ // File descriptor of the image backing the encrypted storage - Will be null if encrypted
+ // storage is not enabled. */
+ @Nullable private final ParcelFileDescriptor mEncryptedStoreFd;
@Override
public int describeContents() {
@@ -48,6 +51,7 @@
public void writeToParcel(@NonNull Parcel out, int flags) {
mConfigFd.writeToParcel(out, flags);
mInstanceImgFd.writeToParcel(out, flags);
+ if (mEncryptedStoreFd != null) mEncryptedStoreFd.writeToParcel(out, flags);
}
@NonNull
@@ -78,14 +82,27 @@
return mInstanceImgFd;
}
+ /**
+ * @return File descriptor of image backing the encrypted storage.
+ * <p>This method will return null if encrypted storage is not enabled.
+ */
+ @Nullable
+ ParcelFileDescriptor getEncryptedStoreFd() {
+ return mEncryptedStoreFd;
+ }
+
VirtualMachineDescriptor(
- @NonNull ParcelFileDescriptor configFd, @NonNull ParcelFileDescriptor instanceImgFd) {
+ @NonNull ParcelFileDescriptor configFd,
+ @NonNull ParcelFileDescriptor instanceImgFd,
+ @Nullable ParcelFileDescriptor encryptedStoreFd) {
mConfigFd = configFd;
mInstanceImgFd = instanceImgFd;
+ mEncryptedStoreFd = encryptedStoreFd;
}
private VirtualMachineDescriptor(Parcel in) {
mConfigFd = requireNonNull(in.readFileDescriptor());
mInstanceImgFd = requireNonNull(in.readFileDescriptor());
+ mEncryptedStoreFd = in.readFileDescriptor();
}
}
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/apkverify/src/v4.rs b/libs/apkverify/src/v4.rs
index 6c085f6..94abf99 100644
--- a/libs/apkverify/src/v4.rs
+++ b/libs/apkverify/src/v4.rs
@@ -146,6 +146,11 @@
/// Read a stream for an APK file and creates a corresponding `V4Signature` struct that digests
/// the APK file. Note that the signing is not done.
+ /// Important: callers of this function are expected to verify the validity of the passed |apk|.
+ /// To be more specific, they should check that |apk| corresponds to a regular file, as calling
+ /// lseek on directory fds is not defined in the standard, and on ext4 it will return (off_t)-1
+ /// (see: https://bugzilla.kernel.org/show_bug.cgi?id=200043), which will result in this
+ /// function OOMing.
pub fn create(
mut apk: &mut R,
block_size: usize,
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 65d51d2..1a40e45 100644
--- a/libs/vbmeta/src/lib.rs
+++ b/libs/vbmeta/src/lib.rs
@@ -18,21 +18,14 @@
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, MaybeUninit};
-use std::os::raw::c_uint;
+use std::mem::{size_of, transmute, MaybeUninit};
use std::path::Path;
use std::ptr::null_mut;
-use std::slice;
use thiserror::Error;
pub use crate::descriptor::{Descriptor, Descriptors};
@@ -69,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.
@@ -96,14 +86,17 @@
) -> Result<Self, VbMetaImageVerificationError> {
// Check for a footer in the image or assume it's an entire VBMeta image.
image.seek(SeekFrom::Start(offset + size)).map_err(VbMetaImageParseError::Io)?;
- let footer = read_avb_footer(&mut image).map_err(VbMetaImageParseError::Io)?;
- let (vbmeta_offset, vbmeta_size) = if let Some(footer) = footer {
- if footer.vbmeta_offset > size || footer.vbmeta_size > size - footer.vbmeta_offset {
- return Err(VbMetaImageParseError::InvalidFooter.into());
+ let (vbmeta_offset, vbmeta_size) = match read_avb_footer(&mut image) {
+ Ok(footer) => {
+ if footer.vbmeta_offset > size || footer.vbmeta_size > size - footer.vbmeta_offset {
+ return Err(VbMetaImageParseError::InvalidFooter.into());
+ }
+ (footer.vbmeta_offset, footer.vbmeta_size)
}
- (footer.vbmeta_offset, footer.vbmeta_size)
- } else {
- (0, size)
+ Err(VbMetaImageParseError::InvalidFooter) => (0, size),
+ Err(e) => {
+ return Err(e.into());
+ }
};
image.seek(SeekFrom::Start(offset + vbmeta_offset)).map_err(VbMetaImageParseError::Io)?;
// Verify the image before examining it to check the size.
@@ -128,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>()
@@ -142,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;
@@ -166,42 +159,36 @@
// 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)),
}
}
/// Read the AVB footer, if present, given a reader that's positioned at the end of the image.
-fn read_avb_footer<R: Read + Seek>(image: &mut R) -> io::Result<Option<AvbFooter>> {
+fn read_avb_footer<R: Read + Seek>(image: &mut R) -> Result<AvbFooter, VbMetaImageParseError> {
image.seek(SeekFrom::Current(-(size_of::<AvbFooter>() as i64)))?;
+ let mut raw_footer = [0u8; size_of::<AvbFooter>()];
+ image.read_exact(&mut raw_footer)?;
// SAFETY: the slice is the same size as the struct which only contains simple data types.
- let mut footer = unsafe {
- let mut footer = MaybeUninit::<AvbFooter>::uninit();
- let footer_slice =
- slice::from_raw_parts_mut(&mut footer as *mut _ as *mut u8, size_of::<AvbFooter>());
- image.read_exact(footer_slice)?;
- footer.assume_init()
- };
+ let mut footer = unsafe { transmute::<[u8; size_of::<AvbFooter>()], AvbFooter>(raw_footer) };
// SAFETY: the function updates the struct in-place.
if unsafe { avb_footer_validate_and_byteswap(&footer, &mut footer) } {
- Ok(Some(footer))
+ Ok(footer)
} else {
- Ok(None)
+ Err(VbMetaImageParseError::InvalidFooter)
}
}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 028ac1f..ecaadf8 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -46,6 +46,7 @@
use_avb: true,
avb_private_key: ":microdroid_sign_key",
avb_algorithm: "SHA256_RSA4096",
+ avb_hash_algorithm: "sha256",
partition_name: "system",
deps: [
"init_second_stage",
@@ -71,7 +72,6 @@
"atrace",
"debuggerd",
"linker",
- "linkerconfig",
"tombstoned.microdroid",
"tombstone_transmit.microdroid",
"cgroups.json",
@@ -83,13 +83,15 @@
"microdroid_manifest",
"microdroid_plat_sepolicy_and_mapping.sha256",
"microdroid_property_contexts",
- "mke2fs",
+ "mke2fs.microdroid",
// TODO(b/195425111) these should be added automatically
"libcrypto", // used by many (init_second_stage, microdroid_manager, toybox, etc)
"liblzma", // used by init_second_stage
"libvm_payload", // used by payload to interact with microdroid manager
+
+ "prng_seeder_microdroid",
] + microdroid_shell_and_utilities,
multilib: {
common: {
@@ -214,6 +216,7 @@
},
avb_private_key: ":microdroid_sign_key",
avb_algorithm: "SHA256_RSA4096",
+ avb_hash_algorithm: "sha256",
file_contexts: ":microdroid_vendor_file_contexts.gen",
// For deterministic output, use fake_timestamp, hard-coded uuid
fake_timestamp: "1611569676",
diff --git a/microdroid/init.rc b/microdroid/init.rc
index a48ba4b..7402481 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -17,6 +17,10 @@
start ueventd
+ # Generate empty linker config to suppress warnings
+ write /linkerconfig/ld.config.txt \#
+ chmod 644 /linkerconfig/ld.config.txt
+
# If VM is debuggable, send logs to outside ot the VM via the serial console.
# If non-debuggable, logs are internally consumed at /dev/null
on early-init && property:ro.boot.microdroid.debuggable=1
diff --git a/microdroid/kernel/README.md b/microdroid/kernel/README.md
index 12d8770..e2ac617 100644
--- a/microdroid/kernel/README.md
+++ b/microdroid/kernel/README.md
@@ -60,7 +60,7 @@
For x86\_64,
```bash
-cp out/dist/bzImage <android_checkout>/packages/modules/Virtualization/microdroid/kernel/arm64/kernel-5.15
+cp out/dist/bzImage <android_checkout>/packages/modules/Virtualization/microdroid/kernel/x86_64/kernel-5.15
```
### For official updates
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 3c490f4..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};
@@ -77,7 +78,6 @@
const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
const DEBUGGABLE_PROP: &str = "ro.boot.microdroid.debuggable";
-const APK_MOUNT_DONE_PROP: &str = "microdroid_manager.apk.mounted";
// SYNC WITH virtualizationservice/src/crosvm.rs
const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
@@ -161,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")
}
@@ -383,15 +384,16 @@
None
};
+ let mut zipfuse = Zipfuse::default();
+
// Before reading a file from the APK, start zipfuse
- run_zipfuse(
+ zipfuse.mount(
MountForExec::Allowed,
"fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
Path::new("/dev/block/mapper/microdroid-apk"),
Path::new(VM_APK_CONTENTS_PATH),
- Some(APK_MOUNT_DONE_PROP),
- )
- .context("Failed to run zipfuse")?;
+ "microdroid_manager.apk.mounted".to_owned(),
+ )?;
// Restricted APIs are only allowed to be used by platform or test components. Infer this from
// the use of a VM config file since those can only be used by platform and test components.
@@ -414,7 +416,7 @@
verified_data.extra_apks_data.len()
));
}
- mount_extra_apks(&config)?;
+ mount_extra_apks(&config, &mut zipfuse)?;
// Wait until apex config is done. (e.g. linker configuration for apexes)
wait_for_apex_config_done()?;
@@ -428,8 +430,8 @@
control_service("stop", "tombstoned")?;
}
- // Wait until zipfuse has mounted the APK so we can access the payload
- wait_for_property_true(APK_MOUNT_DONE_PROP).context("Failed waiting for APK mount done")?;
+ // Wait until zipfuse has mounted the APKs so we can access the payload
+ zipfuse.wait_until_done()?;
register_vm_payload_service(allow_restricted_apis, service.clone(), dice_context)?;
@@ -480,21 +482,40 @@
Disallowed,
}
-fn run_zipfuse(
- noexec: MountForExec,
- option: &str,
- zip_path: &Path,
- mount_dir: &Path,
- ready_prop: Option<&str>,
-) -> Result<Child> {
- let mut cmd = Command::new(ZIPFUSE_BIN);
- if let MountForExec::Disallowed = noexec {
- cmd.arg("--noexec");
+#[derive(Default)]
+struct Zipfuse {
+ ready_properties: Vec<String>,
+}
+
+impl Zipfuse {
+ fn mount(
+ &mut self,
+ noexec: MountForExec,
+ option: &str,
+ zip_path: &Path,
+ mount_dir: &Path,
+ ready_prop: String,
+ ) -> Result<Child> {
+ let mut cmd = Command::new(ZIPFUSE_BIN);
+ if let MountForExec::Disallowed = noexec {
+ cmd.arg("--noexec");
+ }
+ cmd.args(["-p", &ready_prop, "-o", option]);
+ cmd.arg(zip_path).arg(mount_dir);
+ self.ready_properties.push(ready_prop);
+ cmd.spawn().with_context(|| format!("Failed to run zipfuse for {mount_dir:?}"))
}
- if let Some(property_name) = ready_prop {
- cmd.args(["-p", property_name]);
+
+ fn wait_until_done(self) -> Result<()> {
+ // We check the last-started check first in the hope that by the time it is done
+ // all or most of the others will also be done, minimising the number of times we
+ // block on a property.
+ for property in self.ready_properties.into_iter().rev() {
+ wait_for_property_true(&property)
+ .with_context(|| format!("Failed waiting for {property}"))?;
+ }
+ Ok(())
}
- cmd.arg("-o").arg(option).arg(zip_path).arg(mount_dir).spawn().context("Spawn zipfuse")
}
fn write_apex_payload_data(
@@ -664,21 +685,20 @@
})
}
-fn mount_extra_apks(config: &VmPayloadConfig) -> Result<()> {
+fn mount_extra_apks(config: &VmPayloadConfig, zipfuse: &mut Zipfuse) -> Result<()> {
// For now, only the number of apks is important, as the mount point and dm-verity name is fixed
for i in 0..config.extra_apks.len() {
- let mount_dir = format!("/mnt/extra-apk/{}", i);
+ let mount_dir = format!("/mnt/extra-apk/{i}");
create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
// don't wait, just detach
- run_zipfuse(
+ zipfuse.mount(
MountForExec::Disallowed,
"fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
- Path::new(&format!("/dev/block/mapper/extra-apk-{}", i)),
+ Path::new(&format!("/dev/block/mapper/extra-apk-{i}")),
Path::new(&mount_dir),
- None,
- )
- .context("Failed to zipfuse extra apks")?;
+ format!("microdroid_manager.extra_apk.mounted.{i}"),
+ )?;
}
Ok(())
@@ -777,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 0f2a39c..f209784 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -17,17 +17,22 @@
use crate::helpers;
use core::fmt;
use core::mem;
-use core::num::NonZeroUsize;
-use core::ops;
+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],
}
@@ -43,8 +48,10 @@
InvalidFlags(u32),
/// Header describes configuration data that doesn't fit in the expected buffer.
InvalidSize(usize),
+ /// Header entry is missing.
+ MissingEntry(Entry),
/// Header entry is invalid.
- InvalidEntry(Entry),
+ InvalidEntry(Entry, EntryError),
}
impl fmt::Display for Error {
@@ -55,15 +62,41 @@
Self::UnsupportedVersion(x, y) => write!(f, "Version {x}.{y} not supported"),
Self::InvalidFlags(v) => write!(f, "Flags value {v:#x} is incorrect or reserved"),
Self::InvalidSize(sz) => write!(f, "Total size ({sz:#x}) overflows reserved region"),
- Self::InvalidEntry(e) => write!(f, "Entry {e:?} is invalid"),
+ Self::MissingEntry(entry) => write!(f, "Mandatory {entry:?} entry is missing"),
+ Self::InvalidEntry(entry, e) => write!(f, "Invalid {entry:?} entry: {e}"),
}
}
}
pub type Result<T> = result::Result<T, Error>;
+#[derive(Debug)]
+pub enum EntryError {
+ /// Offset isn't between the fixed minimum value and size of configuration data.
+ InvalidOffset(usize),
+ /// Size must be zero when offset is and not be when it isn't.
+ InvalidSize(usize),
+ /// Entry isn't fully within the configuration data structure.
+ OutOfBounds { offset: usize, size: usize, limit: usize },
+}
+
+impl fmt::Display for EntryError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::InvalidOffset(offset) => write!(f, "Invalid offset: {offset:#x?}"),
+ Self::InvalidSize(sz) => write!(f, "Invalid size: {sz:#x?}"),
+ Self::OutOfBounds { offset, size, limit } => {
+ let range = Header::PADDED_SIZE..*limit;
+ let entry = *offset..(*offset + *size);
+ write!(f, "Out of bounds: {entry:#x?} must be within range {range:#x?}")
+ }
+ }
+ }
+}
+
impl Header {
const MAGIC: u32 = u32::from_ne_bytes(*b"pvmf");
+ const VERSION_1_0: u32 = Self::version(1, 0);
const PADDED_SIZE: usize =
helpers::unchecked_align_up(mem::size_of::<Self>(), mem::size_of::<u64>());
@@ -83,8 +116,43 @@
self.total_size() - Self::PADDED_SIZE
}
- fn get(&self, entry: Entry) -> HeaderEntry {
- self.entries[entry as usize]
+ fn get_body_range(&self, entry: Entry) -> Result<Option<Range<usize>>> {
+ let e = self.entries[entry as usize];
+ let offset = e.offset as usize;
+ let size = e.size as usize;
+
+ match self._get_body_range(offset, size) {
+ Ok(r) => Ok(r),
+ Err(EntryError::InvalidSize(0)) => {
+ // As our bootloader currently uses this (non-compliant) case, permit it for now.
+ log::warn!("Config entry {entry:?} uses non-zero offset with zero size");
+ // TODO(b/262181812): Either make this case valid or fix the bootloader.
+ Ok(None)
+ }
+ Err(e) => Err(Error::InvalidEntry(entry, e)),
+ }
+ }
+
+ fn _get_body_range(
+ &self,
+ offset: usize,
+ size: usize,
+ ) -> result::Result<Option<Range<usize>>, EntryError> {
+ match (offset, size) {
+ (0, 0) => Ok(None),
+ (0, size) | (_, size @ 0) => Err(EntryError::InvalidSize(size)),
+ _ => {
+ let start = offset
+ .checked_sub(Header::PADDED_SIZE)
+ .ok_or(EntryError::InvalidOffset(offset))?;
+ let end = start
+ .checked_add(size)
+ .filter(|x| *x <= self.body_size())
+ .ok_or(EntryError::OutOfBounds { offset, size, limit: self.total_size() })?;
+
+ Ok(Some(start..end))
+ }
+ }
}
}
@@ -105,38 +173,11 @@
size: u32,
}
-impl HeaderEntry {
- pub fn is_empty(&self) -> bool {
- self.offset() == 0 && self.size() == 0
- }
-
- pub fn fits_in(&self, max_size: usize) -> bool {
- (Header::PADDED_SIZE..max_size).contains(&self.offset())
- && NonZeroUsize::new(self.size())
- .and_then(|s| s.checked_add(self.offset()))
- .filter(|&x| x.get() <= max_size)
- .is_some()
- }
-
- pub fn as_body_range(&self) -> ops::Range<usize> {
- let start = self.offset() - Header::PADDED_SIZE;
-
- start..(start + self.size())
- }
-
- pub fn offset(&self) -> usize {
- self.offset as usize
- }
-
- pub fn size(&self) -> usize {
- self.size as usize
- }
-}
-
#[derive(Debug)]
pub struct Config<'a> {
- header: &'a Header,
body: &'a mut [u8],
+ bcc_range: Range<usize>,
+ dp_range: Option<Range<usize>>,
}
impl<'a> Config<'a> {
@@ -152,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));
}
@@ -161,40 +202,26 @@
return Err(Error::InvalidFlags(header.flags));
}
- let total_size = header.total_size();
-
- // BCC is a mandatory entry of the configuration data.
- if !header.get(Entry::Bcc).fits_in(total_size) {
- return Err(Error::InvalidEntry(Entry::Bcc));
- }
-
- // Debug policy is optional.
- let dp = header.get(Entry::DebugPolicy);
- if !dp.is_empty() && !dp.fits_in(total_size) {
- return Err(Error::InvalidEntry(Entry::DebugPolicy));
- }
+ let bcc_range =
+ header.get_body_range(Entry::Bcc)?.ok_or(Error::MissingEntry(Entry::Bcc))?;
+ let dp_range = header.get_body_range(Entry::DebugPolicy)?;
let body = data
.get_mut(Header::PADDED_SIZE..)
.ok_or(Error::BufferTooSmall)?
.get_mut(..header.body_size())
- .ok_or(Error::InvalidSize(total_size))?;
+ .ok_or_else(|| Error::InvalidSize(header.total_size()))?;
- Ok(Self { header, body })
+ Ok(Self { body, bcc_range, dp_range })
}
/// Get slice containing the platform BCC.
pub fn get_bcc_mut(&mut self) -> &mut [u8] {
- &mut self.body[self.header.get(Entry::Bcc).as_body_range()]
+ &mut self.body[self.bcc_range.clone()]
}
/// Get slice containing the platform debug policy.
pub fn get_debug_policy(&mut self) -> Option<&mut [u8]> {
- let entry = self.header.get(Entry::DebugPolicy);
- if entry.is_empty() {
- None
- } else {
- Some(&mut self.body[entry.as_body_range()])
- }
+ self.dp_range.as_ref().map(|r| &mut self.body[r.clone()])
}
}
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index d307759..1b35c79 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -247,7 +247,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 6c35d91..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;
@@ -86,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/main.rs b/pvmfw/src/main.rs
index 9c5fb60..7d64bf0 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -39,10 +39,10 @@
memory::MemoryTracker,
pci::{find_virtio_devices, PciError, PciInfo},
};
-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,
@@ -70,7 +70,7 @@
let mut pci_root = unsafe { pci_info.make_pci_root() };
find_virtio_devices(&mut pci_root).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/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/benchmark/src/jni/io_vsock_host_jni.cpp b/tests/benchmark/src/jni/io_vsock_host_jni.cpp
index dd32e29..47d5325 100644
--- a/tests/benchmark/src/jni/io_vsock_host_jni.cpp
+++ b/tests/benchmark/src/jni/io_vsock_host_jni.cpp
@@ -20,6 +20,7 @@
#include <jni.h>
#include <time.h>
+using android::base::ErrnoError;
using android::base::Error;
using android::base::Result;
using android::base::WriteStringToFd;
@@ -29,12 +30,18 @@
Result<double> measure_send_rate(int fd, int num_bytes_to_send) {
std::string data;
data.assign(num_bytes_to_send, 'a');
- clock_t start = clock();
+ struct timespec start;
+ if (clock_gettime(CLOCK_MONOTONIC, &start) == -1) {
+ return ErrnoError() << "failed to clock_gettime";
+ }
if (!WriteStringToFd(data, fd)) {
return Error() << "Cannot send data to client";
}
- clock_t end = clock();
- double elapsed_seconds = (double)(end - start) / CLOCKS_PER_SEC;
+ struct timespec finish;
+ if (clock_gettime(CLOCK_MONOTONIC, &finish) == -1) {
+ return ErrnoError() << "failed to clock_gettime";
+ }
+ double elapsed_seconds = finish.tv_sec - start.tv_sec + (finish.tv_nsec - start.tv_nsec) / 1e9;
LOG(INFO) << "Host:Finished sending data in " << elapsed_seconds << " seconds.";
double send_rate = (double)num_bytes_to_send / kNumBytesPerMB / elapsed_seconds;
return {send_rate};
diff --git a/tests/benchmark/src/native/benchmarkbinary.cpp b/tests/benchmark/src/native/benchmarkbinary.cpp
index 56963e6..6cfc71d 100644
--- a/tests/benchmark/src/native/benchmarkbinary.cpp
+++ b/tests/benchmark/src/native/benchmarkbinary.cpp
@@ -118,7 +118,10 @@
}
char buf[kBlockSizeBytes];
- clock_t start = clock();
+ struct timespec start;
+ if (clock_gettime(CLOCK_MONOTONIC, &start) == -1) {
+ return ErrnoError() << "failed to clock_gettime";
+ }
unique_fd fd(open(filename.c_str(), O_RDONLY | O_CLOEXEC));
if (fd.get() == -1) {
return ErrnoError() << "Read: opening " << filename << " failed";
@@ -131,7 +134,12 @@
return ErrnoError() << "failed to read";
}
}
- double elapsed_seconds = ((double)clock() - start) / CLOCKS_PER_SEC;
+ struct timespec finish;
+ if (clock_gettime(CLOCK_MONOTONIC, &finish) == -1) {
+ return ErrnoError() << "failed to clock_gettime";
+ }
+ double elapsed_seconds =
+ finish.tv_sec - start.tv_sec + (finish.tv_nsec - start.tv_nsec) / 1e9;
double file_size_mb = (double)file_size_bytes / kNumBytesPerMB;
return {file_size_mb / elapsed_seconds};
}
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 72a0090..9aed34d 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -248,9 +248,6 @@
vm.clearCallback();
mExecutorService.shutdown();
}
-
- @Override
- public void onRamdump(VirtualMachine vm, ParcelFileDescriptor ramdump) {}
}
public static class BootResult {
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 8b0d6d2..160b679 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();
@@ -264,8 +314,9 @@
@Test
@CddTest(requirements = {"9.17/C-1-1"})
public void vmConfigUnitTests() {
- VirtualMachineConfig minimal =
- newVmConfigBuilder().setPayloadBinaryPath("binary/path").build();
+
+ VirtualMachineConfig.Builder minimalBuilder = newVmConfigBuilder();
+ VirtualMachineConfig minimal = minimalBuilder.setPayloadBinaryPath("binary/path").build();
assertThat(minimal.getApkPath()).isEqualTo(getContext().getPackageCodePath());
assertThat(minimal.getDebugLevel()).isEqualTo(DEBUG_LEVEL_NONE);
@@ -274,6 +325,8 @@
assertThat(minimal.getPayloadBinaryPath()).isEqualTo("binary/path");
assertThat(minimal.getPayloadConfigPath()).isNull();
assertThat(minimal.isProtectedVm()).isEqualTo(isProtectedVm());
+ assertThat(minimal.isEncryptedStorageEnabled()).isFalse();
+ assertThat(minimal.getEncryptedStorageKib()).isEqualTo(0);
int maxCpus = Runtime.getRuntime().availableProcessors();
VirtualMachineConfig.Builder maximalBuilder =
@@ -282,7 +335,8 @@
.setApkPath("/apk/path")
.setNumCpus(maxCpus)
.setDebugLevel(DEBUG_LEVEL_FULL)
- .setMemoryMib(42);
+ .setMemoryMib(42)
+ .setEncryptedStorageKib(1024);
VirtualMachineConfig maximal = maximalBuilder.build();
assertThat(maximal.getApkPath()).isEqualTo("/apk/path");
@@ -292,6 +346,8 @@
assertThat(maximal.getPayloadBinaryPath()).isNull();
assertThat(maximal.getPayloadConfigPath()).isEqualTo("config/path");
assertThat(maximal.isProtectedVm()).isEqualTo(isProtectedVm());
+ assertThat(maximal.isEncryptedStorageEnabled()).isTrue();
+ assertThat(maximal.getEncryptedStorageKib()).isEqualTo(1024);
assertThat(minimal.isCompatibleWith(maximal)).isFalse();
assertThat(minimal.isCompatibleWith(minimal)).isTrue();
@@ -299,6 +355,41 @@
VirtualMachineConfig compatible = maximalBuilder.setNumCpus(1).setMemoryMib(99).build();
assertThat(compatible.isCompatibleWith(maximal)).isTrue();
+
+ // Assert that different encrypted storage size would imply the configs are incompatible
+ VirtualMachineConfig incompatible = minimalBuilder.setEncryptedStorageKib(1048).build();
+ assertThat(incompatible.isCompatibleWith(minimal)).isFalse();
+ }
+
+ @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
@@ -325,6 +416,54 @@
@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";
+
+ try {
+ // 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);
+
+ // The old one must remain deleted, or we'd have two VirtualMachine instances referring
+ // to the same VM.
+ assertThat(vm1.getStatus()).isEqualTo(STATUS_DELETED);
+
+ // Subsequent gets should return this new one.
+ assertThat(vmm.get(vmName)).isSameInstanceAs(vm2);
+ assertThat(vmm.getOrCreate(vmName, config)).isSameInstanceAs(vm2);
+ } finally {
+ 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();
@@ -413,10 +552,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
@@ -441,20 +580,6 @@
}
@Test
- @CddTest(requirements = {
- "9.17/C-1-1",
- })
- public void invalidApkPathIsRejected() {
- VirtualMachineConfig.Builder builder =
- newVmConfigBuilder()
- .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
- .setApkPath("relative/path/to.apk")
- .setDebugLevel(DEBUG_LEVEL_FULL)
- .setMemoryMib(minMemoryRequired());
- assertThrows(IllegalArgumentException.class, () -> builder.build());
- }
-
- @Test
@CddTest(requirements = {"9.17/C-1-1"})
public void invalidVmNameIsRejected() {
VirtualMachineManager vmm = getVirtualMachineManager();
@@ -927,13 +1052,28 @@
}
@Test
- public void importedVmIsEqualToTheOriginalVm() throws Exception {
+ @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ public void importedVmIsEqualToTheOriginalVm_WithoutStorage() throws Exception {
+ TestResults testResults = importedVmIsEqualToTheOriginalVm(false);
+ assertThat(testResults.mEncryptedStoragePath).isEqualTo("");
+ }
+
+ @Test
+ @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ public void importedVmIsEqualToTheOriginalVm_WithStorage() throws Exception {
+ TestResults testResults = importedVmIsEqualToTheOriginalVm(true);
+ assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
+ }
+
+ private TestResults importedVmIsEqualToTheOriginalVm(boolean encryptedStoreEnabled)
+ throws Exception {
// Arrange
- VirtualMachineConfig config =
+ VirtualMachineConfig.Builder builder =
newVmConfigBuilder()
.setPayloadBinaryPath("MicrodroidTestNativeLib.so")
- .setDebugLevel(DEBUG_LEVEL_FULL)
- .build();
+ .setDebugLevel(DEBUG_LEVEL_FULL);
+ if (encryptedStoreEnabled) builder = builder.setEncryptedStorageKib(4096);
+ VirtualMachineConfig config = builder.build();
String vmNameOrig = "test_vm_orig";
String vmNameImport = "test_vm_import";
VirtualMachine vmOrig = forceCreateNewVirtualMachine(vmNameOrig, config);
@@ -953,12 +1093,53 @@
// Asserts
assertFileContentsAreEqualInTwoVms("config.xml", vmNameOrig, vmNameImport);
assertFileContentsAreEqualInTwoVms("instance.img", vmNameOrig, vmNameImport);
+ if (encryptedStoreEnabled) {
+ assertFileContentsAreEqualInTwoVms("storage.img", vmNameOrig, vmNameImport);
+ }
assertThat(vmImport).isNotEqualTo(vmOrig);
vmm.delete(vmNameOrig);
assertThat(vmImport).isEqualTo(vmm.get(vmNameImport));
TestResults testResults = runVmTestService(vmImport);
assertThat(testResults.mException).isNull();
assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
+ return testResults;
+ }
+
+ @Test
+ @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ public void encryptedStorageAvailable() throws Exception {
+ assumeSupportedKernel();
+
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setMemoryMib(minMemoryRequired())
+ .setEncryptedStorageKib(4096)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+
+ TestResults testResults = runVmTestService(vm);
+ 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)
@@ -977,6 +1158,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];
@@ -1005,6 +1196,7 @@
String mExtraApkTestProp;
String mApkContentsPath;
String mEncryptedStoragePath;
+ String[] mEffectiveCapabilities;
}
private TestResults runVmTestService(VirtualMachine vm) throws Exception {
@@ -1028,6 +1220,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 a329fa6..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
@@ -50,9 +50,4 @@
* also use `link_to_death` to handle that.
*/
void onDied(int cid, in DeathReason reason);
-
- /**
- * Called when kernel panic occurs and as a result ramdump is generated from the VM.
- */
- void onRamdump(int cid, in ParcelFileDescriptor ramdump);
}
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 7d24a32..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)
}
@@ -115,6 +129,24 @@
}
}
+fn create_or_update_idsig_file(
+ input_fd: &ParcelFileDescriptor,
+ idsig_fd: &ParcelFileDescriptor,
+) -> Result<()> {
+ let mut input = clone_file(input_fd)?;
+ let metadata = input.metadata().context("failed to get input metadata")?;
+ if !metadata.is_file() {
+ bail!("input is not a regular file");
+ }
+ let mut sig = V4Signature::create(&mut input, 4096, &[], HashAlgorithm::SHA256)
+ .context("failed to create idsig")?;
+
+ let mut output = clone_file(idsig_fd)?;
+ output.set_len(0).context("failed to set_len on the idsig output")?;
+ sig.write_into(&mut output).context("failed to write idsig")?;
+ Ok(())
+}
+
/// Singleton service for allocating globally-unique VM resources, such as the CID, and running
/// singleton servers, like tombstone receiver.
#[derive(Debug, Default)]
@@ -146,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
@@ -239,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 {
@@ -345,12 +391,8 @@
check_manage_access()?;
- let mut input = clone_file(input_fd)?;
- let mut sig = V4Signature::create(&mut input, 4096, &[], HashAlgorithm::SHA256).unwrap();
-
- let mut output = clone_file(idsig_fd)?;
- output.set_len(0).unwrap();
- sig.write_into(&mut output).unwrap();
+ create_or_update_idsig_file(input_fd, idsig_fd)
+ .map_err(|e| Status::new_service_specific_error_str(-1, Some(format!("{:?}", e))))?;
Ok(())
}
@@ -447,25 +489,20 @@
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();
// Start VM service listening for connections from the new CID on port=CID.
- // TODO(b/245727626): Only accept connections from the new VM.
let port = cid;
- match RpcServer::new_vsock(service, port) {
+ match RpcServer::new_vsock(service, cid, port) {
Ok(vm_server) => {
vm_server.start();
return Ok((VmContext::new(global_context, vm_server), cid));
@@ -512,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;
@@ -839,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(());
@@ -881,8 +918,9 @@
// Return whether a partition is exempt from selinux label checks, because we know that it does
// not contain code and is likely to be generated in an app-writable directory.
fn is_safe_app_partition(label: &str) -> bool {
- // See make_payload_disk in payload.rs.
+ // See add_microdroid_system_images & add_microdroid_payload_images in payload.rs.
label == "vm-instance"
+ || label == "encryptedstore"
|| label == "microdroid-apk-idsig"
|| label == "payload-metadata"
|| label.starts_with("extra-idsig-")
@@ -898,7 +936,7 @@
match ctx.selinux_type()? {
| "system_file" // immutable dm-verity protected partition
| "apk_data_file" // APKs of an installed app
- | "staging_data_file" // updated/staged APEX imagess
+ | "staging_data_file" // updated/staged APEX images
| "shell_data_file" // test files created via adb shell
=> Ok(()),
_ => bail!("Label {} is not allowed", ctx),
@@ -973,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))
}
}
@@ -1049,17 +1090,6 @@
}
}
- /// Call all registered callbacks to say that there was a ramdump to download.
- pub fn callback_on_ramdump(&self, cid: Cid, ramdump: File) {
- let callbacks = &*self.0.lock().unwrap();
- let pfd = ParcelFileDescriptor::new(ramdump);
- for callback in callbacks {
- if let Err(e) = callback.onRamdump(cid as i32, &pfd) {
- error!("Error notifying ramdump of VM CID {}: {:?}", cid, e);
- }
- }
- }
-
/// Add a new callback to the set.
fn add(&self, callback: Strong<dyn IVirtualMachineCallback>) {
self.0.lock().unwrap().push(callback);
@@ -1302,4 +1332,50 @@
}
Ok(())
}
+
+ #[test]
+ fn test_create_or_update_idsig_file_empty_apk() -> Result<()> {
+ let apk = tempfile::tempfile().unwrap();
+ let idsig = tempfile::tempfile().unwrap();
+
+ let ret = create_or_update_idsig_file(
+ &ParcelFileDescriptor::new(apk),
+ &ParcelFileDescriptor::new(idsig),
+ );
+ assert!(ret.is_err(), "should fail");
+ Ok(())
+ }
+
+ #[test]
+ fn test_create_or_update_idsig_dir_instead_of_file_for_apk() -> Result<()> {
+ let tmp_dir = tempfile::TempDir::new().unwrap();
+ let apk = File::open(tmp_dir.path()).unwrap();
+ let idsig = tempfile::tempfile().unwrap();
+
+ let ret = create_or_update_idsig_file(
+ &ParcelFileDescriptor::new(apk),
+ &ParcelFileDescriptor::new(idsig),
+ );
+ assert!(ret.is_err(), "should fail");
+ Ok(())
+ }
+
+ /// Verifies that create_or_update_idsig_file won't oom if a fd that corresponds to a directory
+ /// on ext4 filesystem is passed.
+ /// On ext4 lseek on a directory fd will return (off_t)-1 (see:
+ /// https://bugzilla.kernel.org/show_bug.cgi?id=200043), which will result in
+ /// create_or_update_idsig_file ooming while attempting to allocate petabytes of memory.
+ #[test]
+ fn test_create_or_update_idsig_does_not_crash_dir_on_ext4() -> Result<()> {
+ // APEXes are backed by the ext4.
+ let apk = File::open("/apex/com.android.virt/").unwrap();
+ let idsig = tempfile::tempfile().unwrap();
+
+ let ret = create_or_update_idsig_file(
+ &ParcelFileDescriptor::new(apk),
+ &ParcelFileDescriptor::new(idsig),
+ );
+ assert!(ret.is_err(), "should fail");
+ Ok(())
+ }
}
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 fc85ca5..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
@@ -520,15 +525,10 @@
Ok(())
}
- /// Checks if ramdump has been created. If so, send a notification to the user with the handle
- /// to read the ramdump.
+ /// Checks if ramdump has been created. If so, send it to tombstoned.
fn handle_ramdump(&self) -> Result<(), Error> {
let ramdump_path = self.temporary_directory.join("ramdump");
if std::fs::metadata(&ramdump_path)?.len() > 0 {
- let ramdump = File::open(&ramdump_path)
- .context(format!("Failed to open ramdump {:?} for reading", &ramdump_path))?;
- self.callbacks.callback_on_ramdump(self.cid, ramdump);
-
Self::send_ramdump_to_tombstoned(&ramdump_path)?;
}
Ok(())
@@ -536,7 +536,7 @@
fn send_ramdump_to_tombstoned(ramdump_path: &Path) -> Result<(), Error> {
let mut input = File::open(ramdump_path)
- .context(format!("Failed to open raudmp {:?} for reading", ramdump_path))?;
+ .context(format!("Failed to open ramdump {:?} for reading", ramdump_path))?;
let pid = std::process::id() as i32;
let conn = TombstonedConnection::connect(pid, DebuggerdDumpType::Tombstone)
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/src/main.rs b/vm/src/main.rs
index 32b165b..3d2fc00 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -81,10 +81,6 @@
#[clap(long)]
log: Option<PathBuf>,
- /// Path to file where ramdump is recorded on kernel panic
- #[clap(long)]
- ramdump: Option<PathBuf>,
-
/// Debug level of the VM. Supported values: "none" (default), and "full".
#[clap(long, default_value = "none", value_parser = parse_debug_level)]
debug: DebugLevel,
@@ -144,10 +140,6 @@
#[clap(long)]
log: Option<PathBuf>,
- /// Path to file where ramdump is recorded on kernel panic
- #[clap(long)]
- ramdump: Option<PathBuf>,
-
/// Debug level of the VM. Supported values: "none" (default), and "full".
#[clap(long, default_value = "full", value_parser = parse_debug_level)]
debug: DebugLevel,
@@ -268,7 +260,6 @@
daemonize,
console,
log,
- ramdump,
debug,
protected,
mem,
@@ -288,7 +279,6 @@
daemonize,
console.as_deref(),
log.as_deref(),
- ramdump.as_deref(),
debug,
protected,
mem,
@@ -304,7 +294,6 @@
daemonize,
console,
log,
- ramdump,
debug,
protected,
mem,
@@ -319,7 +308,6 @@
daemonize,
console.as_deref(),
log.as_deref(),
- ramdump.as_deref(),
debug,
protected,
mem,
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 3f25bba..6096913 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -52,7 +52,6 @@
daemonize: bool,
console_path: Option<&Path>,
log_path: Option<&Path>,
- ramdump_path: Option<&Path>,
debug_level: DebugLevel,
protected: bool,
mem: Option<u32>,
@@ -144,7 +143,7 @@
numCpus: cpus.unwrap_or(1) as i32,
taskProfiles: task_profiles,
});
- run(service, &config, &payload_config_str, daemonize, console_path, log_path, ramdump_path)
+ run(service, &config, &payload_config_str, daemonize, console_path, log_path)
}
const EMPTY_PAYLOAD_APK: &str = "com.android.microdroid.empty_payload";
@@ -182,7 +181,6 @@
daemonize: bool,
console_path: Option<&Path>,
log_path: Option<&Path>,
- ramdump_path: Option<&Path>,
debug_level: DebugLevel,
protected: bool,
mem: Option<u32>,
@@ -214,7 +212,6 @@
daemonize,
console_path,
log_path,
- ramdump_path,
debug_level,
protected,
mem,
@@ -259,7 +256,6 @@
daemonize,
console_path,
log_path,
- /* ramdump_path */ None,
)
}
@@ -282,7 +278,6 @@
daemonize: bool,
console_path: Option<&Path>,
log_path: Option<&Path>,
- ramdump_path: Option<&Path>,
) -> Result<(), Error> {
let console = if let Some(console_path) = console_path {
Some(
@@ -325,27 +320,12 @@
// Wait until the VM or VirtualizationService dies. If we just returned immediately then the
// IVirtualMachine Binder object would be dropped and the VM would be killed.
let death_reason = vm.wait_for_death();
-
- if let Some(path) = ramdump_path {
- save_ramdump_if_available(path, &vm)?;
- }
println!("VM ended: {:?}", death_reason);
}
Ok(())
}
-fn save_ramdump_if_available(path: &Path, vm: &VmInstance) -> Result<(), Error> {
- if let Some(mut ramdump) = vm.get_ramdump() {
- let mut file =
- File::create(path).context(format!("Failed to create ramdump file {:?}", path))?;
- let size = std::io::copy(&mut ramdump, &mut file)
- .context(format!("Failed to save ramdump to file {:?}", path))?;
- eprintln!("Ramdump ({} bytes) saved to {:?}", size, path);
- }
- Ok(())
-}
-
fn parse_extra_apk_list(apk: &Path, config_path: &str) -> Result<Vec<String>, Error> {
let mut archive = ZipArchive::new(File::open(apk)?)?;
let config_file = archive.by_name(config_path)?;
diff --git a/vm/vm_shell.sh b/vm/vm_shell.sh
index c0dd38f..29cc7da 100755
--- a/vm/vm_shell.sh
+++ b/vm/vm_shell.sh
@@ -14,16 +14,33 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# vm_shell.sh shows the VMs running in the Android device and connects to it
-# Usage:
-# vm_shell [cid]
-#
-# cid: CID of the VM to connect to. If omitted, the list of CIDs available are shown
+# vm_shell.sh: utilities to interact with Microdroid VMs
+
+function print_help() {
+ echo "vm_shell.sh provides utilities to interact with Microdroid VMs"
+ echo ""
+ echo "Available commands:"
+ echo " connect [cid] - establishes adb connection with the VM"
+ echo " cid - cid of the VM to connect to. If not specified user will "
+ echo " be promted to select one from the list of available cids"
+ echo ""
+ echo " start-microdroid [--auto-connect] [-- extra_args]"
+ echo " Starts a Microdroid VM. Args after the -- will be"
+ echo " passed through to the invocation of the "
+ echo " /apex/com.android.virt/bin/vm run-microdroid binary."
+ echo ""
+ echo " E.g.:"
+ echo " vm_shell start-microdroid -- --cpu 5"
+ echo ""
+ echo " --auto-connect - automatically connects to the started VMs"
+ echo ""
+ echo " help - prints this help message"
+}
function connect_vm() {
cid=$1
echo Connecting to CID ${cid}
- adb disconnect localhost:8000
+ adb disconnect localhost:8000 2>/dev/null
adb forward tcp:8000 vsock:${cid}:5555
adb connect localhost:8000
adb -s localhost:8000 root
@@ -32,26 +49,63 @@
exit 0
}
-selected_cid=$1
-available_cids=$(adb shell /apex/com.android.virt/bin/vm list | awk 'BEGIN { FS="[:,]" } /cid/ { print $2; }')
+function list_cids() {
+ local selected_cid=$1
+ local available_cids=$(adb shell /apex/com.android.virt/bin/vm list | awk 'BEGIN { FS="[:,]" } /cid/ { print $2; }')
+ echo "${available_cids}"
+}
-if [ -z "${available_cids}" ]; then
- echo No VM is available
- exit 1
-fi
+function handle_connect_cmd() {
+ selected_cid=$1
-if [ ! -n "${selected_cid}" ]; then
- PS3="Select CID of VM to adb-shell into: "
- select cid in ${available_cids}
- do
- selected_cid=${cid}
- break
+ available_cids=$(list_cids)
+
+ if [ -z "${available_cids}" ]; then
+ echo No VM is available
+ exit 1
+ fi
+
+ if [ ! -n "${selected_cid}" ]; then
+ PS3="Select CID of VM to adb-shell into: "
+ select cid in ${available_cids}
+ do
+ selected_cid=${cid}
+ break
+ done
+ fi
+
+ if [[ ! " ${available_cids[*]} " =~ " ${selected_cid} " ]]; then
+ echo VM of CID $selected_cid does not exist. Available CIDs: ${available_cids}
+ exit 1
+ fi
+
+ connect_vm ${selected_cid}
+}
+
+function handle_start_microdroid_cmd() {
+ while [[ "$#" -gt 0 ]]; do
+ case $1 in
+ --auto-connect) auto_connect=true; ;;
+ --) shift; passthrough_args="$@"; break ;;
+ *) echo "Unknown argument: $1"; exit 1 ;;
+ esac
+ shift
done
-fi
+ if [[ "${auto_connect}" == true ]]; then
+ adb shell /apex/com.android.virt/bin/vm run-microdroid -d "${passthrough_args}"
+ sleep 2
+ handle_connect_cmd
+ else
+ adb shell /apex/com.android.virt/bin/vm run-microdroid "${passthrough_args}"
+ fi
+}
-if [[ ! " ${available_cids[*]} " =~ " ${selected_cid} " ]]; then
- echo VM of CID $selected_cid does not exist. Available CIDs: ${available_cids}
- exit 1
-fi
+cmd=$1
+shift
-connect_vm ${selected_cid}
+case $cmd in
+ connect) handle_connect_cmd "$@" ;;
+ start-microdroid) handle_start_microdroid_cmd "$@" ;;
+ help) print_help ;;
+ *) print_help; exit 1 ;;
+esac
diff --git a/vm_payload/README.md b/vm_payload/README.md
index 6ce6770..bcba9be 100644
--- a/vm_payload/README.md
+++ b/vm_payload/README.md
@@ -60,7 +60,7 @@
C++ can be used, but you will need to include the C++ runtime in your APK along
with your payload, either statically linked (if
-[appropriate](https://developer.android.com/ndk/guides/cpp-support#sr) or as a
+[appropriate](https://developer.android.com/ndk/guides/cpp-support#sr)) or as a
separate .so.
The same is true for other languages such as Rust.
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..5cc2d1e 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,25 +53,40 @@
*
* \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
- * 32-byte values and the value associated with an identifier will not change
- * over the lifetime of the VM instance.
+ * Returns all or part of a 32-byte secret that is bound to this unique VM
+ * instance and the supplied identifier. The secret can be used e.g. as an
+ * encryption key.
+ *
+ * Every VM has a secret that is derived from a device-specific value known to
+ * the hypervisor, the code that runs in the VM and its non-modifiable
+ * configuration; it is not made available to the host OS.
+ *
+ * This function performs a further derivation from the VM secret and the
+ * supplied identifier. As long as the VM identity doesn't change the same value
+ * will be returned for the same identifier, even if the VM is stopped &
+ * restarted or the device rebooted.
+ *
+ * If multiple secrets are required for different purposes, a different
+ * identifier should be used for each. The identifiers otherwise are arbitrary
+ * byte sequences and do not need to be kept secret; typically they are
+ * hardcoded in the calling code.
*
* \param identifier identifier of the secret to return.
* \param identifier_size size of the secret identifier.
* \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 +97,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 +110,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 28b440e..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)
}
@@ -136,7 +135,7 @@
// safely be taken by new_spibinder.
let service = unsafe { new_spibinder(service) };
if let Some(service) = service {
- match RpcServer::new_vsock(service, port) {
+ match RpcServer::new_vsock(service, libc::VMADDR_CID_HOST, port) {
Ok(server) => {
if let Some(on_ready) = on_ready {
// SAFETY: We're calling the callback with the parameter specified within the
@@ -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/vmbase/Android.bp b/vmbase/Android.bp
index 7a36a0a..5ed436c 100644
--- a/vmbase/Android.bp
+++ b/vmbase/Android.bp
@@ -68,7 +68,7 @@
"libspin_nostd",
],
whole_static_libs: [
- "libarm-optimized-routines-mem",
+ "librust_baremetal",
],
apex_available: ["com.android.virt"],
}
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 20b7f02..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
@@ -190,11 +268,6 @@
}
})
}
-
- /// Get ramdump
- pub fn get_ramdump(&self) -> Option<File> {
- self.state.get_ramdump()
- }
}
impl Debug for VmInstance {
@@ -222,7 +295,6 @@
struct VmState {
death_reason: Option<DeathReason>,
reported_state: VirtualMachineState,
- ramdump: Option<File>,
}
impl Monitor<VmState> {
@@ -239,14 +311,6 @@
self.state.lock().unwrap().reported_state = state;
self.cv.notify_all();
}
-
- fn set_ramdump(&self, ramdump: File) {
- self.state.lock().unwrap().ramdump = Some(ramdump);
- }
-
- fn get_ramdump(&self) -> Option<File> {
- self.state.lock().unwrap().ramdump.as_ref().and_then(|f| f.try_clone().ok())
- }
}
struct VirtualMachineCallback {
@@ -302,12 +366,6 @@
Ok(())
}
- fn onRamdump(&self, _cid: i32, ramdump: &ParcelFileDescriptor) -> BinderResult<()> {
- let ramdump: File = ramdump.as_ref().try_clone().unwrap();
- self.state.set_ramdump(ramdump);
- Ok(())
- }
-
fn onDied(&self, cid: i32, reason: AidlDeathReason) -> BinderResult<()> {
let reason = reason.into();
self.state.notify_death(reason);