Compare commits

...

34 Commits

Author SHA1 Message Date
Jake Correnti 7d3ff6c39c ci: do not exclude nitro crate
The main branch not being able to compile should not happen. This should
help prevent a similar scenario from happening in the future.

Signed-off-by: Jake Correnti <jakecorrenti+github@proton.me>
2025-06-16 17:14:38 -04:00
Jake Correnti f901021254 nitro: libkrun: fix macOS compilation failure
macOS was failing to compile with EFI=1 due to AWS nitro related code
compiling when it should not have been.

Signed-off-by: Jake Correnti <jakecorrenti+github@proton.me>
2025-06-16 17:14:38 -04:00
Jake Correnti 7e2239ae07 vmm: fix worker thread panic
If the worker thread panics when trying to convert memory to or from
private, it leaves the VMM process waiting indefinitely for the sender
to send some sort of message over the channel. Rather than panicking, we
should print an error and send a message back over the channel to stop
the VM.

Signed-off-by: Jake Correnti <jakecorrenti+github@proton.me>
2025-06-16 16:01:01 +01:00
Ruoqing He 0be2d439a9 arch: Remove `round_up` and `round_down`
We have replaced usage of `round_up` and `round_down`, remove those
unused functions.

Signed-off-by: Ruoqing He <heruoqing@iscas.ac.cn>
2025-06-16 15:16:36 +01:00
Ruoqing He 78d13aafff rusabaga_gfx: Introduce align module from `vmm-sys-util`
Use macros from `vmm-sys-util` for aligning in `round_up_to_page_size`
implementation.

Signed-off-by: Ruoqing He <heruoqing@iscas.ac.cn>
2025-06-16 15:16:36 +01:00
Ruoqing He 5c90daa2f7 vmm: Introduce align module from `vmm-sys-util`
Use macros from `vmm-sys-util` for aligning in vmm crate.

Signed-off-by: Ruoqing He <heruoqing@iscas.ac.cn>
2025-06-16 15:16:36 +01:00
Ruoqing He 0258af1295 arch: Introduce align module from `vmm-sys-util`
Use macros from `vmm-sys-util` for aligning in arch crate.

Signed-off-by: Ruoqing He <heruoqing@iscas.ac.cn>
2025-06-16 15:16:36 +01:00
Tyler Fanelli 885d642c43 .github/darwin: Exclude nitro crate
The nitro workspace member is not supported on macOS and has
Linux-exclusive dependencies. Exclude the crate when running
clippy on macOS.

Signed-off-by: Tyler Fanelli <tfanelli@redhat.com>
2025-06-12 12:07:17 +01:00
Tyler Fanelli e81f9b73a0 nitro: Add API to configure enclave start flags
Signed-off-by: Tyler Fanelli <tfanelli@redhat.com>
2025-06-12 12:07:17 +01:00
Tyler Fanelli 13d0351806 nitro: Add IPC socket for enclave vsock output
To give more control to the user, allow for the krun_add_vsock_port API
to forward enclave data to/from and IPC socket set up by a consumer of
the library.

Signed-off-by: Tyler Fanelli <tfanelli@redhat.com>
2025-06-12 12:07:17 +01:00
Tyler Fanelli 01a748bbe6 nitro: Run preliminary (unconfigurable) enclave
Uses the nitro-enclaves crate to create and run an enclave in debug mode.

Signed-off-by: Tyler Fanelli <tfanelli@redhat.com>
2025-06-12 12:07:17 +01:00
Tyler Fanelli e5434228c9 examples: Create nitro enclaves example
The nitro example will run a nitro enclave in debug mode and print the
vsock data from the enclave to the console.

Signed-off-by: Tyler Fanelli <tfanelli@redhat.com>
2025-06-12 12:07:17 +01:00
Tyler Fanelli fd839ead34 nitro: Init module, collect enclave resources
The nitro module/crate will serve as the main management layer for nitro
enclaves. Collecting enclave resources from a krun context will allow us
to re-use the existing libkrun APIs for nitro enclaves.

Signed-off-by: Tyler Fanelli <tfanelli@redhat.com>
2025-06-12 12:07:17 +01:00
Tyler Fanelli 73227fcca5 nitro: Require initial vsock connection from guest
The guest/libkrun process will initiate the communication of vsock data
from a nitro enclave.

Signed-off-by: Tyler Fanelli <tfanelli@redhat.com>
2025-06-12 12:07:17 +01:00
Tyler Fanelli 0ba624a03b lib: Add API to set path of nitro enclaves image
Nitro enclaves' memory regions are defined by enclave image files.
Introduce a libkrun API to set an enclave's image from a file path as
well as the type of enclave image it is.

Currently, only Enclave Image Format (EIF) files are supported for nitro
enclaves.

Signed-off-by: Tyler Fanelli <tfanelli@redhat.com>
2025-06-12 12:07:17 +01:00
Tyler Fanelli 6f8bc92b04 Introduce nitro flavor and feature
The nitro flavor introduces support for libkrun acting as a "driver" and
manager for AWS Nitro Enclaves.

Signed-off-by: Tyler Fanelli <tfanelli@redhat.com>
2025-06-12 12:07:17 +01:00
Jake Correnti 11479de00f devices: vmm: Update IOAPIC IRQ routes
Update the IOAPIC IRQ routes to use the new wrapped
`kvm_bindings::KvmIrqRouting` instead of
`kvm_bindings::kvm_irq_routing`.

Signed-off-by: Jake Correnti <jakecorrenti+github@proton.me>
2025-06-06 11:16:17 -04:00
Jake Correnti fb5c6e2fbf Update rust-vmm/kvm dependencies
Signed-off-by: Jake Correnti <jakecorrenti+github@proton.me>
2025-06-06 11:16:17 -04:00
Geoffrey Goodman b80a18ac0f makefile: do not couple gpu to efi feature
This tweaks the `EFI=1` make option so that it doesn't automatically bring
along the `gpu` feature. Since `EFI` resets `FEATURE_FLAGS`, the test
for `GPU` and `SND` (not implied by `efi` rust feature) are evaluated
after.

Signed-off-by: Geoffrey Goodman <geoff@goodman.dev>
2025-06-05 11:37:38 +01:00
Sergio Lopez 05bbf88e72 Bump version to 1.13.0
Set up the stage for a new release, including fixes for nested
virt in M4, aarch64 registers, and a new log API.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2025-06-03 15:04:53 -04:00
Sergio Lopez 052bd8ec1d vmm: bump kbs-types and drop tee-sev
Bump kbs-types dependency to version 0.11 and drop the use of the
tee-sev which we no longer use (and it's broken in kbs-types).

Signed-off-by: Sergio Lopez <slp@redhat.com>
2025-06-03 18:26:18 +01:00
Sergio Lopez 6a3b27833c arch/aarch64: replace offset__of with a safe macro
We were using an unsafe macro with undefined behavior and, with
the latest compiler, it generates broken code. Replace it with
a cleaner alternative.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2025-06-03 16:59:31 +01:00
Sergio Lopez 136b6c49c6 hvf: enable EL2 and GICv3 in ID_AA64PFR0_EL1
For correctness sake, enable EL2 and GICv3 flags in ID_AA64PFR0_EL1.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2025-06-03 16:46:21 +01:00
Sergio Lopez c8c5185dab hvf: mask out SME in ID_AA64PFR1_EL1
If SME is present in ID_AA64PFR1_EL1 in the VM, the guest will break
after enabling the MMU. I don't see an architectural reason for this to
happen, so I suspect it's a deliberate action of some macOS component to
avoid exposing the feature to VMs.

In any case, let's just mask out SME from ID_AA64PFR1_EL1 to fix nested
virt on M4 devices.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2025-06-03 16:46:21 +01:00
Matej Hrica 22d7b61b2b Remove orphaned source files which are never used
Remove vmm_config/console.rs file whis is never used (there is no corresponding
`mod console`) and device_manager/mmio.rs which has been split into 2 versions:
device_manager/kvm/mmio.rs and device_manager/hvf/mmio.rs

Signed-off-by: Matej Hrica <mhrica@redhat.com>
2025-06-03 16:45:56 +01:00
Matej Hrica 3d08c75533 Upgrade env_logger dependency
Use newest version of env_logger. This is needed to fix a problem where output
to a pipe ignores RUST_LOG_STYLE=always option and colors don't work.

Signed-off-by: Matej Hrica <mhrica@redhat.com>
2025-06-03 16:44:40 +01:00
Matej Hrica 826ffe0c94 Declare env_logger as dependency only in the top level crate
The correct way to use the env_logger crate is to only depend on it the toplevel
aplication crate. In other crates we should just use the `log` crate facade
(which we already do).

Drop the env_logger dependency from all of our internal crates, and just keep it
in the the `libkrun` crate.

Signed-off-by: Matej Hrica <mhrica@redhat.com>
2025-06-03 16:44:40 +01:00
Matej Hrica c226f2286a chroot_vm: Support redirecting libkrun log to a pipe
Use the newer krun_init_log to support redirecting the log to a pipe.

Signed-off-by: Matej Hrica <mhrica@redhat.com>
2025-06-03 16:44:40 +01:00
Matej Hrica 03924f4d6c chroot_vm: Set default log level to "warn"
chroot_vm is meant as an example and program to showcase libkrun APIs, we should
at least show error messages by default even without RUST_LOG env variable.

Signed-off-by: Matej Hrica <mhrica@redhat.com>
2025-06-03 16:44:40 +01:00
Matej Hrica 2176075a54 Introduce a new krun_init_log() that replaces krun_set_log_level
Introduce a new krun_init_log public API function to allow for much more
detailed configuration of logging by the application. The main improvment is
is the ability to specify a file descriptor to write the log to.

Signed-off-by: Matej Hrica <mhrica@redhat.com>
2025-06-03 16:44:40 +01:00
Matej Hrica 1d54577077 examples/boot_efi+external_kernel: Make connect_to_passt const correct
The argument should be const (same as `cmdline.passt_socket_path` we pass in).

Signed-off-by: Matej Hrica <mhrica@redhat.com>
2025-05-29 13:59:40 +02:00
Matej Hrica b805f3a1a0 examples/chroot_vm: Fix connect_to_passt function
The function needs to accept socket_path as an argument, like expected at the
call site. This used to compile with older gcc, but the code was wrong.

Signed-off-by: Matej Hrica <mhrica@redhat.com>
2025-05-29 13:59:40 +02:00
Sergio Lopez 5c3ecd66c6 Bump version 1.12.2
This release is intended to simplify packaging by dropping rangemap
as a dependency and include the security fix for crossbeam-channel.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2025-05-20 09:47:19 -04:00
Sergio Lopez 25a972e33e vmm: drop use of rangemap crate
It's not packaged in Fedora and I don't think it adds enough value
to justify its addition.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2025-05-19 16:07:32 -04:00
37 changed files with 1366 additions and 993 deletions

654
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
LIBRARY_HEADER = include/libkrun.h
ABI_VERSION=1
FULL_VERSION=1.12.1
FULL_VERSION=1.13.0
INIT_SRC = init/init.c
KBS_INIT_SRC = init/tee/kbs/kbs.h \
@ -27,9 +27,6 @@ ifeq ($(SEV),1)
INIT_SRC += $(SNP_INIT_SRC)
BUILD_INIT = 0
endif
ifeq ($(GPU),1)
FEATURE_FLAGS += --features gpu
endif
ifeq ($(VIRGL_RESOURCE_MAP2),1)
FEATURE_FLAGS += --features virgl_resource_map2
endif
@ -39,12 +36,20 @@ endif
ifeq ($(NET),1)
FEATURE_FLAGS += --features net
endif
ifeq ($(EFI),1)
VARIANT = -efi
FEATURE_FLAGS := --features efi # EFI Implies blk and net
BUILD_INIT = 0
endif
ifeq ($(GPU),1)
FEATURE_FLAGS += --features gpu
endif
ifeq ($(SND),1)
FEATURE_FLAGS += --features snd
endif
ifeq ($(EFI),1)
VARIANT = -efi
FEATURE_FLAGS := --features efi,gpu
ifeq ($(NITRO),1)
VARIANT = -nitro
FEATURE_FLAGS := --features nitro
BUILD_INIT = 0
endif
@ -91,6 +96,9 @@ $(LIBRARY_RELEASE_$(OS)): $(INIT_BINARY)
ifeq ($(SEV),1)
mv target/release/libkrun.so target/release/$(KRUN_BASE_$(OS))
endif
ifeq ($(NITRO),1)
mv target/release/libkrun.so target/release/$(KRUN_BASE_$(OS))
endif
ifeq ($(OS),Darwin)
ifeq ($(EFI),1)
install_name_tool -id libkrun-efi.dylib target/release/libkrun.dylib

View File

@ -5,6 +5,7 @@ LDFLAGS_aarch64_Linux = -lkrun
LDFLAGS_arm64_Darwin = -L/opt/homebrew/lib -lkrun
LDFLAGS_sev = -lkrun-sev
LDFLAGS_efi = -L/opt/homebrew/lib -lkrun-efi
LDFLAGS_nitro = -lkrun-nitro
CFLAGS = -O2 -g -I../include
ROOTFS_DISTRO := fedora
ROOTFS_DIR = rootfs_$(ROOTFS_DISTRO)
@ -42,6 +43,9 @@ ifeq ($(OS),Darwin)
codesign --entitlements chroot_vm.entitlements --force -s - $@
endif
nitro: nitro.c
gcc -o $@ $< $(CFLAGS) $(LDFLAGS_nitro)
# Build the rootfs to be used with chroot_vm.
rootfs:
mkdir -p $(ROOTFS_DIR)
@ -50,4 +54,4 @@ rootfs:
podman rm libkrun_chroot_vm
clean:
rm -rf chroot_vm $(ROOTFS_DIR) launch-tee boot_efi external_kernel
rm -rf chroot_vm $(ROOTFS_DIR) launch-tee boot_efi external_kernel nitro

View File

@ -90,7 +90,7 @@ bool parse_cmdline(int argc, char *const argv[], struct cmdline *cmdline)
return false;
}
int connect_to_passt(char *socket_path)
int connect_to_passt(char const *socket_path)
{
struct sockaddr_un addr;
int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);

View File

@ -6,6 +6,7 @@
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -34,6 +35,8 @@ static void print_help(char *const name)
"Usage: %s [OPTIONS] NEWROOT COMMAND [COMMAND_ARGS...]\n"
"OPTIONS: \n"
" -h --help Show help\n"
" --log=PATH Write libkrun log to file or named pipe at PATH\n"
" --color-log=PATH Write libkrun log to file or named pipe at PATH, use color\n"
" --net=NET_MODE Set network mode\n"
" --passt-socket=PATH Instead of starting passt, connect to passt socket at PATH"
"NET_MODE can be either TSI (default) or PASST\n"
@ -47,6 +50,8 @@ static void print_help(char *const name)
static const struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ "log", required_argument, NULL, 'L' },
{ "color-log", required_argument, NULL, 'C' },
{ "net_mode", required_argument, NULL, 'N' },
{ "passt-socket", required_argument, NULL, 'P' },
{ NULL, 0, NULL, 0 }
@ -54,12 +59,27 @@ static const struct option long_options[] = {
struct cmdline {
bool show_help;
int log_target;
uint32_t log_style;
enum net_mode net_mode;
char const *passt_socket_path;
char const *new_root;
char *const *guest_argv;
};
bool cmdline_set_log_target(struct cmdline *cmdline, const char *arg) {
int fd = open(arg, O_WRONLY);
if (fd < 0) {
perror(arg);
return false;
}
if (cmdline->log_target > 0) {
close(cmdline->log_target);
}
cmdline->log_target = fd;
return true;
}
bool parse_cmdline(int argc, char *const argv[], struct cmdline *cmdline)
{
assert(cmdline != NULL);
@ -71,6 +91,8 @@ bool parse_cmdline(int argc, char *const argv[], struct cmdline *cmdline)
.passt_socket_path = NULL,
.new_root = NULL,
.guest_argv = NULL,
.log_target = KRUN_LOG_TARGET_DEFAULT,
.log_style = KRUN_LOG_STYLE_AUTO
};
int option_index = 0;
@ -81,6 +103,14 @@ bool parse_cmdline(int argc, char *const argv[], struct cmdline *cmdline)
case 'h':
cmdline->show_help = true;
return true;
case 'C':
cmdline->log_style = KRUN_LOG_STYLE_ALWAYS;
/* fall through */
case 'L':
if (!cmdline_set_log_target(cmdline, optarg)) {
return false;
}
break;
case 'N':
if (strcasecmp("TSI", optarg) == 0) {
cmdline->net_mode = NET_MODE_TSI;
@ -119,7 +149,7 @@ bool parse_cmdline(int argc, char *const argv[], struct cmdline *cmdline)
return false;
}
int connect_to_passt()
int connect_to_passt(char const *socket_path)
{
struct sockaddr_un addr;
int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
@ -130,7 +160,7 @@ int connect_to_passt()
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, "/tmp/passt_1.socket", sizeof(addr.sun_path) - 1);
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
if (connect(socket_fd, (const struct sockaddr *) &addr, sizeof(addr)) < 0) {
perror("Failed to bind passt socket");
@ -217,8 +247,8 @@ int main(int argc, char *const argv[])
return 0;
}
// Set the log level to "off".
err = krun_set_log_level(0);
// Set the log level to "warn".
err = krun_init_log(cmdline.log_target, KRUN_LOG_LEVEL_WARN, cmdline.log_style, 0);
if (err) {
errno = -err;
perror("Error configuring log level");

View File

@ -149,7 +149,7 @@ bool parse_cmdline(int argc, char *const argv[], struct cmdline *cmdline)
return false;
}
int connect_to_passt(char *socket_path)
int connect_to_passt(char const *socket_path)
{
struct sockaddr_un addr;
int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);

224
examples/nitro.c Normal file
View File

@ -0,0 +1,224 @@
/*
* This is an example implementing running an example AWS nitro enclave with
* libkrun.
*
* Given a nitro enclave image, run the image in a nitro enclave with 1 vCPU and
* 256 MiB of memory allocated.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <libkrun.h>
#include <getopt.h>
#include <stdbool.h>
#include <assert.h>
#include <pthread.h>
#define MAX_ARGS_LEN 4096
#ifndef MAX_PATH
#define MAX_PATH 4096
#endif
#define IPC_SOCK_PATH "/tmp/krun_nitro_example_ipc.sock"
static void print_help(char *const name)
{
fprintf(stderr,
"Usage: %s EIF_FILE [COMMAND_ARGS...]\n"
"OPTIONS: \n"
" -h --help Show help\n"
"\n"
"ENCLAVE_IMAGE: The enclave image to run\n",
name
);
}
static const struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ NULL, 0, NULL, 0 }
};
struct cmdline {
bool show_help;
const char *eif_path;
};
bool parse_cmdline(int argc, char *const argv[], struct cmdline *cmdline)
{
int c, option_index = 0;
assert(cmdline != NULL);
// set the defaults
*cmdline = (struct cmdline){
.show_help = false,
.eif_path = NULL,
};
// the '+' in optstring is a GNU extension that disables permutating argv
while ((c = getopt_long(argc, argv, "+h", long_options, &option_index)) != -1) {
switch (c) {
case 'h':
cmdline->show_help = true;
return true;
case '?':
return false;
default:
fprintf(stderr, "internal argument parsing error (returned character code 0x%x)\n", c);
return false;
}
}
if (optind < argc) {
cmdline->eif_path = argv[optind];
return true;
} else
fprintf(stderr, "Missing EIF_FILE argument");
return false;
}
void *listen_enclave_output(void *opaque)
{
int ret, fd = (int) opaque, sock, len;
char buf[512];
struct sockaddr_un client_sockaddr;
sock = accept(fd, (struct sockaddr *) &client_sockaddr, &len);
if (sock < 1)
return (void *) -1;
for (;;) {
ret = read(sock, &buf, 512);
if (ret <= 0)
break;
else if (ret < 512) {
buf[ret] = '\0';
}
printf("%s", buf);
}
}
int main(int argc, char *const argv[])
{
int ret, ctx_id, err, i, sock_fd, enable = 1;
struct cmdline cmdline;
struct sockaddr_un addr;
pthread_t thread;
if (!parse_cmdline(argc, argv, &cmdline)) {
putchar('\n');
print_help(argv[0]);
return -1;
}
if (cmdline.show_help){
print_help(argv[0]);
return 0;
}
// Set the log level to "off".
err = krun_set_log_level(0);
if (err) {
errno = -err;
perror("Error configuring log level");
return -1;
}
// Create the configuration context.
ctx_id = krun_create_ctx();
if (ctx_id < 0) {
errno = -ctx_id;
perror("Error creating configuration context");
return -1;
}
// Configure the number of vCPUs (1) and the amount of RAM (512 MiB).
if (err = krun_set_vm_config(ctx_id, 1, 512)) {
errno = -err;
perror("Error configuring the number of vCPUs and/or the amount of RAM");
return -1;
}
// Set the nitro enclave image specified on the command line.
if (err = krun_nitro_set_image(ctx_id, cmdline.eif_path,
KRUN_NITRO_IMG_TYPE_EIF)) {
errno = -err;
perror("Error configuring nitro enclave image");
return -1;
}
// Configure the nitro enclave to run in debug mode.
if (err = krun_nitro_set_start_flags(ctx_id, KRUN_NITRO_START_FLAG_DEBUG)) {
errno = -err;
perror("Error configuring nitro enclave start flags");
return -1;
}
// Create and initialize UNIX IPC socket for reading enclave output.
sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock_fd < 0) {
perror("Error creating UNIX IPC socket for enclave communication");
return -1;
}
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, IPC_SOCK_PATH);
// Listen on the socket for enclave output.
unlink(IPC_SOCK_PATH);
ret = bind(sock_fd, (struct sockaddr *) &addr, sizeof(addr));
if (ret < 0) {
perror("Error binding socket");
close(sock_fd);
exit(1);
}
ret = listen(sock_fd, 1);
if (ret < 0) {
perror("Error listening on socket");
close(sock_fd);
exit(1);
}
// Configure the IPC socket to read output from the enclave. The "port"
// argument is ignored.
if (err = krun_add_vsock_port(ctx_id, 0, IPC_SOCK_PATH)) {
close(sock_fd);
errno = -err;
perror("Error configuring enclave vsock");
return -1;
}
ret = pthread_create(&thread, NULL, listen_enclave_output,
(void *) sock_fd);
if (ret < 0) {
perror("unable to create new listener thread");
close(sock_fd);
exit(1);
}
// Start and enter the microVM. Unless there is some error while creating the microVM
// this function never returns.
if (err = krun_start_enter(ctx_id)) {
close(sock_fd);
errno = -err;
perror("Error creating the microVM");
return -1;
}
ret = pthread_join(thread, NULL);
if (ret < 0) {
perror("unable to join listener thread");
close(sock_fd);
exit(1);
}
return 0;
}

Binary file not shown.

View File

@ -26,6 +26,44 @@ extern "C" {
*/
int32_t krun_set_log_level(uint32_t level);
#define KRUN_LOG_TARGET_DEFAULT -1
#define KRUN_LOG_LEVEL_OFF 0
#define KRUN_LOG_LEVEL_ERROR 1
#define KRUN_LOG_LEVEL_WARN 2
#define KRUN_LOG_LEVEL_INFO 3
#define KRUN_LOG_LEVEL_DEBUG 4
#define KRUN_LOG_LEVEL_TRACE 5
#define KRUN_LOG_STYLE_AUTO 0
#define KRUN_LOG_STYLE_ALWAYS 1
#define KRUN_LOG_STYLE_NEVER 2
#define KRUN_LOG_OPTION_NO_ENV 1
/**
* Initializes logging for the library.
*
* Arguments:
* "target_fd" - File descriptor to write log to. Note that using a file descriptor pointing to a regular file on
* filesystem might slow down the VM.
* Use KRUN_LOG_TARGET_DEFAULT to use the default target for log output (stderr).
*
* "level" - Level is an integer specifying the level of verbosity, higher number means more verbose log.
* The log levels are described by the constants: KRUN_LOG_LEVEL_{OFF, ERROR, WARN, INFO, DEBUG, TRACE}
*
* "style" - Enable/disable usage of terminal escape sequences (to display colors)
* One of: KRUN_LOG_STYLE_{AUTO, ALWAYS, NEVER}.
*
* "options" - Bitmask of logging options, use 0 for default options.
* KRUN_LOG_OPTION_NO_ENV to disallow environment variables to override these settings.
*
* Returns:
* Zero on success or a negative error number on failure.
*/
int32_t krun_init_log(int target_fd, uint32_t level, uint32_t style, uint32_t options);
/**
* Creates a configuration context.
*
@ -585,6 +623,29 @@ int32_t krun_check_nested_virt(void);
*/
int32_t krun_split_irqchip(uint32_t ctx_id, bool enable);
#define KRUN_NITRO_IMG_TYPE_EIF 1
/**
* Configure a Nitro Enclaves image.
*
* Arguments:
* "ctx_id" - the configuration context ID.
* "image_path" - a null-terminated string representing the path of the image
* in the host.
* "image_type" - the type of enclave image being provided.
*/
int32_t krun_nitro_set_image(uint32_t ctx_id, const char *image_path,
uint32_t image_type);
#define KRUN_NITRO_START_FLAG_DEBUG (1 << 0)
/**
* Configure a Nitro Enclave's start flags.
*
* Arguments:
* "ctx_id" - the configuration context ID.
* "start_flags" - Start flags.
*/
int32_t krun_nitro_set_start_flags(uint32_t ctx_id, uint64_t start_flags);
/**
* Starts and enters the microVM with the configured parameters. The VMM will attempt to take over
* stdin/stdout to manage them on behalf of the process running inside the isolated environment,

View File

@ -12,6 +12,7 @@ efi = []
[dependencies]
libc = ">=0.2.39"
vm-memory = { version = ">=0.13", features = ["backend-mmap"] }
vmm-sys-util = ">= 0.14"
arch_gen = { path = "../arch_gen" }
smbios = { path = "../smbios" }

View File

@ -5,11 +5,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.
use std::{mem, num::TryFromIntError, result};
use std::{mem, mem::offset_of, num::TryFromIntError, result};
use super::super::get_fdt_addr;
use kvm_bindings::{
user_pt_regs, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG, KVM_REG_ARM64_SYSREG_CRM_MASK,
kvm_regs, user_pt_regs, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG, KVM_REG_ARM64_SYSREG_CRM_MASK,
KVM_REG_ARM64_SYSREG_CRM_SHIFT, KVM_REG_ARM64_SYSREG_CRN_MASK, KVM_REG_ARM64_SYSREG_CRN_SHIFT,
KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP0_SHIFT, KVM_REG_ARM64_SYSREG_OP1_MASK,
KVM_REG_ARM64_SYSREG_OP1_SHIFT, KVM_REG_ARM64_SYSREG_OP2_MASK, KVM_REG_ARM64_SYSREG_OP2_SHIFT,
@ -42,24 +42,9 @@ const PSR_D_BIT: u64 = 0x0000_0200;
// Taken from arch/arm64/kvm/inject_fault.c.
const PSTATE_FAULT_BITS_64: u64 = PSR_MODE_EL1h | PSR_A_BIT | PSR_F_BIT | PSR_I_BIT | PSR_D_BIT;
// Following are macros that help with getting the ID of a aarch64 core register.
// This is a macro that helps with getting the ID of a aarch64 core register.
// The core register are represented by the user_pt_regs structure. Look for it in
// arch/arm64/include/uapi/asm/ptrace.h.
// This macro gets the offset of a structure (i.e `str`) member (i.e `field`) without having
// an instance of that structure.
// It uses a null pointer to retrieve the offset to the field.
// Inspired by C solution: `#define offsetof(str, f) ((size_t)(&((str *)0)->f))`.
// Doing `offset__of!(user_pt_regs, pstate)` in our rust code will trigger the following:
// unsafe { &(*(0 as *const user_pt_regs)).pstate as *const _ as usize }
// The dereference expression produces an lvalue, but that lvalue is not actually read from,
// we're just doing pointer math on it, so in theory, it should safe.
macro_rules! offset__of {
($str:ty, $field:ident) => {
unsafe { &(*(std::ptr::null::<user_pt_regs>())).$field as *const _ as usize }
};
}
macro_rules! arm64_core_reg {
($reg: tt) => {
// As per `kvm_arm_copy_reg_indices`, the id of a core register can be obtained like this:
@ -87,7 +72,7 @@ macro_rules! arm64_core_reg {
KVM_REG_ARM64 as u64
| KVM_REG_SIZE_U64 as u64
| u64::from(KVM_REG_ARM_CORE)
| ((offset__of!(user_pt_regs, $reg) / mem::size_of::<u32>()) as u64)
| (((offset_of!(kvm_regs, regs) + offset_of!(user_pt_regs, $reg)) / mem::size_of::<u32>()) as u64)
};
}
@ -126,14 +111,12 @@ arm64_sys_reg!(MPIDR_EL1, 3, 0, 0, 0, 5);
/// * `mem` - Reserved DRAM for current VM.
pub fn setup_regs(vcpu: &VcpuFd, cpu_id: u8, boot_ip: u64, mem: &GuestMemoryMmap) -> Result<()> {
// Get the register index of the PSTATE (Processor State) register.
#[allow(deref_nullptr)]
vcpu.set_one_reg(arm64_core_reg!(pstate), &PSTATE_FAULT_BITS_64.to_le_bytes())
.map_err(Error::SetCoreRegister)?;
// Other vCPUs are powered off initially awaiting PSCI wakeup.
if cpu_id == 0 {
// Setting the PC (Processor Counter) to the current program address (kernel address).
#[allow(deref_nullptr)]
vcpu.set_one_reg(arm64_core_reg!(pc), &boot_ip.to_le_bytes())
.map_err(Error::SetCoreRegister)?;
@ -141,7 +124,6 @@ pub fn setup_regs(vcpu: &VcpuFd, cpu_id: u8, boot_ip: u64, mem: &GuestMemoryMmap
// "The device tree blob (dtb) must be placed on an 8-byte boundary and must
// not exceed 2 megabytes in size." -> https://www.kernel.org/doc/Documentation/arm64/booting.txt.
// We are choosing to place it the end of DRAM. See `get_fdt_addr`.
#[allow(deref_nullptr)]
vcpu.set_one_reg(arm64_core_reg!(regs), &get_fdt_addr(mem).to_le_bytes())
.map_err(Error::SetCoreRegister)?;
}

View File

@ -17,8 +17,9 @@ pub use self::macos::*;
use std::fmt::Debug;
use crate::{round_up, ArchMemoryInfo};
use crate::ArchMemoryInfo;
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryMmap};
use vmm_sys_util::align_upwards;
#[cfg(feature = "efi")]
use smbios;
@ -44,7 +45,7 @@ pub fn arch_memory_regions(
initrd_size: u64,
) -> (ArchMemoryInfo, Vec<(GuestAddress, usize)>) {
let page_size: usize = unsafe { libc::sysconf(libc::_SC_PAGESIZE).try_into().unwrap() };
let dram_size = round_up(size, page_size);
let dram_size = align_upwards!(size, page_size);
let ram_last_addr = layout::DRAM_MEM_START + (dram_size as u64);
let shm_start_addr = ((ram_last_addr / 0x4000_0000) + 1) * 0x4000_0000;
@ -97,8 +98,9 @@ pub fn get_kernel_start() -> u64 {
/// Returns the memory address where the initrd could be loaded.
pub fn initrd_load_addr(guest_mem: &GuestMemoryMmap, initrd_size: usize) -> super::Result<u64> {
let round_to_pagesize = |size| (size + (super::PAGE_SIZE - 1)) & !(super::PAGE_SIZE - 1);
match GuestAddress(get_fdt_addr(guest_mem)).checked_sub(round_to_pagesize(initrd_size) as u64) {
match GuestAddress(get_fdt_addr(guest_mem))
.checked_sub(align_upwards!(initrd_size, super::PAGE_SIZE) as u64)
{
Some(offset) => {
if guest_mem.address_in_range(offset) {
Ok(offset.raw_value())

View File

@ -48,12 +48,3 @@ pub struct InitrdConfig {
/// Default (smallest) memory page size for the supported architectures.
pub const PAGE_SIZE: usize = 4096;
pub fn round_up(size: usize, align: usize) -> usize {
let page_mask = align - 1;
(size + page_mask) & !page_mask
}
pub fn round_down(size: usize, align: usize) -> usize {
let page_mask = !(align - 1);
size & page_mask
}

View File

@ -17,12 +17,13 @@ pub mod msr;
/// Logic for configuring x86_64 registers.
pub mod regs;
use crate::{round_up, ArchMemoryInfo, InitrdConfig};
use crate::{ArchMemoryInfo, InitrdConfig};
use arch_gen::x86::bootparam::{boot_params, E820_RAM};
use vm_memory::Bytes;
use vm_memory::{
Address, ByteValued, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion,
};
use vmm_sys_util::align_upwards;
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
// trait (in this case `ByteValued`) where:
@ -73,7 +74,7 @@ pub fn arch_memory_regions(
) -> (ArchMemoryInfo, Vec<(GuestAddress, usize)>) {
let page_size: usize = unsafe { libc::sysconf(libc::_SC_PAGESIZE).try_into().unwrap() };
let size = round_up(size, page_size);
let size = align_upwards!(size, page_size);
// It's safe to cast MMIO_MEM_START to usize because it fits in a u32 variable
// (It points to an address in the 32 bit space).
@ -155,7 +156,7 @@ pub fn arch_memory_regions(
) -> (ArchMemoryInfo, Vec<(GuestAddress, usize)>) {
let page_size: usize = unsafe { libc::sysconf(libc::_SC_PAGESIZE).try_into().unwrap() };
let size = round_up(size, page_size);
let size = align_upwards!(size, page_size);
if let Some(kernel_load_addr) = kernel_load_addr {
if size < (kernel_load_addr + kernel_size as u64) as usize {
panic!("Kernel doesn't fit in RAM");

View File

@ -5,7 +5,7 @@ authors = ["Amazon Firecracker team <firecracker-devel@amazon.com>"]
edition = "2021"
[dependencies]
vmm-sys-util = "0.12.1"
vmm-sys-util = ">= 0.14"
[target.'cfg(target_os = "linux")'.dependencies]
kvm-bindings = { version = ">=0.11", features = ["fam-wrappers"] }

View File

@ -13,11 +13,11 @@ efi = ["blk", "net"]
gpu = ["rutabaga_gfx", "thiserror", "zerocopy", "zerocopy-derive"]
snd = ["pw", "thiserror"]
virgl_resource_map2 = []
nitro = []
[dependencies]
bitflags = "1.2.0"
crossbeam-channel = ">=0.5.15"
env_logger = "0.9.0"
libc = ">=0.2.39"
libloading = "0.8"
log = "0.4.0"

View File

@ -1,7 +1,7 @@
use crossbeam_channel::unbounded;
use kvm_bindings::{
kvm_enable_cap, kvm_irq_routing, kvm_irq_routing_entry, kvm_irq_routing_entry__bindgen_ty_1,
kvm_irq_routing_msi, KVM_CAP_SPLIT_IRQCHIP, KVM_IRQ_ROUTING_MSI,
kvm_enable_cap, kvm_irq_routing_entry, kvm_irq_routing_entry__bindgen_ty_1,
kvm_irq_routing_msi, KvmIrqRouting, KVM_CAP_SPLIT_IRQCHIP, KVM_IRQ_ROUTING_MSI,
};
use kvm_ioctls::{Error, VmFd};
@ -125,20 +125,10 @@ impl IoApic {
(0..IOAPIC_NUM_PINS).for_each(|i| ioapic.add_msi_route(i));
let mut irq_routing = utils::sized_vec::vec_with_array_field::<
kvm_irq_routing,
kvm_irq_routing_entry,
>(ioapic.irq_routes.len());
irq_routing[0].nr = ioapic.irq_routes.len() as u32;
irq_routing[0].flags = 0;
unsafe {
let entries_slice: &mut [kvm_irq_routing_entry] =
irq_routing[0].entries.as_mut_slice(ioapic.irq_routes.len());
entries_slice.copy_from_slice(ioapic.irq_routes.as_slice());
}
vm.set_gsi_routing(&irq_routing[0])?;
let mut routing = KvmIrqRouting::new(ioapic.irq_routes.len()).unwrap();
let routing_entires = routing.as_mut_slice();
routing_entires.copy_from_slice(ioapic.irq_routes.as_slice());
vm.set_gsi_routing(&routing)?;
Ok(ioapic)
}

View File

@ -21,7 +21,7 @@ pub mod console;
pub mod descriptor_utils;
pub mod device;
pub mod file_traits;
#[cfg(not(feature = "tee"))]
#[cfg(not(any(feature = "tee", feature = "nitro")))]
pub mod fs;
#[cfg(feature = "gpu")]
pub mod gpu;
@ -42,7 +42,7 @@ pub use self::balloon::*;
pub use self::block::{Block, CacheType};
pub use self::console::*;
pub use self::device::*;
#[cfg(not(feature = "tee"))]
#[cfg(not(any(feature = "tee", feature = "nitro")))]
pub use self::fs::*;
#[cfg(feature = "gpu")]
pub use self::gpu::*;

View File

@ -8,6 +8,5 @@ edition = "2021"
crossbeam-channel = ">=0.5.15"
libloading = "0.8"
log = "0.4.0"
env_logger = "0.9.0"
arch = { path = "../arch" }

View File

@ -88,6 +88,10 @@ const CNTHCTL_EL0PCTEN: u64 = 1 << 0;
// Trap accesses to both virtual and physical counter registers.
const CNTHCTL_EL2_BITS: u64 = CNTHCTL_EL0VCTEN | CNTHCTL_EL0PCTEN;
const AA64PFR0_EL1_EL2EN: u64 = 1 << 8;
const AA64PFR0_EL1_GIC3EN: u64 = 1 << 24;
const AA64PFR1_EL1_SMEMASK: u64 = 3 << 24;
const EC_WFX_TRAP: u64 = 0x1;
const EC_AA64_HVC: u64 = 0x16;
const EC_AA64_SMC: u64 = 0x17;
@ -399,6 +403,53 @@ impl HvfVcpu<'_> {
if ret != HV_SUCCESS {
return Err(Error::VcpuInitialRegisters);
}
// Enable EL2 and GICv3 in ID_AA64PFR0_EL1
let val: u64 = 0;
let ret = unsafe {
hv_vcpu_get_sys_reg(
self.vcpuid,
hv_sys_reg_t_HV_SYS_REG_ID_AA64PFR0_EL1,
&val as *const _ as *mut _,
)
};
if ret != HV_SUCCESS {
return Err(Error::VcpuInitialRegisters);
}
let ret = unsafe {
hv_vcpu_set_sys_reg(
self.vcpuid,
hv_sys_reg_t_HV_SYS_REG_ID_AA64PFR0_EL1,
val | AA64PFR0_EL1_EL2EN | AA64PFR0_EL1_GIC3EN,
)
};
if ret != HV_SUCCESS {
return Err(Error::VcpuInitialRegisters);
}
// If SME is enabled in ID_AA64PFR1_EL1 in the VM, the guest will
// break after enabling the MMU. Mask it out.
let val: u64 = 0;
let ret = unsafe {
hv_vcpu_get_sys_reg(
self.vcpuid,
hv_sys_reg_t_HV_SYS_REG_ID_AA64PFR1_EL1,
&val as *const _ as *mut _,
)
};
if ret != HV_SUCCESS {
return Err(Error::VcpuInitialRegisters);
}
let ret = unsafe {
hv_vcpu_set_sys_reg(
self.vcpuid,
hv_sys_reg_t_HV_SYS_REG_ID_AA64PFR1_EL1,
val & !AA64PFR1_EL1_SMEMASK,
)
};
if ret != HV_SUCCESS {
return Err(Error::VcpuInitialRegisters);
}
} else {
let ret = unsafe {
hv_vcpu_set_reg(self.vcpuid, hv_reg_t_HV_REG_CPSR, PSTATE_EL1_FAULT_BITS_64)

View File

@ -1,6 +1,6 @@
[package]
name = "libkrun"
version = "1.12.1"
version = "1.13.0"
authors = ["Sergio Lopez <slp@redhat.com>"]
edition = "2021"
build = "build.rs"
@ -14,10 +14,11 @@ efi = [ "blk", "net" ]
gpu = []
snd = []
virgl_resource_map2 = []
nitro = [ "dep:nitro", "dep:nitro-enclaves" ]
[dependencies]
crossbeam-channel = ">=0.5.15"
env_logger = "0.9.0"
env_logger = "0.11"
libc = ">=0.2.39"
libloading = "0.8"
log = "0.4.0"
@ -34,6 +35,8 @@ hvf = { path = "../hvf" }
[target.'cfg(target_os = "linux")'.dependencies]
kvm-bindings = { version = ">=0.11", features = ["fam-wrappers"] }
kvm-ioctls = ">=0.21"
nitro = { path = "../nitro", optional = true }
nitro-enclaves = { version = "0.2.0", optional = true }
vm-memory = ">=0.13"
[lib]

View File

@ -8,11 +8,12 @@ use std::env;
use std::ffi::CStr;
#[cfg(target_os = "linux")]
use std::ffi::CString;
#[cfg(all(target_arch = "x86_64", not(feature = "tee")))]
use std::fs::File;
#[cfg(target_os = "linux")]
use std::os::fd::AsRawFd;
use std::os::fd::RawFd;
use std::os::fd::{FromRawFd, RawFd};
#[cfg(feature = "nitro")]
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use std::slice;
use std::sync::atomic::{AtomicI32, Ordering};
@ -27,7 +28,7 @@ use devices::virtio::block::ImageType;
use devices::virtio::net::device::VirtioNetBackend;
#[cfg(feature = "blk")]
use devices::virtio::CacheType;
use env_logger::Env;
use env_logger::{Env, Target};
#[cfg(not(feature = "efi"))]
use libc::size_t;
use libc::{c_char, c_int};
@ -51,6 +52,12 @@ use vmm::vmm_config::machine_config::VmConfig;
use vmm::vmm_config::net::NetworkInterfaceConfig;
use vmm::vmm_config::vsock::VsockDeviceConfig;
#[cfg(feature = "nitro")]
use nitro::enclaves::NitroEnclave;
#[cfg(feature = "nitro")]
use nitro_enclaves::launch::StartFlags;
// Value returned on success. We use libc's errors otherwise.
const KRUN_SUCCESS: i32 = 0;
// Maximum number of arguments/environment variables we allow
@ -152,6 +159,10 @@ struct ContextConfig {
console_output: Option<PathBuf>,
vmm_uid: Option<libc::uid_t>,
vmm_gid: Option<libc::gid_t>,
#[cfg(feature = "nitro")]
nitro_image_path: Option<PathBuf>,
#[cfg(feature = "nitro")]
nitro_start_flags: StartFlags,
}
impl ContextConfig {
@ -296,22 +307,143 @@ impl ContextConfig {
fn set_vmm_gid(&mut self, vmm_gid: libc::gid_t) {
self.vmm_gid = Some(vmm_gid);
}
#[cfg(feature = "nitro")]
fn set_nitro_image(&mut self, image_path: PathBuf) {
self.nitro_image_path = Some(image_path);
}
#[cfg(feature = "nitro")]
fn set_nitro_start_flags(&mut self, start_flags: StartFlags) {
self.nitro_start_flags = start_flags;
}
}
#[cfg(feature = "nitro")]
impl TryFrom<ContextConfig> for NitroEnclave {
type Error = i32;
fn try_from(ctx: ContextConfig) -> Result<Self, Self::Error> {
let vm_config = ctx.vmr.vm_config();
let Some(mem_size_mib) = vm_config.mem_size_mib else {
error!("memory size not configured");
return Err(-libc::EINVAL);
};
let Some(vcpus) = vm_config.vcpu_count else {
error!("vCPU count not configured");
return Err(-libc::EINVAL);
};
let Some(image_path) = ctx.nitro_image_path else {
error!("nitro image not configured");
return Err(-libc::EINVAL);
};
let Ok(image) = File::open(&image_path) else {
error!("unable to open {}", image_path.display());
return Err(-libc::EINVAL);
};
let Some(port_map) = ctx.unix_ipc_port_map else {
error!("enclave vsock not configured");
return Err(-libc::EINVAL);
};
if port_map.len() > 1 {
error!("too many nitro vsocks detected (max 1)");
return Err(-libc::EINVAL);
}
let ipc_stream = {
let mut vec = Vec::from_iter(port_map.values());
let Some((path, _)) = vec.pop() else {
error!("enclave vsock path not found");
return Err(-libc::EINVAL);
};
UnixStream::connect(path).unwrap()
};
Ok(Self {
image,
mem_size_mib,
vcpus,
ipc_stream,
start_flags: ctx.nitro_start_flags,
})
}
}
static CTX_MAP: Lazy<Mutex<HashMap<u32, ContextConfig>>> = Lazy::new(|| Mutex::new(HashMap::new()));
static CTX_IDS: AtomicI32 = AtomicI32::new(0);
#[no_mangle]
pub extern "C" fn krun_set_log_level(level: u32) -> i32 {
let log_level = match level {
fn log_level_to_filter_str(level: u32) -> &'static str {
match level {
0 => "off",
1 => "error",
2 => "warn",
3 => "info",
4 => "debug",
_ => "trace",
}
}
#[no_mangle]
pub extern "C" fn krun_set_log_level(level: u32) -> i32 {
let filter = log_level_to_filter_str(level);
env_logger::Builder::from_env(Env::default().default_filter_or(filter)).init();
KRUN_SUCCESS
}
mod log_defs {
pub const KRUN_LOG_STYLE_AUTO: u32 = 0;
pub const KRUN_LOG_STYLE_ALWAYS: u32 = 1;
pub const KRUN_LOG_STYLE_NEVER: u32 = 2;
pub const KRUN_LOG_OPTION_NO_ENV: u32 = 1;
}
#[allow(clippy::missing_safety_doc)]
#[no_mangle]
pub unsafe extern "C" fn krun_init_log(target: RawFd, level: u32, style: u32, options: u32) -> i32 {
let target = match target {
..-1 => return -libc::EINVAL,
-1 => Target::default(),
0 /* stdin */ => return -libc::EINVAL,
1 /* stdout */ => Target::Stdout,
2 /* stderr */ => Target::Stderr,
fd => Target::Pipe(Box::new(File::from_raw_fd(fd))),
};
env_logger::Builder::from_env(Env::default().default_filter_or(log_level)).init();
let filter = log_level_to_filter_str(level);
let write_style = match style {
log_defs::KRUN_LOG_STYLE_AUTO => "auto",
log_defs::KRUN_LOG_STYLE_ALWAYS => "always",
log_defs::KRUN_LOG_STYLE_NEVER => "never",
_ => return -libc::EINVAL,
};
let use_env = match options {
0 => true,
log_defs::KRUN_LOG_OPTION_NO_ENV => false,
_ => return -libc::EINVAL,
};
let mut builder = if use_env {
env_logger::Builder::from_env(
Env::new()
.default_filter_or(filter)
.default_write_style_or(write_style),
)
} else {
let mut builder = env_logger::Builder::new();
builder.parse_filters(filter).parse_write_style(write_style);
builder
};
builder.target(target).init();
KRUN_SUCCESS
}
@ -930,6 +1062,11 @@ pub unsafe extern "C" fn krun_add_vsock_port2(
c_filepath: *const c_char,
listen: bool,
) -> i32 {
#[cfg(feature = "nitro")]
if listen {
return -libc::EINVAL;
}
let filepath = match CStr::from_ptr(c_filepath).to_str() {
Ok(f) => PathBuf::from(f.to_string()),
Err(_) => return -libc::EINVAL,
@ -1346,7 +1483,52 @@ pub extern "C" fn krun_setgid(ctx_id: u32, gid: libc::gid_t) -> i32 {
KRUN_SUCCESS
}
#[cfg(feature = "nitro")]
#[allow(clippy::missing_safety_doc)]
#[no_mangle]
pub unsafe extern "C" fn krun_nitro_set_image(ctx_id: u32, c_image_filepath: *const c_char) -> i32 {
let filepath = match CStr::from_ptr(c_image_filepath).to_str() {
Ok(f) => PathBuf::from(f.to_string()),
Err(_) => return -libc::EINVAL,
};
match CTX_MAP.lock().unwrap().entry(ctx_id) {
Entry::Occupied(mut ctx_cfg) => {
let cfg = ctx_cfg.get_mut();
cfg.set_nitro_image(filepath);
}
Entry::Vacant(_) => return -libc::ENOENT,
}
KRUN_SUCCESS
}
#[cfg(feature = "nitro")]
#[allow(clippy::missing_safety_doc)]
#[no_mangle]
pub unsafe extern "C" fn krun_nitro_set_start_flags(ctx_id: u32, start_flags: u64) -> i32 {
let mut flags = StartFlags::empty();
// Only debug mode is supported at the moment. To avoid doing conversion and
// checking if the "start_flags" argument is valid, set the flags to debug mode
// if the "start_flags" argument is greater than zero.
if start_flags > 0 {
flags |= StartFlags::DEBUG;
}
match CTX_MAP.lock().unwrap().entry(ctx_id) {
Entry::Occupied(mut ctx_cfg) => {
let cfg = ctx_cfg.get_mut();
cfg.set_nitro_start_flags(flags);
}
Entry::Vacant(_) => return -libc::ENOENT,
}
KRUN_SUCCESS
}
#[no_mangle]
#[allow(unreachable_code)]
pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 {
#[cfg(target_os = "linux")]
{
@ -1357,6 +1539,9 @@ pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 {
unsafe { libc::prctl(libc::PR_SET_NAME, prname.as_ptr()) };
}
#[cfg(feature = "nitro")]
return krun_start_enter_nitro(ctx_id);
let mut event_manager = match EventManager::new() {
Ok(em) => em,
Err(e) => {
@ -1529,3 +1714,23 @@ pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 {
}
}
}
#[cfg(feature = "nitro")]
#[no_mangle]
fn krun_start_enter_nitro(ctx_id: u32) -> i32 {
let ctx_cfg = match CTX_MAP.lock().unwrap().remove(&ctx_id) {
Some(ctx_cfg) => ctx_cfg,
None => return -libc::ENOENT,
};
let Ok(mut enclave) = NitroEnclave::try_from(ctx_cfg) else {
return -libc::EINVAL;
};
if let Err(e) = enclave.run() {
error!("Error running nitro enclave: {e}");
return -libc::EINVAL;
}
KRUN_SUCCESS
}

15
src/nitro/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "nitro"
version = "0.1.0"
edition = "2021"
[features]
nitro = []
[dependencies]
libc = "0.2.171"
nix = { version = "0.26.0", features = ["ioctl", "poll"] }
vsock = "0.5.1"
[target.'cfg(target_os = "linux")'.dependencies]
nitro-enclaves = "0.2.0"

171
src/nitro/src/enclaves.rs Normal file
View File

@ -0,0 +1,171 @@
// SPDX-License-Identifier: Apache-2.0
use super::error::NitroError;
use nitro_enclaves::{
launch::{ImageType, Launcher, MemoryInfo, PollTimeout, StartFlags},
Device,
};
use nix::{
poll::{poll, PollFd, PollFlags},
sys::{
socket::{connect, socket, AddressFamily, SockFlag, SockType, VsockAddr as NixVsockAddr},
time::{TimeVal, TimeValLike},
},
unistd::read,
};
use std::{
fs::File,
io::{Read, Write},
os::{
fd::{AsRawFd, RawFd},
unix::net::UnixStream,
},
};
use vsock::{VsockAddr, VsockListener};
type Result<T> = std::result::Result<T, NitroError>;
const ENCLAVE_READY_VSOCK_PORT: u32 = 9000;
const CID_TO_CONSOLE_PORT_OFFSET: u32 = 10000;
const VMADDR_CID_PARENT: u32 = 3;
const VMADDR_CID_HYPERVISOR: u32 = 0;
const SO_VM_SOCKETS_CONNECT_TIMEOUT: i32 = 6;
const HEART_BEAT: u8 = 0xb7;
/// Nitro Enclave data.
pub struct NitroEnclave {
/// Enclave image.
pub image: File,
/// Amount of RAM (in MiB).
pub mem_size_mib: usize,
/// Number of vCPUs.
pub vcpus: u8,
/// Path of vsock for initial enclave communication.
pub ipc_stream: UnixStream,
/// Enclave start flags.
pub start_flags: StartFlags,
}
impl NitroEnclave {
/// Run the enclave.
pub fn run(&mut self) -> Result<()> {
let device = Device::open().map_err(NitroError::DeviceOpen)?;
let mut launcher = Launcher::new(&device).map_err(NitroError::VmCreate)?;
let mem = MemoryInfo::new(ImageType::Eif(&mut self.image), self.mem_size_mib);
launcher.set_memory(mem).map_err(NitroError::VmMemorySet)?;
for _ in 0..self.vcpus {
launcher.add_vcpu(None).map_err(NitroError::VcpuAdd)?;
}
let sockaddr = VsockAddr::new(VMADDR_CID_PARENT, ENCLAVE_READY_VSOCK_PORT);
let listener = VsockListener::bind(&sockaddr).map_err(NitroError::HeartbeatBind)?;
let cid = launcher
.start(self.start_flags, None)
.map_err(NitroError::VmStart)?;
// Safe to unwrap.
let cid: u32 = cid.try_into().unwrap();
let poll_timeout = PollTimeout::try_from((&self.image, self.mem_size_mib << 20))
.map_err(NitroError::PollTimeoutCalculate)?;
enclave_check(listener, poll_timeout.into(), cid)?;
self.listen(VMADDR_CID_HYPERVISOR, cid + CID_TO_CONSOLE_PORT_OFFSET)?;
Ok(())
}
fn listen(&mut self, cid: u32, port: u32) -> Result<()> {
let socket_fd = socket(
AddressFamily::Vsock,
SockType::Stream,
SockFlag::empty(),
None,
)
.map_err(|_| NitroError::VsockCreate)?;
let sockaddr = NixVsockAddr::new(cid, port);
vsock_timeout(socket_fd)?;
connect(socket_fd, &sockaddr).map_err(|_| NitroError::VsockConnect)?;
let mut buf = [0u8; 512];
loop {
// Read debug output from vsock.
if let Ok(sz) = read(socket_fd, &mut buf) {
// If there is enclave debug output read, write it to the IPC socket.
if sz > 0 {
self.ipc_stream
.write_all(&buf[..sz])
.map_err(NitroError::IpcWrite)?;
continue;
}
}
break;
}
Ok(())
}
}
fn enclave_check(listener: VsockListener, poll_timeout_ms: libc::c_int, cid: u32) -> Result<()> {
let mut poll_fds = [PollFd::new(listener.as_raw_fd(), PollFlags::POLLIN)];
let result = poll(&mut poll_fds, poll_timeout_ms);
if result == Ok(0) {
return Err(NitroError::PollNoSelectedEvents);
} else if result != Ok(1) {
return Err(NitroError::PollMoreThanOneSelectedEvent);
}
let mut stream = listener.accept().map_err(NitroError::HeartbeatAccept)?;
let mut buf = [0u8];
let bytes = stream.0.read(&mut buf).map_err(NitroError::HeartbeatRead)?;
if bytes != 1 || buf[0] != HEART_BEAT {
return Err(NitroError::EnclaveHeartbeatNotDetected);
}
stream
.0
.write_all(&buf)
.map_err(NitroError::HeartbeatWrite)?;
if stream.1.cid() != cid {
return Err(NitroError::HeartbeatCidMismatch);
}
Ok(())
}
fn vsock_timeout(socket_fd: RawFd) -> Result<()> {
// Set the timeout to 20 seconds.
let timeval = TimeVal::milliseconds(20000);
let ret = unsafe {
libc::setsockopt(
socket_fd,
libc::AF_VSOCK,
SO_VM_SOCKETS_CONNECT_TIMEOUT,
&timeval as *const _ as *const libc::c_void,
size_of::<TimeVal>() as u32,
)
};
if ret != 0 {
return Err(NitroError::VsockSetTimeout);
}
Ok(())
}

71
src/nitro/src/error.rs Normal file
View File

@ -0,0 +1,71 @@
// SPDX-License-Identifier: Apache-2.0
use nitro_enclaves::launch::LaunchError;
use std::{fmt, io};
#[derive(Debug)]
pub enum NitroError {
DeviceOpen(io::Error),
VmCreate(LaunchError),
VmMemorySet(LaunchError),
VcpuAdd(LaunchError),
HeartbeatAccept(io::Error),
HeartbeatBind(io::Error),
HeartbeatRead(io::Error),
HeartbeatWrite(io::Error),
VmStart(LaunchError),
PollTimeoutCalculate(LaunchError),
PollNoSelectedEvents,
PollMoreThanOneSelectedEvent,
EnclaveHeartbeatNotDetected,
HeartbeatCidMismatch,
VsockCreate,
VsockSetTimeout,
VsockConnect,
IpcWrite(io::Error),
}
impl fmt::Display for NitroError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = match self {
NitroError::DeviceOpen(e) => format!("unable to open nitro enclaves device: {e}"),
NitroError::VmCreate(e) => format!("unable to create enclave VM: {e}"),
NitroError::VmMemorySet(e) => format!("unable to set enclave memory regions: {e}"),
NitroError::VcpuAdd(e) => format!("unable to add vCPU to enclave: {e}"),
NitroError::HeartbeatAccept(e) => {
format!("unable to accept enclave heartbeat vsock: {e}")
}
NitroError::HeartbeatBind(e) => {
format!("unable to bind to enclave hearbeat vsock: {e}")
}
NitroError::HeartbeatRead(e) => format!("unable to read enclave hearbeat vsock: {e}"),
NitroError::HeartbeatWrite(e) => {
format!("unable to write to enclave heartbeat vsock: {e}")
}
NitroError::VmStart(e) => format!("unable to start enclave: {e}"),
NitroError::PollTimeoutCalculate(e) => {
format!("unable to calculate vsock poll timeout: {e}")
}
NitroError::PollNoSelectedEvents => {
"no selected poll fds for heartbeat vsock found".to_string()
}
NitroError::PollMoreThanOneSelectedEvent => {
"more than one selected pollfd for heartbeat vsock found".to_string()
}
NitroError::EnclaveHeartbeatNotDetected => {
"enclave heartbeat message not detected".to_string()
}
NitroError::HeartbeatCidMismatch => "enclave heartbeat vsock CID mismatch".to_string(),
NitroError::VsockCreate => "unable to create enclave vsock".to_string(),
NitroError::VsockSetTimeout => {
"unable to set poll timeout for enclave vsock".to_string()
}
NitroError::VsockConnect => "unable to connect to enclave vsock".to_string(),
NitroError::IpcWrite(e) => {
format!("unable to write enclave vsock data to UNIX IPC socket: {e}")
}
};
write!(f, "{}", msg)
}
}

5
src/nitro/src/lib.rs Normal file
View File

@ -0,0 +1,5 @@
#[cfg(feature = "nitro")]
pub mod enclaves;
#[cfg(feature = "nitro")]
mod error;

View File

@ -26,6 +26,7 @@ remain = "0.2"
thiserror = "1.0.23"
zerocopy = "0.6"
log = "0.4"
vmm-sys-util = ">=0.14"
[target.'cfg(unix)'.dependencies]
nix = "0.26.1"

View File

@ -14,6 +14,7 @@ use nix::sys::memfd::MemFdCreateFlag;
use nix::unistd::ftruncate;
use nix::unistd::sysconf;
use nix::unistd::SysconfVar;
use vmm_sys_util::align_upwards;
use crate::rutabaga_os::descriptor::AsRawDescriptor;
use crate::rutabaga_os::descriptor::IntoRawDescriptor;
@ -72,8 +73,7 @@ impl IntoRawDescriptor for SharedMemory {
pub fn round_up_to_page_size(v: u64) -> RutabagaResult<u64> {
let page_size_opt = sysconf(SysconfVar::PAGE_SIZE)?;
if let Some(page_size) = page_size_opt {
let page_mask = (page_size - 1) as u64;
let aligned_size = (v + page_mask) & !page_mask;
let aligned_size = align_upwards!(v, page_size as u64);
Ok(aligned_size)
} else {
Err(RutabagaError::SpecViolation("no page size"))

View File

@ -6,10 +6,9 @@ edition = "2021"
[dependencies]
bitflags = "1.2.0"
env_logger = "0.9.0"
libc = ">=0.2.85"
log = "0.4.0"
vmm-sys-util = "0.12.1"
vmm-sys-util = ">= 0.14"
crossbeam-channel = ">=0.5.15"
[target.'cfg(target_os = "linux")'.dependencies]

View File

@ -12,16 +12,16 @@ blk = []
efi = [ "blk", "net" ]
gpu = []
snd = []
nitro = []
[dependencies]
crossbeam-channel = ">=0.5.15"
env_logger = "0.9.0"
flate2 = "1.0.35"
libc = ">=0.2.39"
linux-loader = { version = "0.13.0", features = ["bzimage", "elf", "pe"] }
log = "0.4.0"
vm-memory = { version = ">=0.13", features = ["backend-mmap"] }
rangemap = "1.5.1"
vmm-sys-util = ">=0.14"
arch = { path = "../arch" }
devices = { path = "../devices" }
@ -31,7 +31,7 @@ polly = { path = "../polly" }
# Dependencies for amd-sev
codicon = { version = "3.0.0", optional = true }
kbs-types = { version = "0.8.0", features = ["tee-sev", "tee-snp"], optional = true }
kbs-types = { version = "0.11.0", features = ["tee-snp"], optional = true }
procfs = { version = "0.12", optional = true }
rdrand = { version = "^0.8", optional = true }
serde = { version = "1.0.125", optional = true }
@ -51,6 +51,3 @@ kvm-ioctls = ">=0.21"
[target.'cfg(target_os = "macos")'.dependencies]
hvf = { path = "../hvf" }
[dev-dependencies]
vmm-sys-util = "0.12.1"

View File

@ -55,7 +55,7 @@ use crate::terminal::term_set_raw_mode;
#[cfg(feature = "blk")]
use crate::vmm_config::block::BlockBuilder;
use crate::vmm_config::boot_source::DEFAULT_KERNEL_CMDLINE;
#[cfg(not(feature = "tee"))]
#[cfg(not(any(feature = "tee", feature = "nitro")))]
use crate::vmm_config::fs::FsDeviceConfig;
#[cfg(target_os = "linux")]
use crate::vstate::KvmContext;
@ -64,7 +64,7 @@ use crate::vstate::MeasuredRegion;
use crate::vstate::{Error as VstateError, Vcpu, VcpuConfig, Vm};
use arch::{ArchMemoryInfo, InitrdConfig};
use device_manager::shm::ShmManager;
#[cfg(not(feature = "tee"))]
#[cfg(not(any(feature = "tee", feature = "nitro")))]
use devices::virtio::{fs::ExportTable, VirtioShmRegion};
use flate2::read::GzDecoder;
#[cfg(feature = "tee")]
@ -78,12 +78,14 @@ use utils::eventfd::EventFd;
use utils::worker_message::WorkerMessage;
#[cfg(all(target_arch = "x86_64", not(feature = "efi"), not(feature = "tee")))]
use vm_memory::mmap::MmapRegion;
#[cfg(not(feature = "tee"))]
#[cfg(not(any(feature = "tee", feature = "nitro")))]
use vm_memory::Address;
use vm_memory::Bytes;
#[cfg(not(feature = "nitro"))]
use vm_memory::GuestMemory;
#[cfg(all(target_arch = "x86_64", not(feature = "tee")))]
use vm_memory::GuestRegionMmap;
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryMmap};
use vm_memory::{GuestAddress, GuestMemoryMmap};
#[cfg(feature = "efi")]
static EDK2_BINARY: &[u8] = include_bytes!("../../../edk2/KRUN_EFI.silent.fd");
@ -784,7 +786,7 @@ pub fn build_microvm(
vm_resources.console_output.clone(),
)?;
#[cfg(not(feature = "tee"))]
#[cfg(not(any(feature = "tee", feature = "nitro")))]
let export_table: Option<ExportTable> = if cfg!(feature = "gpu") {
Some(Default::default())
} else {
@ -805,8 +807,7 @@ pub fn build_microvm(
_sender.clone(),
)?;
}
#[cfg(not(feature = "tee"))]
#[cfg(not(any(feature = "tee", feature = "nitro")))]
attach_fs_devices(
&mut vmm,
&vm_resources.fs,
@ -1579,7 +1580,7 @@ fn attach_mmio_device(
Ok(())
}
#[cfg(not(feature = "tee"))]
#[cfg(not(any(feature = "tee", feature = "nitro")))]
fn attach_fs_devices(
vmm: &mut Vmm,
fs_devs: &[FsDeviceConfig],

View File

@ -1,553 +0,0 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::{fmt, io};
#[cfg(target_arch = "aarch64")]
use arch::aarch64::DeviceInfoForFDT;
use arch::DeviceType;
use devices;
use devices::BusDevice;
use kernel::cmdline as kernel_cmdline;
use kvm_ioctls::{IoEventAddress, VmFd};
#[cfg(target_arch = "aarch64")]
use utils::eventfd::EventFd;
/// Errors for MMIO device manager.
#[derive(Debug)]
pub enum Error {
/// Failed to perform an operation on the bus.
BusError(devices::BusError),
/// Appending to kernel command line failed.
Cmdline(kernel_cmdline::Error),
/// Failure in creating or cloning an event fd.
EventFd(io::Error),
/// No more IRQs are available.
IrqsExhausted,
/// Registering an IO Event failed.
RegisterIoEvent(kvm_ioctls::Error),
/// Registering an IRQ FD failed.
RegisterIrqFd(kvm_ioctls::Error),
/// The device couldn't be found
DeviceNotFound,
/// Failed to update the mmio device.
UpdateFailed,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::BusError(ref e) => write!(f, "failed to perform bus operation: {}", e),
Error::Cmdline(ref e) => {
write!(f, "unable to add device to kernel command line: {}", e)
}
Error::EventFd(ref e) => write!(f, "failed to create or clone event descriptor: {}", e),
Error::IrqsExhausted => write!(f, "no more IRQs are available"),
Error::RegisterIoEvent(ref e) => write!(f, "failed to register IO event: {}", e),
Error::RegisterIrqFd(ref e) => write!(f, "failed to register irqfd: {}", e),
Error::DeviceNotFound => write!(f, "the device couldn't be found"),
Error::UpdateFailed => write!(f, "failed to update the mmio device"),
}
}
}
type Result<T> = ::std::result::Result<T, Error>;
/// This represents the size of the mmio device specified to the kernel as a cmdline option
/// It has to be larger than 0x100 (the offset where the configuration space starts from
/// the beginning of the memory mapped device registers) + the size of the configuration space
/// Currently hardcoded to 4K.
const MMIO_LEN: u64 = 0x1000;
/// Manages the complexities of registering a MMIO device.
pub struct MMIODeviceManager {
pub bus: devices::Bus,
mmio_base: u64,
irq: u32,
last_irq: u32,
id_to_dev_info: HashMap<(DeviceType, String), MMIODeviceInfo>,
}
impl MMIODeviceManager {
/// Create a new DeviceManager handling mmio devices (virtio net, block).
pub fn new(mmio_base: &mut u64, irq_interval: (u32, u32)) -> MMIODeviceManager {
if cfg!(target_arch = "aarch64") {
*mmio_base += MMIO_LEN;
}
MMIODeviceManager {
mmio_base: *mmio_base,
irq: irq_interval.0,
last_irq: irq_interval.1,
bus: devices::Bus::new(),
id_to_dev_info: HashMap::new(),
}
}
/// Register an already created MMIO device to be used via MMIO transport.
pub fn register_mmio_device(
&mut self,
vm: &VmFd,
mmio_device: devices::virtio::MmioTransport,
type_id: u32,
device_id: String,
) -> Result<(u64, u32)> {
if self.irq > self.last_irq {
return Err(Error::IrqsExhausted);
}
for (i, queue_evt) in mmio_device
.locked_device()
.queue_events()
.iter()
.enumerate()
{
let io_addr = IoEventAddress::Mmio(
self.mmio_base + u64::from(devices::virtio::NOTIFY_REG_OFFSET),
);
vm.register_ioevent(queue_evt, &io_addr, i as u32)
.map_err(Error::RegisterIoEvent)?;
}
vm.register_irqfd(mmio_device.locked_device().interrupt_evt(), self.irq)
.map_err(Error::RegisterIrqFd)?;
self.bus
.insert(Arc::new(Mutex::new(mmio_device)), self.mmio_base, MMIO_LEN)
.map_err(Error::BusError)?;
let ret = (self.mmio_base, self.irq);
self.id_to_dev_info.insert(
(DeviceType::Virtio(type_id), device_id),
MMIODeviceInfo {
addr: self.mmio_base,
len: MMIO_LEN,
irq: self.irq,
},
);
self.mmio_base += MMIO_LEN;
self.irq += 1;
Ok(ret)
}
/// Append a registered MMIO device to the kernel cmdline.
#[cfg(target_arch = "x86_64")]
pub fn add_device_to_cmdline(
&mut self,
cmdline: &mut kernel_cmdline::Cmdline,
mmio_base: u64,
irq: u32,
) -> Result<()> {
// as per doc, [virtio_mmio.]device=<size>@<baseaddr>:<irq> needs to be appended
// to kernel commandline for virtio mmio devices to get recognized
// the size parameter has to be transformed to KiB, so dividing hexadecimal value in
// bytes to 1024; further, the '{}' formatting rust construct will automatically
// transform it to decimal
cmdline
.insert(
"virtio_mmio.device",
&format!("{}K@0x{:08x}:{}", MMIO_LEN / 1024, mmio_base, irq),
)
.map_err(Error::Cmdline)
}
#[cfg(target_arch = "aarch64")]
/// Register an early console at some MMIO address.
pub fn register_mmio_serial(
&mut self,
vm: &VmFd,
cmdline: &mut kernel_cmdline::Cmdline,
serial: Arc<Mutex<devices::legacy::Serial>>,
) -> Result<()> {
if self.irq > self.last_irq {
return Err(Error::IrqsExhausted);
}
vm.register_irqfd(&serial.lock().unwrap().interrupt_evt(), self.irq)
.map_err(Error::RegisterIrqFd)?;
self.bus
.insert(serial, self.mmio_base, MMIO_LEN)
.map_err(|err| Error::BusError(err))?;
cmdline
.insert("earlycon", &format!("uart,mmio,0x{:08x}", self.mmio_base))
.map_err(Error::Cmdline)?;
let ret = self.mmio_base;
self.id_to_dev_info.insert(
(DeviceType::Serial, DeviceType::Serial.to_string()),
MMIODeviceInfo {
addr: ret,
len: MMIO_LEN,
irq: self.irq,
},
);
self.mmio_base += MMIO_LEN;
self.irq += 1;
Ok(())
}
#[cfg(target_arch = "aarch64")]
/// Register a MMIO RTC device.
pub fn register_mmio_rtc(&mut self, vm: &VmFd) -> Result<()> {
if self.irq > self.last_irq {
return Err(Error::IrqsExhausted);
}
// Attaching the RTC device.
let rtc_evt = EventFd::new(utils::eventfd::EFD_NONBLOCK).map_err(Error::EventFd)?;
let device = devices::legacy::RTC::new(rtc_evt.try_clone().map_err(Error::EventFd)?);
vm.register_irqfd(&rtc_evt, self.irq)
.map_err(Error::RegisterIrqFd)?;
self.bus
.insert(Arc::new(Mutex::new(device)), self.mmio_base, MMIO_LEN)
.map_err(|err| Error::BusError(err))?;
let ret = self.mmio_base;
self.id_to_dev_info.insert(
(DeviceType::RTC, "rtc".to_string()),
MMIODeviceInfo {
addr: ret,
len: MMIO_LEN,
irq: self.irq,
},
);
self.mmio_base += MMIO_LEN;
self.irq += 1;
Ok(())
}
#[cfg(target_arch = "aarch64")]
/// Gets the information of the devices registered up to some point in time.
pub fn get_device_info(&self) -> &HashMap<(DeviceType, String), MMIODeviceInfo> {
&self.id_to_dev_info
}
/// Gets the the specified device.
pub fn get_device(
&self,
device_type: DeviceType,
device_id: &str,
) -> Option<&Mutex<dyn BusDevice>> {
if let Some(dev_info) = self
.id_to_dev_info
.get(&(device_type, device_id.to_string()))
{
if let Some((_, device)) = self.bus.get_device(dev_info.addr) {
return Some(device);
}
}
None
}
}
/// Private structure for storing information about the MMIO device registered at some address on the bus.
#[derive(Clone, Debug)]
pub struct MMIODeviceInfo {
addr: u64,
irq: u32,
len: u64,
}
#[cfg(target_arch = "aarch64")]
impl DeviceInfoForFDT for MMIODeviceInfo {
fn addr(&self) -> u64 {
self.addr
}
fn irq(&self) -> u32 {
self.irq
}
fn length(&self) -> u64 {
self.len
}
}
#[cfg(test)]
mod tests {
use super::super::super::builder;
use super::*;
use arch;
use devices::virtio::{ActivateResult, Queue, VirtioDevice};
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
use utils::errno;
use utils::eventfd::EventFd;
use vm_memory::{GuestAddress, GuestMemoryMmap};
const QUEUE_SIZES: &[u16] = &[64];
impl MMIODeviceManager {
fn register_virtio_device(
&mut self,
vm: &VmFd,
guest_mem: GuestMemoryMmap,
device: Arc<Mutex<dyn devices::virtio::VirtioDevice>>,
cmdline: &mut kernel_cmdline::Cmdline,
type_id: u32,
device_id: &str,
) -> Result<u64> {
let mmio_device = devices::virtio::MmioTransport::new(guest_mem, device);
let (mmio_base, _irq) =
self.register_mmio_device(vm, mmio_device, type_id, device_id.to_string())?;
#[cfg(target_arch = "x86_64")]
self.add_device_to_cmdline(cmdline, mmio_base, _irq)?;
Ok(mmio_base)
}
}
#[allow(dead_code)]
struct DummyDevice {
dummy: u32,
queues: Vec<Queue>,
queue_evts: [EventFd; 1],
interrupt_evt: EventFd,
}
impl DummyDevice {
pub fn new() -> Self {
DummyDevice {
dummy: 0,
queues: QUEUE_SIZES.iter().map(|&s| Queue::new(s)).collect(),
queue_evts: [EventFd::new(utils::eventfd::EFD_NONBLOCK).expect("cannot create eventFD")],
interrupt_evt: EventFd::new(utils::eventfd::EFD_NONBLOCK).expect("cannot create eventFD"),
}
}
}
impl devices::virtio::VirtioDevice for DummyDevice {
fn avail_features(&self) -> u64 {
0
}
fn acked_features(&self) -> u64 {
0
}
fn set_acked_features(&mut self, _: u64) {}
fn device_type(&self) -> u32 {
0
}
fn queues(&self) -> &[Queue] {
&self.queues
}
fn queues_mut(&mut self) -> &mut [Queue] {
&mut self.queues
}
fn queue_events(&self) -> &[EventFd] {
&self.queue_evts
}
fn interrupt_evt(&self) -> &EventFd {
&self.interrupt_evt
}
fn interrupt_status(&self) -> Arc<AtomicUsize> {
Arc::new(AtomicUsize::new(0))
}
fn ack_features_by_page(&mut self, page: u32, value: u32) {
let _ = page;
let _ = value;
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
let _ = offset;
let _ = data;
}
fn write_config(&mut self, offset: u64, data: &[u8]) {
let _ = offset;
let _ = data;
}
fn activate(&mut self, _: GuestMemoryMmap) -> ActivateResult {
Ok(())
}
fn is_activated(&self) -> bool {
false
}
}
#[test]
fn test_register_virtio_device() {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x1000);
let guest_mem =
GuestMemoryMmap::from_ranges(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]).unwrap();
let mut vm = builder::setup_kvm_vm(&guest_mem).unwrap();
let mut device_manager =
MMIODeviceManager::new(&mut 0xd000_0000, (arch::IRQ_BASE, arch::IRQ_MAX));
let mut cmdline = kernel_cmdline::Cmdline::new(4096);
let dummy = Arc::new(Mutex::new(DummyDevice::new()));
#[cfg(target_arch = "x86_64")]
assert!(builder::setup_interrupt_controller(&mut vm).is_ok());
#[cfg(target_arch = "aarch64")]
assert!(builder::setup_interrupt_controller(&mut vm, 1).is_ok());
assert!(device_manager
.register_virtio_device(vm.fd(), guest_mem, dummy, &mut cmdline, 0, "dummy")
.is_ok());
}
#[test]
fn test_register_too_many_devices() {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x1000);
let guest_mem =
GuestMemoryMmap::from_ranges(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]).unwrap();
let mut vm = builder::setup_kvm_vm(&guest_mem).unwrap();
let mut device_manager =
MMIODeviceManager::new(&mut 0xd000_0000, (arch::IRQ_BASE, arch::IRQ_MAX));
let mut cmdline = kernel_cmdline::Cmdline::new(4096);
#[cfg(target_arch = "x86_64")]
assert!(builder::setup_interrupt_controller(&mut vm).is_ok());
#[cfg(target_arch = "aarch64")]
assert!(builder::setup_interrupt_controller(&mut vm, 1).is_ok());
for _i in arch::IRQ_BASE..=arch::IRQ_MAX {
device_manager
.register_virtio_device(
vm.fd(),
guest_mem.clone(),
Arc::new(Mutex::new(DummyDevice::new())),
&mut cmdline,
0,
"dummy1",
)
.unwrap();
}
assert_eq!(
format!(
"{}",
device_manager
.register_virtio_device(
vm.fd(),
guest_mem,
Arc::new(Mutex::new(DummyDevice::new())),
&mut cmdline,
0,
"dummy2"
)
.unwrap_err()
),
"no more IRQs are available".to_string()
);
}
#[test]
fn test_dummy_device() {
let dummy = DummyDevice::new();
assert_eq!(dummy.device_type(), 0);
assert_eq!(dummy.queues().len(), QUEUE_SIZES.len());
}
#[test]
fn test_error_messages() {
let device_manager =
MMIODeviceManager::new(&mut 0xd000_0000, (arch::IRQ_BASE, arch::IRQ_MAX));
let mut cmdline = kernel_cmdline::Cmdline::new(4096);
let e = Error::Cmdline(
cmdline
.insert(
"virtio_mmio=device",
&format!(
"{}K@0x{:08x}:{}",
MMIO_LEN / 1024,
device_manager.mmio_base,
device_manager.irq
),
)
.unwrap_err(),
);
assert_eq!(
format!("{}", e),
format!(
"unable to add device to kernel command line: {}",
kernel_cmdline::Error::HasEquals
),
);
assert_eq!(
format!("{}", Error::UpdateFailed),
"failed to update the mmio device"
);
assert_eq!(
format!("{}", Error::BusError(devices::BusError::Overlap)),
format!(
"failed to perform bus operation: {}",
devices::BusError::Overlap
)
);
assert_eq!(
format!("{}", Error::IrqsExhausted),
"no more IRQs are available"
);
assert_eq!(
format!("{}", Error::RegisterIoEvent(errno::Error::new(0))),
format!("failed to register IO event: {}", errno::Error::new(0))
);
assert_eq!(
format!("{}", Error::RegisterIrqFd(errno::Error::new(0))),
format!("failed to register irqfd: {}", errno::Error::new(0))
);
}
#[test]
fn test_device_info() {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x1000);
let guest_mem =
GuestMemoryMmap::from_ranges(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]).unwrap();
let vm = builder::setup_kvm_vm(&guest_mem).unwrap();
let mut device_manager =
MMIODeviceManager::new(&mut 0xd000_0000, (arch::IRQ_BASE, arch::IRQ_MAX));
let mut cmdline = kernel_cmdline::Cmdline::new(4096);
let dummy = Arc::new(Mutex::new(DummyDevice::new()));
let type_id = 0;
let id = String::from("foo");
if let Ok(addr) = device_manager.register_virtio_device(
vm.fd(),
guest_mem,
dummy,
&mut cmdline,
type_id,
&id,
) {
assert!(device_manager
.get_device(DeviceType::Virtio(type_id), &id)
.is_some());
assert_eq!(
addr,
device_manager.id_to_dev_info[&(DeviceType::Virtio(type_id), id.clone())].addr
);
assert_eq!(
arch::IRQ_BASE,
device_manager.id_to_dev_info[&(DeviceType::Virtio(type_id), id.clone())].irq
);
}
let id = "bar";
assert!(device_manager
.get_device(DeviceType::Virtio(type_id), &id)
.is_none());
}
}

View File

@ -1,7 +1,8 @@
use std::collections::BTreeMap;
use arch::{round_up, ArchMemoryInfo};
use arch::ArchMemoryInfo;
use vm_memory::GuestAddress;
use vmm_sys_util::align_upwards;
#[derive(Debug)]
pub enum Error {
@ -46,7 +47,7 @@ impl ShmManager {
regions
}
#[cfg(not(feature = "tee"))]
#[cfg(not(any(feature = "tee", feature = "nitro")))]
pub fn fs_region(&self, index: usize) -> Option<&ShmRegion> {
self.fs_regions.get(&index)
}
@ -57,7 +58,7 @@ impl ShmManager {
}
fn create_region(&mut self, size: usize) -> Result<ShmRegion, Error> {
let size = round_up(size, self.page_size);
let size = align_upwards!(size, self.page_size);
let region = ShmRegion {
guest_addr: GuestAddress(self.next_guest_addr),

View File

@ -10,6 +10,7 @@ use libc::{c_int, c_void, siginfo_t};
use std::cell::Cell;
use std::fmt::{Display, Formatter};
use std::io;
use std::ops::Range;
use std::os::unix::io::RawFd;
@ -57,8 +58,6 @@ use vm_memory::{
GuestRegionMmap,
};
use rangemap::RangeMap;
#[cfg(feature = "amd-sev")]
use sev::launch::snp;
@ -457,7 +456,7 @@ pub struct Vm {
#[cfg(feature = "amd-sev")]
pub tee_config: Tee,
pub guest_memfds: RangeMap<u64, (RawFd, u64)>,
pub guest_memfds: Vec<(Range<u64>, RawFd)>,
}
impl Vm {
@ -482,7 +481,7 @@ impl Vm {
supported_cpuid,
#[cfg(target_arch = "x86_64")]
supported_msrs,
guest_memfds: RangeMap::new(),
guest_memfds: Vec::new(),
})
}
@ -521,7 +520,7 @@ impl Vm {
supported_msrs,
tee,
tee_config: tee_config.tee,
guest_memfds: RangeMap::new(),
guest_memfds: Vec::new(),
})
}
@ -560,7 +559,12 @@ impl Vm {
}
pub fn guest_memfd_get(&self, gpa: u64) -> Option<(RawFd, u64)> {
self.guest_memfds.get(&gpa).copied()
for (range, rawfd) in self.guest_memfds.iter() {
if range.contains(&gpa) {
return Some((*rawfd, range.start));
}
}
None
}
#[allow(unused_mut)]
@ -631,7 +635,7 @@ impl Vm {
.set_memory_attributes(attr)
.map_err(Error::SetMemoryAttributes)?;
self.guest_memfds.insert(start..end, (guest_memfd, start));
self.guest_memfds.push((Range { start, end }, guest_memfd));
}
self.next_mem_slot += 1;

View File

@ -1,52 +0,0 @@
use std::collections::VecDeque;
use std::fmt;
use std::sync::{Arc, Mutex};
use devices::virtio::{Console, ConsoleError};
#[derive(Debug)]
pub enum ConsoleConfigError {
/// Failed to create the console device.
CreateConsoleDevice(ConsoleError),
}
impl fmt::Display for ConsoleConfigError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::ConsoleConfigError::*;
match *self {
CreateConsoleDevice(ref e) => write!(f, "Cannot create console device: {:?}", e),
}
}
}
type Result<T> = std::result::Result<T, ConsoleConfigError>;
#[derive(Clone, Debug, PartialEq)]
pub struct ConsoleDeviceConfig {
pub fs_id: String,
pub shared_dir: String,
}
#[derive(Default)]
pub struct FsBuilder {
pub list: VecDeque<Arc<Mutex<Fs>>>,
}
impl FsBuilder {
pub fn new() -> Self {
Self {
list: VecDeque::<Arc<Mutex<Fs>>>::new(),
}
}
pub fn insert(&mut self, config: FsDeviceConfig) -> Result<()> {
let fs_dev = Arc::new(Mutex::new(Self::create_fs(config)?));
self.list.push_back(fs_dev);
Ok(())
}
pub fn create_fs(config: FsDeviceConfig) -> Result<Fs> {
Ok(devices::virtio::Fs::new(config.fs_id, config.shared_dir)
.map_err(FsConfigError::CreateFsDevice)?)
}
}

View File

@ -15,7 +15,9 @@ use libc::{fallocate, madvise, FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, MADV_D
#[cfg(feature = "tee")]
use std::ffi::c_void;
#[cfg(feature = "tee")]
use vm_memory::{guest_memory::GuestMemory, GuestAddress, GuestMemoryRegion, MemoryRegionAddress};
use vm_memory::{
guest_memory::GuestMemory, Address, GuestAddress, GuestMemoryRegion, MemoryRegionAddress,
};
pub fn start_worker_thread(
vmm: Arc<Mutex<super::Vmm>>,
@ -44,21 +46,11 @@ impl super::Vmm {
WorkerMessage::GpuRemoveMapping(s, g, l) => self.remove_mapping(s, g, l),
#[cfg(target_arch = "x86_64")]
WorkerMessage::GsiRoute(sender, entries) => {
let mut irq_routing = utils::sized_vec::vec_with_array_field::<
kvm_bindings::kvm_irq_routing,
kvm_bindings::kvm_irq_routing_entry,
>(entries.len());
irq_routing[0].nr = entries.len() as u32;
irq_routing[0].flags = 0;
unsafe {
let entries_slice: &mut [kvm_bindings::kvm_irq_routing_entry] =
irq_routing[0].entries.as_mut_slice(entries.len());
entries_slice.copy_from_slice(&entries);
}
let mut routing = kvm_bindings::KvmIrqRouting::new(entries.len()).unwrap();
let routing_entries = routing.as_mut_slice();
routing_entries.copy_from_slice(&entries);
sender
.send(self.vm.fd().set_gsi_routing(&irq_routing[0]).is_ok())
.send(self.vm.fd().set_gsi_routing(&routing).is_ok())
.unwrap();
}
#[cfg(target_arch = "x86_64")]
@ -77,15 +69,15 @@ impl super::Vmm {
#[cfg(feature = "tee")]
fn convert_memory(&self, sender: Sender<bool>, properties: MemoryProperties) {
let (guest_memfd, region_start) = self
.kvm_vm()
.guest_memfd_get(properties.gpa)
.unwrap_or_else(|| {
panic!(
"unable to find KVM guest_memfd for memory region corresponding to GPA 0x{:x}",
properties.gpa
)
});
let Some((guest_memfd, region_start)) = self.kvm_vm().guest_memfd_get(properties.gpa)
else {
error!(
"unable to find KVM guest_memfd for memory region corresponding to GPA 0x{:x}",
properties.gpa
);
sender.send(false).unwrap();
return;
};
let attributes: u64 = if properties.private {
KVM_MEMORY_ATTRIBUTE_PRIVATE as u64
@ -100,10 +92,11 @@ impl super::Vmm {
flags: 0,
};
self.kvm_vm()
.fd()
.set_memory_attributes(attr)
.unwrap_or_else(|_| panic!("unable to set memory attributes for memory region corresponding to guest address 0x{:x}", properties.gpa));
if self.kvm_vm().fd().set_memory_attributes(attr).is_err() {
error!("unable to set memory attributes for memory region corresponding to guest address 0x{:x}", properties.gpa);
sender.send(false).unwrap();
return;
}
let region = self
.guest_memory()
@ -122,10 +115,14 @@ impl super::Vmm {
if properties.private {
let region_addr = MemoryRegionAddress(offset);
let host_startaddr = region
.unwrap()
.get_host_address(region_addr)
.expect("host address corresponding to memory region address 0x{:x} not found");
let Ok(host_startaddr) = region.unwrap().get_host_address(region_addr) else {
error!(
"host address corresponding to memory region address 0x{:x} not found",
region_addr.raw_value()
);
sender.send(false).unwrap();
return;
};
let ret = unsafe {
madvise(

View File

@ -5,7 +5,7 @@ edition = "2021"
[dependencies]
test_cases = { path = "../test_cases", features = ["host"] }
anyhow = "1.0.95"
nix = { version = "0.29.0", features = ["resource"] }
nix = { version = "0.29.0", features = ["resource", "fs"] }
macros = { path = "../macros" }
clap = { version = "4.5.27", features = ["derive"] }
tempdir = "0.3.7"