From 7e41bc176b017aab2ed7884e0a9ea5ffea7ac6bc Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Wed, 20 May 2026 17:12:47 +0200 Subject: [PATCH 01/41] examples: replace deprecated krun_set_root_disk/krun_set_data_disk with krun_add_disk Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- examples/boot_efi.c | 2 +- examples/launch-tee.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/boot_efi.c b/examples/boot_efi.c index 5105d46df..5891c35ee 100644 --- a/examples/boot_efi.c +++ b/examples/boot_efi.c @@ -197,7 +197,7 @@ int main(int argc, char *const argv[]) return -1; } - if (err = krun_set_root_disk(ctx_id, cmdline.disk_image)) { + if (err = krun_add_disk(ctx_id, "root", cmdline.disk_image, false)) { errno = -err; perror("Error configuring disk image"); return -1; diff --git a/examples/launch-tee.c b/examples/launch-tee.c index 063cdd5f3..1eadf193e 100644 --- a/examples/launch-tee.c +++ b/examples/launch-tee.c @@ -68,7 +68,7 @@ int main(int argc, char *const argv[]) } // Use the first command line argument as the disk image containing the root fs. - if (err = krun_set_root_disk(ctx_id, argv[1])) { + if (err = krun_add_disk(ctx_id, "root", argv[1], false)) { errno = -err; perror("Error configuring root disk image"); return -1; @@ -114,7 +114,7 @@ int main(int argc, char *const argv[]) return -1; } - if (err = krun_set_data_disk(ctx_id, argv[3])) { + if (err = krun_add_disk(ctx_id, "data", argv[3], false)) { errno = -err; perror("Error configuring the TEE config data disk"); return -1; From 4b2241367b950a09e8f1982eaadb4a73f9a201f6 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Wed, 20 May 2026 17:23:09 +0200 Subject: [PATCH 02/41] lib: remove deprecated krun_set_root_disk and krun_set_data_disk These were replaced by krun_add_disk. Also remove the internal root_block_cfg/data_block_cfg fields and their setters, and simplify get_block_cfg() now that the legacy compat path is gone. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- include/libkrun.h | 39 ----------------- init/init.c | 5 +-- src/libkrun/src/lib.rs | 96 +----------------------------------------- 3 files changed, 3 insertions(+), 137 deletions(-) diff --git a/include/libkrun.h b/include/libkrun.h index 9a71e946d..a912bea53 100644 --- a/include/libkrun.h +++ b/include/libkrun.h @@ -118,45 +118,12 @@ int32_t krun_set_vm_config(uint32_t ctx_id, uint8_t num_vcpus, uint32_t ram_mib) */ int32_t krun_set_root(uint32_t ctx_id, const char *root_path); -/** - * DEPRECATED. Use krun_add_disk instead. - * - * Sets the path to the disk image that contains the file-system to be used as root for the microVM. - * The only supported image format is "raw". - * - * Arguments: - * "ctx_id" - the configuration context ID. - * "disk_path" - a null-terminated string representing the path leading to the disk image that - * contains the root file-system. - * - * Returns: - * Zero on success or a negative error number on failure. - */ -int32_t krun_set_root_disk(uint32_t ctx_id, const char *disk_path); -/** - * DEPRECATED. Use krun_add_disk instead. - * - * Sets the path to the disk image that contains the file-system to be used as - * a data partition for the microVM. The only supported image format is "raw". - * - * Arguments: - * "ctx_id" - the configuration context ID. - * "disk_path" - a null-terminated string representing the path leading to the disk image that - * contains the root file-system. - * - * Returns: - * Zero on success or a negative error number on failure. - */ -int32_t krun_set_data_disk(uint32_t ctx_id, const char *disk_path); /** * Adds a disk image to be used as a general partition for the microVM. The only supported image * format is "raw". * - * This API is mutually exclusive with the deprecated krun_set_root_disk and - * krun_set_data_disk methods and must not be used together. - * * This function deliberately only handles images in the Raw format, because it doesn't allow * specifying an image format, and probing an image's format is dangerous. For more information, * see the security note on `krun_add_disk2`, which allows opening non-Raw images. @@ -183,9 +150,6 @@ int32_t krun_add_disk(uint32_t ctx_id, const char *block_id, const char *disk_pa * Adds a disk image to be used as a general partition for the microVM. The supported * image formats are: "raw" and "qcow2". * - * This API is mutually exclusive with the deprecated krun_set_root_disk and - * krun_set_data_disk methods and must not be used together. - * * SECURITY NOTE: * Non-Raw images can reference other files, which libkrun will automatically open, and to which the * guest will have access. Libkrun should therefore never be asked to open an image in a non-Raw @@ -254,9 +218,6 @@ int32_t krun_add_disk2(uint32_t ctx_id, /** * Adds a disk image to be used as a general partition for the microVM. * - * This API is mutually exclusive with the deprecated krun_set_root_disk and - * krun_set_data_disk methods and must not be used together. - * * SECURITY NOTE: * See the security note for `krun_add_disk2`. * diff --git a/init/init.c b/init/init.c index a65d68015..4a73fad9d 100644 --- a/init/init.c +++ b/init/init.c @@ -210,9 +210,8 @@ static char *get_luks_passphrase(int *pass_len) return_str = NULL; /* - * If a user registered the TEE config data disk with - * krun_set_data_disk(), it would appear as /dev/vdb in the guest. - * Mount this device and read the config. + * If a TEE config data disk was registered, it would appear as + * /dev/vdb in the guest. Mount this device and read the config. */ if (mkdir("/dev", 0755) < 0 && errno != EEXIST) { perror("mkdir(/dev)"); diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 696642325..26274ab10 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -168,10 +168,6 @@ struct ContextConfig { #[cfg(feature = "blk")] block_cfgs: Vec, #[cfg(feature = "blk")] - root_block_cfg: Option, - #[cfg(feature = "blk")] - data_block_cfg: Option, - #[cfg(feature = "blk")] block_root: Option, #[cfg(feature = "tee")] tee_config_file: Option, @@ -275,31 +271,9 @@ impl ContextConfig { self.block_cfgs.push(block_cfg); } - #[cfg(feature = "blk")] - fn set_root_block_cfg(&mut self, block_cfg: BlockDeviceConfig) { - self.root_block_cfg = Some(block_cfg); - } - - #[cfg(feature = "blk")] - fn set_data_block_cfg(&mut self, block_cfg: BlockDeviceConfig) { - self.data_block_cfg = Some(block_cfg); - } - #[cfg(feature = "blk")] fn get_block_cfg(&self) -> Vec { - // For backwards compat, when cfgs is empty (the new API is not used), this needs to be - // root and then data, in that order. Also for backwards compat, root/data are setters and - // need to discard redundant calls. So we have simple setters above and fix up here. - // - // When the new API is used, this is simpler. - if self.block_cfgs.is_empty() { - [&self.root_block_cfg, &self.data_block_cfg] - .into_iter() - .filter_map(|cfg| cfg.clone()) - .collect() - } else { - self.block_cfgs.clone() - } + self.block_cfgs.clone() } #[cfg(feature = "net")] @@ -876,74 +850,6 @@ pub unsafe extern "C" fn krun_add_disk3( } } -#[allow(clippy::missing_safety_doc)] -#[unsafe(no_mangle)] -#[cfg(feature = "blk")] -pub unsafe extern "C" fn krun_set_root_disk(ctx_id: u32, c_disk_path: *const c_char) -> i32 { - unsafe { - let disk_path = match CStr::from_ptr(c_disk_path).to_str() { - Ok(disk) => disk, - Err(_) => return -libc::EINVAL, - }; - - match CTX_MAP.lock().unwrap().entry(ctx_id) { - Entry::Occupied(mut ctx_cfg) => { - let cfg = ctx_cfg.get_mut(); - let block_device_config = BlockDeviceConfig { - block_id: "root".to_string(), - cache_type: CacheType::auto(disk_path), - disk_image_path: disk_path.to_string(), - disk_image_format: ImageType::Raw, - is_disk_read_only: false, - direct_io: false, - #[cfg(not(target_os = "macos"))] - sync_mode: SyncMode::Full, - #[cfg(target_os = "macos")] - sync_mode: SyncMode::Relaxed, - }; - cfg.set_root_block_cfg(block_device_config); - } - Entry::Vacant(_) => return -libc::ENOENT, - } - - KRUN_SUCCESS - } -} - -#[allow(clippy::missing_safety_doc)] -#[unsafe(no_mangle)] -#[cfg(feature = "blk")] -pub unsafe extern "C" fn krun_set_data_disk(ctx_id: u32, c_disk_path: *const c_char) -> i32 { - unsafe { - let disk_path = match CStr::from_ptr(c_disk_path).to_str() { - Ok(disk) => disk, - Err(_) => return -libc::EINVAL, - }; - - match CTX_MAP.lock().unwrap().entry(ctx_id) { - Entry::Occupied(mut ctx_cfg) => { - let cfg = ctx_cfg.get_mut(); - let block_device_config = BlockDeviceConfig { - block_id: "data".to_string(), - cache_type: CacheType::auto(disk_path), - disk_image_path: disk_path.to_string(), - disk_image_format: ImageType::Raw, - is_disk_read_only: false, - direct_io: false, - #[cfg(not(target_os = "macos"))] - sync_mode: SyncMode::Full, - #[cfg(target_os = "macos")] - sync_mode: SyncMode::Relaxed, - }; - cfg.set_data_block_cfg(block_device_config); - } - Entry::Vacant(_) => return -libc::ENOENT, - } - - KRUN_SUCCESS - } -} - /* * Send the VFKIT magic after establishing the connection, * as required by gvproxy in vfkit mode. From f20942b769971cd02633da80c19c8811707ea819 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Wed, 20 May 2026 17:25:46 +0200 Subject: [PATCH 03/41] lib: remove deprecated krun_set_passt_fd, krun_set_gvproxy_path, krun_set_net_mac These were replaced by krun_add_net_unixstream, krun_add_net_unixgram, and krun_add_net_tap (which take mac as a parameter directly). Also remove the internal LegacyNetworkConfig enum, legacy_net_cfg and legacy_mac fields, the compat path in krun_start_enter that converted them to the new net backend, and the now-unused NET_COMPAT_FEATURES constant. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- include/libkrun.h | 58 ++------------------ src/libkrun/src/lib.rs | 121 +---------------------------------------- 2 files changed, 6 insertions(+), 173 deletions(-) diff --git a/include/libkrun.h b/include/libkrun.h index a912bea53..29b401475 100644 --- a/include/libkrun.h +++ b/include/libkrun.h @@ -332,7 +332,7 @@ int32_t krun_add_virtiofs3(uint32_t ctx_id, #define NET_FEATURE_HOST_TSO6 1 << 12 #define NET_FEATURE_HOST_UFO 1 << 14 -/* These are the features enabled by krun_set_passt_fd and krun_set_gvproxy_path. */ +/* These are the default features used by krun_add_net_unixstream and krun_add_net_unixgram. */ #define COMPAT_NET_FEATURES NET_FEATURE_CSUM | NET_FEATURE_GUEST_CSUM | \ NET_FEATURE_GUEST_TSO4 | NET_FEATURE_GUEST_UFO | \ NET_FEATURE_HOST_TSO4 | NET_FEATURE_HOST_UFO @@ -453,57 +453,6 @@ int32_t krun_add_net_tap(uint32_t ctx_id, uint32_t features, uint32_t flags); -/** - * DEPRECATED. Use krun_add_net_unixstream instead. - * - * Configures the networking to use passt. - * Call to this function disables TSI backend to use passt instead. - * - * Arguments: - * "ctx_id" - the configuration context ID. - * "fd" - a file descriptor to communicate with passt - * - * Notes: - * If you never call this function, networking uses the TSI backend. - * This function should be called before krun_set_port_map. - * - * Returns: - * Zero on success or a negative error number on failure. - */ -int32_t krun_set_passt_fd(uint32_t ctx_id, int fd); - -/** - * DEPRECATED. Use krun_add_net_unixgram instead. - * - * Configures the networking to use gvproxy in vfkit mode. - * Call to this function disables TSI backend to use gvproxy instead. - * - * Arguments: - * "ctx_id" - the configuration context ID. - * "c_path" - a null-terminated string representing the path for - * gvproxy's listen-vfkit unixdgram socket. - * - * Notes: - * If you never call this function, networking uses the TSI backend. - * This function should be called before krun_set_port_map. - * - * Returns: - * Zero on success or a negative error number on failure. - */ -int32_t krun_set_gvproxy_path(uint32_t ctx_id, char *c_path); - -/** - * Sets the MAC address for the virtio-net device when using the passt backend. - * - * Arguments: - * "ctx_id" - the configuration context ID. - * "mac" - MAC address as an array of 6 uint8_t entries. - * - * Returns: - * Zero on success or a negative error number on failure. - */ -int32_t krun_set_net_mac(uint32_t ctx_id, uint8_t *const c_mac); - /** * Configures a map of host to guest TCP ports for the microVM. * @@ -526,8 +475,9 @@ int32_t krun_set_net_mac(uint32_t ctx_id, uint8_t *const c_mac); * means that for a map such as "8080:80", applications running inside the guest will also * need to access the service through the "8080" port. * - * If past networking mode is used (krun_set_passt_fd was called), port mapping is not supported - * as an API of libkrun (but you can still do port mapping using command line arguments of passt) + * If passt networking mode is used, port mapping is not supported as an API + * of libkrun (but you can still do port mapping using command line arguments + * of passt) */ int32_t krun_set_port_map(uint32_t ctx_id, const char *const port_map[]); diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 26274ab10..0909776b8 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -142,13 +142,6 @@ impl KrunfwBindings { } } -#[derive(Clone)] -#[cfg(feature = "net")] -enum LegacyNetworkConfig { - VirtioNetPasst(RawFd), - VirtioNetGvproxy(PathBuf), -} - #[derive(Default)] struct ContextConfig { krunfw: Option, @@ -158,10 +151,6 @@ struct ContextConfig { env: Option, args: Option, rlimits: Option, - #[cfg(feature = "net")] - legacy_net_cfg: Option, - #[cfg(feature = "net")] - legacy_mac: Option<[u8; 6]>, net_index: u8, tsi_port_map: Option>, vsock_config: VsockConfig, @@ -276,11 +265,6 @@ impl ContextConfig { self.block_cfgs.clone() } - #[cfg(feature = "net")] - fn set_net_mac(&mut self, mac: [u8; 6]) { - self.legacy_mac = Some(mac); - } - fn set_port_map(&mut self, new_port_map: HashMap) -> Result<(), ()> { if self.net_index != 0 { return Err(()); @@ -878,19 +862,7 @@ const NET_FEATURE_HOST_TSO4: u32 = 1 << 11; const NET_FEATURE_HOST_TSO6: u32 = 1 << 12; #[cfg(feature = "net")] const NET_FEATURE_HOST_UFO: u32 = 1 << 14; -/* - * These are the flags enabled by default on each virtio-net instance - * before the introduction of "krun_add_net_*". They are now used in - * the legacy API ("krun_set_passt_fd" and "krun_set_gvproxy_path") - * for compatiblity reasons. - */ -#[cfg(feature = "net")] -const NET_COMPAT_FEATURES: u32 = NET_FEATURE_CSUM - | NET_FEATURE_GUEST_CSUM - | NET_FEATURE_GUEST_TSO4 - | NET_FEATURE_GUEST_UFO - | NET_FEATURE_HOST_TSO4 - | NET_FEATURE_HOST_UFO; + #[cfg(feature = "net")] const NET_ALL_FEATURES: u32 = NET_FEATURE_CSUM | NET_FEATURE_GUEST_CSUM @@ -1094,79 +1066,6 @@ pub unsafe extern "C" fn krun_add_net_tap( -libc::EINVAL } -#[allow(clippy::missing_safety_doc)] -#[unsafe(no_mangle)] -#[cfg(feature = "net")] -pub unsafe extern "C" fn krun_set_passt_fd(ctx_id: u32, fd: c_int) -> i32 { - if fd < 0 { - return -libc::EINVAL; - } - - match CTX_MAP.lock().unwrap().entry(ctx_id) { - Entry::Occupied(mut ctx_cfg) => { - let cfg = ctx_cfg.get_mut(); - // The legacy interface only supports a single network interface. - if cfg.net_index != 0 { - return -libc::EINVAL; - } - cfg.legacy_net_cfg = Some(LegacyNetworkConfig::VirtioNetPasst(fd)); - } - Entry::Vacant(_) => return -libc::ENOENT, - } - KRUN_SUCCESS -} - -#[allow(clippy::missing_safety_doc)] -#[unsafe(no_mangle)] -#[cfg(feature = "net")] -pub unsafe extern "C" fn krun_set_gvproxy_path(ctx_id: u32, c_path: *const c_char) -> i32 { - unsafe { - let path_str = match CStr::from_ptr(c_path).to_str() { - Ok(path) => path, - Err(e) => { - debug!("Error parsing gvproxy_path: {e:?}"); - return -libc::EINVAL; - } - }; - - let path = PathBuf::from(path_str); - - match CTX_MAP.lock().unwrap().entry(ctx_id) { - Entry::Occupied(mut ctx_cfg) => { - let cfg = ctx_cfg.get_mut(); - // The legacy interface only supports a single network interface. - if cfg.net_index != 0 { - return -libc::EINVAL; - } - cfg.legacy_net_cfg = Some(LegacyNetworkConfig::VirtioNetGvproxy(path)); - } - Entry::Vacant(_) => return -libc::ENOENT, - } - KRUN_SUCCESS - } -} - -#[allow(clippy::missing_safety_doc)] -#[unsafe(no_mangle)] -#[cfg(feature = "net")] -pub unsafe extern "C" fn krun_set_net_mac(ctx_id: u32, c_mac: *const u8) -> i32 { - unsafe { - let mac: [u8; 6] = match slice::from_raw_parts(c_mac, 6).try_into() { - Ok(m) => m, - 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_net_mac(mac); - } - Entry::Vacant(_) => return -libc::ENOENT, - } - KRUN_SUCCESS - } -} - #[allow(clippy::missing_safety_doc)] #[unsafe(no_mangle)] pub unsafe extern "C" fn krun_set_port_map(ctx_id: u32, c_port_map: *const *const c_char) -> i32 { @@ -2891,22 +2790,6 @@ pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 { return -libc::EINVAL; } - #[cfg(feature = "net")] - { - if let Some(legacy_net_cfg) = ctx_cfg.legacy_net_cfg.clone() { - let backend = match legacy_net_cfg { - LegacyNetworkConfig::VirtioNetGvproxy(path) => { - VirtioNetBackend::UnixgramPath(path, true) - } - LegacyNetworkConfig::VirtioNetPasst(fd) => VirtioNetBackend::UnixstreamFd(fd), - }; - let mac = ctx_cfg - .legacy_mac - .unwrap_or([0x5a, 0x94, 0xef, 0xe4, 0x0c, 0xee]); - create_virtio_net(&mut ctx_cfg, backend, mac, NET_COMPAT_FEATURES); - } - } - match &ctx_cfg.vsock_config { VsockConfig::Disabled => (), VsockConfig::Explicit { tsi_flags } => { @@ -2923,7 +2806,7 @@ pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 { // Implicit vsock configuration - use heuristics // Check if TSI should be enabled based on network configuration #[cfg(feature = "net")] - let enable_tsi = ctx_cfg.vmr.net.list.is_empty() && ctx_cfg.legacy_net_cfg.is_none(); + let enable_tsi = ctx_cfg.vmr.net.list.is_empty(); #[cfg(not(feature = "net"))] let enable_tsi = true; From 2ff9230659bafdb818102ae29eaee86979ac5568 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Wed, 20 May 2026 17:28:19 +0200 Subject: [PATCH 04/41] tests/examples: replace krun_set_log_level with krun_init_log Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- examples/boot_efi.c | 2 +- examples/consoles.c | 2 +- examples/external_kernel.c | 2 +- examples/launch-tee.c | 2 +- examples/nitro.c | 2 +- tests/test_cases/src/test_augmentfs.rs | 2 +- tests/test_cases/src/test_freebsd_boot.rs | 9 +++++++-- .../src/test_freebsd_gvproxy_tcp_guest_connect.rs | 11 ++++++++--- .../src/test_freebsd_gvproxy_tcp_guest_listen.rs | 11 ++++++++--- tests/test_cases/src/test_multiport_console.rs | 2 +- tests/test_cases/src/test_net/mod.rs | 2 +- tests/test_cases/src/test_root_disk_remount.rs | 7 ++++++- tests/test_cases/src/test_tsi_tcp_guest_connect.rs | 2 +- tests/test_cases/src/test_tsi_tcp_guest_listen.rs | 2 +- tests/test_cases/src/test_virtiofs_misc.rs | 2 +- tests/test_cases/src/test_virtiofs_root_ro.rs | 7 ++++++- tests/test_cases/src/test_vm_config.rs | 2 +- tests/test_cases/src/test_vsock_guest_connect.rs | 2 +- 18 files changed, 48 insertions(+), 23 deletions(-) diff --git a/examples/boot_efi.c b/examples/boot_efi.c index 5891c35ee..089fd39e9 100644 --- a/examples/boot_efi.c +++ b/examples/boot_efi.c @@ -169,7 +169,7 @@ int main(int argc, char *const argv[]) } // Set the log level to "off". - err = krun_set_log_level(0); + err = krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_OFF, KRUN_LOG_STYLE_AUTO, 0); if (err) { errno = -err; perror("Error configuring log level"); diff --git a/examples/consoles.c b/examples/consoles.c index 30a17a492..fe3a98271 100644 --- a/examples/consoles.c +++ b/examples/consoles.c @@ -119,7 +119,7 @@ int main(int argc, char *const argv[]) const char *const *command_args = (argc > 3) ? (const char *const *)&argv[3] : NULL; const char *const envp[] = { 0 }; - krun_set_log_level(KRUN_LOG_LEVEL_WARN); + krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_WARN, KRUN_LOG_STYLE_AUTO, 0); int err; int ctx_id = krun_create_ctx(); diff --git a/examples/external_kernel.c b/examples/external_kernel.c index 14649881d..ab857719d 100644 --- a/examples/external_kernel.c +++ b/examples/external_kernel.c @@ -218,7 +218,7 @@ int main(int argc, char *const argv[]) } // Set the log level to "off". - err = krun_set_log_level(0); + err = krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_OFF, KRUN_LOG_STYLE_AUTO, 0); if (err) { errno = -err; diff --git a/examples/launch-tee.c b/examples/launch-tee.c index 1eadf193e..df5f78f66 100644 --- a/examples/launch-tee.c +++ b/examples/launch-tee.c @@ -45,7 +45,7 @@ int main(int argc, char *const argv[]) } // Set the log level to "error". - err = krun_set_log_level(1); + err = krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_ERROR, KRUN_LOG_STYLE_AUTO, 0); if (err) { errno = -err; perror("Error configuring log level"); diff --git a/examples/nitro.c b/examples/nitro.c index 2a80e02fd..2b4578d54 100644 --- a/examples/nitro.c +++ b/examples/nitro.c @@ -180,7 +180,7 @@ int main(int argc, char *const argv[]) // Enable debug output if configured. log_level = (cmdline.debug) ? KRUN_LOG_LEVEL_DEBUG : KRUN_LOG_LEVEL_OFF; - err = krun_set_log_level(log_level); + err = krun_init_log(KRUN_LOG_TARGET_DEFAULT, log_level, KRUN_LOG_STYLE_AUTO, 0); if (err) { errno = -err; perror("Error configuring log level"); diff --git a/tests/test_cases/src/test_augmentfs.rs b/tests/test_cases/src/test_augmentfs.rs index 24032033c..a3d87c636 100644 --- a/tests/test_cases/src/test_augmentfs.rs +++ b/tests/test_cases/src/test_augmentfs.rs @@ -48,7 +48,7 @@ mod host { let marker: &'static [u8] = b"virtual-file-marker-content-12345"; unsafe { - krun_call!(krun_set_log_level(KRUN_LOG_LEVEL_TRACE))?; + krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; diff --git a/tests/test_cases/src/test_freebsd_boot.rs b/tests/test_cases/src/test_freebsd_boot.rs index 866007f74..6b669fed9 100644 --- a/tests/test_cases/src/test_freebsd_boot.rs +++ b/tests/test_cases/src/test_freebsd_boot.rs @@ -7,7 +7,7 @@ mod host { use super::*; use crate::common_freebsd::{freebsd_assets, normalize_serial_output, setup_kernel_and_enter}; - use crate::{ShouldRun, Test, TestOutcome, TestSetup, krun_call, krun_call_u32}; + use crate::{krun_call, krun_call_u32, ShouldRun, Test, TestOutcome, TestSetup}; use krun_sys::*; impl Test for TestFreeBsdBoot { @@ -26,7 +26,12 @@ mod host { fn start_vm(self: Box, test_setup: TestSetup) -> anyhow::Result<()> { let assets = freebsd_assets().expect("FreeBSD assets must be present when test runs"); unsafe { - krun_call!(krun_set_log_level(KRUN_LOG_LEVEL_TRACE))?; + krun_call!(krun_init_log( + KRUN_LOG_TARGET_DEFAULT, + KRUN_LOG_LEVEL_TRACE, + KRUN_LOG_STYLE_AUTO, + 0 + ))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; setup_kernel_and_enter(ctx, test_setup, assets)?; diff --git a/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_connect.rs b/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_connect.rs index 007058ab7..bf0994e60 100644 --- a/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_connect.rs +++ b/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_connect.rs @@ -28,8 +28,8 @@ mod host { freebsd_assets, normalize_serial_output, setup_gvproxy_backend, setup_kernel_and_enter, }; use crate::test_net::gvproxy::gvproxy_path; - use crate::{ShouldRun, Test, TestOutcome, TestSetup}; use crate::{krun_call, krun_call_u32}; + use crate::{ShouldRun, Test, TestOutcome, TestSetup}; use krun_sys::*; use std::thread; @@ -52,7 +52,12 @@ mod host { thread::spawn(move || self.tcp_tester.run_server(listener)); unsafe { - krun_call!(krun_set_log_level(KRUN_LOG_LEVEL_TRACE))?; + krun_call!(krun_init_log( + KRUN_LOG_TARGET_DEFAULT, + KRUN_LOG_LEVEL_TRACE, + KRUN_LOG_STYLE_AUTO, + 0 + ))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; setup_gvproxy_backend(ctx, &test_setup)?; @@ -78,8 +83,8 @@ mod host { #[guest] mod guest { use super::*; - use crate::Test; use crate::freebsd_network::configure_virtio_net_ip; + use crate::Test; impl Test for TestFreeBsdGvproxyTcpGuestConnect { fn in_guest(self: Box) { diff --git a/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_listen.rs b/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_listen.rs index 00ccebc79..7001c5f5b 100644 --- a/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_listen.rs +++ b/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_listen.rs @@ -26,8 +26,8 @@ mod host { freebsd_assets, normalize_serial_output, setup_gvproxy_backend, setup_kernel_and_enter, }; use crate::test_net::gvproxy::{gvproxy_path, setup_gvproxy_port_forward}; - use crate::{ShouldRun, Test, TestOutcome, TestSetup}; use crate::{krun_call, krun_call_u32}; + use crate::{ShouldRun, Test, TestOutcome, TestSetup}; use krun_sys::*; use std::net::Ipv4Addr; use std::thread; @@ -50,7 +50,12 @@ mod host { let assets = freebsd_assets().expect("freebsd assets must be available"); unsafe { - krun_call!(krun_set_log_level(KRUN_LOG_LEVEL_TRACE))?; + krun_call!(krun_init_log( + KRUN_LOG_TARGET_DEFAULT, + KRUN_LOG_LEVEL_TRACE, + KRUN_LOG_STYLE_AUTO, + 0 + ))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; let net_sock = setup_gvproxy_backend(ctx, &test_setup)?; @@ -89,8 +94,8 @@ mod host { #[guest] mod guest { use super::*; - use crate::Test; use crate::freebsd_network::configure_virtio_net_ip; + use crate::Test; impl Test for TestFreeBsdGvproxyTcpGuestListen { fn in_guest(self: Box) { diff --git a/tests/test_cases/src/test_multiport_console.rs b/tests/test_cases/src/test_multiport_console.rs index 8fcbf6033..2948b7369 100644 --- a/tests/test_cases/src/test_multiport_console.rs +++ b/tests/test_cases/src/test_multiport_console.rs @@ -50,7 +50,7 @@ mod host { impl Test for TestMultiportConsole { fn start_vm(self: Box, test_setup: TestSetup) -> anyhow::Result<()> { unsafe { - krun_call!(krun_set_log_level(KRUN_LOG_LEVEL_TRACE))?; + krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_disable_implicit_console(ctx))?; diff --git a/tests/test_cases/src/test_net/mod.rs b/tests/test_cases/src/test_net/mod.rs index 394464115..a27637e13 100644 --- a/tests/test_cases/src/test_net/mod.rs +++ b/tests/test_cases/src/test_net/mod.rs @@ -122,7 +122,7 @@ mod host { thread::spawn(move || tcp_tester.run_server(listener)); unsafe { - krun_call!(krun_set_log_level(KRUN_LOG_LEVEL_TRACE))?; + krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; diff --git a/tests/test_cases/src/test_root_disk_remount.rs b/tests/test_cases/src/test_root_disk_remount.rs index 11a445a81..81c5623e4 100644 --- a/tests/test_cases/src/test_root_disk_remount.rs +++ b/tests/test_cases/src/test_root_disk_remount.rs @@ -98,7 +98,12 @@ mod host { let test_case = CString::new(test_setup.test_case)?; unsafe { - krun_call!(krun_set_log_level(KRUN_LOG_LEVEL_TRACE))?; + krun_call!(krun_init_log( + KRUN_LOG_TARGET_DEFAULT, + KRUN_LOG_LEVEL_TRACE, + KRUN_LOG_STYLE_AUTO, + 0 + ))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; diff --git a/tests/test_cases/src/test_tsi_tcp_guest_connect.rs b/tests/test_cases/src/test_tsi_tcp_guest_connect.rs index c3bb6c33d..158810c48 100644 --- a/tests/test_cases/src/test_tsi_tcp_guest_connect.rs +++ b/tests/test_cases/src/test_tsi_tcp_guest_connect.rs @@ -31,7 +31,7 @@ mod host { let listener = self.tcp_tester.create_server_socket(); thread::spawn(move || self.tcp_tester.run_server(listener)); unsafe { - krun_call!(krun_set_log_level(KRUN_LOG_LEVEL_TRACE))?; + krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; setup_fs_and_enter(ctx, test_setup)?; diff --git a/tests/test_cases/src/test_tsi_tcp_guest_listen.rs b/tests/test_cases/src/test_tsi_tcp_guest_listen.rs index c1a8d8721..4a0f9fd62 100644 --- a/tests/test_cases/src/test_tsi_tcp_guest_listen.rs +++ b/tests/test_cases/src/test_tsi_tcp_guest_listen.rs @@ -33,7 +33,7 @@ mod host { self.tcp_tester.run_client(); }); - krun_call!(krun_set_log_level(KRUN_LOG_LEVEL_TRACE))?; + krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; let ctx = krun_call_u32!(krun_create_ctx())?; let port_mapping = format!("{PORT}:{PORT}"); let port_mapping = CString::new(port_mapping).unwrap(); diff --git a/tests/test_cases/src/test_virtiofs_misc.rs b/tests/test_cases/src/test_virtiofs_misc.rs index 189ba6297..e89e28530 100644 --- a/tests/test_cases/src/test_virtiofs_misc.rs +++ b/tests/test_cases/src/test_virtiofs_misc.rs @@ -17,7 +17,7 @@ mod host { impl Test for TestVirtioFsMisc { fn start_vm(self: Box, test_setup: TestSetup) -> anyhow::Result<()> { unsafe { - krun_call!(krun_set_log_level(KRUN_LOG_LEVEL_TRACE))?; + krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 1024))?; setup_fs_and_enter(ctx, test_setup)?; diff --git a/tests/test_cases/src/test_virtiofs_root_ro.rs b/tests/test_cases/src/test_virtiofs_root_ro.rs index 31cbd1cb2..21332153e 100644 --- a/tests/test_cases/src/test_virtiofs_root_ro.rs +++ b/tests/test_cases/src/test_virtiofs_root_ro.rs @@ -42,7 +42,12 @@ mod host { let envp = [null()]; unsafe { - krun_call!(krun_set_log_level(KRUN_LOG_LEVEL_TRACE))?; + krun_call!(krun_init_log( + KRUN_LOG_TARGET_DEFAULT, + KRUN_LOG_LEVEL_TRACE, + KRUN_LOG_STYLE_AUTO, + 0 + ))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; diff --git a/tests/test_cases/src/test_vm_config.rs b/tests/test_cases/src/test_vm_config.rs index 207f2b460..bd5c9addf 100644 --- a/tests/test_cases/src/test_vm_config.rs +++ b/tests/test_cases/src/test_vm_config.rs @@ -17,7 +17,7 @@ mod host { impl Test for TestVmConfig { fn start_vm(self: Box, test_setup: TestSetup) -> anyhow::Result<()> { unsafe { - krun_call!(krun_set_log_level(KRUN_LOG_LEVEL_TRACE))?; + krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, self.num_cpus, self.ram_mib))?; setup_fs_and_enter(ctx, test_setup)?; diff --git a/tests/test_cases/src/test_vsock_guest_connect.rs b/tests/test_cases/src/test_vsock_guest_connect.rs index 686d15654..84fec166d 100644 --- a/tests/test_cases/src/test_vsock_guest_connect.rs +++ b/tests/test_cases/src/test_vsock_guest_connect.rs @@ -65,7 +65,7 @@ mod host { thread::spawn(move || server(listener)); unsafe { - krun_call!(krun_set_log_level(KRUN_LOG_LEVEL_TRACE))?; + krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_add_vsock_port( ctx, From 3f9a4ed53fd25cb9f70b788754290d202165f921 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Wed, 20 May 2026 17:29:17 +0200 Subject: [PATCH 05/41] lib: propagate KRUN_NITRO_DEBUG flag in krun_init_log krun_set_log_level set KRUN_NITRO_DEBUG when level==4 (debug), but krun_init_log did not. Fix the omission so removing krun_set_log_level doesn't regress nitro debug logging. Also fix the condition to level >= 4 so that trace (level 5) also enables nitro debug. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- src/libkrun/src/lib.rs | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 0909776b8..93b72efc0 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -419,26 +419,6 @@ fn log_level_to_filter_str(level: u32) -> &'static str { } } -#[unsafe(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)) - .format_timestamp_micros() - .init(); - - #[cfg(feature = "aws-nitro")] - { - // Notify krun-awsnitro to enable debug for log level. - if level == 4 { - let mut debug = KRUN_NITRO_DEBUG.lock().unwrap(); - - *debug = true; - } - } - - KRUN_SUCCESS -} - mod log_defs { pub const KRUN_LOG_STYLE_AUTO: u32 = 0; pub const KRUN_LOG_STYLE_ALWAYS: u32 = 1; @@ -487,6 +467,14 @@ pub unsafe extern "C" fn krun_init_log(target: RawFd, level: u32, style: u32, op }; builder.format_timestamp_micros().target(target).init(); + #[cfg(feature = "aws-nitro")] + { + // Notify krun-awsnitro to enable debug for log level. + if level >= 4 { + *KRUN_NITRO_DEBUG.lock().unwrap() = true; + } + } + KRUN_SUCCESS } } From 71fdbea3250a1c198145cd87b57d3f19aec19557 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Wed, 20 May 2026 17:30:47 +0200 Subject: [PATCH 06/41] lib: remove deprecated krun_set_log_level Superseded by krun_init_log which provides control over target fd, log style, and env override options. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- include/libkrun.h | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/include/libkrun.h b/include/libkrun.h index 29b401475..d34d255c4 100644 --- a/include/libkrun.h +++ b/include/libkrun.h @@ -10,24 +10,6 @@ extern "C" { #include #include -/** - * Sets the log level for the library. - * - * Arguments: - * "level" can be one of the following values: - * 0: Off - * 1: Error - * 2: Warn - * 3: Info - * 4: Debug - * 5: Trace - * - * Returns: - * Zero on success or a negative error number on failure. - */ -int32_t krun_set_log_level(uint32_t level); - - #define KRUN_LOG_TARGET_DEFAULT -1 #define KRUN_LOG_LEVEL_OFF 0 From a4c5cff61a2002acd0b63ef1635472eb4565f7b6 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Wed, 20 May 2026 17:44:40 +0200 Subject: [PATCH 07/41] lib: remove unsupported krun_set_mapped_volumes This function has been returning -EINVAL unconditionally. Remove it. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- include/libkrun.h | 16 ---------------- src/libkrun/src/lib.rs | 10 ---------- 2 files changed, 26 deletions(-) diff --git a/include/libkrun.h b/include/libkrun.h index d34d255c4..efcb2af5c 100644 --- a/include/libkrun.h +++ b/include/libkrun.h @@ -226,22 +226,6 @@ int32_t krun_add_disk2(uint32_t ctx_id, bool direct_io, uint32_t sync_mode); -/** - * NO LONGER SUPPORTED. DO NOT USE. - * - * Configures the mapped volumes for the microVM. Only supported on macOS, on Linux use - * user_namespaces and bind-mounts instead. Not available in libkrun-SEV. - * - * Arguments: - * "ctx_id" - the configuration context ID. - * "mapped_volumes" - an array of string pointers with format "host_path:guest_path" representing - * the volumes to be mapped inside the microVM - * - * Returns: - * Zero on success or a negative error number on failure. - */ -int32_t krun_set_mapped_volumes(uint32_t ctx_id, const char *const mapped_volumes[]); - /** * Adds an independent virtio-fs device pointing to a host's directory with a tag. * diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 93b72efc0..39309c796 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -664,16 +664,6 @@ pub unsafe extern "C" fn krun_add_virtiofs3( } } -#[allow(clippy::missing_safety_doc)] -#[unsafe(no_mangle)] -#[cfg(not(feature = "tee"))] -pub unsafe extern "C" fn krun_set_mapped_volumes( - _ctx_id: u32, - _c_mapped_volumes: *const *const c_char, -) -> i32 { - -libc::EINVAL -} - #[allow(clippy::missing_safety_doc)] #[unsafe(no_mangle)] #[cfg(feature = "blk")] From 25578e50c54738c875c9a3d60e2b5866eadb8ee1 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Wed, 20 May 2026 17:54:07 +0200 Subject: [PATCH 08/41] tests/examples: use explicit krun_add_virtio_console_default everywhere Replace implicit console creation with explicit krun_add_virtio_console_default calls. Also replace krun_set_console_output in nitro.c with krun_add_virtio_console_default. No test or example relies on implicit console injection anymore. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- examples/boot_efi.c | 6 ++++++ examples/chroot_vm.c | 6 ++++++ examples/external_kernel.c | 7 +++++++ examples/gui_vm/src/main.rs | 15 +++++++++++---- examples/launch-tee.c | 6 ++++++ examples/nitro.c | 4 ++-- tests/test_cases/src/common_freebsd.rs | 5 ++--- tests/test_cases/src/test_augmentfs.rs | 7 +++++++ tests/test_cases/src/test_net/mod.rs | 14 +++++++++++++- tests/test_cases/src/test_net_perf.rs | 7 +++++++ tests/test_cases/src/test_pjdfstest.rs | 7 +++++++ tests/test_cases/src/test_root_disk_remount.rs | 7 +++++++ .../test_cases/src/test_tsi_tcp_guest_connect.rs | 14 +++++++++++++- tests/test_cases/src/test_tsi_tcp_guest_listen.rs | 14 +++++++++++++- tests/test_cases/src/test_virtiofs_misc.rs | 14 +++++++++++++- tests/test_cases/src/test_virtiofs_root_ro.rs | 7 +++++++ tests/test_cases/src/test_vm_config.rs | 14 +++++++++++++- tests/test_cases/src/test_vsock_guest_connect.rs | 7 +++++++ 18 files changed, 147 insertions(+), 14 deletions(-) diff --git a/examples/boot_efi.c b/examples/boot_efi.c index 089fd39e9..5b84e2c56 100644 --- a/examples/boot_efi.c +++ b/examples/boot_efi.c @@ -191,6 +191,12 @@ int main(int argc, char *const argv[]) return -1; } + if (err = krun_add_virtio_console_default(ctx_id, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO)) { + errno = -err; + perror("Error configuring console"); + return -1; + } + if (err = krun_set_firmware(ctx_id, cmdline.efi_fw)) { errno = -err; perror("Error configuring EFI FW path"); diff --git a/examples/chroot_vm.c b/examples/chroot_vm.c index b0ab0a05e..9bfce23f6 100644 --- a/examples/chroot_vm.c +++ b/examples/chroot_vm.c @@ -308,6 +308,12 @@ int main(int argc, char *const argv[]) return -1; } + if (err = krun_add_virtio_console_default(ctx_id, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO)) { + errno = -err; + perror("Error configuring console"); + return -1; + } + // Configure vhost-user RNG if requested if (cmdline.vhost_user_rng_socket != NULL) { // Test sentinel-terminated array: auto-detect queue count, use custom size diff --git a/examples/external_kernel.c b/examples/external_kernel.c index ab857719d..9b2b96f6c 100644 --- a/examples/external_kernel.c +++ b/examples/external_kernel.c @@ -243,6 +243,13 @@ int main(int argc, char *const argv[]) return -1; } + if (err = krun_add_virtio_console_default(ctx_id, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO)) + { + errno = -err; + perror("Error configuring console"); + return -1; + } + if (cmdline.boot_disk) { if (err = krun_add_disk(ctx_id, "boot", cmdline.boot_disk, 0)) diff --git a/examples/gui_vm/src/main.rs b/examples/gui_vm/src/main.rs index 660a2e21a..fe230a957 100644 --- a/examples/gui_vm/src/main.rs +++ b/examples/gui_vm/src/main.rs @@ -9,9 +9,9 @@ use krun_sys::{ KRUN_LOG_LEVEL_TRACE, KRUN_LOG_LEVEL_WARN, KRUN_LOG_STYLE_ALWAYS, KRUN_LOG_TARGET_DEFAULT, VIRGLRENDERER_RENDER_SERVER, VIRGLRENDERER_THREAD_SYNC, VIRGLRENDERER_USE_ASYNC_FENCE_CB, VIRGLRENDERER_USE_EGL, VIRGLRENDERER_VENUS, krun_add_display, krun_add_input_device, - krun_add_input_device_fd, krun_create_ctx, krun_display_set_dpi, - krun_display_set_physical_size, krun_display_set_refresh_rate, krun_init_log, - krun_set_display_backend, krun_set_exec, krun_set_gpu_options2, krun_set_root, + krun_add_input_device_fd, krun_add_virtio_console_default, krun_create_ctx, + krun_display_set_dpi, krun_display_set_physical_size, krun_display_set_refresh_rate, + krun_init_log, krun_set_display_backend, krun_set_exec, krun_set_gpu_options2, krun_set_root, krun_set_vm_config, krun_start_enter, }; use log::LevelFilter; @@ -22,7 +22,7 @@ use std::fs::{File, OpenOptions}; use std::mem::size_of_val; use anyhow::Context; -use std::os::fd::IntoRawFd; +use std::os::fd::{AsRawFd, IntoRawFd}; use std::path::PathBuf; use std::process::exit; use std::ptr::null; @@ -150,6 +150,13 @@ fn krun_thread( krun_call!(krun_set_vm_config(ctx, 4, 4096))?; + krun_call!(krun_add_virtio_console_default( + ctx, + std::io::stdin().as_raw_fd(), + std::io::stdout().as_raw_fd(), + std::io::stderr().as_raw_fd(), + ))?; + krun_call!(krun_set_gpu_options2( ctx, VIRGLRENDERER_USE_EGL diff --git a/examples/launch-tee.c b/examples/launch-tee.c index df5f78f66..cc64a6c48 100644 --- a/examples/launch-tee.c +++ b/examples/launch-tee.c @@ -67,6 +67,12 @@ int main(int argc, char *const argv[]) return -1; } + if (err = krun_add_virtio_console_default(ctx_id, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO)) { + errno = -err; + perror("Error configuring console"); + return -1; + } + // Use the first command line argument as the disk image containing the root fs. if (err = krun_add_disk(ctx_id, "root", argv[1], false)) { errno = -err; diff --git a/examples/nitro.c b/examples/nitro.c index 2b4578d54..379e28d89 100644 --- a/examples/nitro.c +++ b/examples/nitro.c @@ -203,9 +203,9 @@ int main(int argc, char *const argv[]) return -1; } - if (err = krun_set_console_output(ctx_id, "/dev/stdout")) { + if (err = krun_add_virtio_console_default(ctx_id, -1, STDOUT_FILENO, -1)) { errno = -err; - perror("Error configuring the console output"); + perror("Error configuring the console"); return -1; } diff --git a/tests/test_cases/src/common_freebsd.rs b/tests/test_cases/src/common_freebsd.rs index 274806fd4..5f3cdb0e4 100644 --- a/tests/test_cases/src/common_freebsd.rs +++ b/tests/test_cases/src/common_freebsd.rs @@ -9,8 +9,8 @@ use std::process::Command; use std::time::{SystemTime, UNIX_EPOCH}; use crate::test_net::get_krun_add_net_unixgram; -use crate::test_net::gvproxy::{Gvproxy, wait_for_socket}; -use crate::{TestSetup, krun_call}; +use crate::test_net::gvproxy::{wait_for_socket, Gvproxy}; +use crate::{krun_call, TestSetup}; use krun_sys::*; pub struct FreeBsdAssets { @@ -209,7 +209,6 @@ unsafe fn do_setup_and_enter( CString::new(config_iso.as_os_str().as_bytes()).context("CString::new")?; // FreeBSD requires a serial console; virtio console is not supported. - krun_call!(krun_disable_implicit_console(ctx))?; krun_call!(krun_add_serial_console_default(ctx, serial_read_fd, 1))?; // Kernel cmdline: mount vtbd0 as root via cd9660 and hand off to init-freebsd. diff --git a/tests/test_cases/src/test_augmentfs.rs b/tests/test_cases/src/test_augmentfs.rs index a3d87c636..46643b4f3 100644 --- a/tests/test_cases/src/test_augmentfs.rs +++ b/tests/test_cases/src/test_augmentfs.rs @@ -21,6 +21,7 @@ mod host { use crate::{krun_call, krun_call_u32}; use krun_sys::*; use std::ffi::CString; + use std::os::fd::AsRawFd; use std::ptr::null_mut; impl Test for TestAugmentFs { @@ -51,6 +52,12 @@ mod host { krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; + krun_call!(krun_add_virtio_console_default( + ctx, + std::io::stdin().as_raw_fd(), + std::io::stdout().as_raw_fd(), + std::io::stderr().as_raw_fd(), + ))?; // Disable the implicit init — we'll inject it ourselves. krun_call!(krun_disable_implicit_init(ctx))?; diff --git a/tests/test_cases/src/test_net/mod.rs b/tests/test_cases/src/test_net/mod.rs index a27637e13..75cafc1a6 100644 --- a/tests/test_cases/src/test_net/mod.rs +++ b/tests/test_cases/src/test_net/mod.rs @@ -92,6 +92,7 @@ mod host { use crate::common::setup_fs_and_enter; use crate::{Test, TestOutcome, TestSetup, krun_call, krun_call_u32}; use krun_sys::*; + use std::os::fd::AsRawFd; use std::thread; impl Test for TestNet { @@ -122,13 +123,24 @@ mod host { thread::spawn(move || tcp_tester.run_server(listener)); unsafe { - krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; + krun_call!(krun_init_log( + KRUN_LOG_TARGET_DEFAULT, + KRUN_LOG_LEVEL_TRACE, + KRUN_LOG_STYLE_AUTO, + 0 + ))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; // Backend-specific setup (self.setup_backend)(ctx, &test_setup)?; + krun_call!(krun_add_virtio_console_default( + ctx, + std::io::stdin().as_raw_fd(), + std::io::stdout().as_raw_fd(), + std::io::stderr().as_raw_fd(), + ))?; setup_fs_and_enter(ctx, test_setup)?; } Ok(()) diff --git a/tests/test_cases/src/test_net_perf.rs b/tests/test_cases/src/test_net_perf.rs index f43c4020a..1de42842c 100644 --- a/tests/test_cases/src/test_net_perf.rs +++ b/tests/test_cases/src/test_net_perf.rs @@ -155,6 +155,7 @@ mod host { use crate::common::setup_fs_and_enter; use crate::{Test, TestOutcome, TestSetup, krun_call, krun_call_u32}; use krun_sys::*; + use std::os::fd::AsRawFd; use std::process::{Child, Command, Stdio}; const CONTAINERFILE: &str = "\ @@ -360,6 +361,12 @@ RUN dnf install -y iperf3 && dnf clean all // Backend-specific setup (self.setup_backend)(ctx, &test_setup)?; + krun_call!(krun_add_virtio_console_default( + ctx, + std::io::stdin().as_raw_fd(), + std::io::stdout().as_raw_fd(), + std::io::stderr().as_raw_fd(), + ))?; setup_fs_and_enter(ctx, test_setup)?; } Ok(()) diff --git a/tests/test_cases/src/test_pjdfstest.rs b/tests/test_cases/src/test_pjdfstest.rs index 5bb6cafe0..a4af584c8 100644 --- a/tests/test_cases/src/test_pjdfstest.rs +++ b/tests/test_cases/src/test_pjdfstest.rs @@ -9,6 +9,7 @@ mod host { use crate::{ShouldRun, Test, TestOutcome, TestSetup, krun_call, krun_call_u32}; use krun_sys::*; use std::ffi::CString; + use std::os::fd::AsRawFd; use macros::env_or_default; @@ -54,6 +55,12 @@ mod host { unsafe { let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 2, 1024))?; + krun_call!(krun_add_virtio_console_default( + ctx, + std::io::stdin().as_raw_fd(), + std::io::stdout().as_raw_fd(), + std::io::stderr().as_raw_fd(), + ))?; setup_fs_and_enter_with_env(ctx, test_setup, &[host_os_env.as_c_str()])?; } Ok(()) diff --git a/tests/test_cases/src/test_root_disk_remount.rs b/tests/test_cases/src/test_root_disk_remount.rs index 81c5623e4..388b68bc3 100644 --- a/tests/test_cases/src/test_root_disk_remount.rs +++ b/tests/test_cases/src/test_root_disk_remount.rs @@ -18,6 +18,7 @@ mod host { use krun_sys::*; use nix::libc; use std::ffi::CString; + use std::os::fd::AsRawFd; use std::process::Command; use std::ptr::null; @@ -106,6 +107,12 @@ mod host { ))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; + krun_call!(krun_add_virtio_console_default( + ctx, + std::io::stdin().as_raw_fd(), + std::io::stdout().as_raw_fd(), + std::io::stderr().as_raw_fd(), + ))?; let argv = [test_case.as_ptr(), null()]; let envp = [null()]; diff --git a/tests/test_cases/src/test_tsi_tcp_guest_connect.rs b/tests/test_cases/src/test_tsi_tcp_guest_connect.rs index 158810c48..523447748 100644 --- a/tests/test_cases/src/test_tsi_tcp_guest_connect.rs +++ b/tests/test_cases/src/test_tsi_tcp_guest_connect.rs @@ -24,6 +24,7 @@ mod host { use crate::{Test, TestSetup}; use crate::{krun_call, krun_call_u32}; use krun_sys::*; + use std::os::fd::AsRawFd; use std::thread; impl Test for TestTsiTcpGuestConnect { @@ -31,9 +32,20 @@ mod host { let listener = self.tcp_tester.create_server_socket(); thread::spawn(move || self.tcp_tester.run_server(listener)); unsafe { - krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; + krun_call!(krun_init_log( + KRUN_LOG_TARGET_DEFAULT, + KRUN_LOG_LEVEL_TRACE, + KRUN_LOG_STYLE_AUTO, + 0 + ))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; + krun_call!(krun_add_virtio_console_default( + ctx, + std::io::stdin().as_raw_fd(), + std::io::stdout().as_raw_fd(), + std::io::stderr().as_raw_fd(), + ))?; setup_fs_and_enter(ctx, test_setup)?; } Ok(()) diff --git a/tests/test_cases/src/test_tsi_tcp_guest_listen.rs b/tests/test_cases/src/test_tsi_tcp_guest_listen.rs index 4a0f9fd62..adf6aa43e 100644 --- a/tests/test_cases/src/test_tsi_tcp_guest_listen.rs +++ b/tests/test_cases/src/test_tsi_tcp_guest_listen.rs @@ -23,6 +23,7 @@ mod host { use crate::{Test, TestSetup, krun_call, krun_call_u32}; use krun_sys::*; use std::ffi::CString; + use std::os::fd::AsRawFd; use std::ptr::null; use std::thread; @@ -33,7 +34,12 @@ mod host { self.tcp_tester.run_client(); }); - krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; + krun_call!(krun_init_log( + KRUN_LOG_TARGET_DEFAULT, + KRUN_LOG_LEVEL_TRACE, + KRUN_LOG_STYLE_AUTO, + 0 + ))?; let ctx = krun_call_u32!(krun_create_ctx())?; let port_mapping = format!("{PORT}:{PORT}"); let port_mapping = CString::new(port_mapping).unwrap(); @@ -41,6 +47,12 @@ mod host { krun_call!(krun_set_port_map(ctx, port_map.as_ptr()))?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; + krun_call!(krun_add_virtio_console_default( + ctx, + std::io::stdin().as_raw_fd(), + std::io::stdout().as_raw_fd(), + std::io::stderr().as_raw_fd(), + ))?; setup_fs_and_enter(ctx, test_setup)?; println!("OK"); } diff --git a/tests/test_cases/src/test_virtiofs_misc.rs b/tests/test_cases/src/test_virtiofs_misc.rs index e89e28530..da413e883 100644 --- a/tests/test_cases/src/test_virtiofs_misc.rs +++ b/tests/test_cases/src/test_virtiofs_misc.rs @@ -13,13 +13,25 @@ mod host { use crate::{krun_call, krun_call_u32}; use krun_sys::*; use std::io::Read; + use std::os::fd::AsRawFd; impl Test for TestVirtioFsMisc { fn start_vm(self: Box, test_setup: TestSetup) -> anyhow::Result<()> { unsafe { - krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; + krun_call!(krun_init_log( + KRUN_LOG_TARGET_DEFAULT, + KRUN_LOG_LEVEL_TRACE, + KRUN_LOG_STYLE_AUTO, + 0 + ))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 1024))?; + krun_call!(krun_add_virtio_console_default( + ctx, + std::io::stdin().as_raw_fd(), + std::io::stdout().as_raw_fd(), + std::io::stderr().as_raw_fd(), + ))?; setup_fs_and_enter(ctx, test_setup)?; } Ok(()) diff --git a/tests/test_cases/src/test_virtiofs_root_ro.rs b/tests/test_cases/src/test_virtiofs_root_ro.rs index 21332153e..8e18ac1d5 100644 --- a/tests/test_cases/src/test_virtiofs_root_ro.rs +++ b/tests/test_cases/src/test_virtiofs_root_ro.rs @@ -22,6 +22,7 @@ mod host { use krun_sys::*; use std::ffi::CString; use std::fs; + use std::os::fd::AsRawFd; use std::os::unix::ffi::OsStrExt; use std::ptr::null; @@ -50,6 +51,12 @@ mod host { ))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; + krun_call!(krun_add_virtio_console_default( + ctx, + std::io::stdin().as_raw_fd(), + std::io::stdout().as_raw_fd(), + std::io::stderr().as_raw_fd(), + ))?; // Use "/dev/root" tag (KRUN_FS_ROOT_TAG) with read_only=true krun_call!(krun_add_virtiofs3( diff --git a/tests/test_cases/src/test_vm_config.rs b/tests/test_cases/src/test_vm_config.rs index bd5c9addf..a0c0d7795 100644 --- a/tests/test_cases/src/test_vm_config.rs +++ b/tests/test_cases/src/test_vm_config.rs @@ -13,13 +13,25 @@ mod host { use crate::{Test, TestSetup}; use crate::{krun_call, krun_call_u32}; use krun_sys::*; + use std::os::fd::AsRawFd; impl Test for TestVmConfig { fn start_vm(self: Box, test_setup: TestSetup) -> anyhow::Result<()> { unsafe { - krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; + krun_call!(krun_init_log( + KRUN_LOG_TARGET_DEFAULT, + KRUN_LOG_LEVEL_TRACE, + KRUN_LOG_STYLE_AUTO, + 0 + ))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, self.num_cpus, self.ram_mib))?; + krun_call!(krun_add_virtio_console_default( + ctx, + std::io::stdin().as_raw_fd(), + std::io::stdout().as_raw_fd(), + std::io::stderr().as_raw_fd(), + ))?; setup_fs_and_enter(ctx, test_setup)?; } Ok(()) diff --git a/tests/test_cases/src/test_vsock_guest_connect.rs b/tests/test_cases/src/test_vsock_guest_connect.rs index 84fec166d..6e1a27a3b 100644 --- a/tests/test_cases/src/test_vsock_guest_connect.rs +++ b/tests/test_cases/src/test_vsock_guest_connect.rs @@ -41,6 +41,7 @@ mod host { use krun_sys::*; use std::ffi::CString; use std::io::Write; + use std::os::fd::AsRawFd; use std::os::unix::net::UnixListener; use std::os::unix::prelude::OsStrExt; use std::{mem, thread}; @@ -73,6 +74,12 @@ mod host { sock_path_cstr.as_ptr() ))?; krun_call!(krun_set_vm_config(ctx, 1, 1024))?; + krun_call!(krun_add_virtio_console_default( + ctx, + std::io::stdin().as_raw_fd(), + std::io::stdout().as_raw_fd(), + std::io::stderr().as_raw_fd(), + ))?; setup_fs_and_enter(ctx, test_setup)?; } Ok(()) From 02099c962c67d698da1a9d77d0e5570e4b382d7b Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Wed, 20 May 2026 18:02:16 +0200 Subject: [PATCH 09/41] lib: remove krun_disable_implicit_console, krun_set_console_output, and implicit console Console creation is now fully explicit via krun_add_virtio_console_default or krun_add_virtio_console_multiport. No console is created unless the caller requests one. Remove the disable_implicit_console field from VmResources, the implicit console and serial device creation paths in builder.rs, the console_output field and setter on VmResources, and krun_set_console_output (kept only behind cfg(aws-nitro) where NitroEnclave still needs it). Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- examples/consoles.c | 6 -- include/libkrun.h | 41 +--------- src/libkrun/src/lib.rs | 61 ++++++-------- src/vmm/src/builder.rs | 82 +++---------------- src/vmm/src/resources.rs | 17 +--- .../test_cases/src/test_multiport_console.rs | 9 +- 6 files changed, 52 insertions(+), 164 deletions(-) diff --git a/examples/consoles.c b/examples/consoles.c index fe3a98271..23ee8226f 100644 --- a/examples/consoles.c +++ b/examples/consoles.c @@ -125,12 +125,6 @@ int main(int argc, char *const argv[]) int ctx_id = krun_create_ctx(); if (ctx_id < 0) { errno = -ctx_id; perror("krun_create_ctx"); return 1; } - if ((err = krun_disable_implicit_console(ctx_id))) { - errno = -err; - perror("krun_disable_implicit_console"); - return 1; - } - int console_id = krun_add_virtio_console_multiport(ctx_id); if (console_id < 0) { errno = -console_id; diff --git a/include/libkrun.h b/include/libkrun.h index efcb2af5c..fb3e72a5f 100644 --- a/include/libkrun.h +++ b/include/libkrun.h @@ -911,22 +911,6 @@ int32_t krun_add_vsock(uint32_t ctx_id, uint32_t tsi_features); */ int32_t krun_get_shutdown_eventfd(uint32_t ctx_id); -/** - * Configures the console device to ignore stdin and write the output to "c_filepath". - * - * Arguments: - * "ctx_id" - the configuration context ID. - * "filepath" - a null-terminated string representing the path of the file to write the - * console output. - * - * Notes: - * This API only applies to the implicitly created console. If the implicit console is - * disabled via `krun_disable_implicit_console` the operation is a NOOP. Additionally, - * this API does not have any effect on consoles created via the `krun_add_*_console_default` - * APIs. - */ -int32_t krun_set_console_output(uint32_t ctx_id, const char *c_filepath); - /** * Configures uid which is set right before the microVM is started. * @@ -1037,19 +1021,7 @@ int32_t krun_split_irqchip(uint32_t ctx_id, bool enable); * krun_disable_implicit_* functions now to ease migration. */ -/* - * Do not create an implicit console device in the guest. By using this API, - * libkrun will create zero console devices on behalf of the user. Any - * console devices needed by the user must be added manually via other API - * calls. - * - * Arguments: - * "ctx_id" - the configuration context ID. - * - * Returns: - * Zero on success or a negative error number on failure. - */ -int32_t krun_disable_implicit_console(uint32_t ctx_id); + /** * Do not inject the default init binary (/init.krun) into the root @@ -1170,9 +1142,7 @@ int32_t krun_set_kernel_console(uint32_t ctx_id, const char *console_id); * * The function can be called multiple times for adding multiple virtio-console devices. * In the guest, the consoles will appear in the same order as they are added (that is, - * the first added console will be "hvc0", the second "hvc1", ...). However, if the - * implicit console is not disabled via `krun_disable_implicit_console`, the first - * console created with the function will occupy the "hvc1" ID. + * the first added console will be "hvc0", the second "hvc1", ...). * * This function attaches a multi port virtio-console to the guest. If the input, output and error * file descriptors are TTYs, the device will be created with just a single console port (`err_fd` @@ -1200,9 +1170,7 @@ int32_t krun_add_virtio_console_default(uint32_t ctx_id, * * The function can be called multiple times for adding multiple serial devices. * In the guest, the consoles will appear in the same order as they are added (that is, - * the first added console will be "ttyS0", the second "ttyS1", ...). However, if the - * implicit console is not disabled via `krun_disable_implicit_console` on aarch64 or macOS, - * the first console created with the function will occupy the "ttyS1" ID. + * the first added console will be "ttyS0", the second "ttyS1", ...). * * Arguments: * "ctx_id" - the configuration context ID. @@ -1225,8 +1193,7 @@ int32_t krun_add_serial_console_default(uint32_t ctx_id, * * The function can be called multiple times for adding multiple virtio-console devices. * Each device appears in the guest with port 0 accessible as /dev/hvcN (hvc0, hvc1, etc.) in the order - * devices are added. If the implicit console is not disabled via `krun_disable_implicit_console`, - * the first explicitly added device will occupy the "hvc1" ID. Additional ports within each device + * devices are added. Additional ports within each device * (port 1, 2, ...) appear as /dev/vportNpM character devices. * * Arguments: diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 39309c796..50985c60a 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -164,6 +164,8 @@ struct ContextConfig { shutdown_efd: Option, gpu_virgl_flags: Option, gpu_shm_size: Option, + /// Console output path, only used by the aws-nitro TryFrom path. + #[cfg(feature = "aws-nitro")] console_output: Option, vmm_uid: Option, vmm_gid: Option, @@ -1732,27 +1734,12 @@ pub unsafe extern "C" fn krun_add_vhost_user_device( -libc::ENOTSUP } -#[allow(unused_assignments)] -#[unsafe(no_mangle)] -pub extern "C" fn krun_get_shutdown_eventfd(ctx_id: u32) -> i32 { - match CTX_MAP.lock().unwrap().entry(ctx_id) { - Entry::Occupied(mut ctx_cfg) => { - let cfg = ctx_cfg.get_mut(); - if let Some(efd) = cfg.shutdown_efd.as_ref() { - #[cfg(target_os = "macos")] - return efd.get_write_fd(); - #[cfg(target_os = "linux")] - return efd.as_raw_fd(); - } else { - -libc::EINVAL - } - } - Entry::Vacant(_) => -libc::ENOENT, - } -} - +// FIXME: aws-nitro builds its own NitroEnclave from ContextConfig and needs +// the console output path directly. This should be replaced with a proper +// console configuration in the nitro path. #[allow(clippy::missing_safety_doc)] #[unsafe(no_mangle)] +#[cfg(feature = "aws-nitro")] pub unsafe extern "C" fn krun_set_console_output(ctx_id: u32, c_filepath: *const c_char) -> i32 { unsafe { let filepath = match CStr::from_ptr(c_filepath).to_str() { @@ -1775,6 +1762,25 @@ pub unsafe extern "C" fn krun_set_console_output(ctx_id: u32, c_filepath: *const } } +#[allow(unused_assignments)] +#[unsafe(no_mangle)] +pub extern "C" fn krun_get_shutdown_eventfd(ctx_id: u32) -> i32 { + match CTX_MAP.lock().unwrap().entry(ctx_id) { + Entry::Occupied(mut ctx_cfg) => { + let cfg = ctx_cfg.get_mut(); + if let Some(efd) = cfg.shutdown_efd.as_ref() { + #[cfg(target_os = "macos")] + return efd.get_write_fd(); + #[cfg(target_os = "linux")] + return efd.as_raw_fd(); + } else { + -libc::EINVAL + } + } + Entry::Vacant(_) => -libc::ENOENT, + } +} + #[allow(clippy::missing_safety_doc)] #[unsafe(no_mangle)] pub unsafe extern "C" fn krun_set_nested_virt(ctx_id: u32, enabled: bool) -> i32 { @@ -2458,19 +2464,6 @@ pub unsafe extern "C" fn krun_get_default_init( KRUN_SUCCESS } -#[unsafe(no_mangle)] -pub extern "C" fn krun_disable_implicit_console(ctx_id: u32) -> i32 { - match CTX_MAP.lock().unwrap().entry(ctx_id) { - Entry::Occupied(mut ctx_cfg) => { - let cfg = ctx_cfg.get_mut(); - cfg.vmr.disable_implicit_console = true; - } - Entry::Vacant(_) => return -libc::ENOENT, - } - - KRUN_SUCCESS -} - #[unsafe(no_mangle)] pub extern "C" fn krun_disable_implicit_vsock(ctx_id: u32) -> i32 { match CTX_MAP.lock().unwrap().entry(ctx_id) { @@ -2816,10 +2809,6 @@ pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 { ctx_cfg.vmr.set_gpu_shm_size(shm_size); } - if let Some(console_output) = ctx_cfg.console_output { - ctx_cfg.vmr.set_console_output(console_output); - } - if let Some(gid) = ctx_cfg.vmm_gid && unsafe { libc::setgid(gid) } != 0 { diff --git a/src/vmm/src/builder.rs b/src/vmm/src/builder.rs index 21f5ee164..59f3ca7aa 100644 --- a/src/vmm/src/builder.rs +++ b/src/vmm/src/builder.rs @@ -14,7 +14,6 @@ use std::fs::File; use std::io::{self, IsTerminal, Read}; use std::os::fd::AsRawFd; use std::os::fd::{BorrowedFd, FromRawFd}; -use std::path::PathBuf; use std::sync::atomic::AtomicI32; use std::sync::{Arc, Mutex}; @@ -728,17 +727,6 @@ pub fn build_microvm( let mut serial_devices = Vec::new(); - // Create the legacy serial device if we're booting from a firmware - if vm_resources.firmware_config.is_some() && !vm_resources.disable_implicit_console { - serial_devices.push(setup_serial_device( - event_manager, - None, - None, - // Uncomment this to get EFI output when debugging EDK2. - //Some(Box::new(io::stdout())), - )?); - }; - // We can't call to `setup_terminal_raw_mode` until `Vmm` is created, // so let's keep track of FDs connected to legacy serial devices here // and set raw mode on them later. @@ -995,29 +983,15 @@ pub fn build_microvm( attach_rng_device(&mut vmm, event_manager, intc.clone())?; } } - let mut console_id = 0; - if !vm_resources.disable_implicit_console { - attach_console_devices( - &mut vmm, - event_manager, - intc.clone(), - vm_resources, - None, - console_id, - )?; - console_id += 1; - } - - for console_cfg in vm_resources.virtio_consoles.iter() { + for (console_id, console_cfg) in vm_resources.virtio_consoles.iter().enumerate() { attach_console_devices( &mut vmm, event_manager, intc.clone(), vm_resources, Some(console_cfg), - console_id, + console_id as u32, )?; - console_id += 1; } #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] @@ -2129,40 +2103,14 @@ fn attach_fs_devices( fn autoconfigure_console_ports( vmm: &mut Vmm, - vm_resources: &VmResources, + _vm_resources: &VmResources, cfg: Option<&DefaultVirtioConsoleConfig>, - creating_implicit_console: bool, ) -> std::result::Result, StartMicrovmError> { - use self::StartMicrovmError::*; - - let mut console_output_path: Option = None; - if let Some(path) = vm_resources.console_output.clone() - && !vm_resources.disable_implicit_console - && creating_implicit_console + let (input_fd, output_fd, err_fd) = match cfg { + Some(c) => (c.input_fd, c.output_fd, c.err_fd), + None => (STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO), + }; { - console_output_path = Some(path) - } - - if let Some(console_output_path) = console_output_path { - let file = File::create(console_output_path).map_err(OpenConsoleFile)?; - // Manually emulate our Legacy behavior: In the case of output_path we have always used the - // stdin to determine the console size - let stdin_fd = unsafe { BorrowedFd::borrow_raw(STDIN_FILENO) }; - let term_fd = if isatty(stdin_fd).is_ok_and(|v| v) { - port_io::term_fd(stdin_fd.as_raw_fd()).unwrap() - } else { - port_io::term_fixed_size(0, 0) - }; - Ok(vec![PortDescription::console( - Some(port_io::input_empty().unwrap()), - Some(port_io::output_file(file).unwrap()), - term_fd, - )]) - } else { - let (input_fd, output_fd, err_fd) = match cfg { - Some(c) => (c.input_fd, c.output_fd, c.err_fd), - None => (STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO), - }; let input_is_terminal = input_fd >= 0 && isatty(unsafe { BorrowedFd::borrow_raw(input_fd) }).unwrap_or(false); let output_is_terminal = @@ -2190,7 +2138,8 @@ fn autoconfigure_console_ports( forwarding_sigint = true; let sigint_input = port_io::PortInputSigInt::new(); let sigint_input_fd = sigint_input.sigint_evt().as_raw_fd(); - register_sigint_handler(sigint_input_fd).map_err(RegisterFsSigwinch)?; + register_sigint_handler(sigint_input_fd) + .map_err(StartMicrovmError::RegisterFsSigwinch)?; Some(Box::new(sigint_input) as _) } #[cfg(not(target_os = "linux"))] @@ -2323,16 +2272,11 @@ fn attach_console_devices( ) -> std::result::Result<(), StartMicrovmError> { use self::StartMicrovmError::*; - let creating_implicit_console = cfg.is_none(); - let ports = match cfg { - None => autoconfigure_console_ports(vmm, vm_resources, None, creating_implicit_console)?, - Some(VirtioConsoleConfigMode::Autoconfigure(autocfg)) => autoconfigure_console_ports( - vmm, - vm_resources, - Some(autocfg), - creating_implicit_console, - )?, + None => autoconfigure_console_ports(vmm, vm_resources, None)?, + Some(VirtioConsoleConfigMode::Autoconfigure(autocfg)) => { + autoconfigure_console_ports(vmm, vm_resources, Some(autocfg))? + } Some(VirtioConsoleConfigMode::Explicit(ports)) => create_explicit_ports(vmm, ports)?, }; diff --git a/src/vmm/src/resources.rs b/src/vmm/src/resources.rs index 4e1a4889e..4ad037687 100644 --- a/src/vmm/src/resources.rs +++ b/src/vmm/src/resources.rs @@ -8,6 +8,7 @@ use std::fs::File; #[cfg(feature = "tee")] use std::io::BufReader; use std::os::fd::RawFd; +#[cfg(feature = "tee")] use std::path::PathBuf; #[cfg(feature = "tee")] @@ -189,16 +190,13 @@ pub struct VmResources { #[cfg(feature = "vhost-user")] /// Vhost-user device configurations pub vhost_user_devices: Vec, - /// File to send console output. - pub console_output: Option, /// SMBIOS OEM Strings pub smbios_oem_strings: Option>, /// Whether to enable nested virtualization. pub nested_enabled: bool, /// Whether to enable split irqchip pub split_irqchip: bool, - /// Do not create an implicit console device in the guest - pub disable_implicit_console: bool, + /// The console id to use for console= in the kernel cmdline pub kernel_console: Option, /// Serial consoles to attach to the guest @@ -358,10 +356,6 @@ impl VmResources { self.gpu_shm_size = Some(shm_size); } - pub fn set_console_output(&mut self, console_output: PathBuf) { - self.console_output = Some(console_output); - } - /// Sets a network device to be attached when the VM starts. #[cfg(feature = "net")] pub fn add_network_interface( @@ -400,12 +394,10 @@ impl VmResources { #[cfg(test)] mod tests { - #[cfg(feature = "gpu")] - use crate::resources::DisplayBackendConfig; use crate::resources::VmResources; use crate::vmm_config::kernel_cmdline::KernelCmdlineConfig; use crate::vmm_config::machine_config::{CpuFeaturesTemplate, VmConfig, VmConfigError}; - use crate::vmm_config::vsock::tests::{TempSockFile, default_config}; + use crate::vmm_config::vsock::tests::{default_config, TempSockFile}; use crate::vstate::VcpuConfig; use utils::tempfile::TempFile; @@ -440,11 +432,10 @@ mod tests { input_backends: Vec::new(), #[cfg(feature = "vhost-user")] vhost_user_devices: Vec::new(), - console_output: None, smbios_oem_strings: None, nested_enabled: false, split_irqchip: false, - disable_implicit_console: false, + serial_consoles: Vec::new(), virtio_consoles: Vec::new(), kernel_console: None, diff --git a/tests/test_cases/src/test_multiport_console.rs b/tests/test_cases/src/test_multiport_console.rs index 2948b7369..144cee482 100644 --- a/tests/test_cases/src/test_multiport_console.rs +++ b/tests/test_cases/src/test_multiport_console.rs @@ -50,11 +50,14 @@ mod host { impl Test for TestMultiportConsole { fn start_vm(self: Box, test_setup: TestSetup) -> anyhow::Result<()> { unsafe { - krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; + krun_call!(krun_init_log( + KRUN_LOG_TARGET_DEFAULT, + KRUN_LOG_LEVEL_TRACE, + KRUN_LOG_STYLE_AUTO, + 0 + ))?; let ctx = krun_call_u32!(krun_create_ctx())?; - krun_call!(krun_disable_implicit_console(ctx))?; - // Add a default console (as with other tests this uses stdout for writing "OK") krun_call!(krun_add_virtio_console_default( ctx, From 5b7a3be83f8a428a439a37dadcf30fdbf4c3734f Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Wed, 20 May 2026 18:06:49 +0200 Subject: [PATCH 10/41] tests/examples: use explicit krun_add_vsock everywhere - test_tsi_tcp_guest_connect: add krun_add_vsock(ctx, KRUN_TSI_HIJACK_INET) - test_tsi_tcp_guest_listen: same - test_vsock_guest_connect: add krun_add_vsock(ctx, 0) - chroot_vm.c: replace krun_disable_implicit_vsock + vhost-user with explicit krun_add_vsock when not using vhost-user-vsock No test or example relies on implicit vsock creation anymore. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- examples/chroot_vm.c | 18 ++++++++++-------- .../src/test_tsi_tcp_guest_connect.rs | 1 + .../src/test_tsi_tcp_guest_listen.rs | 1 + .../test_cases/src/test_vsock_guest_connect.rs | 12 +++++++++--- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/examples/chroot_vm.c b/examples/chroot_vm.c index 9bfce23f6..990c92e13 100644 --- a/examples/chroot_vm.c +++ b/examples/chroot_vm.c @@ -363,14 +363,8 @@ int main(int argc, char *const argv[]) printf("Using vhost-user sound backend at %s\n", cmdline.vhost_user_snd_socket); } - // Configure vhost-user vsock if requested + // Configure vsock: either vhost-user or built-in with TSI if (cmdline.vhost_user_vsock_socket != NULL) { - // Disable the implicit vsock device to avoid conflict - if (!check_krun_error(krun_disable_implicit_vsock(ctx_id), - "Error disabling implicit vsock")) { - return -1; - } - if (!check_krun_error(krun_add_vhost_user_device(ctx_id, KRUN_VIRTIO_DEVICE_VSOCK, cmdline.vhost_user_vsock_socket, NULL, KRUN_VHOST_USER_VSOCK_NUM_QUEUES, @@ -425,8 +419,16 @@ int main(int argc, char *const argv[]) return -1; } + // Add built-in vsock with TSI when not using vhost-user-vsock + if (cmdline.vhost_user_vsock_socket == NULL) { + if (err = krun_add_vsock(ctx_id, KRUN_TSI_HIJACK_INET)) { + errno = -err; + perror("Error configuring vsock"); + return -1; + } + } + // Map port 18000 in the host to 8000 in the guest (if networking uses TSI) - // Skip port mapping when using vhost-user-vsock (TSI requires built-in vsock) if (cmdline.net_mode == NET_MODE_TSI && cmdline.vhost_user_vsock_socket == NULL) { if (err = krun_set_port_map(ctx_id, &port_map[0])) { errno = -err; diff --git a/tests/test_cases/src/test_tsi_tcp_guest_connect.rs b/tests/test_cases/src/test_tsi_tcp_guest_connect.rs index 523447748..3369371e7 100644 --- a/tests/test_cases/src/test_tsi_tcp_guest_connect.rs +++ b/tests/test_cases/src/test_tsi_tcp_guest_connect.rs @@ -39,6 +39,7 @@ mod host { 0 ))?; let ctx = krun_call_u32!(krun_create_ctx())?; + krun_call!(krun_add_vsock(ctx, KRUN_TSI_HIJACK_INET))?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; krun_call!(krun_add_virtio_console_default( ctx, diff --git a/tests/test_cases/src/test_tsi_tcp_guest_listen.rs b/tests/test_cases/src/test_tsi_tcp_guest_listen.rs index adf6aa43e..61c3d9b78 100644 --- a/tests/test_cases/src/test_tsi_tcp_guest_listen.rs +++ b/tests/test_cases/src/test_tsi_tcp_guest_listen.rs @@ -45,6 +45,7 @@ mod host { let port_mapping = CString::new(port_mapping).unwrap(); let port_map = [port_mapping.as_ptr(), null()]; + krun_call!(krun_add_vsock(ctx, KRUN_TSI_HIJACK_INET))?; krun_call!(krun_set_port_map(ctx, port_map.as_ptr()))?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; krun_call!(krun_add_virtio_console_default( diff --git a/tests/test_cases/src/test_vsock_guest_connect.rs b/tests/test_cases/src/test_vsock_guest_connect.rs index 6e1a27a3b..2c101b008 100644 --- a/tests/test_cases/src/test_vsock_guest_connect.rs +++ b/tests/test_cases/src/test_vsock_guest_connect.rs @@ -36,8 +36,8 @@ mod host { use super::*; use crate::common::setup_fs_and_enter; - use crate::{Test, TestSetup}; use crate::{krun_call, krun_call_u32}; + use crate::{Test, TestSetup}; use krun_sys::*; use std::ffi::CString; use std::io::Write; @@ -66,8 +66,14 @@ mod host { thread::spawn(move || server(listener)); unsafe { - krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; + krun_call!(krun_init_log( + KRUN_LOG_TARGET_DEFAULT, + KRUN_LOG_LEVEL_TRACE, + KRUN_LOG_STYLE_AUTO, + 0 + ))?; let ctx = krun_call_u32!(krun_create_ctx())?; + krun_call!(krun_add_vsock(ctx, 0))?; krun_call!(krun_add_vsock_port( ctx, VSOCK_PORT, @@ -93,7 +99,7 @@ mod guest { use crate::Test; use nix::libc::VMADDR_CID_HOST; - use nix::sys::socket::{AddressFamily, SockFlag, SockType, VsockAddr, connect, socket}; + use nix::sys::socket::{connect, socket, AddressFamily, SockFlag, SockType, VsockAddr}; use std::io::Write; use std::os::fd::AsRawFd; From 9b6a0f23ae5b627cbaff940fdd8cab22ed7ef063 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Wed, 20 May 2026 18:06:57 +0200 Subject: [PATCH 11/41] lib: remove krun_disable_implicit_vsock and implicit vsock creation Vsock creation is now fully explicit via krun_add_vsock(). No vsock device is created unless the caller requests one. Remove the Implicit variant from VsockConfig, the implicit vsock creation heuristics in krun_start_enter, and krun_disable_implicit_vsock. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- include/libkrun.h | 18 ------------------ src/libkrun/src/lib.rs | 40 ---------------------------------------- src/vmm/src/resources.rs | 8 +++----- 3 files changed, 3 insertions(+), 63 deletions(-) diff --git a/include/libkrun.h b/include/libkrun.h index fb3e72a5f..496a12a4f 100644 --- a/include/libkrun.h +++ b/include/libkrun.h @@ -882,10 +882,6 @@ int32_t krun_add_vsock_port2(uint32_t ctx_id, /** * Add a vsock device with specified TSI features. * - * By default, libkrun creates a vsock device implicitly with TSI hijacking - * enabled based on heuristics. To use this function, you must first call - * krun_disable_implicit_vsock() to disable the implicit vsock device. - * * Currently only one vsock device is supported. Calling this function * multiple times will return an error. * @@ -1111,20 +1107,6 @@ int32_t krun_fs_add_overlay_file(uint32_t ctx_id, const char *fs_tag, int32_t krun_fs_add_overlay_dir(uint32_t ctx_id, const char *fs_tag, const char *path, uint32_t mode); -/** - * Disable the implicit vsock device. - * - * By default, libkrun creates a vsock device automatically. This function - * disables that behavior entirely - no vsock device will be created. - * - * Arguments: - * "ctx_id" - the configuration context ID. - * - * Returns: - * Zero on success or a negative error number on failure. - */ -int32_t krun_disable_implicit_vsock(uint32_t ctx_id); - /* * Specify the value of `console=` in the kernel commandline. * diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 50985c60a..1e40ec73e 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -2464,19 +2464,6 @@ pub unsafe extern "C" fn krun_get_default_init( KRUN_SUCCESS } -#[unsafe(no_mangle)] -pub extern "C" fn krun_disable_implicit_vsock(ctx_id: u32) -> i32 { - match CTX_MAP.lock().unwrap().entry(ctx_id) { - Entry::Occupied(mut ctx_cfg) => { - let cfg = ctx_cfg.get_mut(); - cfg.vsock_config = VsockConfig::Disabled; - } - Entry::Vacant(_) => return -libc::ENOENT, - } - - KRUN_SUCCESS -} - #[unsafe(no_mangle)] pub extern "C" fn krun_add_vsock(ctx_id: u32, tsi_features: u32) -> i32 { let tsi_flags = match TsiFlags::from_bits(tsi_features) { @@ -2773,33 +2760,6 @@ pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 { }; ctx_cfg.vmr.set_vsock_device(vsock_device_config).unwrap(); } - VsockConfig::Implicit => { - // Implicit vsock configuration - use heuristics - // Check if TSI should be enabled based on network configuration - #[cfg(feature = "net")] - let enable_tsi = ctx_cfg.vmr.net.list.is_empty(); - #[cfg(not(feature = "net"))] - let enable_tsi = true; - - let has_ipc_map = ctx_cfg.unix_ipc_port_map.is_some(); - - if enable_tsi || has_ipc_map { - let (tsi_flags, host_port_map) = if enable_tsi { - (TsiFlags::HIJACK_INET, ctx_cfg.tsi_port_map) - } else { - (TsiFlags::empty(), None) - }; - - let vsock_device_config = VsockDeviceConfig { - vsock_id: "vsock0".to_string(), - guest_cid: 3, - host_port_map, - unix_ipc_port_map: ctx_cfg.unix_ipc_port_map.clone(), - tsi_flags, - }; - ctx_cfg.vmr.set_vsock_device(vsock_device_config).unwrap(); - } - } } if let Some(virgl_flags) = ctx_cfg.gpu_virgl_flags { diff --git a/src/vmm/src/resources.rs b/src/vmm/src/resources.rs index 4ad037687..639cca4b2 100644 --- a/src/vmm/src/resources.rs +++ b/src/vmm/src/resources.rs @@ -132,13 +132,11 @@ pub enum PortConfig { /// Configuration for the vsock device #[derive(Debug, Default, Clone, Eq, PartialEq)] pub enum VsockConfig { - /// Default behavior - vsock created implicitly with heuristics-based TSI + /// No vsock device #[default] - Implicit, + Disabled, /// Explicit configuration with specified TSI features Explicit { tsi_flags: TsiFlags }, - /// Vsock device disabled - Disabled, } /// A data structure that encapsulates the device configurations @@ -397,7 +395,7 @@ mod tests { use crate::resources::VmResources; use crate::vmm_config::kernel_cmdline::KernelCmdlineConfig; use crate::vmm_config::machine_config::{CpuFeaturesTemplate, VmConfig, VmConfigError}; - use crate::vmm_config::vsock::tests::{default_config, TempSockFile}; + use crate::vmm_config::vsock::tests::{TempSockFile, default_config}; use crate::vstate::VcpuConfig; use utils::tempfile::TempFile; From 9d9144ca4a9dcbf8103455a61b77129afca5908a Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Wed, 20 May 2026 18:07:23 +0200 Subject: [PATCH 12/41] include: remove stale implicit resource creation comment All krun_disable_implicit_* functions are gone. The 2.0 API requires explicit resource creation. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- include/libkrun.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/include/libkrun.h b/include/libkrun.h index 496a12a4f..63e1ffb01 100644 --- a/include/libkrun.h +++ b/include/libkrun.h @@ -1010,15 +1010,6 @@ int32_t krun_get_max_vcpus(void); */ int32_t krun_split_irqchip(uint32_t ctx_id, bool enable); -/* - * NOTE: Implicit resource creation is a legacy convenience. The 2.0 API - * (see https://github.com/containers/libkrun/issues/634) will not create - * any implicit resources. Callers should start using the - * krun_disable_implicit_* functions now to ease migration. - */ - - - /** * Do not inject the default init binary (/init.krun) into the root * filesystem. Must be called before krun_set_root(). From 2b740251404fa985b36edb0a9972900fcf967839 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Wed, 6 May 2026 15:00:35 -0400 Subject: [PATCH 13/41] init: scaffold Rust krun-init crate and wire into build system Replace the C-based build_default_init() in src/devices/build.rs with a Rust crate (init/) compiled via a cargo subprocess. The new build.rs probes whether the active rustc supports the $(uname -m)-unknown-linux-musl target (for a static binary) and falls back to the native target with a user-visible warning if not. The KRUN_INIT_BINARY_PATH override mechanism is preserved so that out-of-tree binaries (e.g. pre-built SEV or TDX images) can still be injected without rebuilding. Signed-off-by: Jake Correnti Assisted-by: Claude Code:claude-sonnet-4.6 --- Cargo.lock | 340 ++++++++++++++++++------------------ Cargo.toml | 1 + Makefile | 3 + init/Cargo.toml | 22 +++ init/src/main.rs | 3 + src/arch/Cargo.toml | 10 +- src/cpuid/Cargo.toml | 6 +- src/devices/Cargo.toml | 6 +- src/init-blob/Cargo.toml | 5 + src/init-blob/build.rs | 181 ++++++++++++++----- src/kernel/Cargo.toml | 2 +- src/libkrun/Cargo.toml | 10 +- src/rutabaga_gfx/Cargo.toml | 2 +- src/smbios/Cargo.toml | 2 +- src/utils/Cargo.toml | 4 +- src/vmm/Cargo.toml | 10 +- 16 files changed, 372 insertions(+), 235 deletions(-) create mode 100644 init/Cargo.toml create mode 100644 init/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 3847ccbde..bf662792e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,9 +25,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -46,9 +46,9 @@ checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -92,9 +92,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base64" @@ -127,7 +127,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cexpr", "clang-sys", "itertools", @@ -135,7 +135,7 @@ dependencies = [ "quote", "regex", "rustc-hash", - "shlex", + "shlex 1.3.0", "syn", ] @@ -167,9 +167,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "block-buffer" @@ -182,9 +182,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bzip2" @@ -216,14 +216,14 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.57" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -339,15 +339,15 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "env_filter" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", "regex", @@ -355,9 +355,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ "anstream", "anstyle", @@ -384,13 +384,12 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" dependencies = [ "cfg-if", "libc", - "libredox", ] [[package]] @@ -421,6 +420,30 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -474,9 +497,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ "allocator-api2", "equivalent", @@ -497,9 +520,9 @@ checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] name = "imago" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e8e4b92aa0dd860579cfba776dbf0918a3a7ac5cb601af7d3fc835e71592a5b" +checksum = "ae7cfee876c698a1a2ed9c705ab18f21acbed82110f19b51cc458de73426fe2c" dependencies = [ "async-trait", "bincode", @@ -517,12 +540,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -560,9 +583,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" dependencies = [ "jiff-static", "log", @@ -573,9 +596,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" dependencies = [ "proc-macro2", "quote", @@ -594,10 +617,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -630,7 +655,7 @@ dependencies = [ "linux-loader", "tdx", "vm-memory", - "vmm-sys-util 0.14.0", + "vmm-sys-util", ] [[package]] @@ -657,7 +682,7 @@ version = "0.1.0-1.18.0" dependencies = [ "kvm-bindings", "kvm-ioctls", - "vmm-sys-util 0.14.0", + "vmm-sys-util", ] [[package]] @@ -682,13 +707,13 @@ dependencies = [ "log", "lru", "nix 0.30.1", - "rand 0.9.2", + "rand 0.9.4", "thiserror 2.0.18", "vhost", "virtio-bindings", "vm-fdt", "vm-memory", - "vmm-sys-util 0.15.0", + "vmm-sys-util", "zerocopy", ] @@ -697,7 +722,7 @@ name = "krun-display" version = "0.1.0" dependencies = [ "bindgen", - "bitflags 2.11.0", + "bitflags 2.11.1", "log", "static_assertions", "thiserror 2.0.18", @@ -713,12 +738,22 @@ dependencies = [ "log", ] +[[package]] +name = "krun-init" +version = "0.1.0-1.18.1" +dependencies = [ + "libc", + "nix 0.30.1", + "serde", + "serde_json", +] + [[package]] name = "krun-input" version = "0.1.0" dependencies = [ "bindgen", - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "log", "static_assertions", @@ -753,7 +788,7 @@ dependencies = [ "pkg-config", "remain", "thiserror 1.0.69", - "vmm-sys-util 0.14.0", + "vmm-sys-util", "winapi", "zerocopy", ] @@ -775,7 +810,7 @@ dependencies = [ "libc", "log", "nix 0.30.1", - "vmm-sys-util 0.14.0", + "vmm-sys-util", "windows-sys", ] @@ -784,7 +819,7 @@ name = "krun-vmm" version = "0.1.0-1.18.0" dependencies = [ "bitfield", - "bitflags 2.11.0", + "bitflags 2.11.1", "bzip2", "crossbeam-channel", "flate2", @@ -810,7 +845,7 @@ dependencies = [ "serde_json", "tdx", "vm-memory", - "vmm-sys-util 0.14.0", + "vmm-sys-util", "zstd", ] @@ -824,23 +859,23 @@ dependencies = [ [[package]] name = "kvm-bindings" -version = "0.12.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a537873e15e8daabb416667e606d9b0abc2a8fb9a45bd5853b888ae0ead82f9" +checksum = "4b3c06ff73c7ce03e780887ec2389d62d2a2a9ddf471ab05c2ff69207cd3f3b4" dependencies = [ - "vmm-sys-util 0.14.0", + "vmm-sys-util", ] [[package]] name = "kvm-ioctls" -version = "0.22.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8f7370330b4f57981e300fa39b02088f2f2a5c2d0f1f994e8090589619c56d" +checksum = "333f77a20344a448f3f70664918135fddeb804e938f28a99d685bd92926e0b19" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "kvm-bindings", "libc", - "vmm-sys-util 0.14.0", + "vmm-sys-util", ] [[package]] @@ -851,9 +886,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libkrun" @@ -890,18 +925,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "libredox" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" -dependencies = [ - "bitflags 2.11.0", - "libc", - "plain", - "redox_syscall", -] - [[package]] name = "linux-loader" version = "0.13.2" @@ -919,24 +942,24 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "lru" -version = "0.16.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9" dependencies = [ - "hashbrown 0.16.1", + "hashbrown 0.17.1", ] [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "memoffset" @@ -978,10 +1001,10 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b5b539a76e3f555fb143c3e67d5e05fa1d5fece02a515f6ecf41b3f1a081f58" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "nix 0.26.4", - "rand 0.9.2", + "rand 0.9.4", "vsock", ] @@ -991,7 +1014,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6436c562bcdb6f192e0e59f627bff5b0b88f2e1c48264079f4f1d6da42bec2d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "nix 0.26.4", "vsock", @@ -1016,7 +1039,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", @@ -1025,11 +1048,11 @@ dependencies = [ [[package]] name = "nix" -version = "0.31.2" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", @@ -1082,15 +1105,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "portable-atomic" @@ -1100,9 +1117,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] @@ -1158,9 +1175,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha", "rand_core 0.9.5", @@ -1202,15 +1219,6 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" -[[package]] -name = "redox_syscall" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" -dependencies = [ - "bitflags 2.11.0", -] - [[package]] name = "regex" version = "1.12.3" @@ -1253,9 +1261,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -1272,7 +1280,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -1287,9 +1295,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -1323,9 +1331,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -1351,6 +1359,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signal-hook" version = "0.3.18" @@ -1373,9 +1387,15 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "sm3" @@ -1426,9 +1446,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.45" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" dependencies = [ "filetime", "libc", @@ -1437,17 +1457,17 @@ dependencies = [ [[package]] name = "tdx" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad59e5bf374211a1fdd8e7439a07d5a5e617fe97f5cf21d03bcd1bf8c82b73af" +checksum = "83943e37cf46979f711ad11489c641fa058fd0fae92c122d1fc26a664e82acab" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "iocuddle", "kvm-bindings", "kvm-ioctls", "libc", "uuid", - "vmm-sys-util 0.12.1", + "vmm-sys-util", ] [[package]] @@ -1492,9 +1512,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.50.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "pin-project-lite", ] @@ -1532,9 +1552,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" @@ -1562,9 +1582,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -1584,11 +1604,11 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c76d90ce3c6b37d610a5304c9a445cfff580cf8b4b9fd02fb256aaf68552c28a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "uuid", "vm-memory", - "vmm-sys-util 0.15.0", + "vmm-sys-util", ] [[package]] @@ -1620,26 +1640,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "vmm-sys-util" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1435039746e20da4f8d507a72ee1b916f7b4b05af7a91c093d2c6561934ede" -dependencies = [ - "bitflags 1.3.2", - "libc", -] - -[[package]] -name = "vmm-sys-util" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d21f366bf22bfba3e868349978766a965cbe628c323d58e026be80b8357ab789" -dependencies = [ - "bitflags 1.3.2", - "libc", -] - [[package]] name = "vmm-sys-util" version = "0.15.0" @@ -1652,21 +1652,21 @@ dependencies = [ [[package]] name = "vsock" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b82aeb12ad864eb8cd26a6c21175d0bdc66d398584ee6c93c76964c3bcfc78ff" +checksum = "6ba782755fc073877e567c2253c0be48e4aa9a254c232d36d3985dfae0bd5205" dependencies = [ "libc", - "nix 0.31.2", + "nix 0.31.3", ] [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -1675,14 +1675,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -1693,9 +1693,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1703,9 +1703,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -1716,9 +1716,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -1751,7 +1751,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", @@ -1803,6 +1803,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -1852,7 +1858,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags 2.11.1", "indexmap", "log", "serde", @@ -1894,18 +1900,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 35b1dbba4..6e85e8c60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "init", "src/libkrun", "src/init-blob", "src/input", diff --git a/Makefile b/Makefile index 6f6a3c4fa..9de26c976 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,9 @@ endif ifeq ($(VHOST_USER),1) FEATURE_FLAGS += --features vhost-user endif +ifeq ($(TIMESYNC),1) + FEATURE_FLAGS += --features timesync +endif ifeq ($(AWS_NITRO),1) VARIANT = -awsnitro FEATURE_FLAGS := --features aws-nitro,net diff --git a/init/Cargo.toml b/init/Cargo.toml new file mode 100644 index 000000000..b1b7d264b --- /dev/null +++ b/init/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "krun-init" +version = "0.1.0-1.18.1" +edition = "2024" +description = "PID-1 init binary for libkrun guest VMs" +license = "Apache-2.0" +repository = "https://github.com/containers/libkrun" + +[[bin]] +name = "krun-init" +path = "src/main.rs" + +[features] +amd-sev = [] +tdx = [] +timesync = [] + +[dependencies] +libc = "0.2" +nix = { version = "0.30", features = ["fs", "hostname", "ioctl", "mount", "process", "reboot", "resource", "signal", "socket", "term", "uio"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" diff --git a/init/src/main.rs b/init/src/main.rs new file mode 100644 index 000000000..5b2e5a6c3 --- /dev/null +++ b/init/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("hello, world!"); +} diff --git a/src/arch/Cargo.toml b/src/arch/Cargo.toml index 9e4d3da1c..c066d359c 100644 --- a/src/arch/Cargo.toml +++ b/src/arch/Cargo.toml @@ -14,17 +14,17 @@ tdx = [ "tee", "dep:tdx" ] [dependencies] libc = ">=0.2.39" -vm-memory = { version = "0.17", features = ["backend-mmap"] } -vmm-sys-util = "0.14" +vm-memory = { version = "=0.17.1", features = ["backend-mmap"] } +vmm-sys-util = "0.15" arch_gen = { package = "krun-arch-gen", version = "=0.1.0-1.18.0", path = "../arch_gen" } smbios = { package = "krun-smbios", version = "=0.1.0-1.18.0", path = "../smbios" } utils = { package = "krun-utils", version = "=0.1.0-1.18.0", path = "../utils" } [target.'cfg(target_os = "linux")'.dependencies] -kvm-bindings = { version = "0.12", features = ["fam-wrappers"] } -kvm-ioctls = "0.22" -tdx = { version = "0.1.0", optional = true } +kvm-bindings = { version = "0.14", features = ["fam-wrappers"] } +kvm-ioctls = "0.24" +tdx = { version = "0.1.1", optional = true } [target.'cfg(target_arch = "x86_64")'.dependencies] linux-loader = { version = "0.13.2", features = ["elf"] } diff --git a/src/cpuid/Cargo.toml b/src/cpuid/Cargo.toml index bbbfa98d5..f3c4ab884 100644 --- a/src/cpuid/Cargo.toml +++ b/src/cpuid/Cargo.toml @@ -11,8 +11,8 @@ repository = "https://github.com/containers/libkrun" tdx = [] [dependencies] -vmm-sys-util = "0.14" +vmm-sys-util = "0.15" [target.'cfg(target_os = "linux")'.dependencies] -kvm-bindings = { version = "0.12", features = ["fam-wrappers"] } -kvm-ioctls = "0.22" +kvm-bindings = { version = "0.14", features = ["fam-wrappers"] } +kvm-ioctls = "0.24" diff --git a/src/devices/Cargo.toml b/src/devices/Cargo.toml index 326ac30d5..584de4358 100644 --- a/src/devices/Cargo.toml +++ b/src/devices/Cargo.toml @@ -33,7 +33,7 @@ thiserror = { version = "2.0", optional = true } vhost = { version = "0.15", optional = true, features = ["vhost-user-frontend"] } vmm-sys-util = { version = "0.15", optional = true } virtio-bindings = "0.2.0" -vm-memory = { version = "0.17", features = ["backend-mmap"] } +vm-memory = { version = "=0.17.1", features = ["backend-mmap"] } zerocopy = { version = "0.8.26", optional = true, features = ["derive"] } krun_display = { package = "krun-display", version = "0.1.0", path = "../display", optional = true, features = ["bindgen_clang_runtime"] } krun_input = { package = "krun-input", version = "0.1.0", path = "../input", features = ["bindgen_clang_runtime"], optional = true } @@ -51,8 +51,8 @@ lru = ">=0.9" [target.'cfg(target_os = "linux")'.dependencies] rutabaga_gfx = { package = "krun-rutabaga-gfx", version = "=0.1.0-1.18.0", path = "../rutabaga_gfx", features = ["x"], optional = true } caps = "0.5.5" -kvm-bindings = { version = "0.12", features = ["fam-wrappers"] } -kvm-ioctls = "0.22" +kvm-bindings = { version = "0.14", features = ["fam-wrappers"] } +kvm-ioctls = "0.24" [target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies] vm-fdt = ">= 0.2.0" diff --git a/src/init-blob/Cargo.toml b/src/init-blob/Cargo.toml index 6be3f306c..1a32151c2 100644 --- a/src/init-blob/Cargo.toml +++ b/src/init-blob/Cargo.toml @@ -7,5 +7,10 @@ license = "Apache-2.0" repository = "https://github.com/containers/libkrun" build = "build.rs" +[features] +amd-sev = [] +tdx = [] +timesync = [] + [lib] path = "src/lib.rs" diff --git a/src/init-blob/build.rs b/src/init-blob/build.rs index 49a4346d2..eb47e6979 100644 --- a/src/init-blob/build.rs +++ b/src/init-blob/build.rs @@ -1,65 +1,162 @@ -use std::ffi::OsStr; +use std::env; use std::path::PathBuf; use std::process::Command; -fn build_default_init() -> PathBuf { - let manifest_dir = PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()); - let libkrun_root = manifest_dir.join("../.."); - let init_src = libkrun_root.join("init/init.c"); - let dhcp_src = libkrun_root.join("init/dhcp.c"); +fn musl_target_for_host() -> &'static str { + let host = env::var("HOST").unwrap_or_default(); + if host.starts_with("aarch64") { + "aarch64-unknown-linux-musl" + } else { + "x86_64-unknown-linux-musl" + } +} + +fn musl_supported(rustc: &str) -> bool { + let musl_target = musl_target_for_host(); + let output = Command::new(rustc) + .args(["--target", musl_target, "--print", "sysroot"]) + .output(); + match output { + Ok(o) if o.status.success() => { + let sysroot = PathBuf::from(String::from_utf8_lossy(&o.stdout).trim()); + sysroot + .join("lib/rustlib") + .join(musl_target) + .join("lib") + .exists() + } + _ => false, + } +} + +/// Return a rustc binary that has the musl target's std library available. +/// +/// Tries the active rustc first. If that fails, searches ~/.rustup/toolchains/ +/// for a stable toolchain that does support musl — covering the common case +/// where the system package manager's rustc (e.g. Fedora's /usr/bin/rustc) +/// is used as the workspace compiler but the user also has a rustup toolchain +/// with musl support installed. +fn find_musl_rustc(default_rustc: &str) -> Option { + if musl_supported(default_rustc) { + return Some(PathBuf::from(default_rustc)); + } + + let home = env::var_os("HOME")?; + let toolchains = PathBuf::from(home).join(".rustup").join("toolchains"); + let mut candidates: Vec = std::fs::read_dir(&toolchains) + .ok()? + .flatten() + .map(|e| e.path().join("bin").join("rustc")) + .filter(|p| p.exists()) + .collect(); - let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + // Prefer stable toolchains over nightly/beta. + candidates.sort_by_key(|p| !p.to_string_lossy().contains("stable")); + + candidates + .into_iter() + .find(|rustc| musl_supported(rustc.to_str().unwrap_or(""))) +} + +fn build_rust_init() -> PathBuf { + let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); + let workspace_root = manifest_dir.join("../.."); + let init_manifest = workspace_root.join("init/Cargo.toml"); + + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + // Separate target dir avoids conflicting with the parent workspace cargo lock. + let init_target_dir = out_dir.join("init-target"); let init_bin = out_dir.join("init"); - println!("cargo:rerun-if-env-changed=CC_LINUX"); - println!("cargo:rerun-if-env-changed=CC"); - println!("cargo:rerun-if-env-changed=TIMESYNC"); - println!("cargo:rerun-if-changed={}", init_src.display()); - println!("cargo:rerun-if-changed={}", dhcp_src.display()); - println!( - "cargo:rerun-if-changed={}", - libkrun_root.join("init/jsmn.h").display() - ); + let musl_target = musl_target_for_host(); + let profile = env::var("PROFILE").unwrap_or_else(|_| "release".to_string()); + let default_cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); + let default_rustc = env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string()); + println!( "cargo:rerun-if-changed={}", - libkrun_root.join("init/dhcp.h").display() + workspace_root.join("init/src").display() ); + println!("cargo:rerun-if-changed={}", init_manifest.display()); + println!("cargo:rerun-if-env-changed=TIMESYNC"); + + // Resolve which rustc (and paired cargo) to use for the init binary. + let (rustc, cargo, use_musl) = match find_musl_rustc(&default_rustc) { + Some(musl_rustc) => { + // Use the cargo from the same toolchain bin/ directory so that + // it inherits the same sysroot and target support. + let cargo = musl_rustc + .parent() + .map(|bin| bin.join("cargo")) + .filter(|p| p.exists()) + .map(|p| p.to_string_lossy().into_owned()) + .unwrap_or(default_cargo); + (musl_rustc.to_string_lossy().into_owned(), cargo, true) + } + None => { + println!( + "cargo:warning=musl target not available; krun-init will be dynamically linked. \ + Run `rustup target add $(uname -m)-unknown-linux-musl` for a static binary." + ); + (default_rustc, default_cargo, false) + } + }; - let mut init_cc_flags = vec!["-O2", "-static", "-Wall"]; - if std::env::var_os("TIMESYNC").as_deref() == Some(OsStr::new("1")) { - init_cc_flags.push("-D__TIMESYNC__"); + let mut cmd = Command::new(&cargo); + cmd.arg("build") + .arg("--manifest-path") + .arg(&init_manifest) + .arg("--target-dir") + .arg(&init_target_dir) + .env("RUSTC", &rustc); + + if profile == "release" { + cmd.arg("--release"); } - let cc_value = std::env::var("CC_LINUX") - .or_else(|_| std::env::var("CC")) - .unwrap_or_else(|_| "cc".to_string()); - let mut cc_parts = cc_value.split_ascii_whitespace(); - let cc = cc_parts.next().expect("CC_LINUX/CC must not be empty"); - let status = Command::new(cc) - .args(cc_parts) - .args(&init_cc_flags) - .arg("-o") - .arg(&init_bin) - .arg(&init_src) - .arg(&dhcp_src) - .status() - .unwrap_or_else(|e| panic!("failed to execute {cc}: {e}")); + if use_musl { + cmd.arg("--target").arg(musl_target); + } + let mut features: Vec<&str> = Vec::new(); + if cfg!(feature = "amd-sev") { + features.push("amd-sev"); + } + if cfg!(feature = "tdx") { + features.push("tdx"); + } + if env::var_os("TIMESYNC").is_some_and(|v| v == "1") { + features.push("timesync"); + } + if !features.is_empty() { + cmd.arg("--features").arg(features.join(",")); + } + + let status = cmd + .status() + .unwrap_or_else(|e| panic!("failed to run {cargo}: {e}")); if !status.success() { - panic!("failed to compile init/init.c: {status}"); + panic!("failed to build krun-init"); } + + let built = if use_musl { + // Cross-compilation: cargo places the binary at /// + init_target_dir + .join(musl_target) + .join(&profile) + .join("krun-init") + } else { + init_target_dir.join(&profile).join("krun-init") + }; + std::fs::copy(&built, &init_bin).unwrap_or_else(|e| panic!("failed to copy krun-init: {e}")); + init_bin } fn main() { - let init_binary_path = std::env::var_os("KRUN_INIT_BINARY_PATH") + let init_binary_path = env::var_os("KRUN_INIT_BINARY_PATH") .map(PathBuf::from) - .unwrap_or_else(|| { - let init_path = build_default_init(); - // SAFETY: The build script is single threaded. - unsafe { std::env::set_var("KRUN_INIT_BINARY_PATH", &init_path) }; - init_path - }); + .unwrap_or_else(build_rust_init); println!( "cargo:rustc-env=KRUN_INIT_BINARY_PATH={}", init_binary_path.display() diff --git a/src/kernel/Cargo.toml b/src/kernel/Cargo.toml index 34dde25dd..ca6fec136 100644 --- a/src/kernel/Cargo.toml +++ b/src/kernel/Cargo.toml @@ -7,6 +7,6 @@ license = "Apache-2.0" repository = "https://github.com/containers/libkrun" [dependencies] -vm-memory = { version = "0.17", features = ["backend-mmap"] } +vm-memory = { version = "=0.17.1", features = ["backend-mmap"] } utils = { package = "krun-utils", version = "=0.1.0-1.18.0", path = "../utils" } diff --git a/src/libkrun/Cargo.toml b/src/libkrun/Cargo.toml index 7cc28c0a0..7ab669fbf 100644 --- a/src/libkrun/Cargo.toml +++ b/src/libkrun/Cargo.toml @@ -10,8 +10,8 @@ repository = "https://github.com/containers/libkrun" [features] tee = ["vmm/tee", "devices/tee"] -amd-sev = ["blk", "tee", "vmm/amd-sev", "devices/amd-sev"] -tdx = ["blk", "tee", "vmm/tdx", "devices/tdx"] +amd-sev = ["blk", "tee", "vmm/amd-sev", "devices/amd-sev", "init-blob/amd-sev"] +tdx = ["blk", "tee", "vmm/tdx", "devices/tdx", "init-blob/tdx"] net = ["devices/net", "vmm/net"] blk = ["devices/blk", "vmm/blk"] gpu = ["vmm/gpu", "devices/gpu", "krun_display"] @@ -40,11 +40,11 @@ vmm = { package = "krun-vmm", version = "=0.1.0-1.18.0", path = "../vmm" } hvf = { package = "krun-hvf", version = "=0.1.0-1.18.0", path = "../hvf" } [target.'cfg(target_os = "linux")'.dependencies] -kvm-bindings = { version = "0.12", features = ["fam-wrappers"] } -kvm-ioctls = "0.22" +kvm-bindings = { version = "0.14", features = ["fam-wrappers"] } +kvm-ioctls = "0.24" aws-nitro = { package = "krun-aws-nitro", version = "=0.1.0-1.18.0", path = "../aws_nitro", optional = true } nitro-enclaves = { version = "0.5.0", optional = true } -vm-memory = { version = "0.17", features = ["backend-mmap"] } +vm-memory = { version = "=0.17.1", features = ["backend-mmap"] } [lib] name = "krun" diff --git a/src/rutabaga_gfx/Cargo.toml b/src/rutabaga_gfx/Cargo.toml index d06bc619a..3bfe820a4 100644 --- a/src/rutabaga_gfx/Cargo.toml +++ b/src/rutabaga_gfx/Cargo.toml @@ -27,7 +27,7 @@ remain = "0.2" thiserror = "1.0.23" zerocopy = { version = "0.8.26", features = ["derive"] } log = "0.4" -vmm-sys-util = "0.14" +vmm-sys-util = "0.15" [target.'cfg(unix)'.dependencies] nix = { version = "0.30.1", features = ["event", "feature", "fs", "mman", "socket", "uio", "ioctl"] } diff --git a/src/smbios/Cargo.toml b/src/smbios/Cargo.toml index ebdf74153..7bd17804f 100644 --- a/src/smbios/Cargo.toml +++ b/src/smbios/Cargo.toml @@ -7,4 +7,4 @@ license = "Apache-2.0" repository = "https://github.com/containers/libkrun" [dependencies] -vm-memory = { version = "0.17", features = ["backend-mmap"] } +vm-memory = { version = "=0.17.1", features = ["backend-mmap"] } diff --git a/src/utils/Cargo.toml b/src/utils/Cargo.toml index 5b2e88757..240b0b983 100644 --- a/src/utils/Cargo.toml +++ b/src/utils/Cargo.toml @@ -14,11 +14,11 @@ log = "0.4.0" [target.'cfg(unix)'.dependencies] libc = ">=0.2.85" nix = "0.30.1" -vmm-sys-util = "0.14" +vmm-sys-util = "0.15" crossbeam-channel = ">=0.5.15" [target.'cfg(target_os = "linux")'.dependencies] -kvm-bindings = { version = "0.12", features = ["fam-wrappers"] } +kvm-bindings = { version = "0.14", features = ["fam-wrappers"] } [target.'cfg(target_os = "macos")'.dependencies] nix = { version = "0.30.1", features = ["fs"] } diff --git a/src/vmm/Cargo.toml b/src/vmm/Cargo.toml index c16bd6ff9..299717d3c 100644 --- a/src/vmm/Cargo.toml +++ b/src/vmm/Cargo.toml @@ -26,8 +26,8 @@ libc = ">=0.2.39" linux-loader = { version = "0.13.2", features = ["bzimage", "elf", "pe"] } log = "0.4.0" nix = { version = "0.30.1", features = ["fs", "term"] } -vm-memory = { version = "0.17.0", features = ["backend-mmap"] } -vmm-sys-util = "0.14" +vm-memory = { version = "=0.17.1", features = ["backend-mmap"] } +vmm-sys-util = "0.15" krun_display = { package = "krun-display", version = "0.1.0", path = "../display", optional = true, features = ["bindgen_clang_runtime"] } krun_input = { package = "krun-input", version = "0.1.0", path = "../input", optional = true, features = ["bindgen_clang_runtime"] } @@ -52,9 +52,9 @@ cpuid = { package = "krun-cpuid", version = "=0.1.0-1.18.0", path = "../cpuid" } zstd = "0.13" [target.'cfg(target_os = "linux")'.dependencies] -tdx = { version = "0.1.0", optional = true } -kvm-bindings = { version = "0.12", features = ["fam-wrappers"] } -kvm-ioctls = "0.22" +tdx = { version = "0.1.1", optional = true } +kvm-bindings = { version = "0.14", features = ["fam-wrappers"] } +kvm-ioctls = "0.24" [target.'cfg(target_os = "macos")'.dependencies] hvf = { package = "krun-hvf", version = "=0.1.0-1.18.0", path = "../hvf" } From d8f40421e52b850dad2e83a93ef3502c469dc8d8 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Wed, 6 May 2026 15:10:26 -0400 Subject: [PATCH 14/41] init: implement filesystem mounting Add init/src/fs.rs with: - mount_once(): helper that treats EBUSY as success - mount_filesystems(): mounts devtmpfs, proc, sysfs, cgroup2, devpts, tmpfs(/dev/shm), and creates the /dev/fd symlink - is_mount_point(): parses /proc/mounts (avoids triggering Podman auto-mounts that stat() would cause) - mount_tmpfs(): mounts a tmpfs at an arbitrary path Implement mount_tee_block_root() function used by both SEV and TDX features to mount /dev/vda and chroot into it. For amd-sev this replaces the previous LUKS/KBS attestation path entirely. The SEV and TDX boot paths are now identical at the init level. Signed-off-by: Jake Correnti Assisted-by: Claude Code:claude-sonnet-4.6 --- Cargo.lock | 1 + init/Cargo.toml | 1 + init/src/fs.rs | 113 +++++++++++++++++++++++++++++++++++++++++++++++ init/src/main.rs | 5 ++- 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 init/src/fs.rs diff --git a/Cargo.lock b/Cargo.lock index bf662792e..24de495c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -742,6 +742,7 @@ dependencies = [ name = "krun-init" version = "0.1.0-1.18.1" dependencies = [ + "anyhow", "libc", "nix 0.30.1", "serde", diff --git a/init/Cargo.toml b/init/Cargo.toml index b1b7d264b..fa8af0a98 100644 --- a/init/Cargo.toml +++ b/init/Cargo.toml @@ -16,6 +16,7 @@ tdx = [] timesync = [] [dependencies] +anyhow = "1" libc = "0.2" nix = { version = "0.30", features = ["fs", "hostname", "ioctl", "mount", "process", "reboot", "resource", "signal", "socket", "term", "uio"] } serde = { version = "1", features = ["derive"] } diff --git a/init/src/fs.rs b/init/src/fs.rs new file mode 100644 index 000000000..d230a3f0e --- /dev/null +++ b/init/src/fs.rs @@ -0,0 +1,113 @@ +use anyhow::Context; +use nix::errno::Errno; +use nix::mount::{self, MsFlags}; +use nix::unistd; +use std::fs::{self, File}; +use std::io::{BufRead, BufReader}; +use std::os::unix::fs as unix_fs; + +/// Mount, treating EBUSY (already mounted) as success. +fn mount_once( + src: Option<&str>, + target: &str, + fstype: Option<&str>, + flags: MsFlags, +) -> anyhow::Result<()> { + match mount::mount(src, target, fstype, flags, None::<&str>) { + Ok(()) => Ok(()), + Err(Errno::EBUSY) => Ok(()), + Err(e) => Err(e).with_context(|| format!("mount {target}")), + } +} + +pub fn mount_filesystems() -> anyhow::Result<()> { + let base_flags = MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_RELATIME; + fs::create_dir_all("/dev").context("create /dev")?; + fs::create_dir_all("/proc").context("create /proc")?; + fs::create_dir_all("/sys").context("create /sys")?; + + mount_once( + Some("devtmpfs"), + "/dev", + Some("devtmpfs"), + MsFlags::MS_RELATIME, + )?; + + mount_once( + Some("proc"), + "/proc", + Some("proc"), + MsFlags::MS_NODEV | base_flags, + )?; + + mount_once( + Some("sysfs"), + "/sys", + Some("sysfs"), + MsFlags::MS_NODEV | base_flags, + )?; + + mount_once( + Some("cgroup2"), + "/sys/fs/cgroup", + Some("cgroup2"), + MsFlags::MS_NODEV | base_flags, + )?; + + fs::create_dir_all("/dev/pts").context("create /dev/pts")?; + fs::create_dir_all("/dev/shm").context("create /dev/shm")?; + + mount_once(Some("devpts"), "/dev/pts", Some("devpts"), base_flags)?; + mount_once(Some("tmpfs"), "/dev/shm", Some("tmpfs"), base_flags)?; + + // Best-effort; may already exist. + let _ = unix_fs::symlink("/proc/self/fd", "/dev/fd"); + + Ok(()) +} + +/// Returns true if path is listed as a mount point in /proc/mounts. +/// +/// Uses /proc/mounts instead of stat() because Podman arranges tmpfs +/// auto-mounts that would be triggered by a stat call. +pub fn is_mount_point(path: &str) -> bool { + let Ok(f) = File::open("/proc/mounts") else { + return false; + }; + for line in BufReader::new(f).lines().map_while(Result::ok) { + let mut parts = line.split_whitespace(); + let _ = parts.next(); // device + if parts.next() == Some(path) { + return true; + } + } + false +} + +pub fn mount_tmpfs(path: &str) -> anyhow::Result<()> { + mount::mount( + Some("tmpfs"), + path, + Some("tmpfs"), + MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_NODEV | MsFlags::MS_RELATIME, + None::<&str>, + ) + .with_context(|| format!("mount tmpfs at {path}")) +} + +/// Mount /dev/vda as ext4, then pivot root into it. +#[cfg(any(feature = "amd-sev", feature = "tdx"))] +pub fn mount_tee_block_device() -> anyhow::Result<()> { + fs::create_dir_all("/tmp/vda").context("create /tmp/vda")?; + + mount_once( + Some("/dev/vda"), + "/tmp/vda", + Some("ext4"), + MsFlags::MS_RELATIME, + )?; + unistd::chdir("/tmp/vda").context("chdir /tmp/vda")?; + + mount_once(Some("."), "/", None::<&str>, MsFlags::MS_MOVE)?; + unistd::chroot(".").context("chroot .") +} diff --git a/init/src/main.rs b/init/src/main.rs index 5b2e5a6c3..45fb2ec26 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -1,3 +1,6 @@ +mod fs; + fn main() { - println!("hello, world!"); + #[cfg(any(feature = "amd-sev", feature = "tdx"))] + fs::mount_tee_block_device().expect("mount block root failed"); } From 269e54e53ee2223a097fad908f7a3cfeb5953a10 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Wed, 6 May 2026 15:25:00 -0400 Subject: [PATCH 15/41] init: implement KRUN_BLOCK_ROOT_DEVICE pivot and shared root mount Extend fs.rs with: - try_mount(): mounts with a known fstype, or probes /proc/filesystems when fstype is None - mount_block_root_device(): handles KRUN_BLOCK_ROOT_DEVICE by mounting the block device at /newroot, issuing KRUN_REMOVE_ROOT_DIR_IOCTL to drop the virtiofs temporary root, then pivoting with MS_MOVE - mount_shared_root(): sets MS_REC|MS_SHARED propagation on / Signed-off-by: Jake Correnti Assisted-by: Claude Code:claude-sonnet-4.6 --- init/src/fs.rs | 84 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/init/src/fs.rs b/init/src/fs.rs index d230a3f0e..32e81ef6b 100644 --- a/init/src/fs.rs +++ b/init/src/fs.rs @@ -1,11 +1,14 @@ -use anyhow::Context; +use anyhow::{Context, bail}; use nix::errno::Errno; use nix::mount::{self, MsFlags}; use nix::unistd; +use std::env; use std::fs::{self, File}; use std::io::{BufRead, BufReader}; use std::os::unix::fs as unix_fs; +const KRUN_REMOVE_ROOT_DIR_IOCTL: libc::c_ulong = 0x7603; + /// Mount, treating EBUSY (already mounted) as success. fn mount_once( src: Option<&str>, @@ -111,3 +114,82 @@ pub fn mount_tee_block_device() -> anyhow::Result<()> { mount_once(Some("."), "/", None::<&str>, MsFlags::MS_MOVE)?; unistd::chroot(".").context("chroot .") } + +/// Mount source onto target, trying each non-virtual filesystem listed in +/// /proc/filesystems when fstype is None. +pub fn try_mount( + source: &str, + target: &str, + fstype: Option<&str>, + flags: MsFlags, + data: Option<&str>, +) -> anyhow::Result<()> { + if let Some(fs) = fstype { + return mount::mount(Some(source), target, Some(fs), flags, data) + .with_context(|| format!("mount {source} -> {target} as {fs}")); + } + + let f = File::open("/proc/filesystems").context("open /proc/filesystems")?; + for line in BufReader::new(f).lines().map_while(Result::ok) { + if line.starts_with("nodev") { + continue; + } + let fs = line.trim(); + if mount::mount(Some(source), target, Some(fs), flags, data).is_ok() { + return Ok(()); + } + } + bail!("no supported filesystem found for {source}") +} + +/// Handle KRUN_BLOCK_ROOT_DEVICE: mount the block device at /newroot, +/// ask the virtiofs device to remove the temporary root, then pivot. +pub fn mount_block_root_device() -> anyhow::Result<()> { + let Some(krun_root) = env::var_os("KRUN_BLOCK_ROOT_DEVICE") else { + return Ok(()); + }; + let krun_root = krun_root.to_string_lossy().into_owned(); + + fs::create_dir_all("/newroot").context("create /newroot")?; + + let fstype = env::var("KRUN_BLOCK_ROOT_FSTYPE").ok(); + let options = env::var("KRUN_BLOCK_ROOT_OPTIONS").ok(); + + try_mount( + &krun_root, + "/newroot", + fstype.as_deref(), + MsFlags::empty(), + options.as_deref(), + )?; + + unistd::chdir("/newroot").context("chdir /newroot")?; + + // Ask the virtiofs device to tear down the temporary root directory. + let fd = unsafe { libc::open(c"/".as_ptr().cast(), libc::O_RDONLY) }; + if fd >= 0 { + unsafe { libc::ioctl(fd, KRUN_REMOVE_ROOT_DIR_IOCTL as _) }; + unsafe { libc::close(fd) }; + } + + mount::mount(Some("."), "/", None::<&str>, MsFlags::MS_MOVE, None::<&str>) + .context("pivot root MS_MOVE")?; + + unistd::chroot(".").context("chroot after block root pivot")?; + + // Re-mount standard filesystems now that we're in the new root. + mount_filesystems()?; + + Ok(()) +} + +pub fn mount_shared_root() -> anyhow::Result<()> { + mount::mount( + None::<&str>, + "/", + None::<&str>, + MsFlags::MS_REC | MsFlags::MS_SHARED, + None::<&str>, + ) + .context("set MS_SHARED on root mount") +} From 29bb5a05972f823b1c7699fc93296b63680cfde6 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Wed, 6 May 2026 15:33:40 -0400 Subject: [PATCH 16/41] init: implement DHCP client Port init/dhcp.c to Rust in init/src/dhcp.rs. The public surface is a single do_dhcp(iface) function with the same behaviour as the C version: - Sends DHCPDISCOVER with Rapid Commit (option 80) - On DHCPACK: applies address, route, MTU, and DNS directly - On DHCPOFFER: completes the 4-way handshake, then applies - On no response: returns Ok (VM may be IPv6-only) Netlink structs not exposed by libc (ifinfomsg, ifaddrmsg, rtmsg) are defined locally with #[repr(C)]. sockaddr_nl and sockaddr_in are zero-initialised via mem::zeroed() to handle opaque padding fields. Signed-off-by: Jake Correnti Assisted-by: Claude Code:claude-sonnet-4.6 --- init/Cargo.toml | 2 +- init/src/dhcp.rs | 541 +++++++++++++++++++++++++++++++++++++++++++++++ init/src/main.rs | 1 + 3 files changed, 543 insertions(+), 1 deletion(-) create mode 100644 init/src/dhcp.rs diff --git a/init/Cargo.toml b/init/Cargo.toml index fa8af0a98..1748ddfeb 100644 --- a/init/Cargo.toml +++ b/init/Cargo.toml @@ -18,6 +18,6 @@ timesync = [] [dependencies] anyhow = "1" libc = "0.2" -nix = { version = "0.30", features = ["fs", "hostname", "ioctl", "mount", "process", "reboot", "resource", "signal", "socket", "term", "uio"] } +nix = { version = "0.30", features = ["fs", "hostname", "ioctl", "mount", "net", "process", "reboot", "resource", "signal", "socket", "term", "uio"] } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/init/src/dhcp.rs b/init/src/dhcp.rs new file mode 100644 index 000000000..89e4d62aa --- /dev/null +++ b/init/src/dhcp.rs @@ -0,0 +1,541 @@ +use anyhow::{Context, bail}; +use nix::errno::Errno; +use nix::net::if_::if_nametoindex; +use nix::sys::socket::{ + self, AddressFamily, MsgFlags, SockFlag, SockProtocol, SockType, SockaddrIn, sockopt, +}; +use nix::sys::time::{TimeVal, TimeValLike}; +use nix::unistd::getpid; +use std::ffi::OsString; +use std::io::Error as IoError; +use std::mem; +use std::net::{Ipv4Addr, SocketAddrV4}; +use std::os::fd::{AsRawFd, FromRawFd, OwnedFd}; +use std::slice; + +const DHCP_BUFFER_SIZE: usize = 576; +const DHCP_OPTIONS_SIZE: usize = 60; +const DHCP_OPTIONS_OFFSET: usize = 240; +const DHCP_OPTIONS_END: u8 = 0xff; +const DHCP_MSG_OFFER: u8 = 2; +const DHCP_MSG_ACK: u8 = 5; + +#[repr(C, packed)] +struct DhcpPacket { + op: u8, + htype: u8, + hlen: u8, + hops: u8, + xid: u32, + secs: u16, + flags: u16, + ciaddr: u32, + yiaddr: u32, + siaddr: u32, + giaddr: u32, + chaddr: [u8; 16], + sname: [u8; 64], + file: [u8; 128], + magic: u32, + options: [u8; DHCP_OPTIONS_SIZE], +} + +impl DhcpPacket { + fn zeroed() -> Self { + // SAFETY: DhcpPacket is plain-old-data with no padding invariants. + unsafe { mem::zeroed() } + } + + fn as_bytes(&self) -> &[u8] { + // SAFETY: packed repr, no padding; reading as bytes is valid. + unsafe { slice::from_raw_parts(self as *const _ as *const u8, mem::size_of::()) } + } +} + +struct DhcpOptionsWriter<'a> { + buf: &'a mut [u8], + pos: usize, +} + +impl<'a> DhcpOptionsWriter<'a> { + fn new(buf: &'a mut [u8]) -> Self { + Self { buf, pos: 0 } + } + + fn push(&mut self, code: u8, data: &[u8]) { + self.buf[self.pos] = code; + self.buf[self.pos + 1] = data.len() as u8; + self.buf[self.pos + 2..self.pos + 2 + data.len()].copy_from_slice(data); + self.pos += 2 + data.len(); + } + + fn finish(self) { + self.buf[self.pos] = DHCP_OPTIONS_END; + } +} + +struct DhcpOptions<'a>(&'a [u8]); + +impl<'a> Iterator for DhcpOptions<'a> { + type Item = (u8, &'a [u8]); + + fn next(&mut self) -> Option { + loop { + let opt = *self.0.first()?; + if opt == DHCP_OPTIONS_END { + self.0 = &[]; + return None; + } + self.0 = &self.0[1..]; + if opt == 0 { + continue; + } + let len = *self.0.first()? as usize; + self.0 = &self.0[1..]; + let data = self.0.get(..len)?; + self.0 = &self.0[len..]; + return Some((opt, data)); + } + } +} + +// libc doesn't expose ifinfomsg, ifaddrmsg, or rtmsg — define them locally. + +#[repr(C)] +struct IfInfoMsg { + ifi_family: u8, + _pad: u8, + ifi_type: u16, + ifi_index: i32, + ifi_flags: u32, + ifi_change: u32, +} + +#[repr(C)] +struct IfAddrMsg { + ifa_family: u8, + ifa_prefixlen: u8, + ifa_flags: u8, + ifa_scope: u8, + ifa_index: u32, +} + +#[repr(C)] +struct RtMsg { + rtm_family: u8, + rtm_dst_len: u8, + rtm_src_len: u8, + rtm_tos: u8, + rtm_table: u8, + rtm_protocol: u8, + rtm_scope: u8, + rtm_type: u8, + rtm_flags: u32, +} + +unsafe fn struct_as_bytes(v: &T) -> &[u8] { + unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::()) } +} + +fn nl_send(sock: libc::c_int, buf: &[u8]) -> anyhow::Result<()> { + // Use mem::zeroed() so the opaque nl_pad field is correctly initialised. + let mut sa: libc::sockaddr_nl = unsafe { mem::zeroed() }; + sa.nl_family = libc::AF_NETLINK as libc::sa_family_t; + + let iov = libc::iovec { + iov_base: buf.as_ptr() as *mut _, + iov_len: buf.len(), + }; + // Use zeroed() rather than a struct literal: musl's msghdr has private + // padding fields (__pad1, __pad2) that cannot be named in a literal. + let mut msg: libc::msghdr = unsafe { mem::zeroed() }; + msg.msg_name = &sa as *const _ as *mut _; + msg.msg_namelen = mem::size_of_val(&sa) as u32; + msg.msg_iov = &iov as *const _ as *mut _; + msg.msg_iovlen = 1; + let ret = unsafe { libc::sendmsg(sock, &msg, 0) }; + if ret < 0 { + bail!("nl_send: {}", IoError::last_os_error()); + } + Ok(()) +} + +fn nl_recv(sock: libc::c_int, buf: &mut [u8]) -> anyhow::Result { + let mut sa: libc::sockaddr_nl = unsafe { mem::zeroed() }; + let iov = libc::iovec { + iov_base: buf.as_mut_ptr() as *mut _, + iov_len: buf.len(), + }; + let mut msg: libc::msghdr = unsafe { mem::zeroed() }; + msg.msg_name = &mut sa as *mut _ as *mut _; + msg.msg_namelen = mem::size_of_val(&sa) as u32; + msg.msg_iov = &iov as *const _ as *mut _; + msg.msg_iovlen = 1; + let ret = unsafe { libc::recvmsg(sock, &mut msg, 0) }; + if ret < 0 { + bail!("nl_recv: {}", IoError::last_os_error()); + } + Ok(ret as usize) +} + +fn add_rtattr(buf: &mut [u8], msg_len: &mut usize, rta_type: u16, data: &[u8]) { + let rta_len = (4 + data.len()) as u16; + let aligned_start = (*msg_len + 3) & !3; + let end = aligned_start + ((rta_len as usize + 3) & !3); + assert!(end <= buf.len(), "netlink buffer too small"); + + buf[aligned_start..aligned_start + 2].copy_from_slice(&rta_len.to_ne_bytes()); + buf[aligned_start + 2..aligned_start + 4].copy_from_slice(&rta_type.to_ne_bytes()); + buf[aligned_start + 4..aligned_start + 4 + data.len()].copy_from_slice(data); + buf[aligned_start + 4 + data.len()..end].fill(0); + + *msg_len = end; + buf[0..4].copy_from_slice(&(*msg_len as u32).to_ne_bytes()); +} + +fn nl_check_ack(buf: &[u8], recv_len: usize, op: &str) -> anyhow::Result<()> { + let min = mem::size_of::() + mem::size_of::(); + if recv_len < min { + bail!("{op}: netlink response too short"); + } + let nlh = unsafe { &*(buf.as_ptr() as *const libc::nlmsghdr) }; + if nlh.nlmsg_type != libc::NLMSG_ERROR as u16 { + bail!( + "{op}: expected NLMSG_ERROR ACK, got type {}", + nlh.nlmsg_type + ); + } + let err_offset = mem::size_of::(); + let err = i32::from_ne_bytes(buf[err_offset..err_offset + 4].try_into().unwrap()); + if err != 0 { + bail!("{op}: netlink error {err}"); + } + Ok(()) +} + +fn nl_hdr(buf: &mut [u8], msg_len: usize, nlmsg_type: u16, flags: u16) { + let nlh = libc::nlmsghdr { + nlmsg_len: msg_len as u32, + nlmsg_type, + nlmsg_flags: flags, + nlmsg_seq: 1, + nlmsg_pid: getpid().as_raw() as u32, + }; + buf[..mem::size_of_val(&nlh)].copy_from_slice(unsafe { struct_as_bytes(&nlh) }); +} + +fn set_mtu(nl_sock: libc::c_int, iface_index: i32, mtu: u32) -> anyhow::Result<()> { + let mut buf = [0u8; 4096]; + let base = mem::size_of::() + mem::size_of::(); + let mut msg_len = base; + + nl_hdr( + &mut buf, + base, + libc::RTM_NEWLINK, + (libc::NLM_F_REQUEST | libc::NLM_F_ACK) as u16, + ); + + let ifi = IfInfoMsg { + ifi_family: libc::AF_UNSPEC as u8, + _pad: 0, + ifi_type: libc::ARPHRD_ETHER, + ifi_index: iface_index, + ifi_flags: 0, + ifi_change: 0, + }; + let ifi_off = mem::size_of::(); + buf[ifi_off..ifi_off + mem::size_of_val(&ifi)] + .copy_from_slice(unsafe { struct_as_bytes(&ifi) }); + + add_rtattr(&mut buf, &mut msg_len, libc::IFLA_MTU, &mtu.to_ne_bytes()); + + nl_send(nl_sock, &buf[..msg_len])?; + let recv_len = nl_recv(nl_sock, &mut buf)?; + nl_check_ack(&buf, recv_len, "set_mtu") +} + +fn mod_addr4( + nl_sock: libc::c_int, + iface_index: i32, + cmd: u16, + addr: u32, + prefix_len: u8, +) -> anyhow::Result<()> { + let mut buf = [0u8; 4096]; + let base = mem::size_of::() + mem::size_of::(); + let mut msg_len = base; + + nl_hdr( + &mut buf, + base, + cmd, + (libc::NLM_F_REQUEST | libc::NLM_F_CREATE | libc::NLM_F_ACK) as u16, + ); + + let ifa = IfAddrMsg { + ifa_family: libc::AF_INET as u8, + ifa_prefixlen: prefix_len, + ifa_flags: 0, + ifa_scope: libc::RT_SCOPE_UNIVERSE, + ifa_index: iface_index as u32, + }; + let ifa_off = mem::size_of::(); + buf[ifa_off..ifa_off + mem::size_of_val(&ifa)] + .copy_from_slice(unsafe { struct_as_bytes(&ifa) }); + + let addr_bytes = addr.to_ne_bytes(); + add_rtattr(&mut buf, &mut msg_len, libc::IFA_LOCAL, &addr_bytes); + add_rtattr(&mut buf, &mut msg_len, libc::IFA_ADDRESS, &addr_bytes); + + nl_send(nl_sock, &buf[..msg_len])?; + let recv_len = nl_recv(nl_sock, &mut buf)?; + nl_check_ack(&buf, recv_len, "mod_addr4") +} + +fn mod_route4( + nl_sock: libc::c_int, + iface_index: i32, + cmd: u16, + gateway: u32, +) -> anyhow::Result<()> { + let mut buf = [0u8; 4096]; + let base = mem::size_of::() + mem::size_of::(); + let mut msg_len = base; + + nl_hdr( + &mut buf, + base, + cmd, + (libc::NLM_F_REQUEST | libc::NLM_F_CREATE | libc::NLM_F_ACK) as u16, + ); + + let rtm = RtMsg { + rtm_family: libc::AF_INET as u8, + rtm_dst_len: 0, + rtm_src_len: 0, + rtm_tos: 0, + rtm_table: libc::RT_TABLE_MAIN, + rtm_protocol: libc::RTPROT_BOOT, + rtm_scope: libc::RT_SCOPE_UNIVERSE, + rtm_type: libc::RTN_UNICAST, + rtm_flags: 0, + }; + let rtm_off = mem::size_of::(); + buf[rtm_off..rtm_off + mem::size_of_val(&rtm)] + .copy_from_slice(unsafe { struct_as_bytes(&rtm) }); + + add_rtattr( + &mut buf, + &mut msg_len, + libc::RTA_OIF, + &(iface_index as u32).to_ne_bytes(), + ); + add_rtattr(&mut buf, &mut msg_len, libc::RTA_DST, &0u32.to_ne_bytes()); + add_rtattr( + &mut buf, + &mut msg_len, + libc::RTA_GATEWAY, + &gateway.to_ne_bytes(), + ); + + nl_send(nl_sock, &buf[..msg_len])?; + let recv_len = nl_recv(nl_sock, &mut buf)?; + nl_check_ack(&buf, recv_len, "mod_route4") +} + +fn dhcp_msg_type(response: &[u8]) -> u8 { + DhcpOptions(response.get(DHCP_OPTIONS_OFFSET..).unwrap_or(&[])) + .find(|&(code, _)| code == 53) + .and_then(|(_, data)| data.first().copied()) + .unwrap_or(0) +} + +fn handle_dhcp_ack(nl_sock: libc::c_int, iface_index: i32, response: &[u8]) -> anyhow::Result<()> { + if response.len() < DHCP_OPTIONS_OFFSET + 1 { + bail!("DHCPACK too short ({} bytes)", response.len()); + } + + let addr = u32::from_ne_bytes(response[16..20].try_into().unwrap()); + if addr == 0 { + bail!("DHCPACK: yiaddr is 0.0.0.0"); + } + + let mut netmask: u32 = 0; + let mut router: u32 = 0; + let mut mtu: u16 = 65520; + let mut resolv_conf = String::new(); + + for (opt, data) in DhcpOptions(response.get(DHCP_OPTIONS_OFFSET..).unwrap_or(&[])) { + match opt { + 1 if data.len() >= 4 => { + netmask = u32::from_ne_bytes(data[..4].try_into().unwrap()); + } + 3 if data.len() >= 4 => { + router = u32::from_ne_bytes(data[..4].try_into().unwrap()); + } + 6 => { + for chunk in data.chunks_exact(4) { + resolv_conf.push_str(&format!( + "nameserver {}.{}.{}.{}\n", + chunk[0], chunk[1], chunk[2], chunk[3] + )); + } + } + 26 if data.len() >= 2 => { + mtu = u16::from_be_bytes(data[..2].try_into().unwrap()).clamp(1280, 65520); + } + _ => {} + } + } + + if !resolv_conf.is_empty() + && let Err(e) = std::fs::write("/etc/resolv.conf", &resolv_conf) + { + eprintln!("Warning: couldn't write /etc/resolv.conf: {e}"); + } + + let prefix_len = u32::from_be(netmask).leading_ones() as u8; + + mod_addr4(nl_sock, iface_index, libc::RTM_NEWADDR, addr, prefix_len) + .context("add address from DHCP")?; + mod_route4(nl_sock, iface_index, libc::RTM_NEWROUTE, router) + .context("add default route from DHCP")?; + let _ = set_mtu(nl_sock, iface_index, mtu as u32); + + Ok(()) +} + +pub fn do_dhcp(iface: &str) -> anyhow::Result<()> { + let iface_index = if_nametoindex(iface).with_context(|| format!("if_nametoindex({iface})"))?; + + let raw = unsafe { libc::socket(libc::AF_NETLINK, libc::SOCK_RAW, libc::NETLINK_ROUTE) }; + if raw < 0 { + bail!("socket(AF_NETLINK): {}", IoError::last_os_error()); + } + let nl_sock = unsafe { OwnedFd::from_raw_fd(raw) }; + + let mut nl_sa: libc::sockaddr_nl = unsafe { mem::zeroed() }; + nl_sa.nl_family = libc::AF_NETLINK as libc::sa_family_t; + nl_sa.nl_pid = getpid().as_raw() as u32; + if unsafe { + libc::bind( + nl_sock.as_raw_fd(), + &nl_sa as *const _ as *const libc::sockaddr, + mem::size_of_val(&nl_sa) as u32, + ) + } < 0 + { + bail!("bind(netlink): {}", IoError::last_os_error()); + } + + let sock = socket::socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + Some(SockProtocol::Udp), + ) + .context("socket(AF_INET)")?; + + socket::setsockopt(&sock, sockopt::Broadcast, &true).context("setsockopt(SO_BROADCAST)")?; + socket::setsockopt(&sock, sockopt::BindToDevice, &OsString::from(iface)) + .context("setsockopt(SO_BINDTODEVICE)")?; + + let bind_addr = SockaddrIn::from(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 68)); + socket::bind(sock.as_raw_fd(), &bind_addr).context("bind(UDP 68)")?; + + let mut pkt = DhcpPacket::zeroed(); + pkt.op = 1; + pkt.htype = 1; + pkt.hlen = 6; + pkt.xid = (getpid().as_raw() as u32).to_be(); + pkt.flags = 0x8000u16.to_be(); + pkt.magic = 0x63825363u32.to_be(); + + let mut mac_ifr: libc::ifreq = unsafe { mem::zeroed() }; + let name_bytes = iface.as_bytes(); + unsafe { + std::ptr::copy_nonoverlapping( + name_bytes.as_ptr() as *const libc::c_char, + mac_ifr.ifr_name.as_mut_ptr(), + name_bytes.len().min(libc::IFNAMSIZ - 1), + ); + } + if unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCGIFHWADDR as _, &mut mac_ifr) } < 0 { + bail!("ioctl(SIOCGIFHWADDR): {}", IoError::last_os_error()); + } + let sa_data = unsafe { mac_ifr.ifr_ifru.ifru_hwaddr.sa_data }; + for (dst, src) in pkt.chaddr.iter_mut().zip(sa_data.iter().take(6)) { + // We need to allow the unnecessary cast, because this will cause clippy to fail on aarch64 + // without it + #[allow(clippy::unnecessary_cast)] + { + *dst = *src as u8; + } + } + + let mut opts = DhcpOptionsWriter::new(&mut pkt.options); + opts.push(53, &[1]); // Discover + opts.push(80, &[]); // Rapid Commit + opts.finish(); + + let dest = SockaddrIn::from(SocketAddrV4::new(Ipv4Addr::BROADCAST, 67)); + + socket::setsockopt( + &sock, + sockopt::ReceiveTimeout, + &TimeVal::microseconds(100_000), + ) + .context("setsockopt(SO_RCVTIMEO)")?; + + let pkt_bytes = pkt.as_bytes(); + socket::sendto(sock.as_raw_fd(), pkt_bytes, &dest, MsgFlags::empty()) + .context("sendto(DISCOVER)")?; + + let mut response = [0u8; DHCP_BUFFER_SIZE]; + let (recv_len, from) = match socket::recvfrom::(sock.as_raw_fd(), &mut response) { + Ok(r) => r, + Err(Errno::EAGAIN) => return Ok(()), // timeout — no DHCP server + Err(e) => bail!("recvfrom: {e}"), + }; + + let msg_type = dhcp_msg_type(&response[..recv_len]); + + if msg_type == DHCP_MSG_ACK { + handle_dhcp_ack( + nl_sock.as_raw_fd(), + iface_index as i32, + &response[..recv_len], + )?; + } else if msg_type == DHCP_MSG_OFFER { + let offered_addr = u32::from_ne_bytes(response[16..20].try_into().unwrap()); + let server_addr = from.map(|a| u32::from(a.ip())).unwrap_or(0); + + pkt.options = [0; DHCP_OPTIONS_SIZE]; + let mut opts = DhcpOptionsWriter::new(&mut pkt.options); + opts.push(53, &[3]); // Request + opts.push(50, &offered_addr.to_ne_bytes()); // Requested IP + opts.push(54, &server_addr.to_ne_bytes()); // Server ID + opts.finish(); + + let pkt_bytes = pkt.as_bytes(); + socket::sendto(sock.as_raw_fd(), pkt_bytes, &dest, MsgFlags::empty()) + .context("sendto(REQUEST)")?; + + let (recv_len2, _) = socket::recvfrom::(sock.as_raw_fd(), &mut response) + .context("no DHCPACK received")?; + let ack_type = dhcp_msg_type(&response[..recv_len2]); + if ack_type != DHCP_MSG_ACK { + bail!("expected DHCPACK, got type {ack_type}"); + } + handle_dhcp_ack( + nl_sock.as_raw_fd(), + iface_index as i32, + &response[..recv_len2], + )?; + } else { + bail!("unexpected DHCP message type {msg_type}"); + } + + Ok(()) +} diff --git a/init/src/main.rs b/init/src/main.rs index 45fb2ec26..c0f28e5f2 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -1,3 +1,4 @@ +mod dhcp; mod fs; fn main() { From 118a1fd3bcf4857020fa0e4d6cacabdded649970 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Wed, 6 May 2026 15:35:18 -0400 Subject: [PATCH 17/41] init: implement JSON config parsing Add init/src/config.rs, replacing the hand-rolled jsmn-based parser with serde_json. Parses /.krun_config.json (or KRUN_CONFIG env var) and returns a Config struct with: - argv: Entrypoint ++ (args | Cmd), or None if absent - workdir: WorkingDir or Cwd - tmpfs: first tmpfs mount destination not already mounted Environment variables from the Env array are applied during parsing, with HOME and TERM always overwritten, all others set only if unset. A missing or unparseable config file is silently ignored. Signed-off-by: Jake Correnti Assisted-by: Claude Code:claude-sonnet-4.6 --- init/src/config.rs | 100 +++++++++++++++++++++++++++++++++++++++++++++ init/src/main.rs | 1 + 2 files changed, 101 insertions(+) create mode 100644 init/src/config.rs diff --git a/init/src/config.rs b/init/src/config.rs new file mode 100644 index 000000000..aed73827c --- /dev/null +++ b/init/src/config.rs @@ -0,0 +1,100 @@ +use anyhow::{Context, Result}; +use serde::Deserialize; +use std::env; +use std::fs; + +const CONFIG_FILE_PATH: &str = "/.krun_config.json"; + +// The krun OCI runtime passes a full OCI runtime-spec config.json as the +// config file. The fields we care about live inside "process". +#[derive(Deserialize, Default)] +struct ProcessConfig { + args: Option>, + env: Option>, + cwd: Option, +} + +#[cfg(target_os = "linux")] +#[derive(Deserialize, Default)] +struct Mount { + #[serde(rename = "destination")] + destination: Option, + #[serde(rename = "type")] + mount_type: Option, + #[serde(rename = "source")] + source: Option, +} + +#[derive(Deserialize, Default)] +struct RawConfig { + process: Option, + // Flat format: "args"/"env"/"cwd" at the top level (used by simple configs and tests). + // Only consulted when "process" is absent. + args: Option>, + env: Option>, + cwd: Option, + #[cfg(target_os = "linux")] + mounts: Option>, +} + +#[derive(Default)] +pub struct Config { + pub argv: Option>, + pub workdir: Option, + #[cfg(target_os = "linux")] + pub tmpfs: Option, +} + +pub fn load(#[cfg(target_os = "linux")] is_mount_point: impl Fn(&str) -> bool) -> Config { + let path = env::var("KRUN_CONFIG").unwrap_or_else(|_| CONFIG_FILE_PATH.to_string()); + + let Ok(raw) = parse_file(&path) else { + return Config::default(); + }; + + let process = raw.process.unwrap_or(ProcessConfig { + args: raw.args, + env: raw.env, + cwd: raw.cwd, + }); + + // Apply environment variables from the process config. + for entry in process.env.unwrap_or_default() { + let Some((key, val)) = entry.split_once('=') else { + continue; + }; + let overwrite = matches!(key, "HOME" | "TERM"); + if env::var(key).is_err() || overwrite { + // SAFETY: single-threaded at this point. + unsafe { env::set_var(key, val) }; + } + } + + let argv = process.args.filter(|v| !v.is_empty()); + let workdir = process.cwd; + + // Find the first tmpfs mount whose destination is not already mounted. + #[cfg(target_os = "linux")] + let tmpfs = raw.mounts.unwrap_or_default().into_iter().find_map(|m| { + let dest = m.destination?; + let ty = m.mount_type.as_deref().unwrap_or(""); + let src = m.source.as_deref().unwrap_or(""); + if ty == "tmpfs" && src == "tmpfs" && !is_mount_point(&dest) { + Some(dest) + } else { + None + } + }); + + Config { + argv, + workdir, + #[cfg(target_os = "linux")] + tmpfs, + } +} + +fn parse_file(path: &str) -> Result { + let data = fs::read(path).with_context(|| format!("read {path}"))?; + serde_json::from_slice(&data).with_context(|| format!("parse {path}")) +} diff --git a/init/src/main.rs b/init/src/main.rs index c0f28e5f2..32565f21f 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -1,3 +1,4 @@ +mod config; mod dhcp; mod fs; From 9c934927e1785134f0604d2baa5f6fc67b71c9a9 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Wed, 6 May 2026 15:38:19 -0400 Subject: [PATCH 18/41] init: implement network interface setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add setup_network() and setup_dhcp() to env.rs. setup_network() brings up lo unconditionally. setup_dhcp() checks that the interface exists before calling do_dhcp(), and logs a warning on failure rather than aborting (DHCP failure is non-fatal — the VM may be IPv6-only or have no network). Signed-off-by: Jake Correnti Assisted-by: Claude Code:claude-sonnet-4.6 --- init/src/env.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ init/src/main.rs | 1 + 2 files changed, 62 insertions(+) create mode 100644 init/src/env.rs diff --git a/init/src/env.rs b/init/src/env.rs new file mode 100644 index 000000000..ff997bc32 --- /dev/null +++ b/init/src/env.rs @@ -0,0 +1,61 @@ + +#[cfg(target_os = "linux")] +use std::ffi::CString; +#[cfg(target_os = "linux")] +use std::mem; +#[cfg(target_os = "linux")] +use std::ptr; + +#[cfg(target_os = "linux")] +pub fn setup_network(iface: &str) { + let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) }; + if sock < 0 { + return; + } + let mut ifr: libc::ifreq = unsafe { mem::zeroed() }; + let lo = b"lo\0"; + unsafe { + ptr::copy_nonoverlapping( + lo.as_ptr() as *const libc::c_char, + ifr.ifr_name.as_mut_ptr(), + lo.len(), + ); + ifr.ifr_ifru.ifru_flags |= libc::IFF_UP as libc::c_short; + libc::ioctl(sock, libc::SIOCSIFFLAGS as _, &ifr); + } + + #[cfg(target_os = "linux")] + setup_dhcp(iface, sock); + + unsafe { libc::close(sock) }; +} + +#[cfg(not(target_os = "linux"))] +pub fn setup_network() {} + +#[cfg(target_os = "linux")] +fn setup_dhcp(iface: &str, sock: i32) { + if std::env::var("KRUN_DHCP").as_deref() != Ok("1") { + return; + } + + let mut ifr: libc::ifreq = unsafe { mem::zeroed() }; + let name = CString::new(iface).unwrap(); + unsafe { + ptr::copy_nonoverlapping( + name.as_ptr(), + ifr.ifr_name.as_mut_ptr(), + name.as_bytes_with_nul().len().min(libc::IFNAMSIZ), + ); + } + let exists = unsafe { libc::ioctl(sock, libc::SIOCGIFFLAGS as _, &mut ifr) } == 0; + if exists { + unsafe { + ifr.ifr_ifru.ifru_flags |= libc::IFF_UP as libc::c_short; + libc::ioctl(sock, libc::SIOCSIFFLAGS as _, &ifr); + } + if let Err(e) = crate::dhcp::do_dhcp(iface) { + eprintln!("Warning: DHCP configuration for {iface} failed: {e}"); + } + } +} diff --git a/init/src/main.rs b/init/src/main.rs index 32565f21f..5b8124850 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -1,5 +1,6 @@ mod config; mod dhcp; +mod env; mod fs; fn main() { From ab072ca4d976e1a56f144798f45212c4cd579409 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Wed, 6 May 2026 15:39:41 -0400 Subject: [PATCH 19/41] init: implement environment and resource setup Extend env.rs with: - apply_hostname(): sets hostname from HOSTNAME env var, defaulting to "localhost" - apply_env(): maps KRUN_HOME -> HOME and KRUN_TERM -> TERM - apply_rlimits(): parses the KRUN_RLIMITS comma-separated list of id,cur,max triples and applies each via setrlimit(2) Signed-off-by: Jake Correnti Assisted-by: Claude Code:claude-sonnet-4.6 --- init/src/env.rs | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/init/src/env.rs b/init/src/env.rs index ff997bc32..2f13acc49 100644 --- a/init/src/env.rs +++ b/init/src/env.rs @@ -1,4 +1,4 @@ - +use std::env; #[cfg(target_os = "linux")] use std::ffi::CString; #[cfg(target_os = "linux")] @@ -59,3 +59,43 @@ fn setup_dhcp(iface: &str, sock: i32) { } } } + +pub fn apply_hostname() { + let hostname = env::var("HOSTNAME").unwrap_or_else(|_| "localhost".into()); + let _ = nix::unistd::sethostname(&hostname); +} + +pub fn apply_env() { + if let Ok(home) = env::var("KRUN_HOME") { + unsafe { env::set_var("HOME", home) }; + } + if let Ok(term) = env::var("KRUN_TERM") { + unsafe { env::set_var("TERM", term) }; + } +} + +pub fn apply_rlimits() { + let Ok(rlimits) = env::var("KRUN_RLIMITS") else { + return; + }; + for item in rlimits.split(',') { + let Some((id_s, rest)) = item.split_once('=') else { + continue; + }; + let Some((cur_s, max_s)) = rest.split_once(':') else { + continue; + }; + let (Ok(id), Ok(cur), Ok(max)) = ( + id_s.parse::(), + cur_s.parse::(), + max_s.parse::(), + ) else { + continue; + }; + let rlim = libc::rlimit { + rlim_cur: cur, + rlim_max: max, + }; + unsafe { libc::setrlimit(id as _, &rlim) }; + } +} From c3b058275a266d71e4b0c53bb00f758bfeeab138 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Wed, 6 May 2026 15:40:56 -0400 Subject: [PATCH 20/41] init: implement I/O redirects and workload launch Add exec.rs with: - setup_redirects(): walks /sys/class/virtio-ports and dup2s krun-stdin/stdout/stderr onto the corresponding file descriptors - set_exit_code(): reports the workload exit code to the host via KRUN_EXIT_CODE_IOCTL, only when the root fs is virtiofs - run_workload(): forks so PID 1 can reap children; the child calls exec_workload() which sets up redirects and execvp's the argv. Parent waits for the child, reports exit code, syncs, and reboots. KRUN_INIT_PID1=1 skips the fork and exec_workload directly as PID 1. Signed-off-by: Jake Correnti Assisted-by: Claude Code:claude-sonnet-4.6 --- init/src/exec.rs | 123 +++++++++++++++++++++++++++++++++++++++++++++++ init/src/main.rs | 1 + 2 files changed, 124 insertions(+) create mode 100644 init/src/exec.rs diff --git a/init/src/exec.rs b/init/src/exec.rs new file mode 100644 index 000000000..c8468a4c8 --- /dev/null +++ b/init/src/exec.rs @@ -0,0 +1,123 @@ +#[cfg(target_os = "linux")] +use nix::fcntl::{self, OFlag}; +#[cfg(target_os = "linux")] +use nix::sys::reboot::{self, RebootMode}; +#[cfg(target_os = "linux")] +use nix::sys::stat::Mode; +use nix::sys::wait::{self, WaitStatus}; +use nix::unistd::{self, ForkResult}; +use std::env; +use std::ffi::CString; +#[cfg(target_os = "linux")] +use std::fs; +#[cfg(target_os = "linux")] +use std::path::Path; +use std::process; + +#[cfg(target_os = "linux")] +use nix::sys::statfs::{self, FsType}; +#[cfg(target_os = "linux")] +use std::os::fd::AsRawFd; + +#[cfg(target_os = "linux")] +const KRUN_EXIT_CODE_IOCTL: libc::c_ulong = 0x7602; +#[cfg(target_os = "linux")] +// 0x6573_5546 fits in i32, so the cast to FsType's inner c_long is safe on +// both 32-bit (c_long = i32) and 64-bit (c_long = i64) targets. +const VIRTIOFS_MAGIC: libc::c_long = 0x6573_5546; + +#[cfg(target_os = "linux")] +pub fn setup_redirects() { + let Ok(ports_dir) = fs::read_dir("/sys/class/virtio-ports") else { + return; + }; + for entry in ports_dir.flatten() { + let name_path = entry.path().join("name"); + let Ok(port_name) = fs::read_to_string(&name_path) else { + continue; + }; + let (fd, flags) = match port_name.trim_end_matches('\n') { + "krun-stdin" => (libc::STDIN_FILENO, libc::O_RDONLY), + "krun-stdout" => (libc::STDOUT_FILENO, libc::O_WRONLY), + "krun-stderr" => (libc::STDERR_FILENO, libc::O_WRONLY), + _ => continue, + }; + let dev = CString::new(format!("/dev/{}", entry.file_name().to_string_lossy())).unwrap(); + let new_fd = unsafe { libc::open(dev.as_ptr(), flags) }; + if new_fd >= 0 && new_fd != fd { + // new_fd != fd: dup it onto the target and close the spare. + unsafe { + libc::dup2(new_fd, fd); + libc::close(new_fd); + } + } + // new_fd == fd: device opened directly onto the target fd (happens when + // the target was already closed); it is already in the right place. + // new_fd < 0: open failed; leave the existing fd untouched. + } +} + +#[cfg(target_os = "linux")] +pub fn set_exit_code(code: i32) { + let Ok(fs) = statfs::statfs(Path::new("/")) else { + return; + }; + if fs.filesystem_type() != FsType(VIRTIOFS_MAGIC as _) { + return; + } + if let Ok(fd) = fcntl::open(Path::new("/"), OFlag::O_RDONLY, Mode::empty()) { + unsafe { libc::ioctl(fd.as_raw_fd(), KRUN_EXIT_CODE_IOCTL as _, code) }; + } +} + +#[cfg(not(target_os = "linux"))] +pub fn set_exit_code(_code: i32) {} + +pub fn run_workload(argv: &[String]) -> ! { + if env::var("KRUN_INIT_PID1") == Ok("1".to_owned()) { + exec_workload(argv); + } + + match unsafe { unistd::fork() } { + Err(_) => { + set_exit_code(125); + process::exit(125); + } + Ok(ForkResult::Child) => exec_workload(argv), + Ok(ForkResult::Parent { child }) => { + let code = loop { + match wait::waitpid(None, None) { + Ok(WaitStatus::Exited(pid, c)) if pid == child => break c, + Ok(WaitStatus::Signaled(pid, sig, _)) if pid == child => { + break sig as i32 + 128; + } + _ => continue, + } + }; + set_exit_code(code); + unistd::sync(); + #[cfg(target_os = "linux")] + let _ = reboot::reboot(RebootMode::RB_AUTOBOOT); + process::exit(code) + } + } +} + +fn exec_workload(argv: &[String]) -> ! { + #[cfg(target_os = "linux")] + setup_redirects(); + + let c_argv: Vec = argv + .iter() + .map(|s| CString::new(s.as_str()).unwrap()) + .collect(); + + let Err(e) = unistd::execvp(&c_argv[0], &c_argv); + let code = if e == nix::errno::Errno::ENOENT { + 127 + } else { + 126 + }; + eprintln!("Couldn't execute '{}': {e}", argv[0]); + process::exit(code); +} diff --git a/init/src/main.rs b/init/src/main.rs index 5b8124850..e6e10ee66 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -1,6 +1,7 @@ mod config; mod dhcp; mod env; +mod exec; mod fs; fn main() { From a8ccd6b693c1705e3865e4c90811f14bb3341ac2 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Wed, 6 May 2026 15:42:51 -0400 Subject: [PATCH 21/41] init: wire main.rs with full boot sequence Connect all modules in main() in order: 1. mount_block_root() [amd-sev | tdx] 2. mount_filesystems() 3. mount_block_root_device() [KRUN_BLOCK_ROOT_DEVICE] 4. mount_shared_root() 5. setsid + TIOCSCTTY 6. setup_network() 7. config::load() 8. mount_tmpfs() [config tmpfs mount] 9. apply_env / apply_hostname / apply_rlimits 10. chdir to workdir 11. run_workload(argv) Signed-off-by: Jake Correnti Assisted-by: Claude Code:claude-sonnet-4.6 --- init/src/main.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/init/src/main.rs b/init/src/main.rs index e6e10ee66..21db1faab 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -1,10 +1,68 @@ mod config; +#[cfg(target_os = "linux")] mod dhcp; mod env; mod exec; +#[cfg(target_os = "linux")] mod fs; -fn main() { +fn main() -> anyhow::Result<()> { #[cfg(any(feature = "amd-sev", feature = "tdx"))] - fs::mount_tee_block_device().expect("mount block root failed"); + fs::mount_tee_block_device()?; + + #[cfg(target_os = "linux")] + { + fs::mount_filesystems()?; + fs::mount_block_root_device()?; + fs::mount_shared_root()?; + } + + unsafe { + libc::setsid(); + libc::ioctl(0, libc::TIOCSCTTY as _, 1i32); + } + + env::setup_network( + #[cfg(target_os = "linux")] + "eth0", + ); + + #[cfg(target_os = "linux")] + let cfg = config::load(fs::is_mount_point); + #[cfg(not(target_os = "linux"))] + let cfg = config::load(); + + #[cfg(target_os = "linux")] + if let Some(ref path) = cfg.tmpfs { + fs::mount_tmpfs(path)?; + } + + env::apply_env(); + env::apply_hostname(); + env::apply_rlimits(); + + if let Some(ref workdir) = std::env::var("KRUN_WORKDIR").ok().or(cfg.workdir) { + let _ = nix::unistd::chdir(workdir.as_str()); + } + + // The kernel places everything after `--` in the cmdline as this + // process's argv[1..]. The C init built exec_argv by replacing argv[0] + // with KRUN_INIT (or /bin/sh) and keeping argv[1..] in every branch. + let proc_args: Vec = std::env::args().collect(); + + let argv: Vec = if let Ok(init) = std::env::var("KRUN_INIT") { + // KRUN_INIT holds the binary; kernel cmdline args are the arguments. + let mut v = vec![init]; + v.extend_from_slice(&proc_args[1..]); + v + } else if let Some(v) = cfg.argv { + v + } else if proc_args.len() > 1 { + // No KRUN_INIT and no config: treat proc_args[1..] as the command. + proc_args.into_iter().skip(1).collect() + } else { + vec!["/bin/sh".to_string()] + }; + + exec::run_workload(&argv); } From bbd9a648a3ea72ea8bb110c6a8285137d87af3f9 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Wed, 6 May 2026 16:37:44 -0400 Subject: [PATCH 22/41] init: add FreeBSD platform helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add init/src/freebsd.rs with: - kenv_get(): reads a variable from the FreeBSD kernel environment via kenv(2), which is the source of env vars for init before the process environment is set up - populate_env_from_kenv(): imports the known KRUN_* variables from kenv into std::env at startup so the rest of the code can use std::env::var uniformly on both platforms - open_console(): replicates login_tty(3) without linking libutil — revokes existing opens of /dev/console, opens it, creates a new session via setsid(2), sets the controlling terminal via TIOCSCTTY, and dup2s it onto stdio; falls back to /dev/null + /init.log - mount_config_iso() / unmount_config_iso(): mounts the KRUN_CONFIG ISO 9660 image at /mnt via nmount(2) so the JSON config file can be read, then unmounts it afterwards Signed-off-by: Jake Correnti Assisted-by: Claude Code:claude-sonnet-4.6 --- init/src/freebsd.rs | 168 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 init/src/freebsd.rs diff --git a/init/src/freebsd.rs b/init/src/freebsd.rs new file mode 100644 index 000000000..13a52d742 --- /dev/null +++ b/init/src/freebsd.rs @@ -0,0 +1,168 @@ +use std::ffi::CString; + +unsafe extern "C" { + fn revoke(path: *const libc::c_char) -> libc::c_int; +} + +const KENV_MVALLEN: usize = 128; +const ISO_DEV: &str = "/dev/iso9660/KRUN_CONFIG"; +const ISO_MOUNT: &str = "/mnt"; +pub const ISO_CONFIG_PATH: &str = "/mnt/krun_config.json"; + +const KENV_VARS: &[&str] = &[ + "HOSTNAME", + "KRUN_CONFIG", + "KRUN_HOME", + "KRUN_INIT", + "KRUN_INIT_PID1", + "KRUN_RLIMITS", + "KRUN_TERM", + "KRUN_WORKDIR", +]; + +fn kenv_get(name: &str) -> Option { + let c_name = CString::new(name).ok()?; + let mut buf = vec![0u8; KENV_MVALLEN + 1]; + let ret = unsafe { + libc::kenv( + libc::KENV_GET, + c_name.as_ptr(), + buf.as_mut_ptr() as *mut libc::c_char, + (KENV_MVALLEN + 1) as i32, + ) + }; + if ret < 0 { + return None; + } + let s = unsafe { std::ffi::CStr::from_ptr(buf.as_ptr() as *const libc::c_char) }; + Some(s.to_string_lossy().into_owned()) +} + +/// Populate the process environment from the FreeBSD kernel environment. +/// +/// On FreeBSD, init runs before the process environment is set up, so +/// variables like KRUN_INIT must be read from kenv(2) rather than getenv(3). +pub fn populate_env_from_kenv() { + for &var in KENV_VARS { + if let Some(val) = kenv_get(var) { + unsafe { std::env::set_var(var, val) }; + } + } +} + +/// Open /dev/console and make it the controlling terminal. +/// +/// Replicates login_tty(3) inline to avoid a libutil dependency: +/// revoke any existing opens, open the device, create a new session, +/// set the controlling terminal via TIOCSCTTY, then dup2 into stdio. +/// Falls back to /dev/null + /init.log if the console cannot be opened. +pub fn open_console() { + let console = b"/dev/console\0"; + unsafe { revoke(console.as_ptr() as *const libc::c_char) }; + + let fd = unsafe { + libc::open( + console.as_ptr() as *const libc::c_char, + libc::O_RDWR | libc::O_NONBLOCK, + ) + }; + + if fd < 0 { + fallback_console(); + return; + } + + let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) }; + unsafe { libc::fcntl(fd, libc::F_SETFL, flags & !libc::O_NONBLOCK) }; + + unsafe { + libc::setsid(); + libc::ioctl(fd, libc::TIOCSCTTY, 0); + libc::dup2(fd, libc::STDIN_FILENO); + libc::dup2(fd, libc::STDOUT_FILENO); + libc::dup2(fd, libc::STDERR_FILENO); + if fd > libc::STDERR_FILENO { + libc::close(fd); + } + } +} + +fn fallback_console() { + let null = b"/dev/null\0"; + let log = b"/init.log\0"; + + let null_fd = unsafe { libc::open(null.as_ptr().cast(), libc::O_RDWR) }; + if null_fd >= 0 && null_fd != libc::STDIN_FILENO { + unsafe { + libc::dup2(null_fd, libc::STDIN_FILENO); + libc::close(null_fd); + } + } + + let log_fd = unsafe { + libc::open( + log.as_ptr().cast(), + libc::O_WRONLY | libc::O_APPEND | libc::O_CREAT, + 0o644u32, + ) + }; + let out_fd = if log_fd >= 0 { + log_fd + } else { + libc::STDIN_FILENO + }; + unsafe { + libc::dup2(out_fd, libc::STDOUT_FILENO); + libc::dup2(libc::STDOUT_FILENO, libc::STDERR_FILENO); + if log_fd >= 0 && log_fd != libc::STDOUT_FILENO { + libc::close(log_fd); + } + } +} + +/// Mount the KRUN_CONFIG ISO image at /mnt via nmount(2). +/// Returns true on success. +pub fn mount_config_iso() -> bool { + let _ = std::fs::create_dir_all(ISO_MOUNT); + + let fstype_key = b"fstype\0"; + let fstype_val = b"cd9660\0"; + let fspath_key = b"fspath\0"; + let fspath_cstr = CString::new(ISO_MOUNT).unwrap(); + let from_key = b"from\0"; + let from_cstr = CString::new(ISO_DEV).unwrap(); + + let mut iov = [ + libc::iovec { + iov_base: fstype_key.as_ptr() as *mut _, + iov_len: fstype_key.len(), + }, + libc::iovec { + iov_base: fstype_val.as_ptr() as *mut _, + iov_len: fstype_val.len(), + }, + libc::iovec { + iov_base: fspath_key.as_ptr() as *mut _, + iov_len: fspath_key.len(), + }, + libc::iovec { + iov_base: fspath_cstr.as_ptr() as *mut _, + iov_len: fspath_cstr.as_bytes_with_nul().len(), + }, + libc::iovec { + iov_base: from_key.as_ptr() as *mut _, + iov_len: from_key.len(), + }, + libc::iovec { + iov_base: from_cstr.as_ptr() as *mut _, + iov_len: from_cstr.as_bytes_with_nul().len(), + }, + ]; + + unsafe { libc::nmount(iov.as_mut_ptr(), iov.len() as u32, libc::MNT_RDONLY) == 0 } +} + +pub fn unmount_config_iso() { + let mount_cstr = CString::new(ISO_MOUNT).unwrap(); + unsafe { libc::unmount(mount_cstr.as_ptr(), 0) }; +} From 120bea8cc2ff88564cd3c01ee319e740830b7e24 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Wed, 6 May 2026 16:38:06 -0400 Subject: [PATCH 23/41] init: wire FreeBSD platform support Connect the FreeBSD helpers into the boot sequence: - open_console() and populate_env_from_kenv() are called at the very start of main() before anything else - setsid/TIOCSCTTY are Linux-only; open_console() handles session setup on FreeBSD - setlogin("root") is called on FreeBSD after console setup - KRUN_DHCP and DHCP setup are Linux-only - If KRUN_CONFIG is not set, mount_config_iso() is attempted; the ISO is unmounted immediately after config::load() returns - fs::* mounts and mount_shared_root are Linux-only - exec_workload() calls open_console() on FreeBSD instead of setup_redirects(), giving the child process a fresh controlling terminal before execvp Signed-off-by: Jake Correnti Assisted-by: Claude Code:claude-sonnet-4.6 --- init/src/exec.rs | 2 ++ init/src/main.rs | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/init/src/exec.rs b/init/src/exec.rs index c8468a4c8..577f41308 100644 --- a/init/src/exec.rs +++ b/init/src/exec.rs @@ -106,6 +106,8 @@ pub fn run_workload(argv: &[String]) -> ! { fn exec_workload(argv: &[String]) -> ! { #[cfg(target_os = "linux")] setup_redirects(); + #[cfg(target_os = "freebsd")] + crate::freebsd::open_console(); let c_argv: Vec = argv .iter() diff --git a/init/src/main.rs b/init/src/main.rs index 21db1faab..72b410a46 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -3,10 +3,18 @@ mod config; mod dhcp; mod env; mod exec; +#[cfg(target_os = "freebsd")] +mod freebsd; #[cfg(target_os = "linux")] mod fs; fn main() -> anyhow::Result<()> { + #[cfg(target_os = "freebsd")] + freebsd::open_console(); + + #[cfg(target_os = "freebsd")] + freebsd::populate_env_from_kenv(); + #[cfg(any(feature = "amd-sev", feature = "tdx"))] fs::mount_tee_block_device()?; @@ -22,16 +30,29 @@ fn main() -> anyhow::Result<()> { libc::ioctl(0, libc::TIOCSCTTY as _, 1i32); } + #[cfg(target_os = "freebsd")] + unsafe { + libc::setlogin(b"root\0".as_ptr().cast()) + }; + env::setup_network( #[cfg(target_os = "linux")] "eth0", ); + #[cfg(target_os = "freebsd")] + let iso_mounted = std::env::var("KRUN_CONFIG").is_err() && freebsd::mount_config_iso(); + #[cfg(target_os = "linux")] let cfg = config::load(fs::is_mount_point); #[cfg(not(target_os = "linux"))] let cfg = config::load(); + #[cfg(target_os = "freebsd")] + if iso_mounted { + freebsd::unmount_config_iso(); + } + #[cfg(target_os = "linux")] if let Some(ref path) = cfg.tmpfs { fs::mount_tmpfs(path)?; From 5ef9975a85542127be369a4790000bbc374c6249 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Fri, 8 May 2026 14:18:54 -0400 Subject: [PATCH 24/41] ci: fix FreeBSD cross-compilation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the C-based BSD init build rule (which referenced the now-deleted init/init.c) with a cargo build rule targeting the correct Rust triple. Makefile: - Remove dead INIT_SRC = init/init.c variable. - Derive FREEBSD_RUST_TARGET from the host ARCH with arm64→aarch64 substitution to get the correct Rust triple. - Set CARGO_BSD_RUSTFLAGS with the clang cross-linker flags (mirroring the existing CC_BSD setup) so cargo can link for FreeBSD. - aarch64-unknown-freebsd is a Tier 3 target with no prebuilt std; use +nightly -Z build-std for that case. setup-build-env: - Add rustup target add x86_64-unknown-freebsd (Tier 2, prebuilt std). - Install nightly toolchain + rust-src for the aarch64 FreeBSD case. cross-compilation.yml: - Add clang to the Linux cross-compilation dependencies so the FreeBSD linker flags resolve correctly on Linux runners. Signed-off-by: Jake Correnti Assisted-by: Claude Code:claude-sonnet-4.6 --- .github/actions/setup-build-env/action.yml | 3 +++ .github/workflows/cross-compilation.yml | 4 ++-- Makefile | 25 ++++++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/.github/actions/setup-build-env/action.yml b/.github/actions/setup-build-env/action.yml index f407b81c2..fc2aa34f0 100644 --- a/.github/actions/setup-build-env/action.yml +++ b/.github/actions/setup-build-env/action.yml @@ -9,6 +9,9 @@ runs: rustup update stable rustup default stable rustup component add rustfmt clippy + rustup target add x86_64-unknown-freebsd + rustup toolchain install nightly + rustup component add rust-src --toolchain nightly - name: Cache Cargo dependencies uses: actions/cache@v4 diff --git a/.github/workflows/cross-compilation.yml b/.github/workflows/cross-compilation.yml index 40273f800..0fe86dcfe 100644 --- a/.github/workflows/cross-compilation.yml +++ b/.github/workflows/cross-compilation.yml @@ -35,7 +35,7 @@ jobs: - name: Install cross-compilation dependencies run: | sudo apt-get update - sudo apt-get install -y --no-install-recommends lld + sudo apt-get install -y --no-install-recommends clang lld - name: Build FreeBSD init on Linux run: make BUILD_BSD_INIT=1 -- init/init-freebsd @@ -52,7 +52,7 @@ jobs: - name: Install cross-compilation dependencies run: | sudo apt-get update - sudo apt-get install -y --no-install-recommends lld + sudo apt-get install -y --no-install-recommends clang lld - name: Build FreeBSD init on Linux aarch64 run: make BUILD_BSD_INIT=1 -- init/init-freebsd diff --git a/Makefile b/Makefile index 9de26c976..1c20ae9bc 100644 --- a/Makefile +++ b/Makefile @@ -20,8 +20,6 @@ AWS_NITRO_INIT_SRC = \ AWS_NITRO_INIT_LD_FLAGS = -larchive -lnsm -INIT_SRC = init/init.c - ifeq ($(SEV),1) VARIANT = -sev FEATURE_FLAGS := --features amd-sev @@ -143,6 +141,7 @@ else endif # Cross-compile on macOS with the LLVM linker (brew install lld) CC_BSD=$(CLANG) -target $(ARCH)-unknown-freebsd -fuse-ld=lld -stdlib=libc++ -Wl,-strip-debug --sysroot $(SYSROOT_BSD) + CARGO_BSD_RUSTFLAGS = -C linker=$(CLANG) -C link-arg=-target -C link-arg=$(ARCH)-unknown-freebsd -C link-arg=-fuse-ld=lld -C link-arg=-stdlib=libc++ -C link-arg=--sysroot=$(abspath $(SYSROOT_BSD)) else ifeq ($(OS),Linux) # Linux -> FreeBSD cross-compilation ifeq ($(SYSROOT_BSD),) @@ -153,16 +152,34 @@ else endif # Cross-compile on Linux with clang CC_BSD=$(CLANG) -target $(ARCH)-unknown-freebsd -fuse-ld=lld -Wl,-strip-debug --sysroot $(SYSROOT_BSD) + CARGO_BSD_RUSTFLAGS = -C linker=$(CLANG) -C link-arg=-target -C link-arg=$(ARCH)-unknown-freebsd -C link-arg=-fuse-ld=lld -C link-arg=--sysroot=$(abspath $(SYSROOT_BSD)) else # Build on FreeBSD host CC_BSD=$(CC) SYSROOT_BSD_TARGET = + CARGO_BSD_RUSTFLAGS = +endif + +FREEBSD_RUST_TARGET = $(subst arm64,aarch64,$(ARCH))-unknown-freebsd + +# aarch64-unknown-freebsd is Tier 3: no prebuilt std, requires nightly + build-std. +ifeq ($(FREEBSD_RUST_TARGET),aarch64-unknown-freebsd) + CARGO_BSD_TOOLCHAIN = +nightly + CARGO_BSD_EXTRA_FLAGS = -Z build-std +else + CARGO_BSD_TOOLCHAIN = + CARGO_BSD_EXTRA_FLAGS = endif ifeq ($(BUILD_BSD_INIT),1) INIT_BINARY_BSD = init/init-freebsd -$(INIT_BINARY_BSD): $(INIT_SRC) $(SYSROOT_BSD_TARGET) - $(CC_BSD) -std=c23 -O2 -static -Wall -o $@ $(INIT_SRC) -lutil +$(INIT_BINARY_BSD): $(shell find init/src -name '*.rs') init/Cargo.toml $(SYSROOT_BSD_TARGET) + RUSTFLAGS="$(CARGO_BSD_RUSTFLAGS)" \ + cargo $(CARGO_BSD_TOOLCHAIN) build --release \ + $(CARGO_BSD_EXTRA_FLAGS) \ + --manifest-path init/Cargo.toml \ + --target $(FREEBSD_RUST_TARGET) + cp target/$(FREEBSD_RUST_TARGET)/release/krun-init $@ endif # Sysroot preparation rules for cross-compilation on macOS From 9dacc9c0493158033b85630913ef16006ac4c11c Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Thu, 7 May 2026 15:23:20 -0400 Subject: [PATCH 25/41] init: port timesync clock_worker to Rust Implements the timesync feature behind the `timesync` cargo feature flag. Receives host-side nanosecond timestamps over AF_VSOCK/SOCK_DGRAM on port 123 and applies them via clock_settime when the delta exceeds 100ms. Signed-off-by: Jake Correnti Assisted-by: Claude Code:claude-sonnet-4.6 --- init/src/main.rs | 5 ++++ init/src/timesync.rs | 59 ++++++++++++++++++++++++++++++++++++++++++ src/devices/Cargo.toml | 1 + src/init-blob/build.rs | 4 +-- src/libkrun/Cargo.toml | 1 + 5 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 init/src/timesync.rs diff --git a/init/src/main.rs b/init/src/main.rs index 72b410a46..8aaf25392 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -7,6 +7,8 @@ mod exec; mod freebsd; #[cfg(target_os = "linux")] mod fs; +#[cfg(feature = "timesync")] +mod timesync; fn main() -> anyhow::Result<()> { #[cfg(target_os = "freebsd")] @@ -85,5 +87,8 @@ fn main() -> anyhow::Result<()> { vec!["/bin/sh".to_string()] }; + #[cfg(feature = "timesync")] + timesync::run(); + exec::run_workload(&argv); } diff --git a/init/src/timesync.rs b/init/src/timesync.rs new file mode 100644 index 000000000..128365112 --- /dev/null +++ b/init/src/timesync.rs @@ -0,0 +1,59 @@ +use std::mem; + +const TSYNC_PORT: u32 = 123; +const NANOS_IN_SECOND: u64 = 1_000_000_000; +const DELTA_SYNC: u64 = 100_000_000; // 100ms — don't bother adjusting for smaller drifts + +pub fn run() { + let sock = unsafe { libc::socket(libc::AF_VSOCK, libc::SOCK_DGRAM, 0) }; + if sock < 0 { + return; + } + + let mut addr: libc::sockaddr_vm = unsafe { mem::zeroed() }; + addr.svm_family = libc::AF_VSOCK as _; + addr.svm_port = TSYNC_PORT; + addr.svm_cid = libc::VMADDR_CID_ANY; + + if unsafe { + libc::bind( + sock, + &addr as *const _ as *const libc::sockaddr, + mem::size_of_val(&addr) as _, + ) + } < 0 + { + unsafe { libc::close(sock) }; + return; + } + + std::thread::Builder::new() + .name("timesync".into()) + .spawn(move || { + loop { + let mut buf = [0u8; 8]; + let n = unsafe { libc::recv(sock, buf.as_mut_ptr() as *mut _, buf.len(), 0) }; + if n < 0 { + break; + } + if n != 8 { + continue; + } + + let host_ns = u64::from_le_bytes(buf); + + let mut guest_ts: libc::timespec = unsafe { mem::zeroed() }; + unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, &mut guest_ts) }; + let guest_ns = guest_ts.tv_sec as u64 * NANOS_IN_SECOND + guest_ts.tv_nsec as u64; + + if host_ns.abs_diff(guest_ns) > DELTA_SYNC { + let host_ts = libc::timespec { + tv_sec: (host_ns / NANOS_IN_SECOND) as libc::time_t, + tv_nsec: (host_ns % NANOS_IN_SECOND) as libc::c_long, + }; + unsafe { libc::clock_settime(libc::CLOCK_REALTIME, &host_ts) }; + } + } + }) + .unwrap(); +} diff --git a/src/devices/Cargo.toml b/src/devices/Cargo.toml index 584de4358..f63035d57 100644 --- a/src/devices/Cargo.toml +++ b/src/devices/Cargo.toml @@ -19,6 +19,7 @@ input = ["zerocopy", "krun_input"] virgl_resource_map2 = [] aws-nitro = [] test_utils = [] +timesync = [] vhost-user = ["vhost", "vmm-sys-util"] [dependencies] diff --git a/src/init-blob/build.rs b/src/init-blob/build.rs index eb47e6979..da5afa9d3 100644 --- a/src/init-blob/build.rs +++ b/src/init-blob/build.rs @@ -78,8 +78,6 @@ fn build_rust_init() -> PathBuf { workspace_root.join("init/src").display() ); println!("cargo:rerun-if-changed={}", init_manifest.display()); - println!("cargo:rerun-if-env-changed=TIMESYNC"); - // Resolve which rustc (and paired cargo) to use for the init binary. let (rustc, cargo, use_musl) = match find_musl_rustc(&default_rustc) { Some(musl_rustc) => { @@ -125,7 +123,7 @@ fn build_rust_init() -> PathBuf { if cfg!(feature = "tdx") { features.push("tdx"); } - if env::var_os("TIMESYNC").is_some_and(|v| v == "1") { + if cfg!(feature = "timesync") { features.push("timesync"); } if !features.is_empty() { diff --git a/src/libkrun/Cargo.toml b/src/libkrun/Cargo.toml index 7ab669fbf..711fd3450 100644 --- a/src/libkrun/Cargo.toml +++ b/src/libkrun/Cargo.toml @@ -19,6 +19,7 @@ input = ["krun_input", "vmm/input", "devices/input"] virgl_resource_map2 = ["devices/virgl_resource_map2"] aws-nitro = ["vmm/aws-nitro", "devices/aws-nitro", "dep:aws-nitro", "dep:nitro-enclaves"] vhost-user = ["vmm/vhost-user", "devices/vhost-user"] +timesync = ["devices/timesync", "init-blob/timesync"] [dependencies] crossbeam-channel = ">=0.5.15" From ed23c02347131e7943fc45d7bf26defe400d4d4c Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Wed, 6 May 2026 15:43:04 -0400 Subject: [PATCH 26/41] init: remove C sources and tee/ attestation code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delete init/init.c, init/dhcp.c, init/dhcp.h, init/jsmn.h, and the entire init/tee/ directory (snp_attest.c/h and the KBS client). The amd-sev feature no longer performs LUKS unlock or KBS attestation — it mounts /dev/vda as ext4 like the tdx path does. Signed-off-by: Jake Correnti Assisted-by: Claude Code:claude-sonnet-4.6 --- init/dhcp.c | 620 -------------------------------------- init/dhcp.h | 61 ---- init/jsmn.h | 494 ------------------------------ init/tee/kbs/kbs.h | 53 ---- init/tee/kbs/kbs_crypto.c | 332 -------------------- init/tee/kbs/kbs_curl.c | 232 -------------- init/tee/kbs/kbs_types.c | 247 --------------- init/tee/kbs/kbs_util.c | 165 ---------- init/tee/snp_attest.c | 220 -------------- init/tee/snp_attest.h | 107 ------- 10 files changed, 2531 deletions(-) delete mode 100644 init/dhcp.c delete mode 100644 init/dhcp.h delete mode 100644 init/jsmn.h delete mode 100644 init/tee/kbs/kbs.h delete mode 100644 init/tee/kbs/kbs_crypto.c delete mode 100644 init/tee/kbs/kbs_curl.c delete mode 100644 init/tee/kbs/kbs_types.c delete mode 100644 init/tee/kbs/kbs_util.c delete mode 100644 init/tee/snp_attest.c delete mode 100644 init/tee/snp_attest.h diff --git a/init/dhcp.c b/init/dhcp.c deleted file mode 100644 index e852bded8..000000000 --- a/init/dhcp.c +++ /dev/null @@ -1,620 +0,0 @@ -/* - * DHCP Client Implementation - * - * Standalone DHCP client for configuring IPv4 network interfaces. - * Translated from Rust implementation in muvm/src/guest/net.rs - */ - -#include "dhcp.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DHCP_BUFFER_SIZE 576 -#define DHCP_MSG_OFFER 2 -#define DHCP_MSG_ACK 5 - -/* Helper function to send netlink message */ -static int nl_send(int sock, struct nlmsghdr *nlh) -{ - struct sockaddr_nl sa = { - .nl_family = AF_NETLINK, - }; - - struct iovec iov = { - .iov_base = nlh, - .iov_len = nlh->nlmsg_len, - }; - - struct msghdr msg = { - .msg_name = &sa, - .msg_namelen = sizeof(sa), - .msg_iov = &iov, - .msg_iovlen = 1, - }; - - return sendmsg(sock, &msg, 0); -} - -/* Helper function to receive netlink response */ -static int nl_recv(int sock, char *buf, size_t len) -{ - struct sockaddr_nl sa; - struct iovec iov = { - .iov_base = buf, - .iov_len = len, - }; - - struct msghdr msg = { - .msg_name = &sa, - .msg_namelen = sizeof(sa), - .msg_iov = &iov, - .msg_iovlen = 1, - }; - - return recvmsg(sock, &msg, 0); -} - -/* Add routing attribute to netlink message */ -static void add_rtattr(struct nlmsghdr *nlh, int type, const void *data, - int len) -{ - int rtalen = RTA_SPACE(len); - struct rtattr *rta = - (struct rtattr *)(((char *)nlh) + NLMSG_ALIGN(nlh->nlmsg_len)); - rta->rta_type = type; - rta->rta_len = RTA_LENGTH(len); - memcpy(RTA_DATA(rta), data, len); - nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + rtalen; -} - -/* Set MTU */ -static int set_mtu(int nl_sock, int iface_index, unsigned int mtu) -{ - char buf[4096]; - struct nlmsghdr *nlh; - struct nlmsgerr *err; - struct ifinfomsg *ifi; - - memset(buf, 0, sizeof(buf)); - nlh = (struct nlmsghdr *)buf; - nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); - nlh->nlmsg_type = RTM_NEWLINK; - nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; - nlh->nlmsg_seq = 1; - nlh->nlmsg_pid = getpid(); - - ifi = (struct ifinfomsg *)NLMSG_DATA(nlh); - ifi->ifi_family = AF_UNSPEC; - ifi->ifi_type = ARPHRD_ETHER; - ifi->ifi_index = iface_index; - - add_rtattr(nlh, IFLA_MTU, &mtu, sizeof(mtu)); - - if (nl_send(nl_sock, nlh) < 0) { - perror("nl_send failed for set_mtu"); - return -1; - } - - /* Receive ACK */ - int len = nl_recv(nl_sock, buf, sizeof(buf)); - if (len < (int)NLMSG_LENGTH(sizeof(struct nlmsgerr))) { - perror("nl_recv failed for set_mtu"); - return -1; - } - - if (nlh->nlmsg_type != NLMSG_ERROR) { - printf("netlink didn't return a valid answer for set_mtu\n"); - return -1; - } - - err = (struct nlmsgerr *)NLMSG_DATA(nlh); - if (err->error != 0) { - printf("netlink returned an error for set_mtu: %d\n", err->error); - return -1; - } - - return 0; -} - -/* Add or delete IPv4 route */ -static int mod_route4(int nl_sock, int iface_index, int cmd, struct in_addr gw) -{ - char buf[4096]; - struct nlmsghdr *nlh; - struct nlmsgerr *err; - struct rtmsg *rtm; - struct in_addr dst = {.s_addr = INADDR_ANY}; - - memset(buf, 0, sizeof(buf)); - nlh = (struct nlmsghdr *)buf; - nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); - nlh->nlmsg_type = cmd; - nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK; - nlh->nlmsg_seq = 1; - nlh->nlmsg_pid = getpid(); - - rtm = (struct rtmsg *)NLMSG_DATA(nlh); - rtm->rtm_family = AF_INET; - rtm->rtm_dst_len = 0; - rtm->rtm_src_len = 0; - rtm->rtm_tos = 0; - rtm->rtm_table = RT_TABLE_MAIN; - rtm->rtm_protocol = RTPROT_BOOT; - rtm->rtm_scope = RT_SCOPE_UNIVERSE; - rtm->rtm_type = RTN_UNICAST; - rtm->rtm_flags = 0; - - add_rtattr(nlh, RTA_OIF, &iface_index, sizeof(iface_index)); - add_rtattr(nlh, RTA_DST, &dst, sizeof(dst)); - add_rtattr(nlh, RTA_GATEWAY, &gw, sizeof(gw)); - - if (nl_send(nl_sock, nlh) < 0) { - perror("nl_send failed for mod_route4"); - return -1; - } - - /* Receive ACK */ - int len = nl_recv(nl_sock, buf, sizeof(buf)); - if (len < (int)NLMSG_LENGTH(sizeof(struct nlmsgerr))) { - perror("nl_recv failed for mod_route4"); - return -1; - } - - if (nlh->nlmsg_type != NLMSG_ERROR) { - printf("netlink didn't return a valid answer for mod_route4\n"); - return -1; - } - - err = (struct nlmsgerr *)NLMSG_DATA(nlh); - if (err->error != 0) { - printf("netlink returned an error for mod_route4: %d\n", err->error); - return -1; - } - - return 0; -} - -/* Add or delete IPv4 address */ -static int mod_addr4(int nl_sock, int iface_index, int cmd, struct in_addr addr, - unsigned char prefix_len) -{ - char buf[4096]; - struct nlmsghdr *nlh; - struct nlmsgerr *err; - struct ifaddrmsg *ifa; - - memset(buf, 0, sizeof(buf)); - nlh = (struct nlmsghdr *)buf; - nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); - nlh->nlmsg_type = cmd; - nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK; - nlh->nlmsg_seq = 1; - nlh->nlmsg_pid = getpid(); - - ifa = (struct ifaddrmsg *)NLMSG_DATA(nlh); - ifa->ifa_family = AF_INET; - ifa->ifa_prefixlen = prefix_len; - ifa->ifa_flags = 0; - ifa->ifa_scope = RT_SCOPE_UNIVERSE; - ifa->ifa_index = iface_index; - - add_rtattr(nlh, IFA_LOCAL, &addr, sizeof(addr)); - add_rtattr(nlh, IFA_ADDRESS, &addr, sizeof(addr)); - - if (nl_send(nl_sock, nlh) < 0) { - perror("nl_send failed for mod_addr4"); - return -1; - } - - /* Receive ACK */ - int len = nl_recv(nl_sock, buf, sizeof(buf)); - if (len < (int)NLMSG_LENGTH(sizeof(struct nlmsgerr))) { - perror("nl_recv failed for mod_addr4"); - return -1; - } - - if (nlh->nlmsg_type != NLMSG_ERROR) { - printf("netlink didn't return a valid answer for mod_addr4\n"); - return -1; - } - - err = (struct nlmsgerr *)NLMSG_DATA(nlh); - if (err->error != 0) { - printf("netlink returned an error for mod_addr4: %d\n", err->error); - return -1; - } - - return 0; -} - -/* Count leading ones in a 32-bit value */ -static unsigned char count_leading_ones(uint32_t val) -{ - unsigned char count = 0; - for (int i = 31; i >= 0; i--) { - if (val & (1U << i)) { - count++; - } else { - break; - } - } - return count; -} - -/* Return the DHCP message type (option 53) from a response, or 0 */ -static unsigned char get_dhcp_msg_type(const unsigned char *response, - ssize_t len) -{ - /* Walk DHCP options (TLV chain starting after the magic cookie) */ - size_t p = 240; - while (p < (size_t)len) { - unsigned char opt = response[p]; - - if (opt == 0xff) /* end */ - break; - if (opt == 0) { /* padding */ - p++; - continue; - } - - if (p + 1 >= (size_t)len) - break; - - unsigned char opt_len = response[p + 1]; - p += 2; - - if (p + opt_len > (size_t)len) - break; - if (opt == 53 && opt_len >= 1) /* Message Type */ - return response[p]; - - p += opt_len; - } - return 0; -} - -/* Parse a DHCP ACK and configure the interface. Returns 0 or -1 on error. */ -static int handle_dhcp_ack(int nl_sock, int iface_index, - const unsigned char *response, ssize_t len) -{ - /* Need at least 240 bytes (DHCP header + magic cookie) + 1 for options */ - if (len < 241) { - printf("DHCPACK too short (%zd bytes)\n", len); - return -1; - } - - /* Parse DHCP response */ - struct in_addr addr; - /* yiaddr is at offset 16-19 in network byte order */ - memcpy(&addr.s_addr, &response[16], sizeof(addr.s_addr)); - - if (addr.s_addr == INADDR_ANY) { - printf("DHCPACK has no address (yiaddr is 0.0.0.0)\n"); - return -1; - } - - struct in_addr netmask = {.s_addr = INADDR_ANY}; - struct in_addr router = {.s_addr = INADDR_ANY}; - /* Clamp MTU to passt's limit */ - uint16_t mtu = 65520; - - FILE *resolv = fopen("/etc/resolv.conf", "w"); - if (!resolv) { - perror("Failed to open /etc/resolv.conf"); - } - - /* Parse DHCP options (start at offset 240 after magic cookie) */ - size_t p = 240; - while (p < (size_t)len) { - unsigned char opt = response[p]; - - if (opt == 0xff) { - /* Option 255: End (of options) */ - break; - } - - if (opt == 0) { /* Padding */ - p++; - continue; - } - - if (p + 1 >= (size_t)len) - break; - - unsigned char opt_len = response[p + 1]; - p += 2; /* Length doesn't include code and length field itself */ - - if (p + opt_len > (size_t)len) { - /* Malformed packet, option length exceeds packet boundary */ - break; - } - - if (opt == 1 && opt_len >= 4) { - /* Option 1: Subnet Mask */ - memcpy(&netmask.s_addr, &response[p], sizeof(netmask.s_addr)); - } else if (opt == 3 && opt_len >= 4) { - /* Option 3: Router */ - memcpy(&router.s_addr, &response[p], sizeof(router.s_addr)); - } else if (opt == 6 && opt_len >= 4) { - /* Option 6: Domain Name Server */ - if (resolv) { - for (int dns_p = p; dns_p + 4 <= p + opt_len; dns_p += 4) { - fprintf(resolv, "nameserver %d.%d.%d.%d\n", response[dns_p], - response[dns_p + 1], response[dns_p + 2], - response[dns_p + 3]); - } - } - } else if (opt == 26 && opt_len >= 2) { - /* Option 26: Interface MTU */ - mtu = (response[p] << 8) | response[p + 1]; - - /* We don't know yet if IPv6 is available: don't go below 1280 B - */ - if (mtu < 1280) - mtu = 1280; - if (mtu > 65520) - mtu = 65520; - } - - p += opt_len; - } - - if (resolv) { - fclose(resolv); - } - - /* Calculate prefix length from netmask */ - unsigned char prefix_len = count_leading_ones(ntohl(netmask.s_addr)); - - if (mod_addr4(nl_sock, iface_index, RTM_NEWADDR, addr, prefix_len) != 0) { - printf("couldn't add the address provided by the DHCP server\n"); - return -1; - } - if (mod_route4(nl_sock, iface_index, RTM_NEWROUTE, router) != 0) { - printf("couldn't add the default route provided by the DHCP server\n"); - return -1; - } - set_mtu(nl_sock, iface_index, mtu); - return 0; -} - -/* Send DISCOVER with Rapid Commit, process ACK, configure address and route */ -int do_dhcp(const char *iface) -{ - struct sockaddr_in bind_addr, dest_addr; - struct dhcp_packet request = {0}; - unsigned char response[DHCP_BUFFER_SIZE]; - struct timeval timeout; - int iface_index; - int broadcast = 1; - int nl_sock = -1; - int sock = -1; - int ret = -1; - - iface_index = if_nametoindex(iface); - if (iface_index == 0) { - perror("Failed to find index for network interface"); - return ret; - } - - nl_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - if (nl_sock < 0) { - perror("Failed to create netlink socket"); - return ret; - } - - struct sockaddr_nl sa = { - .nl_family = AF_NETLINK, - .nl_pid = getpid(), - .nl_groups = 0, - }; - - if (bind(nl_sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) { - perror("Failed to bind netlink socket"); - goto cleanup; - } - - /* Send request (DHCPDISCOVER) */ - sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (sock < 0) { - perror("socket failed"); - goto cleanup; - } - - /* Allow broadcast */ - if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, - sizeof(broadcast)) < 0) { - perror("setsockopt SO_BROADCAST failed"); - goto cleanup; - } - - if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, iface, - strlen(iface) + 1) < 0) { - perror("setsockopt SO_BINDTODEVICE failed"); - goto cleanup; - } - - /* Bind to port 68 (DHCP client) */ - memset(&bind_addr, 0, sizeof(bind_addr)); - bind_addr.sin_family = AF_INET; - bind_addr.sin_port = htons(68); - bind_addr.sin_addr.s_addr = INADDR_ANY; - - if (bind(sock, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) { - perror("bind failed"); - goto cleanup; - } - - request.op = 1; /* BOOTREQUEST */ - request.htype = 1; /* Hardware address type: Ethernet */ - request.hlen = 6; /* Hardware address length */ - request.hops = 0; /* DHCP relay Hops */ - request.xid = - htonl(getpid()); /* Transaction ID: use PID for some randomness */ - request.secs = - 0; /* Seconds elapsed since beginning of acquisition or renewal */ - request.flags = htons(0x8000); /* DHCP message flags: Broadcast */ - request.ciaddr = 0; /* Client IP address (not set yet) */ - request.yiaddr = 0; /* 'your' IP address (server will fill) */ - request.siaddr = 0; /* Server IP address (not set) */ - request.giaddr = 0; /* Relay agent IP address (not set) */ - request.magic = htonl(0x63825363); /* Magic cookie */ - - /* Populate chaddr with the interface's MAC address */ - struct ifreq mac_ifr; - memset(&mac_ifr, 0, sizeof(mac_ifr)); - strncpy(mac_ifr.ifr_name, iface, IFNAMSIZ); - - if (ioctl(sock, SIOCGIFHWADDR, &mac_ifr) < 0) { - perror("ioctl(SIOCGIFHWADDR) failed"); - goto cleanup; - } - memcpy(request.chaddr, mac_ifr.ifr_hwaddr.sa_data, 6); - - /* Build DHCP options */ - int opt_offset = 0; - - /* Option 53: DHCP Message Type = DISCOVER (1) */ - request.options[opt_offset++] = 53; - request.options[opt_offset++] = 1; - request.options[opt_offset++] = 1; - - /* Option 80: Rapid Commit (RFC 4039) */ - request.options[opt_offset++] = 80; - request.options[opt_offset++] = 0; - - /* Option 255: End of options */ - request.options[opt_offset++] = 0xff; - - /* Remaining bytes are padding (up to 300 bytes) */ - - /* Send DHCP DISCOVER */ - memset(&dest_addr, 0, sizeof(dest_addr)); - dest_addr.sin_family = AF_INET; - dest_addr.sin_port = htons(67); - dest_addr.sin_addr.s_addr = INADDR_BROADCAST; - - if (sendto(sock, &request, sizeof(request), 0, - (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) { - perror("sendto failed"); - goto cleanup; - } - - /* Keep IPv6-only fast: set receive timeout to 100ms */ - timeout.tv_sec = 0; - timeout.tv_usec = 100000; - if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < - 0) { - perror("setsockopt SO_RCVTIMEO failed"); - goto cleanup; - } - - /* Get response: DHCPACK (Rapid Commit) or DHCPOFFER */ - struct sockaddr_in from_addr; - socklen_t from_len = sizeof(from_addr); - ssize_t len = recvfrom(sock, response, sizeof(response), 0, - (struct sockaddr *)&from_addr, &from_len); - - if (len <= 0) - goto done; /* No DHCP response — not an error, VM may be IPv6-only */ - - unsigned char msg_type = get_dhcp_msg_type(response, len); - - if (msg_type == DHCP_MSG_ACK) { - /* Rapid Commit — server sent ACK directly */ - close(sock); - sock = -1; - if (handle_dhcp_ack(nl_sock, iface_index, response, len) != 0) - goto cleanup; - } else if (msg_type == DHCP_MSG_OFFER) { - /* - * DHCPOFFER — complete the 4-way handshake by sending DHCPREQUEST - * and waiting for DHCPACK. Servers without Rapid Commit (e.g. - * gvproxy) require this. - */ - struct in_addr offered_addr; - memcpy(&offered_addr.s_addr, &response[16], - sizeof(offered_addr.s_addr)); - - /* Build DHCPREQUEST */ - memset(request.options, 0, sizeof(request.options)); - opt_offset = 0; - - /* Option 53: DHCP Message Type = REQUEST (3) */ - request.options[opt_offset++] = 53; - request.options[opt_offset++] = 1; - request.options[opt_offset++] = 3; - - /* Option 50: Requested IP Address */ - request.options[opt_offset++] = 50; - request.options[opt_offset++] = 4; - memcpy(&request.options[opt_offset], &offered_addr.s_addr, 4); - opt_offset += 4; - - /* Option 54: Server Identifier (from_addr) */ - request.options[opt_offset++] = 54; - request.options[opt_offset++] = 4; - memcpy(&request.options[opt_offset], &from_addr.sin_addr.s_addr, 4); - opt_offset += 4; - - /* Option 255: End */ - request.options[opt_offset++] = 0xff; - - if (sendto(sock, &request, sizeof(request), 0, - (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) { - perror("sendto DHCPREQUEST failed"); - goto cleanup; - } - - from_len = sizeof(from_addr); - len = recvfrom(sock, response, sizeof(response), 0, - (struct sockaddr *)&from_addr, &from_len); - - close(sock); - sock = -1; - - if (len <= 0) { - printf("no DHCPACK received\n"); - goto cleanup; - } - - if (get_dhcp_msg_type(response, len) != DHCP_MSG_ACK) { - printf("expected DHCPACK but got message type %d\n", - get_dhcp_msg_type(response, len)); - goto cleanup; - } - - if (handle_dhcp_ack(nl_sock, iface_index, response, len) != 0) - goto cleanup; - } else { - printf("unexpected DHCP message type %d\n", msg_type); - goto cleanup; - } - -done: - ret = 0; -cleanup: - if (sock >= 0) { - close(sock); - } - if (nl_sock >= 0) { - close(nl_sock); - } - return ret; -} diff --git a/init/dhcp.h b/init/dhcp.h deleted file mode 100644 index 39e20ead7..000000000 --- a/init/dhcp.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * DHCP Client Implementation - * - * Standalone DHCP client for configuring IPv4 network interfaces. - * Translated from Rust implementation in muvm/src/guest/net.rs - */ - -#ifndef DHCP_H -#define DHCP_H - -#include - -/* BOOTP vendor-specific area size (64) - magic cookie (4) */ -#define DHCP_OPTIONS_SIZE 60 - -/* DHCP packet structure (RFC 2131) */ -struct dhcp_packet { - uint8_t op; /* Message op code / message type (1 = BOOTREQUEST) */ - uint8_t htype; /* Hardware address type (1 = Ethernet) */ - uint8_t hlen; /* Hardware address length (6 for Ethernet) */ - uint8_t hops; /* Client sets to zero */ - uint32_t xid; /* Transaction ID */ - uint16_t secs; /* Seconds elapsed since client began address acquisition */ - uint16_t flags; /* Flags (0x8000 = Broadcast) */ - uint32_t ciaddr; /* Client IP address */ - uint32_t yiaddr; /* 'your' (client) IP address */ - uint32_t siaddr; /* IP address of next server to use in bootstrap */ - uint32_t giaddr; /* Relay agent IP address */ - uint8_t chaddr[16]; /* Client hardware address */ - uint8_t sname[64]; /* Optional server host name */ - uint8_t file[128]; /* Boot file name */ - uint32_t magic; /* Magic cookie (0x63825363) */ - uint8_t options[DHCP_OPTIONS_SIZE]; /* Options field */ -} __attribute__((packed)); - -/* - * Perform DHCP discovery and configuration for a network interface - * - * This function: - * 1. Binds a UDP socket to the interface using SO_BINDTODEVICE - * 2. Sends a DHCP DISCOVER message with Rapid Commit option - * 3. Waits up to 100ms for a response: - * - If DHCPACK (Rapid Commit): applies configuration directly - * - If DHCPOFFER: sends DHCPREQUEST and waits for DHCPACK - * - If no response: returns success (VM may be IPv6-only) - * 4. Parses the ACK and configures: - * - IPv4 address with appropriate prefix length - * - Default gateway route - * - DNS servers (overwriting /etc/resolv.conf) - * - Interface MTU - * - * Parameters: - * iface - The name of the network interface to be configured. - * - * Returns: - * 0 on success (whether or not DHCP response was received) - * -1 on error - */ -int do_dhcp(const char *iface); - -#endif /* DHCP_H */ diff --git a/init/jsmn.h b/init/jsmn.h deleted file mode 100644 index 30d37a24a..000000000 --- a/init/jsmn.h +++ /dev/null @@ -1,494 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2010 Serge Zaitsev - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef JSMN_H -#define JSMN_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define JSMN_API static - -/** - * JSON type identifier. Basic types are: - * o Object - * o Array - * o String - * o Other primitive: number, boolean (true/false) or null - */ -typedef enum { - JSMN_UNDEFINED = 0, - JSMN_OBJECT = 1 << 0, - JSMN_ARRAY = 1 << 1, - JSMN_STRING = 1 << 2, - JSMN_PRIMITIVE = 1 << 3 -} jsmntype_t; - -enum jsmnerr { - /* Not enough tokens were provided */ - JSMN_ERROR_NOMEM = -1, - /* Invalid character inside JSON string */ - JSMN_ERROR_INVAL = -2, - /* The string is not a full JSON packet, more bytes expected */ - JSMN_ERROR_PART = -3 -}; - -/** - * JSON token description. - * type type (object, array, string etc.) - * start start position in JSON data string - * end end position in JSON data string - */ -typedef struct jsmntok { - jsmntype_t type; - int start; - int end; - int size; -#ifdef JSMN_PARENT_LINKS - int parent; -#endif -} jsmntok_t; - -/** - * JSON parser. Contains an array of token blocks available. Also stores - * the string being parsed now and current position in that string. - */ -typedef struct jsmn_parser { - unsigned int pos; /* offset in the JSON string */ - unsigned int toknext; /* next token to allocate */ - int toksuper; /* superior token node, e.g. parent object or array */ -} jsmn_parser; - -/** - * Create JSON parser over an array of tokens - */ -JSMN_API void jsmn_init(jsmn_parser *parser); - -/** - * Run JSON parser. It parses a JSON data string into and array of tokens, each - * describing - * a single JSON object. - */ -JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, - jsmntok_t *tokens, const unsigned int num_tokens); - -#ifndef JSMN_HEADER -/** - * Allocates a fresh unused token from the token pool. - */ -static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, - const size_t num_tokens) -{ - jsmntok_t *tok; - if (parser->toknext >= num_tokens) { - return NULL; - } - tok = &tokens[parser->toknext++]; - tok->start = tok->end = -1; - tok->size = 0; -#ifdef JSMN_PARENT_LINKS - tok->parent = -1; -#endif - return tok; -} - -/** - * Fills token type and boundaries. - */ -static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, - const int start, const int end) -{ - token->type = type; - token->start = start; - token->end = end; - token->size = 0; -} - -/** - * Fills next available token with JSON primitive. - */ -static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, - const size_t len, jsmntok_t *tokens, - const size_t num_tokens) -{ - jsmntok_t *token; - int start; - - start = parser->pos; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - switch (js[parser->pos]) { -#ifndef JSMN_STRICT - /* In strict mode primitive must be followed by "," or "}" or "]" */ - case ':': -#endif - case '\t': - case '\r': - case '\n': - case ' ': - case ',': - case ']': - case '}': - goto found; - default: - /* to quiet a warning from gcc*/ - break; - } - /* libkrun: Let's be permissive with non-ASCII bytes - if (js[parser->pos] < 32 || js[parser->pos] >= 127) { - parser->pos = start; - return JSMN_ERROR_INVAL; - } - */ - } -#ifdef JSMN_STRICT - /* In strict mode primitive must be followed by a comma/object/array */ - parser->pos = start; - return JSMN_ERROR_PART; -#endif - -found: - if (tokens == NULL) { - parser->pos--; - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - parser->pos--; - return 0; -} - -/** - * Fills next token with JSON string. - */ -static int jsmn_parse_string(jsmn_parser *parser, char *js, const size_t len, - jsmntok_t *tokens, const size_t num_tokens) -{ - jsmntok_t *token; - - int start = parser->pos; - - /* Skip starting quote */ - parser->pos++; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c = js[parser->pos]; - - /* Quote: end of string */ - if (c == '\"') { - if (tokens == NULL) { - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - return 0; - } - - /* Backslash: Quoted symbol expected */ - if (c == '\\' && parser->pos + 1 < len) { - int i; - parser->pos++; - switch (js[parser->pos]) { - /* Allowed escaped symbols */ - case '\"': - case '/': - case '\\': - case 'b': - case 'f': - case 'r': - case 'n': - case 't': - break; - /* Allows escaped symbol \uXXXX */ - case 'u': { - char unicode[5]; - long ascii; - - parser->pos++; - for (i = 0; - i < 4 && parser->pos < len && js[parser->pos] != '\0'; - i++) { - /* If it isn't a hex character we have an error */ - if (!((js[parser->pos] >= 48 && - js[parser->pos] <= 57) || /* 0-9 */ - (js[parser->pos] >= 65 && - js[parser->pos] <= 70) || /* A-F */ - (js[parser->pos] >= 97 && - js[parser->pos] <= 102))) { /* a-f */ - parser->pos = start; - return JSMN_ERROR_INVAL; - } - unicode[i] = js[parser->pos]; - parser->pos++; - } - - unicode[4] = '\0'; - ascii = strtol(&unicode[0], NULL, 16); - if (ascii < 0 || ascii > 127) { - /* This unicode char doesn't translate directly to ASCII */ - parser->pos = start; - return JSMN_ERROR_INVAL; - } - - parser->pos--; - js[parser->pos] = (char)ascii; - break; - } - /* Unexpected symbol */ - default: - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } - } - parser->pos = start; - return JSMN_ERROR_PART; -} - -/** - * Parse JSON string and fill tokens. - */ -JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, - jsmntok_t *tokens, const unsigned int num_tokens) -{ - int r; - int i; - jsmntok_t *token; - int count = parser->toknext; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c; - jsmntype_t type; - - c = js[parser->pos]; - switch (c) { - case '{': - case '[': - count++; - if (tokens == NULL) { - break; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - return JSMN_ERROR_NOMEM; - } - if (parser->toksuper != -1) { - jsmntok_t *t = &tokens[parser->toksuper]; -#ifdef JSMN_STRICT - /* In strict mode an object or array can't become a key */ - if (t->type == JSMN_OBJECT) { - return JSMN_ERROR_INVAL; - } -#endif - t->size++; -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - } - token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); - token->start = parser->pos; - parser->toksuper = parser->toknext - 1; - break; - case '}': - case ']': - if (tokens == NULL) { - break; - } - type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); -#ifdef JSMN_PARENT_LINKS - if (parser->toknext < 1) { - return JSMN_ERROR_INVAL; - } - token = &tokens[parser->toknext - 1]; - for (;;) { - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - token->end = parser->pos + 1; - parser->toksuper = token->parent; - break; - } - if (token->parent == -1) { - if (token->type != type || parser->toksuper == -1) { - return JSMN_ERROR_INVAL; - } - break; - } - token = &tokens[token->parent]; - } -#else - for (i = parser->toknext - 1; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - parser->toksuper = -1; - token->end = parser->pos + 1; - break; - } - } - /* Error if unmatched closing bracket */ - if (i == -1) { - return JSMN_ERROR_INVAL; - } - for (; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - parser->toksuper = i; - break; - } - } -#endif - break; - case '\"': - r = jsmn_parse_string(parser, (char *)js, len, tokens, num_tokens); - if (r < 0) { - return r; - } - count++; - if (parser->toksuper != -1 && tokens != NULL) { - tokens[parser->toksuper].size++; - } - break; - case '\t': - case '\r': - case '\n': - case ' ': - break; - case ':': - parser->toksuper = parser->toknext - 1; - break; - case ',': - if (tokens != NULL && parser->toksuper != -1 && - tokens[parser->toksuper].type != JSMN_ARRAY && - tokens[parser->toksuper].type != JSMN_OBJECT) { -#ifdef JSMN_PARENT_LINKS - parser->toksuper = tokens[parser->toksuper].parent; -#else - for (i = parser->toknext - 1; i >= 0; i--) { - if (tokens[i].type == JSMN_ARRAY || - tokens[i].type == JSMN_OBJECT) { - if (tokens[i].start != -1 && tokens[i].end == -1) { - parser->toksuper = i; - break; - } - } - } -#endif - } - break; -#ifdef JSMN_STRICT - /* In strict mode primitives are: numbers and booleans */ - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case 't': - case 'f': - case 'n': - /* And they must not be keys of the object */ - if (tokens != NULL && parser->toksuper != -1) { - const jsmntok_t *t = &tokens[parser->toksuper]; - if (t->type == JSMN_OBJECT || - (t->type == JSMN_STRING && t->size != 0)) { - return JSMN_ERROR_INVAL; - } - } -#else - /* In non-strict mode every unquoted value is a primitive */ - default: -#endif - r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); - if (r < 0) { - return r; - } - count++; - if (parser->toksuper != -1 && tokens != NULL) { - tokens[parser->toksuper].size++; - } - break; - -#ifdef JSMN_STRICT - /* Unexpected char in strict mode */ - default: - return JSMN_ERROR_INVAL; -#endif - } - } - - if (tokens != NULL) { - for (i = parser->toknext - 1; i >= 0; i--) { - /* Unmatched opened object or array */ - if (tokens[i].start != -1 && tokens[i].end == -1) { - return JSMN_ERROR_PART; - } - } - } - - return count; -} - -/** - * Creates a new parser based over a given buffer with an array of tokens - * available. - */ -JSMN_API void jsmn_init(jsmn_parser *parser) -{ - parser->pos = 0; - parser->toknext = 0; - parser->toksuper = -1; -} - -#endif /* JSMN_HEADER */ - -#ifdef __cplusplus -} -#endif - -#endif /* JSMN_H */ diff --git a/init/tee/kbs/kbs.h b/init/tee/kbs/kbs.h deleted file mode 100644 index 9549a76a4..000000000 --- a/init/tee/kbs/kbs.h +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -#ifndef _KBS -#define _KBS - -#include -#include -#include - -#include "../snp_attest.h" - -/* - * Identifiers for all possible TEE architectures. - */ -enum tee { - TEE_SEV, - TEE_SGX, - TEE_SNP, - TEE_TDX, -}; - -/* - * The type of KBS operation to be performed. - */ -enum curl_post_type { - KBS_CURL_REQ, - KBS_CURL_ATTEST, - KBS_CURL_GET_KEY, -}; - -// kbs_util.c -char *tee_str(int); -char *find_cookie(char *, char *); -int read_cookie_val(char *, char *); -int json_parse_str(char *, char *, char *); - -// kbs_types.c -int kbs_request_marshal(char *, int, char *); -int kbs_challenge(CURL *, char *, char *, char *); -int kbs_attest(CURL *, char *, struct snp_report *, BIGNUM *, BIGNUM *, char *); -int kbs_get_key(CURL *, char *, char *, EVP_PKEY *, char *); - -// kbs_curl.c -int kbs_curl_post(CURL *, char *, char *, char *, int); -int kbs_curl_get(CURL *, char *, char *, char *, int); - -// kbs_crypto.c -int kbs_tee_pubkey_create(EVP_PKEY **, BIGNUM **, BIGNUM **); -int kbs_nonce_pubkey_hash(char *, EVP_PKEY *, unsigned char **, unsigned int *); -void BN_b64(BIGNUM *, char *); -int rsa_pkey_decrypt(EVP_PKEY *, char *, char **); - -#endif /* _KBS */ diff --git a/init/tee/kbs/kbs_crypto.c b/init/tee/kbs/kbs_crypto.c deleted file mode 100644 index 516fa2c5e..000000000 --- a/init/tee/kbs/kbs_crypto.c +++ /dev/null @@ -1,332 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "kbs.h" - -/* - * Create an OpenSSL TEE public/private key pair. - */ -int kbs_tee_pubkey_create(EVP_PKEY **pkey, BIGNUM **n, BIGNUM **e) -{ - int ret, rc; - EVP_PKEY_CTX *ctx; - - rc = -1; - ctx = NULL; - - /* - * The public/private key pair will use an RSA algorithm. Generate the - * keys' context. - */ - ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); - if (ctx == NULL) { - printf("ERROR: creating TEE public key context\n"); - - return rc; - } - - ret = EVP_PKEY_keygen_init(ctx); - if (ret < 1) { - printf("ERROR: initializing TEE public key generation\n"); - - goto ctx_free; - } - - /* - * Set key generation bits to 2048 and generate the key pair. - */ - ret = EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048); - if (ret < 1) { - printf("ERROR: setting RSA keygen bits\n"); - - goto ctx_free; - } - - *pkey = NULL; - ret = EVP_PKEY_keygen(ctx, pkey); - if (ret < 1) { - printf("ERROR: generating RSA key\n"); - - goto ctx_free; - } - - /* - * Get the modulus and exponents of the key pair. - */ - ret = EVP_PKEY_get_bn_param(*pkey, OSSL_PKEY_PARAM_RSA_N, n); - if (ret < 0 || n == NULL) { - printf("ERROR: getting public key modulus\n"); - - goto ctx_free; - } - - ret = EVP_PKEY_get_bn_param(*pkey, OSSL_PKEY_PARAM_RSA_E, e); - if (ret < 0 || e == NULL) { - printf("ERROR: getting public key exponent\n"); - - goto ctx_free; - } - - rc = 0; - -ctx_free: - EVP_PKEY_CTX_free(ctx); - - return rc; -} - -/* - * Create a SHA512 hash of the nonce and TEE public key to send to the - * attestation server. - */ -int kbs_nonce_pubkey_hash(char *nonce, EVP_PKEY *pkey, unsigned char **hash, - unsigned int *size) -{ - int rc; - EVP_MD_CTX *md_ctx; - BIGNUM *n, *e; - char n_b64[512], e_b64[512]; - - rc = -1; - - /* - * Initialize an MD context and initialize the SHA512 digest. - */ - md_ctx = EVP_MD_CTX_new(); - if (md_ctx == NULL) { - printf("ERROR: generating SHA512 context\n"); - - return rc; - } - - if (EVP_DigestInit_ex(md_ctx, EVP_sha512(), NULL) < 1) { - printf("ERROR: initializing SHA512 hash\n"); - - goto md_ctx_free; - } - - /* - * Update the digest with the data from the nonce. - */ - if (EVP_DigestUpdate(md_ctx, (void *)nonce, strlen(nonce)) < 1) { - printf("ERROR: updating SHA512 digest with nonce\n"); - - goto md_ctx_free; - } - - /* - * Update the digest with the data from the TEE public key. - * - * To do this, we will write the base64 encoding of the TEE public - * key's modulus and exponent. - */ - n = e = NULL; - if (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &n) == 0) { - printf("ERROR: unable to retrieve public key modulus\n"); - - goto md_ctx_free; - } - - if (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e) == 0) { - printf("ERROR: unable to retrieve public key exponent\n"); - - goto md_ctx_free; - } - - /* - * base64-encode the modulus and exponents, and hash the base64 strings - * into the SHA512 digest. - */ - BN_b64(n, n_b64); - BN_b64(e, e_b64); - - if (EVP_DigestUpdate(md_ctx, (void *)n_b64, strlen(n_b64)) < 1) { - printf("ERROR: updating SHA512 digest with public key N\n"); - - goto md_ctx_free; - } - - if (EVP_DigestUpdate(md_ctx, (void *)e_b64, strlen(e_b64)) < 1) { - printf("ERROR: updating SHA512 digest with public key E\n"); - - goto md_ctx_free; - } - - /* - * Allocate the memory to hold the SHA512 hash, and write the SHA512 - * hash to the "hash" byte array. - */ - *hash = (unsigned char *)OPENSSL_malloc(EVP_MD_size(EVP_sha512())); - if (*hash == NULL) { - printf("ERROR: allocating memory for SHA512 hash\n"); - - goto md_ctx_free; - } - - if (EVP_DigestFinal_ex(md_ctx, *hash, size) < 1) { - printf("ERROR: finalizing the SHA512 hash\n"); - - goto hash_free; - } - - rc = 0; - - goto md_ctx_free; - -hash_free: - OPENSSL_free((void *)*hash); - -md_ctx_free: - EVP_MD_CTX_free(md_ctx); - - return rc; -} - -/* - * Using a given RSA public/private key pair, decrypt an encrypted and hex - * encoded string of text. Store the plaintext of the encrypted text into a - * buffer and point "plain_ptr" to said buffer. - */ -int rsa_pkey_decrypt(EVP_PKEY *pkey, char *enc, char **plain_ptr) -{ - int rc; - EVP_PKEY_CTX *ctx; - char enc_bin[4096], *plain; - size_t enc_bin_len, secret_plain_len = 4096; - - rc = -1; - - /* - * Decode the hex-encoded string to its byte format. - */ - if (OPENSSL_hexstr2buf_ex((unsigned char *)enc_bin, 4096, &enc_bin_len, enc, - '\0') != 1) { - printf("Error converting hex to buf\n"); - - return rc; - } - - /* - * Initialize the public key decryption context. - */ - ctx = EVP_PKEY_CTX_new(pkey, NULL); - if (ctx == NULL) { - printf("ERROR: creation of pkey context for decryption\n"); - - return rc; - } - - if (EVP_PKEY_decrypt_init(ctx) <= 0) { - printf("ERROR: creation of decryption context for pkey\n"); - - goto ctx_free; - } - - if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) { - printf("Error setting RSA padding\n"); - - goto ctx_free; - } - - /* - * To first get the length that the plain secret buffer should be, call - * EVP_PKEY_decrypt() with a NULL output buffer argument. Then, - * "secret_plain_len" will contain the proper amount of bytes to - * allocate for the output buffer. - */ - rc = EVP_PKEY_decrypt(ctx, NULL, &secret_plain_len, - (unsigned char *)enc_bin, enc_bin_len); - if (rc <= 0) { - printf("ERROR: finding plaintext passphrase length: %d\n", rc); - - goto ctx_free; - } - - /* - * Allocate the output buffer using "secret_plain_len". - */ - plain = OPENSSL_malloc(secret_plain_len); - if (plain == NULL) - goto ctx_free; - - /* - * Decrypt the string using the OpenSSL RSA public key. - */ - rc = EVP_PKEY_decrypt(ctx, (unsigned char *)plain, &secret_plain_len, - (unsigned char *)enc_bin, enc_bin_len); - if (rc <= 0) { - printf("ERROR: decrypting RSA-encrypted passphrase: %d\n", rc); - OPENSSL_free(plain); - - goto ctx_free; - } - plain[secret_plain_len] = '\0'; - - /* - * Set the "plain_ptr" arg to the plaintext passphrase". - */ - *plain_ptr = plain; - - rc = 0; - -ctx_free: - EVP_PKEY_CTX_free(ctx); - - return rc; -} - -/* - * base64-encode the contents of an OpenSSL BIGNUM. - */ -void BN_b64(BIGNUM *bn, char *str) -{ - BIO *bio; - BIO *b64; - char *bn_bin; - char *bn_b64; - int bn_binlen; - int bn_b64len; - - /* - * Encode the BIGNUM contents to binary. - */ - bn_binlen = BN_num_bytes(bn); - bn_bin = malloc(bn_binlen); - BN_bn2bin(bn, (unsigned char *)bn_bin); - - /* - * Write the binary-encoded string to to a base64-configured OpenSSL - * BIO. - */ - b64 = BIO_new(BIO_f_base64()); - BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); - bio = BIO_new(BIO_s_mem()); - BIO_push(b64, bio); - BIO_write(b64, bn_bin, bn_binlen); - BIO_flush(b64); - - /* - * Retrieve the base64-encoded contents of the BIO, null-terminate the - * string, and copy those contents to the output string. - */ - bn_b64len = BIO_get_mem_data(b64, &bn_b64); - bn_b64[bn_b64len] = '\0'; - - strcpy(str, bn_b64); - - /* - * Cleanup OpenSSL data structures. - */ - BIO_free(b64); - BIO_free(bio); - free(bn_bin); -} diff --git a/init/tee/kbs/kbs_curl.c b/init/tee/kbs/kbs_curl.c deleted file mode 100644 index eb639db72..000000000 --- a/init/tee/kbs/kbs_curl.c +++ /dev/null @@ -1,232 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -#include -#include -#include - -#include "kbs.h" - -#define KBS_CURL_ERR(x) \ - printf("%s: %s\n", __func__, x); \ - return -1; - -static CURLcode kbs_curl_set_headers(CURL *, char *); -size_t cwrite(void *, size_t, size_t, void *); - -/* - * Complete a cURL POST request. POST the "in" string and retrieve the contents - * of the POST request "out" string. - * - * Depending on the type of request, some extra headers may need to be set. - * For example, on a KBS REQUEST, no session ID has been retrieved from the - * attestation server so far. Yet, during a KBS_ATTEST request, a session ID - * has been given from the server and must be added to the headers. - */ -int kbs_curl_post(CURL *curl, char *url, char *in, char *out, int type) -{ - CURLcode code; - struct curl_slist *cks; - char full_url[256], *session_id_label, session_id[256]; - - /* - * Neither the input or output strings should be invalid/NULL. - */ - if (!in) { - KBS_CURL_ERR("Input argument NULL"); - } - - if (!out) { - KBS_CURL_ERR("Output argument NULL"); - } - - if (curl_easy_setopt(curl, CURLOPT_POST, 1L) != CURLE_OK) { - KBS_CURL_ERR("CURLOPT_POST"); - } - - if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cwrite) != CURLE_OK) { - KBS_CURL_ERR("CURLOPT_WRITEFUNCTION"); - } - - /* - * If the operation being completed is a KBS REQUEST, then this is the - * initial request to the attestation server, and there is no session - * ID to make note of. Otherwise, the session ID has been established - * and must be parsed from the cURL cookies data. - */ - cks = NULL; - if (type == KBS_CURL_REQ) { - sprintf(full_url, "%s/kbs/v0/auth", url); - - if (curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "") != CURLE_OK) { - KBS_CURL_ERR("CURLOPT_COOKIEFILE"); - } - - if (kbs_curl_set_headers(curl, NULL) != CURLE_OK) { - KBS_CURL_ERR("CURLOPT_HTTPHEADER"); - } - } else { - sprintf(full_url, "%s/kbs/v0/attest", url); - - if (curl_easy_getinfo(curl, CURLINFO_COOKIELIST, &cks) != CURLE_OK) { - KBS_CURL_ERR("CURLOPT_COOKIELIST"); - } - - session_id_label = NULL; - while (cks) { - session_id_label = find_cookie(cks->data, "session_id"); - - if (session_id_label) - break; - cks = cks->next; - } - - if (session_id_label == NULL) { - KBS_CURL_ERR("No session_id cookie found"); - } - - if (read_cookie_val(session_id_label, session_id) < 0) { - KBS_CURL_ERR("No session_id value for cookie"); - } - - if (kbs_curl_set_headers(curl, (char *)session_id) != CURLE_OK) { - KBS_CURL_ERR("CURLOPT_HTTPHEADER"); - } - } - - if (curl_easy_setopt(curl, CURLOPT_URL, full_url) != CURLE_OK) { - KBS_CURL_ERR("CURLOPT_URL"); - } - - /* - * This is a cURL POST request that will write data to the "out" - * argument. "out" is expected to have been allocated beforehand and - * able to hold the full response from the attestation server. - */ - if (curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(in)) != - CURLE_OK) { - KBS_CURL_ERR("CURLOPT_POSTFIELDSIZE"); - } - - if (curl_easy_setopt(curl, CURLOPT_POSTFIELDS, in) != CURLE_OK) { - KBS_CURL_ERR("CURLOPT_POSTFIELDS"); - } - - if (curl_easy_setopt(curl, CURLOPT_WRITEDATA, out) != CURLE_OK) { - KBS_CURL_ERR("CURLOPT_WRITEDATA"); - } - - code = curl_easy_perform(curl); - if (code != CURLE_OK && code != CURLE_WRITE_ERROR) { - KBS_CURL_ERR("CURL_EASY_PERFORM"); - } - - return 0; -} - -/* - * A cURL GET request. No input is given, and we are simply retrieving data - * from the KBS attestation server. - */ -int kbs_curl_get(CURL *curl, char *url, char *wid, char *out, int type) -{ - CURLcode code; - char full_url[100], *session_id_label, session_id[100]; - struct curl_slist *cookies; - - if (type != KBS_CURL_GET_KEY) { - KBS_CURL_ERR("Invalid KBS operation"); - } - - code = curl_easy_getinfo(curl, CURLINFO_COOKIELIST, &cookies); - if (code != CURLE_OK) { - KBS_CURL_ERR("Cannot retrieve cURL cookies"); - } - - /* - * This API is used by kbs_get_key(), therefore we are expected to have - * a valid session ID by this point. Parse the cURL cookies data to find - * this session ID. - */ - while (cookies != NULL) { - session_id_label = find_cookie(cookies->data, "session_id"); - if (session_id_label) - break; - - cookies = cookies->next; - } - - if (session_id_label == NULL) { - KBS_CURL_ERR("Couldn't find cookie labeled\n"); - } - - /* - * Read the session ID and include it in the cURL headers. - */ - if (read_cookie_val(session_id_label, session_id) < 0) { - KBS_CURL_ERR("Couldn't read cookie value\n"); - } - - if (kbs_curl_set_headers(curl, (char *)session_id) != CURLE_OK) { - KBS_CURL_ERR("CURLOPT_HTTPHEADER"); - } - - /* - * The location of the KBS key is located at - * $ATTESTATION_URL/kbs/v0/key/$WORKLOAD_ID. - */ - sprintf(full_url, "%s/kbs/v0/key/%s", url, wid); - - if (curl_easy_setopt(curl, CURLOPT_URL, full_url) != CURLE_OK) { - KBS_CURL_ERR("CURLOPT_URL"); - } - - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cwrite); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, out); - - code = curl_easy_perform(curl); - if (code != CURLE_OK && code != CURLE_WRITE_ERROR) { - KBS_CURL_ERR("CURL_EASY_PERFORM"); - } - - return 0; -} - -/* - * Set the cURL headers. If the session args is not NULL, that indicates that - * the session ID has been retrieved from attestation server before, and that - * session ID should be included in the headers. - */ -static CURLcode kbs_curl_set_headers(CURL *curl, char *session) -{ - struct curl_slist *slist; - char session_buf[512]; - - slist = NULL; - slist = curl_slist_append(slist, "Accept: application/json"); - slist = curl_slist_append(slist, - "Content-Type: application/json; charset=utf-8"); - - /* - * Add the session ID cookie if the session ID exists. - */ - if (session) { - sprintf(session_buf, "Cookie: session_id=%s", session); - curl_slist_append(slist, session_buf); - } - - /* - * Set the headers. - */ - return curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); -} - -/* - * Simple strcpy() for attestation server responses. Required by a cURL - * operation that writes data. - */ -size_t cwrite(void *data, size_t size, size_t nmemb, void *userp) -{ - strcpy((char *)userp, (char *)data); - - return size; -} diff --git a/init/tee/kbs/kbs_types.c b/init/tee/kbs/kbs_types.c deleted file mode 100644 index b9f512d01..000000000 --- a/init/tee/kbs/kbs_types.c +++ /dev/null @@ -1,247 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -#include -#include -#include - -#include -#include -#include -#include - -#include "kbs.h" - -#include "../snp_attest.h" - -static void kbs_attestation_marshal(struct snp_report *, char *, BIGNUM *, - BIGNUM *, char *); -static void kbs_attestation_marshal_tee_pubkey(char *, BIGNUM *, BIGNUM *); - -/* - * Given a TEE architecture and workload ID, write the JSON string of the - * KBS REQUEST. - */ -int kbs_request_marshal(char *json_request, int tee, char *workload_id) -{ - char *teestr; - - /* - * Retrieve the KBS string equivalent of the TEE enum value. - */ - teestr = tee_str(tee); - if (teestr == NULL) - return -1; - - /* - * Build the KBS REQUEST JSON string. - */ - sprintf(json_request, - "{\"extra-params\":\"{\\\"workload_id\\\":\\\"%s\\\"}\",\"tee\":\"%" - "s\",\"version\":\"0.0.0\"}", - workload_id, teestr); - - return 0; -} - -/* - * Peform a KBS CHALLENGE. - * - * "json_request" is the JSON string of the KBS REQUEST. - * "nonce" is the output argument to be retrieved from the attestation server. - */ -int kbs_challenge(CURL *curl, char *url, char *json_request, char *nonce) -{ - int ret, rc; - char *nonce_json; - - rc = -1; - - nonce_json = (char *)malloc(0x2000); - if (nonce_json == NULL) { - printf("ERROR: unable to allocate JSON nonce buffer\n"); - - return rc; - } - - ret = kbs_curl_post(curl, url, (void *)json_request, (void *)nonce_json, - KBS_CURL_REQ); - if (ret < 0) { - printf("ERROR: could not complete KBS challenge\n"); - - goto out; - } - - /* - * Parse the JSON response from the KBS server to retrieve the nonce. - */ - if (json_parse_str(nonce, "nonce", nonce_json) < 0) { - printf("ERROR: unable to parse nonce from server response\n"); - - goto out; - } - - rc = 0; - -out: - free(nonce_json); - - return rc; -} - -/* - * Send all required materials (attestation report, certificate chain, etc..) - * to the attestation server for attestation. - */ -int kbs_attest(CURL *curl, char *url, struct snp_report *report, BIGNUM *mod, - BIGNUM *exp, char *gen) -{ - int rc; - char *json, errmsg[200]; - - rc = -1; - json = (char *)malloc(0x1000); - if (json == NULL) { - printf("ERROR: unable to allocate JSON buffer\n"); - - return rc; - } - - /* - * Marshal the kbs_types Attestation JSON struct with the given - * attestation report and certificate chain. - */ - kbs_attestation_marshal(report, json, mod, exp, gen); - - /* - * Ensure the error messaging string is empty, because we will - * eventually read this string as indicator of a cURL attestation - * server error. - */ - strcpy(errmsg, ""); - - if (kbs_curl_post(curl, url, json, errmsg, KBS_CURL_ATTEST) < 0) { - printf("ERROR: could not complete KBS attestation\n"); - - rc = -1; - goto out; - } - - /* - * If there is no error message, it can be assumed that the attestation - * was completed successfully. - */ - if (strcmp(errmsg, "") != 0) { - rc = -1; - printf("ATTESTATION ERROR: %s\n", errmsg); - - goto out; - } - - rc = 0; - -out: - free((void *)json); - - return rc; -} - -/* - * Retrieve the secret from the KBS attestation server. - */ -int kbs_get_key(CURL *curl, char *url, char *wid, EVP_PKEY *pkey, char *pass) -{ - int end_idx; - char json[4096]; - char encrypted[4096], *plain; - - /* - * The key is represented as a JSON byte list, copy this JSON list - * string to "json". - */ - if (kbs_curl_get(curl, url, wid, json, KBS_CURL_GET_KEY) < 0) { - printf("ERROR: could not complete KBS passphrase retrieval\n"); - - return -1; - } - - end_idx = strlen(json) - 2; - - memcpy(encrypted, json + 1, end_idx); - encrypted[end_idx] = '\0'; - - if (rsa_pkey_decrypt(pkey, encrypted, &plain) < 0) { - printf("ERROR: could not decrypt passphrase from KBS server\n"); - - return -1; - } - - strcpy(pass, plain); - - OPENSSL_free(plain); - - return 0; -} - -/* - * Marshal a JSON string of the kbs_types Attestation struct from the given - * attestation report and certificate data. - */ -static void kbs_attestation_marshal(struct snp_report *report, char *json, - BIGNUM *mod, BIGNUM *exp, char *gen) -{ - char buf[4096], *report_hexstr; - size_t report_hexstr_len; - - report_hexstr = (char *)malloc(0x1000); - if (report_hexstr == NULL) - return; - - sprintf(buf, "{"); - strcpy(json, buf); - - kbs_attestation_marshal_tee_pubkey(json, mod, exp); - - sprintf(buf, "\"tee-evidence\":\"{"); - strcat(json, buf); - - sprintf(buf, "\\\"gen\\\":\\\"%s\\\",", gen); - strcat(json, buf); - - OPENSSL_buf2hexstr_ex(report_hexstr, 0x1000, &report_hexstr_len, - (unsigned char *)report, sizeof(*report), '\0'); - report_hexstr[report_hexstr_len] = '\0'; - sprintf(buf, "\\\"report\\\":\\\"%s\\\",", report_hexstr); - strcat(json, buf); - - strcat(json, "\\\"cert_chain\\\":\\\"[]\\\"}"); - - strcat(json, "\"}"); -} - -/* - * Marshal a JSON string of the KBS TEE public key. - */ -static void kbs_attestation_marshal_tee_pubkey(char *json, BIGNUM *mod, - BIGNUM *exp) -{ - char mod_b64[512], exp_b64[512]; - char buf[1024]; - - if (mod == NULL || exp == NULL) - return; - - BN_b64(mod, mod_b64); - BN_b64(exp, exp_b64); - - sprintf(buf, "\"tee-pubkey\":{"); - strcat(json, buf); - - sprintf(buf, "\"alg\":\"RSA\","); - strcat(json, buf); - - sprintf(buf, "\"k-mod\":\"%s\",", mod_b64); - strcat(json, buf); - - sprintf(buf, "\"k-exp\":\"%s\"},", exp_b64); - strcat(json, buf); -} diff --git a/init/tee/kbs/kbs_util.c b/init/tee/kbs/kbs_util.c deleted file mode 100644 index 6c0b7c878..000000000 --- a/init/tee/kbs/kbs_util.c +++ /dev/null @@ -1,165 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -#include -#include - -#include "../../jsmn.h" -#include "kbs.h" - -#define MAX_TOKENS 16384 - -static int label_find(char *, char *); - -/* - * Return the string identifier of the inputted TEE architecture. - */ -char *tee_str(int tee) -{ - switch (tee) { - case TEE_SEV: - return "sev"; - case TEE_SGX: - return "sgx"; - case TEE_SNP: - return "snp"; - case TEE_TDX: - return "tdx"; - - /* - * No other TEE architecture is supported. - */ - default: - printf("ERROR: tee_str(): Invalid input\n"); - return NULL; - } -} - -/* - * Parse a given string of cURL cookie data and find the label indicated by the - * "label" argument. This function is essentially a search of a substring - * within a given string. - */ -char *find_cookie(char *cookie_data, char *label) -{ - char *cookie_ptr; - size_t label_len, cookie_len; - - label_len = strlen(label); - cookie_len = strlen(cookie_data); - - cookie_ptr = cookie_data; - for (int i = 0; i < (cookie_len - label_len); i++, cookie_ptr++) { - if (strncmp(cookie_ptr, label, label_len) == 0) - return cookie_ptr; - } - - return NULL; -} - -/* - * From a label in a cURL cookie string, parse its associated value. - */ -int read_cookie_val(char *label, char *buf) -{ - char *ptr; - int ws; - - ws = 0; - ptr = label; - for (ptr = label; *ptr != '\0'; ptr++) { - if (*ptr == ' ' || *ptr == '\t') - ws = 1; - else if (ws == 1) { - strcpy(buf, ptr); - - return 0; - } - } - - return -1; -} - -/* - * Given a JSON string and a "label", parse the string associated with that - * label and write the contents to "out". - */ -int json_parse_str(char *out, char *label, char *json) -{ - int ntokens, eq, rc; - jsmn_parser parser; - jsmntok_t *tokens, *curr, *next; - char *val; - int len; - - rc = -1; - - tokens = (jsmntok_t *)malloc(MAX_TOKENS * sizeof(jsmntok_t)); - if (tokens == NULL) { - printf("ERROR: unable to allocate JSON string\n"); - - return rc; - } - - jsmn_init(&parser); - - ntokens = jsmn_parse(&parser, json, strlen(json), tokens, MAX_TOKENS); - if (ntokens <= 0) { - printf("ERROR: unable to find any tokens in KBS challenge\n"); - - goto out; - } - - /* - * Traverse each token of the JSON string. - */ - for (int i = 0; i < ntokens - 1; i++) { - curr = &tokens[i]; - next = &tokens[i + 1]; - - /* - * Only interested in reading a string. - */ - if (curr->type != JSMN_STRING) - continue; - - /* - * Compare the current token with the label being searched for. - */ - eq = label_find(label, json + curr->start); - if (eq && next->type == JSMN_STRING) { - /* - * Found the string associated with the label, calculate - * its beginning and ending indexes within the JSON - * string and copy the contents over to "out". - */ - val = json + next->start; - len = next->end - next->start; - - memcpy((void *)out, (void *)val, len); - rc = 0; - - goto out; - } - } - -out: - free((void *)tokens); - - return rc; -} - -static int label_find(char *label, char *str) -{ - size_t label_sz; - - label_sz = strlen(label); - - for (int i = 0; i < label_sz; i++) { - if (label[i] != str[i]) - return 0; - if (label[i] != '\0') - continue; - } - - return 1; -} diff --git a/init/tee/snp_attest.c b/init/tee/snp_attest.c deleted file mode 100644 index 8303705ff..000000000 --- a/init/tee/snp_attest.c +++ /dev/null @@ -1,220 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include - -#include "kbs/kbs.h" -#include "snp_attest.h" - -#define NONCE_MAX 1024 -#define JSON_MAX 1024 -#define GEN_MAX 32 - -static int snp_get_report(const uint8_t *, size_t, struct snp_report *); -static int SNP_ATTEST_ERR(char *); -static void json_fmt(char *); - -int snp_attest(char *pass, char *url, char *wid, char *tee_data) -{ - CURL *curl; - char nonce[NONCE_MAX], json[JSON_MAX], gen[GEN_MAX]; - struct snp_report report; - EVP_PKEY *pkey; - BIGNUM *n, *e; - unsigned int hash_size; - uint8_t *hash; - - if (kbs_request_marshal(json, TEE_SNP, wid) < 0) - return SNP_ATTEST_ERR("Unable to marshal KBS REQUEST"); - - curl = curl_easy_init(); - if (curl == NULL) - return SNP_ATTEST_ERR("Unable to initialize cURL instance"); - - if (kbs_challenge(curl, url, json, nonce) < 0) - return SNP_ATTEST_ERR("Unable to retrieve nonce from server"); - - json_fmt(tee_data); - if (json_parse_str(gen, "gen", tee_data) < 0) - return SNP_ATTEST_ERR("Unable to retrieve SNP generation"); - - n = e = NULL; - if (kbs_tee_pubkey_create(&pkey, &n, &e) < 0) - return SNP_ATTEST_ERR("Unable to create TEE public key"); - - if (kbs_nonce_pubkey_hash(nonce, pkey, &hash, &hash_size) < 0) - return SNP_ATTEST_ERR("Unable to hash nonce and public key"); - - if (snp_get_report(hash, hash_size, &report) != EXIT_SUCCESS) - return SNP_ATTEST_ERR("Unable to retrieve attestation report"); - - if (kbs_attest(curl, url, &report, n, e, gen) < 0) - return SNP_ATTEST_ERR("Unable to complete KBS ATTESTATION"); - - curl_easy_reset(curl); - - if (kbs_get_key(curl, url, wid, pkey, pass) < 0) - return SNP_ATTEST_ERR("Unable to retrieve passphrase"); - - return 0; -} - -/* - * A function for the SNP_GET_REPORT ioctl. - * - * SNP_GET_REPORT fills both the attestation report and the certificate - * data. - */ -static int snp_get_report(const uint8_t *data, size_t data_sz, - struct snp_report *report) -{ - int rc = EXIT_FAILURE; - int fd = -1; - struct snp_report_req req; - struct snp_report_resp resp; - struct snp_guest_request_ioctl guest_req; - struct msg_report_resp *report_resp = (struct msg_report_resp *)&resp.data; - - /* - * The kernel will attempt to fill the report, certs, and certs_size, - * Therefore, none of these values can be NULL. - */ - if (report == NULL) { - printf("report is NULL\n"); - rc = EINVAL; - - goto out; - } - - /* - * We will be filling the user_data field of the request with "data". - * Ensure that the data is valid and can fit in the user_data field. - */ - if (data && (data_sz > sizeof(req.user_data) || data_sz == 0)) { - rc = EINVAL; - - goto out; - } - - /* - * Initialize data structures. - */ - memset(&req, 0, sizeof(req)); - - /* - * Copy the data into user_data if it exists. - */ - if (data) - memcpy(&req.user_data, data, data_sz); - - memset(&resp, 0, sizeof(resp)); - - memset(&guest_req, 0, sizeof(guest_req)); - guest_req.msg_version = 1; - guest_req.req_data = (__u64)&req; - guest_req.resp_data = (__u64)&resp; - - /* - * Open the SEV guest device. - */ - errno = 0; - fd = open(SEV_GUEST_DEV, O_RDWR); - if (fd == -1) { - rc = errno; - perror("open"); - - goto out; - } - - /* - * Retrieve the SNP attestation report. - */ - errno = 0; - rc = ioctl(fd, SNP_GET_REPORT, &guest_req); - if (rc == -1) { - rc = errno; - perror("ioctl"); - fprintf(stderr, "errno is %u\n", errno); - fprintf(stderr, "firmware error %#llx\n", guest_req.fw_err); - fprintf(stderr, "report error %x\n", report_resp->status); - - goto out_close; - } - - /* - * Ensure that the report was successfully generated. - */ - if (report_resp->status != 0) { - fprintf(stderr, "firmware error %x\n", report_resp->status); - rc = report_resp->status; - - goto out_close; - } else if (report_resp->report_size > sizeof(*report)) { - fprintf(stderr, "report size is %u bytes (expected %lu)!\n", - report_resp->report_size, sizeof(*report)); - rc = EFBIG; - - goto out_close; - } - - /* - * Copy the report + certs data. - */ - memcpy(report, &report_resp->report, report_resp->report_size); - rc = EXIT_SUCCESS; - -out_close: - if (fd > 0) { - close(fd); - fd = -1; - } -out: - return rc; -} - -static int SNP_ATTEST_ERR(char *errmsg) -{ - printf("SNP ATTEST ERROR: %s\n", errmsg); - - return -1; -} - -/* - * String format an unformatted JSON string: - * - * For example, this string: - * "{\"test\":\"123\"}" - * - * Would become: - * "{"test":"123"}" - */ -static void json_fmt(char *str) -{ - char cpy[strlen(str)]; - size_t sz, cpy_idx; - - sz = strlen(str); - cpy_idx = 0; - - for (int i = 0; i < sz; i++) { - if (str[i] != '\\') - cpy[cpy_idx++] = str[i]; - } - cpy[cpy_idx] = '\0'; - - strcpy(str, cpy); -} diff --git a/init/tee/snp_attest.h b/init/tee/snp_attest.h deleted file mode 100644 index d923d9b76..000000000 --- a/init/tee/snp_attest.h +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -#ifndef _SNP_ATTEST -#define _SNP_ATTEST - -#include - -#include - -#define SEV_GUEST_DEV "/dev/sev-guest" - -/* - * Cryptographic signature (should be signed by the VCEK). - */ -struct signature { - uint8_t r[72]; - uint8_t s[72]; - uint8_t reserved[512 - 144]; -}; - -/* - * Structure containing the security version numbers of each component in the - * Trusted Computing Base (TCB) of the SNP firmware. - */ -union tcb_version { - struct { - uint8_t boot_loader; - uint8_t tee; - uint8_t reserved[4]; - uint8_t snp; - uint8_t microcode; - }; - uint64_t raw; -}; - -/* - * An array of certificates. Consult the AMD SEV GHCB document to understand how - * this table should be built and parsed. - */ -struct cert_table { - struct cert_table_entry { - uuid_t guid; - uint32_t offset; - uint32_t len; - } *entry; -}; - -/* - * SNP attestation report structure. Based off of the attestation report - * structure described in firmware version 1.52. - */ -struct snp_report { - uint32_t version; - uint32_t guest_svn; - uint64_t policy; - uint8_t family_id[16]; - uint8_t image_id[16]; - uint32_t vmpl; - uint32_t signature_algo; - union tcb_version current_tcb; - - /* - * TODO: Change to a "struct platform_info". - */ - uint64_t platform_info; - - uint32_t author_key_en : 1; - uint32_t _reserved_0 : 31; - uint32_t _reserved_1; - uint8_t report_data[64]; - uint8_t measurement[48]; - uint8_t host_data[32]; - uint8_t id_key_digest[48]; - uint8_t author_key_digest[48]; - uint8_t report_id[32]; - uint8_t report_id_ma[32]; - union tcb_version reported_tcb; - uint8_t _reserved_2[24]; - uint8_t chip_id[64]; - union tcb_version committed_tcb; - uint8_t current_build; - uint8_t current_minor; - uint8_t current_major; - uint8_t _reserved_3; - uint8_t committed_build; - uint8_t committed_minor; - uint8_t committed_major; - uint8_t _reserved_4; - union tcb_version launch_tcb; - uint8_t _reserved_5[168]; - struct signature signature; -}; - -/* - * Response from the SNP_GET_EXT_REPORT ioctl. - */ -struct msg_report_resp { - uint32_t status; - uint32_t report_size; - uint8_t reserved[0x20 - 0x8]; - struct snp_report report; -}; - -// snp_attest.c -int snp_attest(char *, char *, char *, char *); - -#endif /* _SNP_ATTEST */ From cf5ba91f5b3de0e72cead477a5273c8897217fa2 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Thu, 28 May 2026 16:22:02 -0400 Subject: [PATCH 27/41] init: remove KRUN_REMOVE_ROOT_DIR_IOCTL Port of cd8b2be0af316fe4c38b6749a888c56ca7699277. The temporary root directory hack has been replaced by NullFs, so the ioctl that cleaned it up is no longer needed. Assisted-by: Claude Code: claude-sonnet-4-6 Signed-off-by: Jake Correnti --- init/src/fs.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/init/src/fs.rs b/init/src/fs.rs index 32e81ef6b..5c0fab924 100644 --- a/init/src/fs.rs +++ b/init/src/fs.rs @@ -7,8 +7,6 @@ use std::fs::{self, File}; use std::io::{BufRead, BufReader}; use std::os::unix::fs as unix_fs; -const KRUN_REMOVE_ROOT_DIR_IOCTL: libc::c_ulong = 0x7603; - /// Mount, treating EBUSY (already mounted) as success. fn mount_once( src: Option<&str>, @@ -165,13 +163,6 @@ pub fn mount_block_root_device() -> anyhow::Result<()> { unistd::chdir("/newroot").context("chdir /newroot")?; - // Ask the virtiofs device to tear down the temporary root directory. - let fd = unsafe { libc::open(c"/".as_ptr().cast(), libc::O_RDONLY) }; - if fd >= 0 { - unsafe { libc::ioctl(fd, KRUN_REMOVE_ROOT_DIR_IOCTL as _) }; - unsafe { libc::close(fd) }; - } - mount::mount(Some("."), "/", None::<&str>, MsFlags::MS_MOVE, None::<&str>) .context("pivot root MS_MOVE")?; From 0e9b9e912f257f2847ab192cafc8185e84cbf3b9 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Thu, 28 May 2026 16:23:17 -0400 Subject: [PATCH 28/41] init: set up a dummy network interface with TSI Port of 2593accd9b57da23912d1824216bcc53866c838d. When TSI is active, brings up dummy0 and assigns it 10.0.0.1/8 so applications that probe for network availability see a configured interface. Silently skips setup if the dummy driver is absent in the kernel. Assisted-by: Claude Code: claude-sonnet-4-6 Signed-off-by: Jake Correnti --- examples/Cargo.lock | 44 +++++++++++++------------- init/src/env.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++ init/src/main.rs | 5 +++ 3 files changed, 102 insertions(+), 22 deletions(-) diff --git a/examples/Cargo.lock b/examples/Cargo.lock index c4c63539f..93ceff859 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -80,9 +80,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "bindgen" @@ -148,9 +148,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.20.7" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" +checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c" dependencies = [ "smallvec", "target-lexicon", @@ -240,9 +240,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "env_logger" @@ -620,9 +620,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -717,9 +717,9 @@ dependencies = [ [[package]] name = "kvm-bindings" -version = "0.12.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a537873e15e8daabb416667e606d9b0abc2a8fb9a45bd5853b888ae0ead82f9" +checksum = "4b3c06ff73c7ce03e780887ec2389d62d2a2a9ddf471ab05c2ff69207cd3f3b4" dependencies = [ "vmm-sys-util", ] @@ -732,15 +732,15 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "memoffset" @@ -983,9 +983,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "termcolor" @@ -1042,9 +1042,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", "toml_datetime", @@ -1087,9 +1087,9 @@ checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" [[package]] name = "vmm-sys-util" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d21f366bf22bfba3e868349978766a965cbe628c323d58e026be80b8357ab789" +checksum = "506c62fdf617a5176827c2f9afbcf1be155b03a9b4bf9617a60dbc07e3a1642f" dependencies = [ "bitflags 1.3.2", "libc", @@ -1143,9 +1143,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] diff --git a/init/src/env.rs b/init/src/env.rs index 2f13acc49..a242ae4cb 100644 --- a/init/src/env.rs +++ b/init/src/env.rs @@ -60,6 +60,81 @@ fn setup_dhcp(iface: &str, sock: i32) { } } +/// Returns true if `tsi_hijack` appears in the kernel command line before any +/// `--` delimiter. Mirrors `tsi_enabled()` in init.c. +#[cfg(target_os = "linux")] +pub fn tsi_enabled() -> bool { + let Ok(cmdline) = std::fs::read_to_string("/proc/cmdline") else { + return false; + }; + cmdline + .split_whitespace() + .take_while(|tok| *tok != "--") + .any(|tok| tok == "tsi_hijack") +} + +/// Brings up `dummy0` and assigns it 203.0.113.1/24 (IANA TEST-NET-3) so +/// that applications probing for network availability see a configured +/// interface when TSI is in use. Silently succeeds when the dummy driver is +/// absent. +/// Mirrors `enable_dummy_interface()` in init.c. +#[cfg(target_os = "linux")] +pub fn enable_dummy_interface() { + use std::net::Ipv4Addr; + + let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) }; + if sock < 0 { + eprintln!("Warning: dummy interface socket failed"); + return; + } + + let mut ifr: libc::ifreq = unsafe { mem::zeroed() }; + let name = b"dummy0\0"; + unsafe { + ptr::copy_nonoverlapping( + name.as_ptr() as *const libc::c_char, + ifr.ifr_name.as_mut_ptr(), + name.len(), + ); + ifr.ifr_ifru.ifru_flags = libc::IFF_UP as libc::c_short; + } + + let ret = unsafe { libc::ioctl(sock, libc::SIOCSIFFLAGS as _, &ifr) }; + if ret < 0 { + let errno = unsafe { *libc::__errno_location() }; + if errno != libc::ENODEV { + eprintln!("Warning: dummy interface up failed"); + } + unsafe { libc::close(sock) }; + return; + } + + // Set IP address to 203.0.113.1 (IANA TEST-NET-3). + let mut sin: libc::sockaddr_in = unsafe { mem::zeroed() }; + sin.sin_family = libc::AF_INET as libc::sa_family_t; + sin.sin_addr.s_addr = u32::from_ne_bytes(Ipv4Addr::new(203, 0, 113, 1).octets()); + unsafe { + ifr.ifr_ifru.ifru_addr = mem::transmute::(sin); + if libc::ioctl(sock, libc::SIOCSIFADDR as _, &ifr) < 0 { + eprintln!("Warning: dummy interface address failed"); + libc::close(sock); + return; + } + } + + // Set netmask to 255.255.255.0. + let mut sin: libc::sockaddr_in = unsafe { mem::zeroed() }; + sin.sin_family = libc::AF_INET as libc::sa_family_t; + sin.sin_addr.s_addr = u32::from_ne_bytes(Ipv4Addr::new(255, 255, 255, 0).octets()); + unsafe { + ifr.ifr_ifru.ifru_netmask = mem::transmute::(sin); + if libc::ioctl(sock, libc::SIOCSIFNETMASK as _, &ifr) < 0 { + eprintln!("Warning: dummy interface mask failed"); + } + libc::close(sock); + } +} + pub fn apply_hostname() { let hostname = env::var("HOSTNAME").unwrap_or_else(|_| "localhost".into()); let _ = nix::unistd::sethostname(&hostname); diff --git a/init/src/main.rs b/init/src/main.rs index 8aaf25392..825e4582d 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -42,6 +42,11 @@ fn main() -> anyhow::Result<()> { "eth0", ); + #[cfg(target_os = "linux")] + if env::tsi_enabled() { + env::enable_dummy_interface(); + } + #[cfg(target_os = "freebsd")] let iso_mounted = std::env::var("KRUN_CONFIG").is_err() && freebsd::mount_config_iso(); From cd3d36ba12b99bd1ebc8c662ff5b7c60f0299d78 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 4 Jun 2026 14:35:12 +0200 Subject: [PATCH 29/41] tests/examples: replace krun_set_root with krun_add_virtiofs3 Replace all callers of krun_set_root with explicit krun_add_virtiofs3(ctx, "/dev/root", path, 0, false) calls. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- README.md | 4 ++-- examples/chroot_vm.c | 2 +- examples/consoles.c | 4 ++-- examples/gui_vm/src/main.rs | 24 +++++++++++++++--------- examples/nitro.c | 2 +- src/libkrun/src/lib.rs | 2 +- tests/test_cases/src/common.rs | 12 +++++++++--- 7 files changed, 31 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 6e26fa774..b1d4a0cf2 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ This is a novel technique called **Transparent Socket Impersonation** which allo #### Enabling TSI -TSI for AF_INET and AF_INET6 is automatically enabled when no network interface is added to the VM. TSI for AF_UNIX is enabled when, in addition to the previous condition, `krun_set_root` has been used to set `/` as root filesystem. +TSI for AF_INET and AF_INET6 is automatically enabled when no network interface is added to the VM. TSI for AF_UNIX is enabled when, in addition to the previous condition, the root filesystem has been configured with `/` as the shared directory. #### Known limitations @@ -92,7 +92,7 @@ While most virtio devices allow the guest to access resources from the host, two ### virtio-fs -When exposing a directory in a filesystem from the host to the guest through virtio-fs devices configured with `krun_set_root` and/or `krun_add_virtiofs`, libkrun **does not** provide any protection against the guest attempting to access other directories in the same filesystem, or even other filesystems in the host. +When exposing a directory in a filesystem from the host to the guest through virtio-fs devices configured with `krun_add_virtiofs` / `krun_add_virtiofs3`, libkrun **does not** provide any protection against the guest attempting to access other directories in the same filesystem, or even other filesystems in the host. A mount point isolation mechanism from the host should be used in combination with virtio-fs. diff --git a/examples/chroot_vm.c b/examples/chroot_vm.c index 990c92e13..f9dfd9b63 100644 --- a/examples/chroot_vm.c +++ b/examples/chroot_vm.c @@ -405,7 +405,7 @@ int main(int argc, char *const argv[]) rlim.rlim_cur = rlim.rlim_max; setrlimit(RLIMIT_NOFILE, &rlim); - if (err = krun_set_root(ctx_id, cmdline.new_root)) { + if (err = krun_add_virtiofs3(ctx_id, KRUN_FS_ROOT_TAG, cmdline.new_root, 0, false)) { errno = -err; perror("Error configuring root path"); return -1; diff --git a/examples/consoles.c b/examples/consoles.c index 23ee8226f..d78c42592 100644 --- a/examples/consoles.c +++ b/examples/consoles.c @@ -189,9 +189,9 @@ int main(int argc, char *const argv[]) return 1; } - if ((err = krun_set_root(ctx_id, root_dir))) { + if ((err = krun_add_virtiofs3(ctx_id, KRUN_FS_ROOT_TAG, root_dir, 0, false))) { errno = -err; - perror("krun_set_root"); + perror("krun_add_virtiofs3"); return 1; } diff --git a/examples/gui_vm/src/main.rs b/examples/gui_vm/src/main.rs index fe230a957..4395a273d 100644 --- a/examples/gui_vm/src/main.rs +++ b/examples/gui_vm/src/main.rs @@ -6,17 +6,17 @@ use gtk_display::{ }; use krun_sys::{ - KRUN_LOG_LEVEL_TRACE, KRUN_LOG_LEVEL_WARN, KRUN_LOG_STYLE_ALWAYS, KRUN_LOG_TARGET_DEFAULT, - VIRGLRENDERER_RENDER_SERVER, VIRGLRENDERER_THREAD_SYNC, VIRGLRENDERER_USE_ASYNC_FENCE_CB, - VIRGLRENDERER_USE_EGL, VIRGLRENDERER_VENUS, krun_add_display, krun_add_input_device, - krun_add_input_device_fd, krun_add_virtio_console_default, krun_create_ctx, - krun_display_set_dpi, krun_display_set_physical_size, krun_display_set_refresh_rate, - krun_init_log, krun_set_display_backend, krun_set_exec, krun_set_gpu_options2, krun_set_root, - krun_set_vm_config, krun_start_enter, + krun_add_display, krun_add_input_device, krun_add_input_device_fd, + krun_add_virtio_console_default, krun_add_virtiofs3, krun_create_ctx, krun_display_set_dpi, + krun_display_set_physical_size, krun_display_set_refresh_rate, krun_init_log, + krun_set_display_backend, krun_set_exec, krun_set_gpu_options2, krun_set_vm_config, + krun_start_enter, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_LEVEL_WARN, KRUN_LOG_STYLE_ALWAYS, + KRUN_LOG_TARGET_DEFAULT, VIRGLRENDERER_RENDER_SERVER, VIRGLRENDERER_THREAD_SYNC, + VIRGLRENDERER_USE_ASYNC_FENCE_CB, VIRGLRENDERER_USE_EGL, VIRGLRENDERER_VENUS, }; use log::LevelFilter; use regex::{Captures, Regex}; -use std::ffi::{CString, c_void}; +use std::ffi::{c_void, CString}; use std::fmt::Display; use std::fs::{File, OpenOptions}; use std::mem::size_of_val; @@ -167,7 +167,13 @@ fn krun_thread( 4096 ))?; - krun_call!(krun_set_root(ctx, args.root_dir.as_ptr()))?; + krun_call!(krun_add_virtiofs3( + ctx, + c"/dev/root".as_ptr(), + args.root_dir.as_ptr(), + 0, + false + ))?; let executable = args.executable.as_ref().unwrap().as_ptr(); let argv: Vec<_> = args.argv.iter().map(|a| a.as_ptr()).collect(); diff --git a/examples/nitro.c b/examples/nitro.c index 379e28d89..a742f1441 100644 --- a/examples/nitro.c +++ b/examples/nitro.c @@ -210,7 +210,7 @@ int main(int argc, char *const argv[]) } // Configure the enclave's rootfs. - if (err = krun_set_root(ctx_id, cmdline.new_root)) { + if (err = krun_add_virtiofs3(ctx_id, KRUN_FS_ROOT_TAG, cmdline.new_root, 0, false)) { errno = -err; perror("Error configuring enclave rootfs"); return -1; diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 1e40ec73e..37ba2f219 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -2853,7 +2853,7 @@ mod test_disable_implicit_init { let ctx = unsafe { krun_create_ctx() } as u32; unsafe { krun_disable_implicit_init(ctx); - krun_set_root(ctx, c"/tmp".as_ptr()); + krun_add_virtiofs3(ctx, c"/dev/root".as_ptr(), c"/tmp".as_ptr(), 0, false); } let ctx_map = CTX_MAP.lock().unwrap(); diff --git a/tests/test_cases/src/common.rs b/tests/test_cases/src/common.rs index 810347791..5f78fad31 100644 --- a/tests/test_cases/src/common.rs +++ b/tests/test_cases/src/common.rs @@ -1,14 +1,14 @@ //! Common utilities used by multiple test use anyhow::Context; -use std::ffi::{CStr, CString, c_char}; +use std::ffi::{c_char, CStr, CString}; use std::fs; use std::fs::create_dir; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; use std::ptr::null; -use crate::{TestSetup, krun_call}; +use crate::{krun_call, TestSetup}; use krun_sys::*; fn copy_guest_agent(dir: &Path) -> anyhow::Result<()> { @@ -52,7 +52,13 @@ pub fn setup_fs_and_enter_with_env( .collect(); envp.push(null()); unsafe { - krun_call!(krun_set_root(ctx, path_str.as_ptr()))?; + krun_call!(krun_add_virtiofs3( + ctx, + c"/dev/root".as_ptr(), + path_str.as_ptr(), + 0, + false, + ))?; krun_call!(krun_set_workdir(ctx, c"/".as_ptr()))?; let test_case_cstr = CString::new(test_setup.test_case).context("CString::new")?; let argv = [test_case_cstr.as_ptr(), null()]; From cf0655fa59f758dd0937287fd34c56ad036d162a Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 4 Jun 2026 14:36:34 +0200 Subject: [PATCH 30/41] lib: remove deprecated krun_set_root Callers should use krun_add_virtiofs3() with KRUN_FS_ROOT_TAG ("/dev/root") instead, which provides explicit control over DAX window size and read-only mode. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- include/libkrun.h | 22 +++------------------- src/libkrun/src/lib.rs | 38 -------------------------------------- 2 files changed, 3 insertions(+), 57 deletions(-) diff --git a/include/libkrun.h b/include/libkrun.h index 63e1ffb01..7019bc8bd 100644 --- a/include/libkrun.h +++ b/include/libkrun.h @@ -85,21 +85,6 @@ int32_t krun_set_vm_config(uint32_t ctx_id, uint8_t num_vcpus, uint32_t ram_mib) */ #define KRUN_FS_ROOT_TAG "/dev/root" -/** - * Sets the path to be use as root for the microVM. Not available in libkrun-SEV. - * - * For more control over the root filesystem (e.g. read-only, DAX window size), - * use krun_add_virtiofs3() with KRUN_FS_ROOT_TAG instead. - * - * Arguments: - * "ctx_id" - the configuration context ID. - * "root_path" - a null-terminated string representing the path to be used as root. - * - * Returns: - * Zero on success or a negative error number on failure. - */ -int32_t krun_set_root(uint32_t ctx_id, const char *root_path); - /** @@ -758,8 +743,7 @@ int32_t krun_set_smbios_oem_strings(uint32_t ctx_id, const char *const oem_strin * * Arguments: * "ctx_id" - the configuration context ID. - * "workdir_path" - the path to the working directory, relative to the root configured with - * "krun_set_root". + * "workdir_path" - the path to the working directory, relative to the root filesystem. * * Returns: * Zero on success or a negative error number on failure. @@ -773,7 +757,7 @@ int32_t krun_set_workdir(uint32_t ctx_id, * * Arguments: * "ctx_id" - the configuration context ID. - * "exec_path" - the path to the executable, relative to the root configured with "krun_set_root". + * "exec_path" - the path to the executable, relative to the root filesystem. * "argv" - an array of string pointers to be passed as arguments. * "envp" - an array of string pointers to be injected as environment variables into the * context of the executable. If NULL, it will auto-generate an array collecting the @@ -1012,7 +996,7 @@ int32_t krun_split_irqchip(uint32_t ctx_id, bool enable); /** * Do not inject the default init binary (/init.krun) into the root - * filesystem. Must be called before krun_set_root(). + * filesystem. Must be called before configuring the root filesystem. * * Arguments: * "ctx_id" - the configuration context ID. diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 37ba2f219..7ae20a835 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -544,44 +544,6 @@ pub extern "C" fn krun_set_vm_config(ctx_id: u32, num_vcpus: u8, ram_mib: u32) - KRUN_SUCCESS } -#[allow(clippy::missing_safety_doc)] -#[unsafe(no_mangle)] -#[cfg(not(any(feature = "tee", feature = "aws-nitro")))] -pub unsafe extern "C" fn krun_set_root(ctx_id: u32, c_root_path: *const c_char) -> i32 { - unsafe { - let root_path = match CStr::from_ptr(c_root_path).to_str() { - Ok(root) => root, - Err(_) => return -libc::EINVAL, - }; - - let fs_id = "/dev/root".to_string(); - let shared_dir = root_path.to_string(); - - match CTX_MAP.lock().unwrap().entry(ctx_id) { - Entry::Occupied(mut ctx_cfg) => { - let cfg = ctx_cfg.get_mut(); - cfg.vmr.add_fs_device(FsDeviceConfig { - fs_id, - shared_dir: Some(shared_dir), - // Default to a conservative 512 MB window. - shm_size: Some(1 << 29), - read_only: false, - virtual_entries: { - let mut v = Vec::new(); - if !cfg.disable_implicit_init { - v.push(init_virtual_entry()); - } - v - }, - }); - } - Entry::Vacant(_) => return -libc::ENOENT, - } - - KRUN_SUCCESS - } -} - #[allow(clippy::missing_safety_doc)] #[unsafe(no_mangle)] #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] From dfb1cac04b43025d1dd86e3c4e699c44eb098aa6 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 4 Jun 2026 13:53:09 +0200 Subject: [PATCH 31/41] init: move PID-1 binary and init-blob crates under init/ Prepare init/ to serve as a self-contained subtree for all init-related crates. Move the Rust PID-1 binary (Cargo.toml, src/) into init/init-binary/ and the init-blob crate from src/init-blob/ to init/init-blob/ so that new crates (init-blob-cdylib, etc.) can be added alongside them. init.c and aws-nitro/ stay at their existing locations. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- .gitignore | 1 + Cargo.toml | 4 ++-- Makefile | 6 +++--- init/{ => init-binary}/Cargo.toml | 0 init/{ => init-binary}/src/config.rs | 0 init/{ => init-binary}/src/dhcp.rs | 0 init/{ => init-binary}/src/env.rs | 0 init/{ => init-binary}/src/exec.rs | 0 init/{ => init-binary}/src/freebsd.rs | 0 init/{ => init-binary}/src/fs.rs | 0 init/{ => init-binary}/src/main.rs | 0 init/{ => init-binary}/src/timesync.rs | 0 {src => init}/init-blob/Cargo.toml | 0 {src => init}/init-blob/build.rs | 4 ++-- {src => init}/init-blob/src/lib.rs | 0 src/libkrun/Cargo.toml | 2 +- 16 files changed, 9 insertions(+), 8 deletions(-) rename init/{ => init-binary}/Cargo.toml (100%) rename init/{ => init-binary}/src/config.rs (100%) rename init/{ => init-binary}/src/dhcp.rs (100%) rename init/{ => init-binary}/src/env.rs (100%) rename init/{ => init-binary}/src/exec.rs (100%) rename init/{ => init-binary}/src/freebsd.rs (100%) rename init/{ => init-binary}/src/fs.rs (100%) rename init/{ => init-binary}/src/main.rs (100%) rename init/{ => init-binary}/src/timesync.rs (100%) rename {src => init}/init-blob/Cargo.toml (100%) rename {src => init}/init-blob/build.rs (97%) rename {src => init}/init-blob/src/lib.rs (100%) diff --git a/.gitignore b/.gitignore index 8a68e65a4..825c31f76 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ __pycache__ *.pyc *~ /libkrun.pc +init/init init/nitro/init examples/chroot_vm examples/launch-tee diff --git a/Cargo.toml b/Cargo.toml index 6e85e8c60..94bfde1cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [workspace] members = [ - "init", + "init/init-binary", "src/libkrun", - "src/init-blob", + "init/init-blob", "src/input", "src/display", "src/utils", diff --git a/Makefile b/Makefile index 1c20ae9bc..4f91b47e4 100644 --- a/Makefile +++ b/Makefile @@ -172,12 +172,12 @@ else endif ifeq ($(BUILD_BSD_INIT),1) -INIT_BINARY_BSD = init/init-freebsd -$(INIT_BINARY_BSD): $(shell find init/src -name '*.rs') init/Cargo.toml $(SYSROOT_BSD_TARGET) +INIT_BINARY_BSD = init/init-binary/init-freebsd +$(INIT_BINARY_BSD): $(shell find init/init-binary/src -name '*.rs') init/init-binary/Cargo.toml $(SYSROOT_BSD_TARGET) RUSTFLAGS="$(CARGO_BSD_RUSTFLAGS)" \ cargo $(CARGO_BSD_TOOLCHAIN) build --release \ $(CARGO_BSD_EXTRA_FLAGS) \ - --manifest-path init/Cargo.toml \ + --manifest-path init/init-binary/Cargo.toml \ --target $(FREEBSD_RUST_TARGET) cp target/$(FREEBSD_RUST_TARGET)/release/krun-init $@ endif diff --git a/init/Cargo.toml b/init/init-binary/Cargo.toml similarity index 100% rename from init/Cargo.toml rename to init/init-binary/Cargo.toml diff --git a/init/src/config.rs b/init/init-binary/src/config.rs similarity index 100% rename from init/src/config.rs rename to init/init-binary/src/config.rs diff --git a/init/src/dhcp.rs b/init/init-binary/src/dhcp.rs similarity index 100% rename from init/src/dhcp.rs rename to init/init-binary/src/dhcp.rs diff --git a/init/src/env.rs b/init/init-binary/src/env.rs similarity index 100% rename from init/src/env.rs rename to init/init-binary/src/env.rs diff --git a/init/src/exec.rs b/init/init-binary/src/exec.rs similarity index 100% rename from init/src/exec.rs rename to init/init-binary/src/exec.rs diff --git a/init/src/freebsd.rs b/init/init-binary/src/freebsd.rs similarity index 100% rename from init/src/freebsd.rs rename to init/init-binary/src/freebsd.rs diff --git a/init/src/fs.rs b/init/init-binary/src/fs.rs similarity index 100% rename from init/src/fs.rs rename to init/init-binary/src/fs.rs diff --git a/init/src/main.rs b/init/init-binary/src/main.rs similarity index 100% rename from init/src/main.rs rename to init/init-binary/src/main.rs diff --git a/init/src/timesync.rs b/init/init-binary/src/timesync.rs similarity index 100% rename from init/src/timesync.rs rename to init/init-binary/src/timesync.rs diff --git a/src/init-blob/Cargo.toml b/init/init-blob/Cargo.toml similarity index 100% rename from src/init-blob/Cargo.toml rename to init/init-blob/Cargo.toml diff --git a/src/init-blob/build.rs b/init/init-blob/build.rs similarity index 97% rename from src/init-blob/build.rs rename to init/init-blob/build.rs index da5afa9d3..4f3e9b547 100644 --- a/src/init-blob/build.rs +++ b/init/init-blob/build.rs @@ -61,7 +61,7 @@ fn find_musl_rustc(default_rustc: &str) -> Option { fn build_rust_init() -> PathBuf { let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); let workspace_root = manifest_dir.join("../.."); - let init_manifest = workspace_root.join("init/Cargo.toml"); + let init_manifest = workspace_root.join("init/init-binary/Cargo.toml"); let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); // Separate target dir avoids conflicting with the parent workspace cargo lock. @@ -75,7 +75,7 @@ fn build_rust_init() -> PathBuf { println!( "cargo:rerun-if-changed={}", - workspace_root.join("init/src").display() + workspace_root.join("init/init-binary/src").display() ); println!("cargo:rerun-if-changed={}", init_manifest.display()); // Resolve which rustc (and paired cargo) to use for the init binary. diff --git a/src/init-blob/src/lib.rs b/init/init-blob/src/lib.rs similarity index 100% rename from src/init-blob/src/lib.rs rename to init/init-blob/src/lib.rs diff --git a/src/libkrun/Cargo.toml b/src/libkrun/Cargo.toml index 711fd3450..e1b367c0e 100644 --- a/src/libkrun/Cargo.toml +++ b/src/libkrun/Cargo.toml @@ -32,7 +32,7 @@ krun_display = { package = "krun-display", version = "0.1.0", path = "../display krun_input = { package = "krun-input", version = "0.1.0", path = "../input", optional = true, features = ["bindgen_clang_runtime"] } devices = { package = "krun-devices", version = "=0.1.0-1.18.0", path = "../devices" } -init-blob = { path = "../init-blob" } +init-blob = { path = "../../init/init-blob" } polly = { package = "krun-polly", version = "=0.1.0-1.18.0", path = "../polly" } utils = { package = "krun-utils", version = "=0.1.0-1.18.0", path = "../utils" } vmm = { package = "krun-vmm", version = "=0.1.0-1.18.0", path = "../vmm" } From a1a0febc22ead8db4249300991eb0bf1b00d6cad Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 4 Jun 2026 13:53:31 +0200 Subject: [PATCH 32/41] init-blob: add ffier-based init config library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add four crates that together produce libkrun-init — a standalone shared library for building init configurations with a C FFI: - init-blob: Config/ConfigBuilder/GuestFile types with #[ffier::exportable]. Serializes to OCI runtime-spec JSON matching what the Rust init (PR #670) parses. ConfigError with FfiError derive for proper error handling across FFI. - init-blob-cdylib: produces libkrun_init.so via ffier bridge macros. Includes krun-init-blob-gen binary for generating C headers and Rust client bindings (strong or --weak mode). - init-blob-via-cdylib: strong Rust client (links libkrun_init.so at load time). For consumers that link both libraries. - init-blob-via-cdylib-weak: weak Rust client (dlsym at runtime). For libkrun, which optionally calls into libkrun-init without a hard link dependency. All crates live under init/ alongside the PID-1 binary. Uses library_tag = 2 (reserving 1 for libkrun) and primitives_prefix = "krun" so KrunStr/KrunBytes are shared. Makefile: gen-init-blob-bindings target, versioned install of libkrun_init.so (SONAME 0), C header, and pkg-config file. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- .gitignore | 3 + Cargo.lock | 129 ++ Cargo.toml | 3 + Makefile | 49 +- include/libkrun_init.h | 196 +++ init/init-blob-cdylib/Cargo.toml | 25 + init/init-blob-cdylib/src/gen.rs | 47 + init/init-blob-cdylib/src/lib.rs | 1 + init/init-blob-via-cdylib-weak/Cargo.toml | 11 + init/init-blob-via-cdylib-weak/src/lib.rs | 1345 +++++++++++++++++++++ init/init-blob-via-cdylib/Cargo.toml | 11 + init/init-blob-via-cdylib/build.rs | 9 + init/init-blob-via-cdylib/src/lib.rs | 724 +++++++++++ init/init-blob/Cargo.toml | 7 + init/init-blob/src/config.rs | 326 +++++ init/init-blob/src/lib.rs | 16 + libkrun_init.pc.in | 24 + 17 files changed, 2922 insertions(+), 4 deletions(-) create mode 100644 include/libkrun_init.h create mode 100644 init/init-blob-cdylib/Cargo.toml create mode 100644 init/init-blob-cdylib/src/gen.rs create mode 100644 init/init-blob-cdylib/src/lib.rs create mode 100644 init/init-blob-via-cdylib-weak/Cargo.toml create mode 100644 init/init-blob-via-cdylib-weak/src/lib.rs create mode 100644 init/init-blob-via-cdylib/Cargo.toml create mode 100644 init/init-blob-via-cdylib/build.rs create mode 100644 init/init-blob-via-cdylib/src/lib.rs create mode 100644 init/init-blob/src/config.rs create mode 100644 libkrun_init.pc.in diff --git a/.gitignore b/.gitignore index 825c31f76..3eba43934 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ __pycache__ *.pyc *~ /libkrun.pc +/libkrun_init.pc init/init init/nitro/init examples/chroot_vm @@ -17,5 +18,7 @@ examples/nitro examples/consoles examples/rootfs_fedora test-prefix + +# Cross-compilation sysroots (downloaded by Makefile) /linux-sysroot /freebsd-sysroot diff --git a/Cargo.lock b/Cargo.lock index 24de495c6..751f27204 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,6 +382,99 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "ffier" +version = "0.1.0" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=9e36967#9e36967ad64ae94f540e1e8b84dccbfd397b6fd9" +dependencies = [ + "ffier-annotations", + "ffier-builtins", + "ffier-rt", +] + +[[package]] +name = "ffier-annotations" +version = "0.1.0" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=9e36967#9e36967ad64ae94f540e1e8b84dccbfd397b6fd9" +dependencies = [ + "ffier-meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ffier-bridge" +version = "0.1.0" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=9e36967#9e36967ad64ae94f540e1e8b84dccbfd397b6fd9" +dependencies = [ + "ffier-meta", + "ffier-schema", + "proc-macro2", + "quote", + "serde_json", + "syn", +] + +[[package]] +name = "ffier-bridge-macros" +version = "0.1.0" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=9e36967#9e36967ad64ae94f540e1e8b84dccbfd397b6fd9" +dependencies = [ + "ffier-bridge", +] + +[[package]] +name = "ffier-builtins" +version = "0.1.0" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=9e36967#9e36967ad64ae94f540e1e8b84dccbfd397b6fd9" +dependencies = [ + "ffier-annotations", + "ffier-rt", +] + +[[package]] +name = "ffier-gen-c-header" +version = "0.1.0" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=9e36967#9e36967ad64ae94f540e1e8b84dccbfd397b6fd9" +dependencies = [ + "ffier-schema", + "serde_json", +] + +[[package]] +name = "ffier-gen-rust-client" +version = "0.1.0" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=9e36967#9e36967ad64ae94f540e1e8b84dccbfd397b6fd9" +dependencies = [ + "ffier-schema", + "serde_json", +] + +[[package]] +name = "ffier-meta" +version = "0.1.0" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=9e36967#9e36967ad64ae94f540e1e8b84dccbfd397b6fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ffier-rt" +version = "0.1.0" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=9e36967#9e36967ad64ae94f540e1e8b84dccbfd397b6fd9" + +[[package]] +name = "ffier-schema" +version = "0.1.0" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=9e36967#9e36967ad64ae94f540e1e8b84dccbfd397b6fd9" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "filetime" version = "0.2.29" @@ -553,6 +646,13 @@ dependencies = [ [[package]] name = "init-blob" version = "0.1.0-1.18.1" +dependencies = [ + "ffier", + "ffier-builtins", + "serde", + "serde_json", + "thiserror 2.0.18", +] [[package]] name = "iocuddle" @@ -749,6 +849,35 @@ dependencies = [ "serde_json", ] +[[package]] +name = "krun-init-blob-cdylib" +version = "0.1.0-1.18.1" +dependencies = [ + "ffier", + "ffier-bridge", + "ffier-bridge-macros", + "ffier-gen-c-header", + "ffier-gen-rust-client", + "ffier-schema", + "init-blob", + "serde_json", +] + +[[package]] +name = "krun-init-blob-via-cdylib" +version = "0.1.0-1.18.1" +dependencies = [ + "ffier", +] + +[[package]] +name = "krun-init-blob-via-cdylib-weak" +version = "0.1.0-1.18.1" +dependencies = [ + "ffier", + "libloading", +] + [[package]] name = "krun-input" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 94bfde1cc..314e8fdd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,9 @@ members = [ "init/init-binary", "src/libkrun", "init/init-blob", + "init/init-blob-cdylib", + "init/init-blob-via-cdylib", + "init/init-blob-via-cdylib-weak", "src/input", "src/display", "src/utils", diff --git a/Makefile b/Makefile index 4f91b47e4..8c6465b1f 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,22 @@ LIBRARY_HEADER = include/libkrun.h LIBRARY_HEADER_DISPLAY = include/libkrun_display.h LIBRARY_HEADER_INPUT = include/libkrun_input.h +LIBRARY_HEADER_INIT = include/libkrun_init.h ABI_VERSION=1 FULL_VERSION=1.18.0 +KRUN_INIT_ABI_VERSION=0 +KRUN_INIT_FULL_VERSION=0.1.0 + +KRUN_INIT_BINARY_Linux = libkrun_init.so.$(KRUN_INIT_FULL_VERSION) +KRUN_INIT_SONAME_Linux = libkrun_init.so.$(KRUN_INIT_ABI_VERSION) +KRUN_INIT_BASE_Linux = libkrun_init.so + +KRUN_INIT_BINARY_Darwin = libkrun_init.$(KRUN_INIT_FULL_VERSION).dylib +KRUN_INIT_SONAME_Darwin = libkrun_init.$(KRUN_INIT_ABI_VERSION).dylib +KRUN_INIT_BASE_Darwin = libkrun_init.dylib + AWS_NITRO_INIT_SRC = \ init/aws-nitro/include/* \ init/aws-nitro/main.c \ @@ -97,11 +109,22 @@ ifeq ($(PREFIX),) PREFIX := /usr/local endif -.PHONY: install clean test test-prefix $(LIBRARY_RELEASE_$(OS)) $(LIBRARY_DEBUG_$(OS)) libkrun.pc clean-sysroot clean-all +.PHONY: install clean test test-prefix gen-init-blob-bindings $(LIBRARY_RELEASE_$(OS)) $(LIBRARY_DEBUG_$(OS)) libkrun.pc libkrun_init.pc clean-sysroot clean-all -all: $(LIBRARY_RELEASE_$(OS)) libkrun.pc +all: $(LIBRARY_RELEASE_$(OS)) libkrun.pc libkrun_init.pc -debug: $(LIBRARY_DEBUG_$(OS)) libkrun.pc +debug: $(LIBRARY_DEBUG_$(OS)) libkrun.pc libkrun_init.pc + +# Regenerate ffier Rust client bindings for init-blob. +# The checked-in files are sufficient for normal builds; run this after +# changing the init-blob ffier API. +FFIER_ALLOWS = \#![allow(dead_code, unused_unsafe, clippy::missing_safety_doc, clippy::needless_lifetimes)] + +gen-init-blob-bindings: + cargo build -p krun-init-blob-cdylib + cargo run --bin krun-init-blob-gen c-header > $(LIBRARY_HEADER_INIT) + { echo '$(FFIER_ALLOWS)'; cargo run --bin krun-init-blob-gen rust-client; } > init/init-blob-via-cdylib/src/lib.rs + { echo '$(FFIER_ALLOWS)'; cargo run --bin krun-init-blob-gen rust-client --weak; } > init/init-blob-via-cdylib-weak/src/lib.rs ifeq ($(OS),Darwin) # If SYSROOT_LINUX is not set and we're on macOS, generate sysroot automatically @@ -247,6 +270,7 @@ ifeq ($(OS),Darwin) mv target/release/libkrun.dylib target/release/$(KRUN_BASE_$(OS)) endif cp target/release/$(KRUN_BASE_$(OS)) $(LIBRARY_RELEASE_$(OS)) + cp target/release/$(KRUN_INIT_BASE_$(OS)) target/release/$(KRUN_INIT_BINARY_$(OS)) $(LIBRARY_DEBUG_$(OS)): $(SYSROOT_TARGET) $(INIT_BINARY_BSD) cargo build $(FEATURE_FLAGS) @@ -257,6 +281,7 @@ ifeq ($(TDX),1) mv target/debug/libkrun.so target/debug/$(KRUN_BASE_$(OS)) endif cp target/debug/$(KRUN_BASE_$(OS)) $(LIBRARY_DEBUG_$(OS)) + cp target/debug/$(KRUN_INIT_BASE_$(OS)) target/debug/$(KRUN_INIT_BINARY_$(OS)) libkrun.pc: libkrun.pc.in Makefile rm -f $@ $@-t @@ -268,16 +293,30 @@ libkrun.pc: libkrun.pc.in Makefile libkrun.pc.in > $@-t mv $@-t $@ -install: libkrun.pc +libkrun_init.pc: libkrun_init.pc.in Makefile + rm -f $@ $@-t + sed -e 's|@prefix@|$(PREFIX)|' \ + -e 's|@libdir@|$(PREFIX)/$(LIBDIR_$(OS))|' \ + -e 's|@includedir@|$(PREFIX)/include|' \ + -e 's|@PACKAGE_NAME@|libkrun_init|' \ + -e 's|@PACKAGE_VERSION@|$(KRUN_INIT_FULL_VERSION)|' \ + libkrun_init.pc.in > $@-t + mv $@-t $@ + +install: libkrun.pc libkrun_init.pc install -d $(DESTDIR)$(PREFIX)/$(LIBDIR_$(OS))/ install -d $(DESTDIR)$(PREFIX)/$(LIBDIR_$(OS))/pkgconfig install -d $(DESTDIR)$(PREFIX)/include install -m 644 $(LIBRARY_HEADER) $(DESTDIR)$(PREFIX)/include install -m 644 $(LIBRARY_HEADER_DISPLAY) $(DESTDIR)$(PREFIX)/include install -m 644 $(LIBRARY_HEADER_INPUT) $(DESTDIR)$(PREFIX)/include + install -m 644 $(LIBRARY_HEADER_INIT) $(DESTDIR)$(PREFIX)/include install -m 644 libkrun.pc $(DESTDIR)$(PREFIX)/$(LIBDIR_$(OS))/pkgconfig + install -m 644 libkrun_init.pc $(DESTDIR)$(PREFIX)/$(LIBDIR_$(OS))/pkgconfig install -m 755 $(LIBRARY_RELEASE_$(OS)) $(DESTDIR)$(PREFIX)/$(LIBDIR_$(OS))/ cd $(DESTDIR)$(PREFIX)/$(LIBDIR_$(OS))/ ; ln -sf $(KRUN_BINARY_$(OS)) $(KRUN_SONAME_$(OS)) ; ln -sf $(KRUN_SONAME_$(OS)) $(KRUN_BASE_$(OS)) + install -m 755 target/release/$(KRUN_INIT_BINARY_$(OS)) $(DESTDIR)$(PREFIX)/$(LIBDIR_$(OS))/ + cd $(DESTDIR)$(PREFIX)/$(LIBDIR_$(OS))/ ; ln -sf $(KRUN_INIT_BINARY_$(OS)) $(KRUN_INIT_SONAME_$(OS)) ; ln -sf $(KRUN_INIT_SONAME_$(OS)) $(KRUN_INIT_BASE_$(OS)) clean: ifeq ($(BUILD_BSD_INIT),1) @@ -285,6 +324,8 @@ ifeq ($(BUILD_BSD_INIT),1) endif cargo clean rm -rf test-prefix + echo '// Generated by: make gen-init-blob-bindings' > init/init-blob-via-cdylib/src/lib.rs + echo '// Generated by: make gen-init-blob-bindings' > init/init-blob-via-cdylib-weak/src/lib.rs cd tests; cargo clean clean-all: clean clean-sysroot diff --git a/include/libkrun_init.h b/include/libkrun_init.h new file mode 100644 index 000000000..c61dfc035 --- /dev/null +++ b/include/libkrun_init.h @@ -0,0 +1,196 @@ +#ifndef LIBKRUN_INIT_H +#define LIBKRUN_INIT_H + +#include +#include +#include +#include + +typedef void* KrunInitConfigError; +typedef void* KrunInitGuestFile; +typedef void* KrunInitConfig; +typedef void* KrunInitConfigBuilder; +typedef void* KrunInitError; /* KrunInitVtableError */ +typedef void* KrunInitPushStr; /* KrunInitVtablePushStr */ + +#ifndef KRUN_PRIMITIVES_DEFINED +#define KRUN_PRIMITIVES_DEFINED + +typedef void* KrunObject; /* KrunInitConfigError | KrunInitGuestFile | KrunInitConfig | KrunInitConfigBuilder */ + +typedef uint64_t KrunResult; +#define KRUN_RESULT_SUCCESS 0 + +/* Caller must ensure data is valid UTF-8 */ +typedef struct { + const char* data; + size_t len; +} KrunStr; + +typedef struct { + const uint8_t* data; + size_t len; +} KrunBytes; + +#define KRUN_STR(s) ((KrunStr){ .data = (s), .len = (s) ? strlen(s) : 0 }) +#if defined(__GNUC__) +#define KRUN_BYTES(arr) ({ \ + _Static_assert( \ + !__builtin_types_compatible_p(typeof(arr), typeof(&(arr)[0])), \ + "KRUN_BYTES() requires an array, not a pointer"); \ + ((KrunBytes){ .data = (const uint8_t*)(arr), .len = sizeof(arr) }); \ +}) +#else +#define KRUN_BYTES(arr) \ + ((KrunBytes){ .data = (const uint8_t*)(arr), .len = sizeof(arr) }) +#endif + +/** + * Stack-allocated temporary handle for passing vtable-based objects. + * Only valid for the duration of the call — the callee borrows, not owns. + */ +typedef struct { + uint32_t type_tag; + uint32_t metadata; + const void *vtable_ptr; + const void *user_data; + uint16_t vtable_size; +} KrunVtableHandle; + +#define KRUN_VTABLE_HANDLE(tag, vtable, self_data) \ + ((KrunVtableHandle){ .type_tag = (tag), .metadata = 0, \ + .vtable_ptr = &(vtable), .user_data = (self_data), \ + .vtable_size = sizeof(vtable) }) + +/** + * Opaque 16-byte handle entry in an object array. + * Do not access fields directly — pass &entry to typed methods. + * Do NOT pass individual entries to destroy. + */ +typedef struct { + uint32_t _tag; + uint32_t _meta; + const void* _ptr; +} KrunObjectArrayEntry; + +/** + * Contiguous array of borrowed handles. + * Returned by methods that produce slices of handles. + * Individual elements must NOT be passed to destroy — + * call free_object_array() to free the entire array. + */ +typedef struct { + const KrunObjectArrayEntry* items; + size_t len; +} KrunObjectArray; + +#define KRUN_OBJECT_ARRAY_GET(arr, i) \ + (assert((size_t)(i) < (arr).len), (KrunObject)(void*)&(arr).items[(i)]) + +#endif /* KRUN_PRIMITIVES_DEFINED */ + +/* Free an owned string returned by the library */ +void krun_init_str_free(KrunStr s); + +/* Free an object array returned by the library */ +void krun_init_free_object_array(KrunObjectArray a); + + +/* ConfigError ------------------------------------------------------- */ + +#define KRUN_INIT_ERROR_CONFIG_INVALID_JSON ((uint64_t)33554435 << 32 | 1) + +/* GuestFile --------------------------------------------------------- */ + +/** Path on the guest root filesystem (e.g. `"/init.krun"`). */ +KrunStr krun_init_guest_file_path(KrunInitGuestFile handle); +/** File contents. */ +KrunBytes krun_init_guest_file_data(KrunInitGuestFile handle); +/** Permission mode bits (e.g. `0o755`). */ +uint32_t krun_init_guest_file_mode(KrunInitGuestFile handle); +/** Whether this file is one-shot (removed after first lookup). */ +bool krun_init_guest_file_one_shot(KrunInitGuestFile handle); +void krun_init_guest_file_destroy(KrunInitGuestFile handle); + +/* Config ------------------------------------------------------------ */ + +/** Start building a new init configuration. */ +KrunInitConfigBuilder krun_init_config_builder(); +/** + * Construct from an OCI runtime-spec config.json string. + * + * The JSON is expected to use the OCI runtime-spec layout: + * `{"process": {"args": [...], "env": [...], "cwd": "..."}, "mounts": [...]}`. + * + * # Errors + * + * Returns `Err` if the JSON is syntactically invalid or contains + * unexpected types. + */ +KrunInitConfig krun_init_config_from_oci_config_json(KrunStr json, KrunInitError* err_out); +/** + * Returns the kernel cmdline argument needed to boot with this init + * (e.g. `"init=/init.krun"`). Pass this to `krun_set_kernel_args`. + */ +KrunStr krun_init_config_kernel_init_arg(KrunInitConfig handle); +/** + * Returns the guest files that need to be injected into the guest + * root filesystem. + */ +KrunObjectArray krun_init_config_guest_files(KrunInitConfig handle); +void krun_init_config_destroy(KrunInitConfig handle); + +/* ConfigBuilder ----------------------------------------------------- */ + +/** Set the full argv: `args[0]` is the executable, `args[1..]` are arguments. */ +void krun_init_config_builder_args(KrunInitConfigBuilder* handle, const KrunStr* argv, size_t argv_len); +/** Set environment variables. Each entry should be `"KEY=value"`. */ +void krun_init_config_builder_env(KrunInitConfigBuilder* handle, const KrunStr* vars, size_t vars_len); +/** Set the guest working directory. */ +void krun_init_config_builder_workdir(KrunInitConfigBuilder* handle, KrunStr dir); +/** Add a mount specification. */ +void krun_init_config_builder_mount(KrunInitConfigBuilder* handle, KrunStr destination, KrunStr fs_type, KrunStr source); +/** Set resource limits. Each entry should be `"id=cur:max"` (e.g. `"7=0:0"`). */ +void krun_init_config_builder_rlimits(KrunInitConfigBuilder* handle, const KrunStr* limits, size_t limits_len); +/** + * Consume the builder, serialize the config, and return the + * finished [`Config`]. + */ +KrunInitConfig krun_init_config_builder_build(KrunInitConfigBuilder* handle); +void krun_init_config_builder_destroy(KrunInitConfigBuilder handle); + +/* KrunInitPushStrVtable --------------------------------------------- */ + +#define KRUN_INIT_PUSH_STR_TYPE_TAG 33554433 + +typedef struct { + void (*drop)(void* self_data); + bool (*push)(void* self_data, KrunStr s); +} KrunInitPushStrVtable; + +/* PushStr (dispatch) ------------------------------------------------ */ + +bool krun_init_push_str_push(KrunInitPushStr handle, KrunStr s); +void krun_init_push_str_destroy(KrunInitPushStr handle); + +/* KrunInitErrorVtable ----------------------------------------------- */ + +#define KRUN_INIT_ERROR_TYPE_TAG 33554434 + +typedef struct { + void (*drop)(void* self_data); + uint32_t (*code)(void* self_data); + void (*message)(void* self_data, KrunInitPushStr writer); + uint64_t (*result)(void* self_data); +} KrunInitErrorVtable; + +/* Error (dispatch) -------------------------------------------------- */ + +uint32_t krun_init_error_code(KrunInitError handle); +void krun_init_error_message(KrunInitError handle, KrunInitPushStr writer); +uint64_t krun_init_error_result(KrunInitError handle); +void krun_init_error_destroy(KrunInitError handle); +KrunStr krun_init_result_name(KrunResult r); +const char* krun_init_result_name_cstr(KrunResult r); + +#endif /* LIBKRUN_INIT_H */ diff --git a/init/init-blob-cdylib/Cargo.toml b/init/init-blob-cdylib/Cargo.toml new file mode 100644 index 000000000..b14e267d8 --- /dev/null +++ b/init/init-blob-cdylib/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "krun-init-blob-cdylib" +version = "0.1.0-1.18.1" +edition = "2021" +description = "FFI bridge for libkrun-init (init binary + config builder)" +license = "Apache-2.0" +repository = "https://github.com/containers/libkrun" + +[lib] +name = "krun_init" +crate-type = ["cdylib"] + +[[bin]] +name = "krun-init-blob-gen" +path = "src/gen.rs" + +[dependencies] +init-blob = { path = "../init-blob" } +ffier = { git = "file:///home/mhrica/Dev2/ffier/ffier.git", rev = "9e36967" } +ffier-bridge = { git = "file:///home/mhrica/Dev2/ffier/ffier.git", rev = "9e36967" } +ffier-bridge-macros = { git = "file:///home/mhrica/Dev2/ffier/ffier.git", rev = "9e36967" } +ffier-gen-c-header = { git = "file:///home/mhrica/Dev2/ffier/ffier.git", rev = "9e36967" } +ffier-gen-rust-client = { git = "file:///home/mhrica/Dev2/ffier/ffier.git", rev = "9e36967" } +ffier-schema = { git = "file:///home/mhrica/Dev2/ffier/ffier.git", rev = "9e36967" } +serde_json = "1" diff --git a/init/init-blob-cdylib/src/gen.rs b/init/init-blob-cdylib/src/gen.rs new file mode 100644 index 000000000..3f1ba6af5 --- /dev/null +++ b/init/init-blob-cdylib/src/gen.rs @@ -0,0 +1,47 @@ +use std::path::PathBuf; + +fn schema_path() -> PathBuf { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let workspace_root = manifest_dir.parent().unwrap().parent().unwrap(); + workspace_root.join("target/ffier-krun_init.json") +} + +fn gen_c_header() { + let path = schema_path(); + let json = std::fs::read_to_string(&path).unwrap_or_else(|e| { + panic!( + "failed to read {}: {e}\nBuild the cdylib first.", + path.display() + ) + }); + let lib: ffier_schema::Library = serde_json::from_str(&json) + .unwrap_or_else(|e| panic!("failed to parse {}: {e}", path.display())); + print!("{}", ffier_gen_c_header::generate(&lib, "LIBKRUN_INIT_H")); +} + +fn gen_rust_client(weak: bool) { + let path = schema_path(); + let opts = ffier_gen_rust_client::Options { weak }; + match ffier_gen_rust_client::generate_from_file_with_options(path.to_str().unwrap(), &opts) { + Ok(src) => print!("{src}"), + Err(e) => { + eprintln!( + "error: {e}\nBuild the cdylib first to generate {}", + path.display() + ); + std::process::exit(1); + } + } +} + +fn main() { + let mut args = std::env::args().skip(1); + match args.next().as_deref() { + Some("c-header") => gen_c_header(), + Some("rust-client") => gen_rust_client(args.next().as_deref() == Some("--weak")), + _ => { + eprintln!("usage: krun-init-blob-gen "); + std::process::exit(1); + } + } +} diff --git a/init/init-blob-cdylib/src/lib.rs b/init/init-blob-cdylib/src/lib.rs new file mode 100644 index 000000000..ffa37fe99 --- /dev/null +++ b/init/init-blob-cdylib/src/lib.rs @@ -0,0 +1 @@ +init_blob::__ffier_krun_init_library!(ffier_bridge_macros::generate); diff --git a/init/init-blob-via-cdylib-weak/Cargo.toml b/init/init-blob-via-cdylib-weak/Cargo.toml new file mode 100644 index 000000000..ee96529d9 --- /dev/null +++ b/init/init-blob-via-cdylib-weak/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "krun-init-blob-via-cdylib-weak" +version = "0.1.0-1.18.1" +edition = "2021" +description = "Weak (dlsym) Rust client for libkrun-init" +license = "Apache-2.0" +repository = "https://github.com/containers/libkrun" + +[dependencies] +ffier = { git = "file:///home/mhrica/Dev2/ffier/ffier.git", rev = "9e36967" } +libloading = "0.8" diff --git a/init/init-blob-via-cdylib-weak/src/lib.rs b/init/init-blob-via-cdylib-weak/src/lib.rs new file mode 100644 index 000000000..0caade8e1 --- /dev/null +++ b/init/init-blob-via-cdylib-weak/src/lib.rs @@ -0,0 +1,1345 @@ +#![allow( + dead_code, + unused_unsafe, + clippy::missing_safety_doc, + clippy::needless_lifetimes +)] +/// Marker trait for types exported as opaque C handles. +pub trait FfiHandle { + const C_HANDLE_NAME: &'static str; + const TYPE_TAG: u32; + unsafe fn as_handle(&self) -> *mut core::ffi::c_void; + fn __from_raw(handle: *mut core::ffi::c_void) -> Self; +} + +/// Maps Rust types to C-compatible representations. +pub trait FfiType { + type CRepr; + const C_TYPE_NAME: &'static str; + const IS_HANDLE: bool = false; + fn into_c(self) -> Self::CRepr; + unsafe fn from_c(repr: Self::CRepr) -> Self; +} + +macro_rules! impl_ffi_identity { + ($($t:ty => $n:expr),* $(,)?) => { $( + impl FfiType for $t { + type CRepr = $t; const C_TYPE_NAME: &'static str = $n; const IS_HANDLE: bool = false; + fn into_c(self) -> Self { self } unsafe fn from_c(r: Self) -> Self { r } + } + )* }; +} +impl_ffi_identity! { + i8 => "int8_t", i16 => "int16_t", i32 => "int32_t", i64 => "int64_t", + u8 => "uint8_t", u16 => "uint16_t", u32 => "uint32_t", u64 => "uint64_t", + isize => "ssize_t", usize => "size_t", bool => "bool", +} + +impl FfiType for &str { + type CRepr = ffier::FfierBytes; + const C_TYPE_NAME: &'static str = "FfierStr"; + const IS_HANDLE: bool = false; + fn into_c(self) -> ffier::FfierBytes { + unsafe { ffier::FfierBytes::from_str(self) } + } + unsafe fn from_c(repr: ffier::FfierBytes) -> Self { + unsafe { + let b = core::slice::from_raw_parts(repr.data, repr.len); + core::str::from_utf8_unchecked(b) + } + } +} + +impl<'a> FfiType for Option<&'a str> { + type CRepr = ffier::FfierBytes; + const C_TYPE_NAME: &'static str = "FfierStr"; + const IS_HANDLE: bool = false; + fn into_c(self) -> ffier::FfierBytes { + match self { + Some(s) => unsafe { ffier::FfierBytes::from_str(s) }, + None => ffier::FfierBytes::EMPTY, + } + } + unsafe fn from_c(repr: ffier::FfierBytes) -> Self { + if repr.data.is_null() { + None + } else { + unsafe { + Some(core::str::from_utf8_unchecked(core::slice::from_raw_parts( + repr.data, repr.len, + ))) + } + } + } +} + +impl FfiType for Box { + type CRepr = ffier::FfierBytes; + const C_TYPE_NAME: &'static str = "FfierStr"; + const IS_HANDLE: bool = false; + fn into_c(self) -> ffier::FfierBytes { + let leaked: &mut str = Box::leak(self); + ffier::FfierBytes { + data: leaked.as_mut_ptr() as *const u8, + len: leaked.len(), + } + } + unsafe fn from_c(repr: ffier::FfierBytes) -> Self { + unsafe { + let slice = core::slice::from_raw_parts_mut(repr.data as *mut u8, repr.len); + Box::from_raw(core::str::from_utf8_unchecked_mut(slice)) + } + } +} + +impl FfiType for &[u8] { + type CRepr = ffier::FfierBytes; + const C_TYPE_NAME: &'static str = "FfierBytes"; + const IS_HANDLE: bool = false; + fn into_c(self) -> ffier::FfierBytes { + unsafe { ffier::FfierBytes::from_bytes(self) } + } + unsafe fn from_c(repr: ffier::FfierBytes) -> Self { + unsafe { + if repr.data.is_null() { + &[] + } else { + core::slice::from_raw_parts(repr.data, repr.len) + } + } + } +} + +impl FfiType for &T { + type CRepr = *mut core::ffi::c_void; + const C_TYPE_NAME: &'static str = T::C_HANDLE_NAME; + const IS_HANDLE: bool = true; + fn into_c(self) -> *mut core::ffi::c_void { + unsafe { self.as_handle() } + } + unsafe fn from_c(_: *mut core::ffi::c_void) -> Self { + unimplemented!("client-side &T from_c") + } +} +impl FfiType for &mut T { + type CRepr = *mut core::ffi::c_void; + const C_TYPE_NAME: &'static str = T::C_HANDLE_NAME; + const IS_HANDLE: bool = true; + fn into_c(self) -> *mut core::ffi::c_void { + unsafe { self.as_handle() } + } + unsafe fn from_c(_: *mut core::ffi::c_void) -> Self { + unimplemented!("client-side &mut T from_c") + } +} + +/// Borrowed slice of handles returned from FFI methods. +/// Elements are borrowed — do NOT destroy them individually. +/// Derefs to `&[T]`, so indexing, iteration, `len()`, etc. all work. +/// Dropping this type frees the backing array. +pub struct ForeignSlice { + raw: ffier::FfierObjectArray, + elements: Box<[core::mem::ManuallyDrop]>, +} + +impl ForeignSlice { + fn from_raw(raw: ffier::FfierObjectArray) -> Self { + let elements: Box<[core::mem::ManuallyDrop]> = (0..raw.len) + .map(|i| { + core::mem::ManuallyDrop::new(T::__from_raw(unsafe { + ffier::ffier_object_array_get(raw, i) + })) + }) + .collect(); + Self { raw, elements } + } +} + +impl core::ops::Deref for ForeignSlice { + type Target = [T]; + fn deref(&self) -> &[T] { + // SAFETY: ManuallyDrop is #[repr(transparent)] over T. + unsafe { core::mem::transmute::<&[core::mem::ManuallyDrop], &[T]>(&self.elements) } + } +} + +impl Drop for ForeignSlice { + fn drop(&mut self) { + unsafe { ffier::ffier_object_array_free(self.raw) }; + } +} + +static KRUN_INIT_ERROR_PAYLOAD: std::sync::OnceLock< + unsafe extern "C" fn(*const core::ffi::c_void, *mut core::ffi::c_void, usize), +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +unsafe fn krun_init_error_payload( + handle: *const core::ffi::c_void, + out_buf: *mut core::ffi::c_void, + buf_size: usize, +) { + unsafe { + (KRUN_INIT_ERROR_PAYLOAD + .get() + .expect("symbol `krun_init_error_payload` not loaded; call require() first"))( + handle, out_buf, buf_size, + ) + } +} + +pub struct ConfigErrorErrorHandle(*mut core::ffi::c_void); +impl ConfigErrorErrorHandle { + fn handle(&self) -> *mut core::ffi::c_void { + self.0 + } +} +impl Drop for ConfigErrorErrorHandle { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { krun_init_error_destroy(self.0) } + } + } +} +impl std::fmt::Debug for ConfigErrorErrorHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ErrorHandle({:?})", self.0) + } +} + +pub struct ConfigErrorInvalidJsonData(ConfigErrorErrorHandle); +impl ConfigErrorInvalidJsonData { + pub fn field_0(&self) -> &str { + let mut __buf = std::mem::MaybeUninit::::uninit(); + unsafe { + krun_init_error_payload( + self.0.handle() as *const core::ffi::c_void, + __buf.as_mut_ptr() as *mut core::ffi::c_void, + core::mem::size_of::(), + ) + }; + unsafe { <&str as FfiType>::from_c(__buf.assume_init()) } + } +} +impl std::fmt::Debug for ConfigErrorInvalidJsonData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "InvalidJson(...)") + } +} + +#[derive(Debug)] +pub enum ConfigError { + InvalidJson(ConfigErrorInvalidJsonData), +} + +impl ConfigError { + pub fn from_ffi(r: ffier::FfierResult, err_handle: *mut core::ffi::c_void) -> Self { + let code = ffier::ffier_result_code(r); + let handle = ConfigErrorErrorHandle(err_handle); + match code { + 1u32 => Self::InvalidJson(ConfigErrorInvalidJsonData(handle)), + other => panic!("unknown {} error code {}", "ConfigError", other), + } + } + fn handle_ptr(&self) -> *mut core::ffi::c_void { + match self { + Self::InvalidJson(d) => d.0.handle(), + } + } +} + +impl std::fmt::Display for ConfigError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + struct FmtWriter(*mut core::ffi::c_void); + impl PushStr for FmtWriter { + fn push(&mut self, s: &str) -> bool { + unsafe { + (&mut *(self.0 as *mut std::fmt::Formatter<'_>)) + .write_str(s) + .is_ok() + } + } + } + let mut __writer = FmtWriter(f as *mut std::fmt::Formatter<'_> as *mut core::ffi::c_void); + let __vtable: &'static PushStrVtable = FmtWriter::__ffier_vtable(); + let mut __temp = ffier::FfierHandle { + type_tag: 33554433u32, + metadata: 0, + value: ffier::VtableHandle { + vtable_ptr: __vtable as *const PushStrVtable as *const core::ffi::c_void, + user_data: &mut __writer as *mut FmtWriter as *const core::ffi::c_void, + vtable_size: core::mem::size_of::() as u16, + }, + }; + let __writer_handle = + &mut __temp as *mut ffier::FfierHandle as *mut core::ffi::c_void; + unsafe { krun_init_error_message(self.handle_ptr(), __writer_handle) }; + Ok(()) + } +} + +impl std::error::Error for ConfigError {} + +static KRUN_INIT_GUEST_FILE_DESTROY: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void), +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_guest_file_destroy(handle: *mut core::ffi::c_void) { + unsafe { + (KRUN_INIT_GUEST_FILE_DESTROY + .get() + .expect("symbol `krun_init_guest_file_destroy` not loaded; call require() first"))( + handle, + ) + } +} + +static KRUN_INIT_GUEST_FILE_PATH: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void) -> <&'static str as FfiType>::CRepr, +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_guest_file_path( + handle: *mut core::ffi::c_void, +) -> <&'static str as FfiType>::CRepr { + unsafe { + (KRUN_INIT_GUEST_FILE_PATH + .get() + .expect("symbol `krun_init_guest_file_path` not loaded; call require() first"))( + handle + ) + } +} + +static KRUN_INIT_GUEST_FILE_DATA: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void) -> <&'static [u8] as FfiType>::CRepr, +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_guest_file_data( + handle: *mut core::ffi::c_void, +) -> <&'static [u8] as FfiType>::CRepr { + unsafe { + (KRUN_INIT_GUEST_FILE_DATA + .get() + .expect("symbol `krun_init_guest_file_data` not loaded; call require() first"))( + handle + ) + } +} + +static KRUN_INIT_GUEST_FILE_MODE: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void) -> ::CRepr, +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_guest_file_mode(handle: *mut core::ffi::c_void) -> ::CRepr { + unsafe { + (KRUN_INIT_GUEST_FILE_MODE + .get() + .expect("symbol `krun_init_guest_file_mode` not loaded; call require() first"))( + handle + ) + } +} + +static KRUN_INIT_GUEST_FILE_ONE_SHOT: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void) -> ::CRepr, +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_guest_file_one_shot( + handle: *mut core::ffi::c_void, +) -> ::CRepr { + unsafe { + (KRUN_INIT_GUEST_FILE_ONE_SHOT + .get() + .expect("symbol `krun_init_guest_file_one_shot` not loaded; call require() first"))( + handle, + ) + } +} + +pub struct GuestFile(*mut core::ffi::c_void); + +impl GuestFile { + #[doc(hidden)] + pub fn __from_raw(ptr: *mut core::ffi::c_void) -> Self { + Self(ptr) + } + #[doc(hidden)] + pub fn __into_raw(self) -> *mut core::ffi::c_void { + let this = std::mem::ManuallyDrop::new(self); + this.0 + } +} + +impl FfiHandle for GuestFile { + const C_HANDLE_NAME: &'static str = "GuestFile"; + const TYPE_TAG: u32 = 33554436u32; + unsafe fn as_handle(&self) -> *mut core::ffi::c_void { + self.0 + } + fn __from_raw(handle: *mut core::ffi::c_void) -> Self { + Self(handle) + } +} + +impl FfiType for GuestFile { + type CRepr = *mut core::ffi::c_void; + const C_TYPE_NAME: &'static str = "GuestFile"; + fn into_c(self) -> *mut core::ffi::c_void { + self.__into_raw() + } + unsafe fn from_c(repr: *mut core::ffi::c_void) -> Self { + Self::__from_raw(repr) + } +} + +impl std::fmt::Debug for GuestFile { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("GuestFile").field(&self.0).finish() + } +} + +impl GuestFile { + #[doc = " Path on the guest root filesystem (e.g. `\"/init.krun\"`)."] + pub fn path(&self) -> &str { + let __raw = unsafe { krun_init_guest_file_path(self.0) }; + unsafe { <&str as FfiType>::from_c(__raw) } + } + #[doc = " File contents."] + pub fn data(&self) -> &[u8] { + let __raw = unsafe { krun_init_guest_file_data(self.0) }; + unsafe { <&[u8] as FfiType>::from_c(__raw) } + } + #[doc = " Permission mode bits (e.g. `0o755`)."] + pub fn mode(&self) -> u32 { + let __raw = unsafe { krun_init_guest_file_mode(self.0) }; + unsafe { ::from_c(__raw) } + } + #[doc = " Whether this file is one-shot (removed after first lookup)."] + pub fn one_shot(&self) -> bool { + let __raw = unsafe { krun_init_guest_file_one_shot(self.0) }; + unsafe { ::from_c(__raw) } + } +} + +impl Drop for GuestFile { + fn drop(&mut self) { + unsafe { krun_init_guest_file_destroy(self.0) } + } +} + +static KRUN_INIT_CONFIG_DESTROY: std::sync::OnceLock = + std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_config_destroy(handle: *mut core::ffi::c_void) { + unsafe { + (KRUN_INIT_CONFIG_DESTROY + .get() + .expect("symbol `krun_init_config_destroy` not loaded; call require() first"))( + handle + ) + } +} + +static KRUN_INIT_CONFIG_BUILDER: std::sync::OnceLock< + unsafe extern "C" fn() -> ::CRepr, +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_config_builder() -> ::CRepr { + unsafe { + (KRUN_INIT_CONFIG_BUILDER + .get() + .expect("symbol `krun_init_config_builder` not loaded; call require() first"))() + } +} + +static KRUN_INIT_CONFIG_FROM_OCI_CONFIG_JSON: std::sync::OnceLock< + unsafe extern "C" fn( + <&'static str as FfiType>::CRepr, + *mut *mut core::ffi::c_void, + ) -> *mut core::ffi::c_void, +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_config_from_oci_config_json( + json: <&'static str as FfiType>::CRepr, + err_out: *mut *mut core::ffi::c_void, +) -> *mut core::ffi::c_void { + unsafe { + (KRUN_INIT_CONFIG_FROM_OCI_CONFIG_JSON.get().expect( + "symbol `krun_init_config_from_oci_config_json` not loaded; call require() first", + ))(json, err_out) + } +} + +static KRUN_INIT_CONFIG_KERNEL_INIT_ARG: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void) -> <&'static str as FfiType>::CRepr, +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_config_kernel_init_arg( + handle: *mut core::ffi::c_void, +) -> <&'static str as FfiType>::CRepr { + unsafe { + (KRUN_INIT_CONFIG_KERNEL_INIT_ARG + .get() + .expect("symbol `krun_init_config_kernel_init_arg` not loaded; call require() first"))( + handle, + ) + } +} + +static KRUN_INIT_CONFIG_GUEST_FILES: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void) -> ffier::FfierObjectArray, +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_config_guest_files( + handle: *mut core::ffi::c_void, +) -> ffier::FfierObjectArray { + unsafe { + (KRUN_INIT_CONFIG_GUEST_FILES + .get() + .expect("symbol `krun_init_config_guest_files` not loaded; call require() first"))( + handle, + ) + } +} + +pub struct Config(*mut core::ffi::c_void); + +impl Config { + #[doc(hidden)] + pub fn __from_raw(ptr: *mut core::ffi::c_void) -> Self { + Self(ptr) + } + #[doc(hidden)] + pub fn __into_raw(self) -> *mut core::ffi::c_void { + let this = std::mem::ManuallyDrop::new(self); + this.0 + } +} + +impl FfiHandle for Config { + const C_HANDLE_NAME: &'static str = "Config"; + const TYPE_TAG: u32 = 33554437u32; + unsafe fn as_handle(&self) -> *mut core::ffi::c_void { + self.0 + } + fn __from_raw(handle: *mut core::ffi::c_void) -> Self { + Self(handle) + } +} + +impl FfiType for Config { + type CRepr = *mut core::ffi::c_void; + const C_TYPE_NAME: &'static str = "Config"; + fn into_c(self) -> *mut core::ffi::c_void { + self.__into_raw() + } + unsafe fn from_c(repr: *mut core::ffi::c_void) -> Self { + Self::__from_raw(repr) + } +} + +impl std::fmt::Debug for Config { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Config").field(&self.0).finish() + } +} + +impl Config { + #[doc = " Start building a new init configuration."] + pub fn builder() -> ConfigBuilder { + let __raw = unsafe { krun_init_config_builder() }; + unsafe { ::from_c(__raw) } + } + #[doc = " Construct from an OCI runtime-spec config.json string."] + #[doc = ""] + #[doc = " The JSON is expected to use the OCI runtime-spec layout:"] + #[doc = " `{\"process\": {\"args\": [...], \"env\": [...], \"cwd\": \"...\"}, \"mounts\": [...]}`."] + #[doc = ""] + #[doc = " # Errors"] + #[doc = ""] + #[doc = " Returns `Err` if the JSON is syntactically invalid or contains"] + #[doc = " unexpected types."] + pub fn from_oci_config_json(json: &str) -> Result { + let mut __err: *mut core::ffi::c_void = core::ptr::null_mut(); + let __raw = unsafe { + krun_init_config_from_oci_config_json( + <&str as FfiType>::into_c(json), + &mut __err as *mut *mut core::ffi::c_void, + ) + }; + if !__raw.is_null() { + Ok(unsafe { ::from_c(__raw) }) + } else { + let __r = unsafe { krun_init_error_result(__err) }; + Err(ConfigError::from_ffi(__r, __err)) + } + } + #[doc = " Returns the kernel cmdline argument needed to boot with this init"] + #[doc = " (e.g. `\"init=/init.krun\"`). Pass this to `krun_set_kernel_args`."] + pub fn kernel_init_arg(&self) -> &str { + let __raw = unsafe { krun_init_config_kernel_init_arg(self.0) }; + unsafe { <&str as FfiType>::from_c(__raw) } + } + #[doc = " Returns the guest files that need to be injected into the guest"] + #[doc = " root filesystem."] + pub fn guest_files(&self) -> ForeignSlice { + ForeignSlice::from_raw(unsafe { krun_init_config_guest_files(self.0) }) + } +} + +impl Drop for Config { + fn drop(&mut self) { + unsafe { krun_init_config_destroy(self.0) } + } +} + +static KRUN_INIT_CONFIG_BUILDER_DESTROY: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void), +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_config_builder_destroy(handle: *mut core::ffi::c_void) { + unsafe { + (KRUN_INIT_CONFIG_BUILDER_DESTROY + .get() + .expect("symbol `krun_init_config_builder_destroy` not loaded; call require() first"))( + handle, + ) + } +} + +static KRUN_INIT_CONFIG_BUILDER_ARGS: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void, *const ffier::FfierBytes, usize), +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_config_builder_args( + handle: *mut core::ffi::c_void, + argv: *const ffier::FfierBytes, + argv_len: usize, +) { + unsafe { + (KRUN_INIT_CONFIG_BUILDER_ARGS + .get() + .expect("symbol `krun_init_config_builder_args` not loaded; call require() first"))( + handle, argv, argv_len, + ) + } +} + +static KRUN_INIT_CONFIG_BUILDER_ENV: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void, *const ffier::FfierBytes, usize), +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_config_builder_env( + handle: *mut core::ffi::c_void, + vars: *const ffier::FfierBytes, + vars_len: usize, +) { + unsafe { + (KRUN_INIT_CONFIG_BUILDER_ENV + .get() + .expect("symbol `krun_init_config_builder_env` not loaded; call require() first"))( + handle, vars, vars_len, + ) + } +} + +static KRUN_INIT_CONFIG_BUILDER_WORKDIR: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void, <&'static str as FfiType>::CRepr), +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_config_builder_workdir( + handle: *mut core::ffi::c_void, + dir: <&'static str as FfiType>::CRepr, +) { + unsafe { + (KRUN_INIT_CONFIG_BUILDER_WORKDIR + .get() + .expect("symbol `krun_init_config_builder_workdir` not loaded; call require() first"))( + handle, dir, + ) + } +} + +static KRUN_INIT_CONFIG_BUILDER_MOUNT: std::sync::OnceLock< + unsafe extern "C" fn( + *mut core::ffi::c_void, + <&'static str as FfiType>::CRepr, + <&'static str as FfiType>::CRepr, + <&'static str as FfiType>::CRepr, + ), +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_config_builder_mount( + handle: *mut core::ffi::c_void, + destination: <&'static str as FfiType>::CRepr, + fs_type: <&'static str as FfiType>::CRepr, + source: <&'static str as FfiType>::CRepr, +) { + unsafe { + (KRUN_INIT_CONFIG_BUILDER_MOUNT + .get() + .expect("symbol `krun_init_config_builder_mount` not loaded; call require() first"))( + handle, + destination, + fs_type, + source, + ) + } +} + +static KRUN_INIT_CONFIG_BUILDER_RLIMITS: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void, *const ffier::FfierBytes, usize), +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_config_builder_rlimits( + handle: *mut core::ffi::c_void, + limits: *const ffier::FfierBytes, + limits_len: usize, +) { + unsafe { + (KRUN_INIT_CONFIG_BUILDER_RLIMITS + .get() + .expect("symbol `krun_init_config_builder_rlimits` not loaded; call require() first"))( + handle, limits, limits_len, + ) + } +} + +static KRUN_INIT_CONFIG_BUILDER_BUILD: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void) -> ::CRepr, +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_config_builder_build( + handle: *mut core::ffi::c_void, +) -> ::CRepr { + unsafe { + (KRUN_INIT_CONFIG_BUILDER_BUILD + .get() + .expect("symbol `krun_init_config_builder_build` not loaded; call require() first"))( + handle, + ) + } +} + +pub struct ConfigBuilder(*mut core::ffi::c_void); + +impl ConfigBuilder { + #[doc(hidden)] + pub fn __from_raw(ptr: *mut core::ffi::c_void) -> Self { + Self(ptr) + } + #[doc(hidden)] + pub fn __into_raw(self) -> *mut core::ffi::c_void { + let this = std::mem::ManuallyDrop::new(self); + this.0 + } +} + +impl FfiHandle for ConfigBuilder { + const C_HANDLE_NAME: &'static str = "ConfigBuilder"; + const TYPE_TAG: u32 = 33554438u32; + unsafe fn as_handle(&self) -> *mut core::ffi::c_void { + self.0 + } + fn __from_raw(handle: *mut core::ffi::c_void) -> Self { + Self(handle) + } +} + +impl FfiType for ConfigBuilder { + type CRepr = *mut core::ffi::c_void; + const C_TYPE_NAME: &'static str = "ConfigBuilder"; + fn into_c(self) -> *mut core::ffi::c_void { + self.__into_raw() + } + unsafe fn from_c(repr: *mut core::ffi::c_void) -> Self { + Self::__from_raw(repr) + } +} + +impl std::fmt::Debug for ConfigBuilder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("ConfigBuilder").field(&self.0).finish() + } +} + +impl ConfigBuilder { + #[doc = " Set the full argv: `args[0]` is the executable, `args[1..]` are arguments."] + pub fn args(self, argv: &[&str]) -> Self { + let mut __handle = { + let this = std::mem::ManuallyDrop::new(self); + this.0 + }; + let __ffi_argv: Vec = argv + .iter() + .map(|s| unsafe { ffier::FfierBytes::from_str(s) }) + .collect(); + unsafe { + krun_init_config_builder_args( + &mut __handle as *mut *mut core::ffi::c_void as *mut core::ffi::c_void, + __ffi_argv.as_ptr(), + __ffi_argv.len(), + ) + }; + Self(__handle) + } + #[doc = " Set environment variables. Each entry should be `\"KEY=value\"`."] + pub fn env(self, vars: &[&str]) -> Self { + let mut __handle = { + let this = std::mem::ManuallyDrop::new(self); + this.0 + }; + let __ffi_vars: Vec = vars + .iter() + .map(|s| unsafe { ffier::FfierBytes::from_str(s) }) + .collect(); + unsafe { + krun_init_config_builder_env( + &mut __handle as *mut *mut core::ffi::c_void as *mut core::ffi::c_void, + __ffi_vars.as_ptr(), + __ffi_vars.len(), + ) + }; + Self(__handle) + } + #[doc = " Set the guest working directory."] + pub fn workdir(self, dir: &str) -> Self { + let mut __handle = { + let this = std::mem::ManuallyDrop::new(self); + this.0 + }; + unsafe { + krun_init_config_builder_workdir( + &mut __handle as *mut *mut core::ffi::c_void as *mut core::ffi::c_void, + <&str as FfiType>::into_c(dir), + ) + }; + Self(__handle) + } + #[doc = " Add a mount specification."] + pub fn mount(self, destination: &str, fs_type: &str, source: &str) -> Self { + let mut __handle = { + let this = std::mem::ManuallyDrop::new(self); + this.0 + }; + unsafe { + krun_init_config_builder_mount( + &mut __handle as *mut *mut core::ffi::c_void as *mut core::ffi::c_void, + <&str as FfiType>::into_c(destination), + <&str as FfiType>::into_c(fs_type), + <&str as FfiType>::into_c(source), + ) + }; + Self(__handle) + } + #[doc = " Set resource limits. Each entry should be `\"id=cur:max\"` (e.g. `\"7=0:0\"`)."] + pub fn rlimits(self, limits: &[&str]) -> Self { + let mut __handle = { + let this = std::mem::ManuallyDrop::new(self); + this.0 + }; + let __ffi_limits: Vec = limits + .iter() + .map(|s| unsafe { ffier::FfierBytes::from_str(s) }) + .collect(); + unsafe { + krun_init_config_builder_rlimits( + &mut __handle as *mut *mut core::ffi::c_void as *mut core::ffi::c_void, + __ffi_limits.as_ptr(), + __ffi_limits.len(), + ) + }; + Self(__handle) + } + #[doc = " Consume the builder, serialize the config, and return the"] + #[doc = " finished [`Config`]."] + pub fn build(self) -> Config { + let mut __handle = { + let this = std::mem::ManuallyDrop::new(self); + this.0 + }; + let __raw = unsafe { + krun_init_config_builder_build( + &mut __handle as *mut *mut core::ffi::c_void as *mut core::ffi::c_void, + ) + }; + unsafe { ::from_c(__raw) } + } +} + +impl Drop for ConfigBuilder { + fn drop(&mut self) { + unsafe { krun_init_config_builder_destroy(self.0) } + } +} + +pub trait PushStr { + fn push(&mut self, s: &str) -> bool; + #[doc(hidden)] + fn __ffier_vtable() -> &'static PushStrVtable + where + Self: Sized, + { + &PushStrVtable { + drop: Some({ + unsafe extern "C" fn __drop_trampoline<__T>(__ud: *mut core::ffi::c_void) { + unsafe { drop(Box::from_raw(__ud as *mut __T)) }; + } + __drop_trampoline:: + }), + push: Some({ + unsafe extern "C" fn __trampoline<__T: PushStr>( + __ud: *mut core::ffi::c_void, + s: <&'static str as FfiType>::CRepr, + ) -> ::CRepr { + let __val = unsafe { &mut *(__ud as *mut __T) }; + let __result = __val.push(unsafe { <&str as FfiType>::from_c(s) }); + ::into_c(__result) + } + __trampoline:: + }), + } + } + #[doc(hidden)] + fn __into_raw_handle(self) -> *mut core::ffi::c_void + where + Self: Sized, + { + let __vtable: &'static PushStrVtable = Self::__ffier_vtable(); + let __user_data = Box::into_raw(Box::new(self)); + let vtable_size: u16 = core::mem::size_of::() + .try_into() + .expect("vtable_size exceeds u16::MAX"); + ffier::ffier_handle_new_with_metadata( + 33554433u32, + 0, + ffier::VtableHandle { + vtable_ptr: __vtable as *const PushStrVtable as *const core::ffi::c_void, + user_data: __user_data as *const core::ffi::c_void, + vtable_size, + }, + ) + } +} + +#[repr(C)] +pub struct PushStrVtable { + pub drop: Option, + pub push: Option< + unsafe extern "C" fn( + *mut core::ffi::c_void, + <&'static str as FfiType>::CRepr, + ) -> ::CRepr, + >, +} + +pub struct VtablePushStr(*mut core::ffi::c_void); + +impl VtablePushStr { + #[doc(hidden)] + pub fn __into_raw(self) -> *mut core::ffi::c_void { + let this = std::mem::ManuallyDrop::new(self); + this.0 + } +} + +impl Drop for VtablePushStr { + fn drop(&mut self) {} +} + +static KRUN_INIT_ERROR_CODE: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void) -> ::CRepr, +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_error_code(handle: *mut core::ffi::c_void) -> ::CRepr { + unsafe { + (KRUN_INIT_ERROR_CODE + .get() + .expect("symbol `krun_init_error_code` not loaded; call require() first"))( + handle + ) + } +} + +static KRUN_INIT_ERROR_MESSAGE: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void, *mut core::ffi::c_void), +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_error_message( + handle: *mut core::ffi::c_void, + writer: *mut core::ffi::c_void, +) { + unsafe { + (KRUN_INIT_ERROR_MESSAGE + .get() + .expect("symbol `krun_init_error_message` not loaded; call require() first"))( + handle, writer, + ) + } +} + +static KRUN_INIT_ERROR_RESULT: std::sync::OnceLock< + unsafe extern "C" fn(*mut core::ffi::c_void) -> ::CRepr, +> = std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_error_result(handle: *mut core::ffi::c_void) -> ::CRepr { + unsafe { + (KRUN_INIT_ERROR_RESULT + .get() + .expect("symbol `krun_init_error_result` not loaded; call require() first"))( + handle + ) + } +} + +static KRUN_INIT_ERROR_DESTROY: std::sync::OnceLock = + std::sync::OnceLock::new(); +#[allow(non_snake_case)] +pub unsafe fn krun_init_error_destroy(handle: *mut core::ffi::c_void) { + unsafe { + (KRUN_INIT_ERROR_DESTROY + .get() + .expect("symbol `krun_init_error_destroy` not loaded; call require() first"))( + handle + ) + } +} + +/// FFI symbols that can be loaded at runtime. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Symbol { + /// `krun_init_error_payload` + KrunInitErrorPayload, + /// `krun_init_guest_file_destroy` + KrunInitGuestFileDestroy, + /// `krun_init_guest_file_path` + KrunInitGuestFilePath, + /// `krun_init_guest_file_data` + KrunInitGuestFileData, + /// `krun_init_guest_file_mode` + KrunInitGuestFileMode, + /// `krun_init_guest_file_one_shot` + KrunInitGuestFileOneShot, + /// `krun_init_config_destroy` + KrunInitConfigDestroy, + /// `krun_init_config_builder` + KrunInitConfigBuilder, + /// `krun_init_config_from_oci_config_json` + KrunInitConfigFromOciConfigJson, + /// `krun_init_config_kernel_init_arg` + KrunInitConfigKernelInitArg, + /// `krun_init_config_guest_files` + KrunInitConfigGuestFiles, + /// `krun_init_config_builder_destroy` + KrunInitConfigBuilderDestroy, + /// `krun_init_config_builder_args` + KrunInitConfigBuilderArgs, + /// `krun_init_config_builder_env` + KrunInitConfigBuilderEnv, + /// `krun_init_config_builder_workdir` + KrunInitConfigBuilderWorkdir, + /// `krun_init_config_builder_mount` + KrunInitConfigBuilderMount, + /// `krun_init_config_builder_rlimits` + KrunInitConfigBuilderRlimits, + /// `krun_init_config_builder_build` + KrunInitConfigBuilderBuild, + /// `krun_init_error_code` + KrunInitErrorCode, + /// `krun_init_error_message` + KrunInitErrorMessage, + /// `krun_init_error_result` + KrunInitErrorResult, + /// `krun_init_error_destroy` + KrunInitErrorDestroy, +} + +impl Symbol { + /// The C symbol name for dlsym lookup. + pub fn c_name(self) -> &'static str { + match self { + Symbol::KrunInitErrorPayload => "krun_init_error_payload", + Symbol::KrunInitGuestFileDestroy => "krun_init_guest_file_destroy", + Symbol::KrunInitGuestFilePath => "krun_init_guest_file_path", + Symbol::KrunInitGuestFileData => "krun_init_guest_file_data", + Symbol::KrunInitGuestFileMode => "krun_init_guest_file_mode", + Symbol::KrunInitGuestFileOneShot => "krun_init_guest_file_one_shot", + Symbol::KrunInitConfigDestroy => "krun_init_config_destroy", + Symbol::KrunInitConfigBuilder => "krun_init_config_builder", + Symbol::KrunInitConfigFromOciConfigJson => "krun_init_config_from_oci_config_json", + Symbol::KrunInitConfigKernelInitArg => "krun_init_config_kernel_init_arg", + Symbol::KrunInitConfigGuestFiles => "krun_init_config_guest_files", + Symbol::KrunInitConfigBuilderDestroy => "krun_init_config_builder_destroy", + Symbol::KrunInitConfigBuilderArgs => "krun_init_config_builder_args", + Symbol::KrunInitConfigBuilderEnv => "krun_init_config_builder_env", + Symbol::KrunInitConfigBuilderWorkdir => "krun_init_config_builder_workdir", + Symbol::KrunInitConfigBuilderMount => "krun_init_config_builder_mount", + Symbol::KrunInitConfigBuilderRlimits => "krun_init_config_builder_rlimits", + Symbol::KrunInitConfigBuilderBuild => "krun_init_config_builder_build", + Symbol::KrunInitErrorCode => "krun_init_error_code", + Symbol::KrunInitErrorMessage => "krun_init_error_message", + Symbol::KrunInitErrorResult => "krun_init_error_result", + Symbol::KrunInitErrorDestroy => "krun_init_error_destroy", + } + } + + /// Check whether this symbol is available in the current process + /// (i.e. the library version is new enough). Does NOT load the symbol. + pub fn is_available(self) -> bool { + unsafe { + let lib = libloading::os::unix::Library::this(); + lib.get::<*const ()>(self.c_name().as_bytes()).is_ok() + } + } +} + +/// Load the given symbols via dlsym. Returns `Ok(())` if all symbols +/// are loaded successfully. Already-loaded symbols are skipped. +/// Fails on the first symbol that cannot be found. +pub fn require(symbols: &[Symbol]) -> Result<(), libloading::Error> { + let lib = unsafe { libloading::os::unix::Library::this() }; + for &sym in symbols { + match sym { + Symbol::KrunInitErrorPayload => { + if KRUN_INIT_ERROR_PAYLOAD.get().is_none() { + let f = unsafe { + *lib.get::(b"krun_init_error_payload\0")? + }; + let _ = KRUN_INIT_ERROR_PAYLOAD.set(f); + } + } + Symbol::KrunInitGuestFileDestroy => { + if KRUN_INIT_GUEST_FILE_DESTROY.get().is_none() { + let f = unsafe { + *lib.get::( + b"krun_init_guest_file_destroy\0", + )? + }; + let _ = KRUN_INIT_GUEST_FILE_DESTROY.set(f); + } + } + Symbol::KrunInitGuestFilePath => { + if KRUN_INIT_GUEST_FILE_PATH.get().is_none() { + let f = unsafe { + *lib.get:: <&'static str as FfiType>::CRepr>( + b"krun_init_guest_file_path\0" + )? + }; + let _ = KRUN_INIT_GUEST_FILE_PATH.set(f); + } + } + Symbol::KrunInitGuestFileData => { + if KRUN_INIT_GUEST_FILE_DATA.get().is_none() { + let f = unsafe { + *lib.get:: <&'static [u8] as FfiType>::CRepr>( + b"krun_init_guest_file_data\0" + )? + }; + let _ = KRUN_INIT_GUEST_FILE_DATA.set(f); + } + } + Symbol::KrunInitGuestFileMode => { + if KRUN_INIT_GUEST_FILE_MODE.get().is_none() { + let f = unsafe { + *lib.get:: ::CRepr>(b"krun_init_guest_file_mode\0")? + }; + let _ = KRUN_INIT_GUEST_FILE_MODE.set(f); + } + } + Symbol::KrunInitGuestFileOneShot => { + if KRUN_INIT_GUEST_FILE_ONE_SHOT.get().is_none() { + let f = unsafe { + *lib.get:: ::CRepr>( + b"krun_init_guest_file_one_shot\0" + )? + }; + let _ = KRUN_INIT_GUEST_FILE_ONE_SHOT.set(f); + } + } + Symbol::KrunInitConfigDestroy => { + if KRUN_INIT_CONFIG_DESTROY.get().is_none() { + let f = unsafe { + *lib.get::( + b"krun_init_config_destroy\0", + )? + }; + let _ = KRUN_INIT_CONFIG_DESTROY.set(f); + } + } + Symbol::KrunInitConfigBuilder => { + if KRUN_INIT_CONFIG_BUILDER.get().is_none() { + let f = unsafe { + *lib.get:: ::CRepr>( + b"krun_init_config_builder\0", + )? + }; + let _ = KRUN_INIT_CONFIG_BUILDER.set(f); + } + } + Symbol::KrunInitConfigFromOciConfigJson => { + if KRUN_INIT_CONFIG_FROM_OCI_CONFIG_JSON.get().is_none() { + let f = unsafe { + *lib.get::::CRepr, + *mut *mut core::ffi::c_void, + ) -> *mut core::ffi::c_void>( + b"krun_init_config_from_oci_config_json\0" + )? + }; + let _ = KRUN_INIT_CONFIG_FROM_OCI_CONFIG_JSON.set(f); + } + } + Symbol::KrunInitConfigKernelInitArg => { + if KRUN_INIT_CONFIG_KERNEL_INIT_ARG.get().is_none() { + let f = unsafe { + *lib.get:: <&'static str as FfiType>::CRepr>( + b"krun_init_config_kernel_init_arg\0", + )? + }; + let _ = KRUN_INIT_CONFIG_KERNEL_INIT_ARG.set(f); + } + } + Symbol::KrunInitConfigGuestFiles => { + if KRUN_INIT_CONFIG_GUEST_FILES.get().is_none() { + let f = unsafe { + *lib.get:: ffier::FfierObjectArray>(b"krun_init_config_guest_files\0")? + }; + let _ = KRUN_INIT_CONFIG_GUEST_FILES.set(f); + } + } + Symbol::KrunInitConfigBuilderDestroy => { + if KRUN_INIT_CONFIG_BUILDER_DESTROY.get().is_none() { + let f = unsafe { + *lib.get::( + b"krun_init_config_builder_destroy\0", + )? + }; + let _ = KRUN_INIT_CONFIG_BUILDER_DESTROY.set(f); + } + } + Symbol::KrunInitConfigBuilderArgs => { + if KRUN_INIT_CONFIG_BUILDER_ARGS.get().is_none() { + let f = unsafe { + *lib.get::(b"krun_init_config_builder_args\0")? + }; + let _ = KRUN_INIT_CONFIG_BUILDER_ARGS.set(f); + } + } + Symbol::KrunInitConfigBuilderEnv => { + if KRUN_INIT_CONFIG_BUILDER_ENV.get().is_none() { + let f = unsafe { + *lib.get::(b"krun_init_config_builder_env\0")? + }; + let _ = KRUN_INIT_CONFIG_BUILDER_ENV.set(f); + } + } + Symbol::KrunInitConfigBuilderWorkdir => { + if KRUN_INIT_CONFIG_BUILDER_WORKDIR.get().is_none() { + let f = unsafe { + *lib.get::::CRepr, + )>(b"krun_init_config_builder_workdir\0")? + }; + let _ = KRUN_INIT_CONFIG_BUILDER_WORKDIR.set(f); + } + } + Symbol::KrunInitConfigBuilderMount => { + if KRUN_INIT_CONFIG_BUILDER_MOUNT.get().is_none() { + let f = unsafe { + *lib.get::::CRepr, + <&'static str as FfiType>::CRepr, + <&'static str as FfiType>::CRepr, + )>(b"krun_init_config_builder_mount\0")? + }; + let _ = KRUN_INIT_CONFIG_BUILDER_MOUNT.set(f); + } + } + Symbol::KrunInitConfigBuilderRlimits => { + if KRUN_INIT_CONFIG_BUILDER_RLIMITS.get().is_none() { + let f = unsafe { + *lib.get::(b"krun_init_config_builder_rlimits\0")? + }; + let _ = KRUN_INIT_CONFIG_BUILDER_RLIMITS.set(f); + } + } + Symbol::KrunInitConfigBuilderBuild => { + if KRUN_INIT_CONFIG_BUILDER_BUILD.get().is_none() { + let f = unsafe { + *lib.get:: ::CRepr>( + b"krun_init_config_builder_build\0" + )? + }; + let _ = KRUN_INIT_CONFIG_BUILDER_BUILD.set(f); + } + } + Symbol::KrunInitErrorCode => { + if KRUN_INIT_ERROR_CODE.get().is_none() { + let f = unsafe { + *lib.get:: ::CRepr>(b"krun_init_error_code\0")? + }; + let _ = KRUN_INIT_ERROR_CODE.set(f); + } + } + Symbol::KrunInitErrorMessage => { + if KRUN_INIT_ERROR_MESSAGE.get().is_none() { + let f = unsafe { + *lib.get::(b"krun_init_error_message\0")? + }; + let _ = KRUN_INIT_ERROR_MESSAGE.set(f); + } + } + Symbol::KrunInitErrorResult => { + if KRUN_INIT_ERROR_RESULT.get().is_none() { + let f = unsafe { + *lib.get:: ::CRepr>(b"krun_init_error_result\0")? + }; + let _ = KRUN_INIT_ERROR_RESULT.set(f); + } + } + Symbol::KrunInitErrorDestroy => { + if KRUN_INIT_ERROR_DESTROY.get().is_none() { + let f = unsafe { + *lib.get::( + b"krun_init_error_destroy\0", + )? + }; + let _ = KRUN_INIT_ERROR_DESTROY.set(f); + } + } + } + } + Ok(()) +} diff --git a/init/init-blob-via-cdylib/Cargo.toml b/init/init-blob-via-cdylib/Cargo.toml new file mode 100644 index 000000000..8ec716f4e --- /dev/null +++ b/init/init-blob-via-cdylib/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "krun-init-blob-via-cdylib" +version = "0.1.0-1.18.1" +edition = "2021" +description = "Strong Rust client for libkrun-init (links libkrun_init.so)" +license = "Apache-2.0" +repository = "https://github.com/containers/libkrun" +build = "build.rs" + +[dependencies] +ffier = { git = "file:///home/mhrica/Dev2/ffier/ffier.git", rev = "9e36967" } diff --git a/init/init-blob-via-cdylib/build.rs b/init/init-blob-via-cdylib/build.rs new file mode 100644 index 000000000..e732044b3 --- /dev/null +++ b/init/init-blob-via-cdylib/build.rs @@ -0,0 +1,9 @@ +fn main() { + println!("cargo:rustc-link-lib=dylib=krun_init"); + + // If krun-sys already found the library directory via pkg-config, + // it's on the linker search path. Otherwise, check KRUN_INIT_LIB_PATH. + if let Ok(path) = std::env::var("KRUN_INIT_LIB_PATH") { + println!("cargo:rustc-link-search=native={path}"); + } +} diff --git a/init/init-blob-via-cdylib/src/lib.rs b/init/init-blob-via-cdylib/src/lib.rs new file mode 100644 index 000000000..64dc31674 --- /dev/null +++ b/init/init-blob-via-cdylib/src/lib.rs @@ -0,0 +1,724 @@ +#![allow( + dead_code, + unused_unsafe, + clippy::missing_safety_doc, + clippy::needless_lifetimes +)] +/// Marker trait for types exported as opaque C handles. +pub trait FfiHandle { + const C_HANDLE_NAME: &'static str; + const TYPE_TAG: u32; + unsafe fn as_handle(&self) -> *mut core::ffi::c_void; + fn __from_raw(handle: *mut core::ffi::c_void) -> Self; +} + +/// Maps Rust types to C-compatible representations. +pub trait FfiType { + type CRepr; + const C_TYPE_NAME: &'static str; + const IS_HANDLE: bool = false; + fn into_c(self) -> Self::CRepr; + unsafe fn from_c(repr: Self::CRepr) -> Self; +} + +macro_rules! impl_ffi_identity { + ($($t:ty => $n:expr),* $(,)?) => { $( + impl FfiType for $t { + type CRepr = $t; const C_TYPE_NAME: &'static str = $n; const IS_HANDLE: bool = false; + fn into_c(self) -> Self { self } unsafe fn from_c(r: Self) -> Self { r } + } + )* }; +} +impl_ffi_identity! { + i8 => "int8_t", i16 => "int16_t", i32 => "int32_t", i64 => "int64_t", + u8 => "uint8_t", u16 => "uint16_t", u32 => "uint32_t", u64 => "uint64_t", + isize => "ssize_t", usize => "size_t", bool => "bool", +} + +impl FfiType for &str { + type CRepr = ffier::FfierBytes; + const C_TYPE_NAME: &'static str = "FfierStr"; + const IS_HANDLE: bool = false; + fn into_c(self) -> ffier::FfierBytes { + unsafe { ffier::FfierBytes::from_str(self) } + } + unsafe fn from_c(repr: ffier::FfierBytes) -> Self { + unsafe { + let b = core::slice::from_raw_parts(repr.data, repr.len); + core::str::from_utf8_unchecked(b) + } + } +} + +impl<'a> FfiType for Option<&'a str> { + type CRepr = ffier::FfierBytes; + const C_TYPE_NAME: &'static str = "FfierStr"; + const IS_HANDLE: bool = false; + fn into_c(self) -> ffier::FfierBytes { + match self { + Some(s) => unsafe { ffier::FfierBytes::from_str(s) }, + None => ffier::FfierBytes::EMPTY, + } + } + unsafe fn from_c(repr: ffier::FfierBytes) -> Self { + if repr.data.is_null() { + None + } else { + unsafe { + Some(core::str::from_utf8_unchecked(core::slice::from_raw_parts( + repr.data, repr.len, + ))) + } + } + } +} + +impl FfiType for Box { + type CRepr = ffier::FfierBytes; + const C_TYPE_NAME: &'static str = "FfierStr"; + const IS_HANDLE: bool = false; + fn into_c(self) -> ffier::FfierBytes { + let leaked: &mut str = Box::leak(self); + ffier::FfierBytes { + data: leaked.as_mut_ptr() as *const u8, + len: leaked.len(), + } + } + unsafe fn from_c(repr: ffier::FfierBytes) -> Self { + unsafe { + let slice = core::slice::from_raw_parts_mut(repr.data as *mut u8, repr.len); + Box::from_raw(core::str::from_utf8_unchecked_mut(slice)) + } + } +} + +impl FfiType for &[u8] { + type CRepr = ffier::FfierBytes; + const C_TYPE_NAME: &'static str = "FfierBytes"; + const IS_HANDLE: bool = false; + fn into_c(self) -> ffier::FfierBytes { + unsafe { ffier::FfierBytes::from_bytes(self) } + } + unsafe fn from_c(repr: ffier::FfierBytes) -> Self { + unsafe { + if repr.data.is_null() { + &[] + } else { + core::slice::from_raw_parts(repr.data, repr.len) + } + } + } +} + +impl FfiType for &T { + type CRepr = *mut core::ffi::c_void; + const C_TYPE_NAME: &'static str = T::C_HANDLE_NAME; + const IS_HANDLE: bool = true; + fn into_c(self) -> *mut core::ffi::c_void { + unsafe { self.as_handle() } + } + unsafe fn from_c(_: *mut core::ffi::c_void) -> Self { + unimplemented!("client-side &T from_c") + } +} +impl FfiType for &mut T { + type CRepr = *mut core::ffi::c_void; + const C_TYPE_NAME: &'static str = T::C_HANDLE_NAME; + const IS_HANDLE: bool = true; + fn into_c(self) -> *mut core::ffi::c_void { + unsafe { self.as_handle() } + } + unsafe fn from_c(_: *mut core::ffi::c_void) -> Self { + unimplemented!("client-side &mut T from_c") + } +} + +/// Borrowed slice of handles returned from FFI methods. +/// Elements are borrowed — do NOT destroy them individually. +/// Derefs to `&[T]`, so indexing, iteration, `len()`, etc. all work. +/// Dropping this type frees the backing array. +pub struct ForeignSlice { + raw: ffier::FfierObjectArray, + elements: Box<[core::mem::ManuallyDrop]>, +} + +impl ForeignSlice { + fn from_raw(raw: ffier::FfierObjectArray) -> Self { + let elements: Box<[core::mem::ManuallyDrop]> = (0..raw.len) + .map(|i| { + core::mem::ManuallyDrop::new(T::__from_raw(unsafe { + ffier::ffier_object_array_get(raw, i) + })) + }) + .collect(); + Self { raw, elements } + } +} + +impl core::ops::Deref for ForeignSlice { + type Target = [T]; + fn deref(&self) -> &[T] { + // SAFETY: ManuallyDrop is #[repr(transparent)] over T. + unsafe { core::mem::transmute::<&[core::mem::ManuallyDrop], &[T]>(&self.elements) } + } +} + +impl Drop for ForeignSlice { + fn drop(&mut self) { + unsafe { ffier::ffier_object_array_free(self.raw) }; + } +} + +unsafe extern "C" { + fn krun_init_error_payload( + handle: *const core::ffi::c_void, + out_buf: *mut core::ffi::c_void, + buf_size: usize, + ); +} + +pub struct ConfigErrorErrorHandle(*mut core::ffi::c_void); +impl ConfigErrorErrorHandle { + fn handle(&self) -> *mut core::ffi::c_void { + self.0 + } +} +impl Drop for ConfigErrorErrorHandle { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { krun_init_error_destroy(self.0) } + } + } +} +impl std::fmt::Debug for ConfigErrorErrorHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ErrorHandle({:?})", self.0) + } +} + +pub struct ConfigErrorInvalidJsonData(ConfigErrorErrorHandle); +impl ConfigErrorInvalidJsonData { + pub fn field_0(&self) -> &str { + let mut __buf = std::mem::MaybeUninit::::uninit(); + unsafe { + krun_init_error_payload( + self.0.handle() as *const core::ffi::c_void, + __buf.as_mut_ptr() as *mut core::ffi::c_void, + core::mem::size_of::(), + ) + }; + unsafe { <&str as FfiType>::from_c(__buf.assume_init()) } + } +} +impl std::fmt::Debug for ConfigErrorInvalidJsonData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "InvalidJson(...)") + } +} + +#[derive(Debug)] +pub enum ConfigError { + InvalidJson(ConfigErrorInvalidJsonData), +} + +impl ConfigError { + pub fn from_ffi(r: ffier::FfierResult, err_handle: *mut core::ffi::c_void) -> Self { + let code = ffier::ffier_result_code(r); + let handle = ConfigErrorErrorHandle(err_handle); + match code { + 1u32 => Self::InvalidJson(ConfigErrorInvalidJsonData(handle)), + other => panic!("unknown {} error code {}", "ConfigError", other), + } + } + fn handle_ptr(&self) -> *mut core::ffi::c_void { + match self { + Self::InvalidJson(d) => d.0.handle(), + } + } +} + +impl std::fmt::Display for ConfigError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + struct FmtWriter(*mut core::ffi::c_void); + impl PushStr for FmtWriter { + fn push(&mut self, s: &str) -> bool { + unsafe { + (&mut *(self.0 as *mut std::fmt::Formatter<'_>)) + .write_str(s) + .is_ok() + } + } + } + let mut __writer = FmtWriter(f as *mut std::fmt::Formatter<'_> as *mut core::ffi::c_void); + let __vtable: &'static PushStrVtable = FmtWriter::__ffier_vtable(); + let mut __temp = ffier::FfierHandle { + type_tag: 33554433u32, + metadata: 0, + value: ffier::VtableHandle { + vtable_ptr: __vtable as *const PushStrVtable as *const core::ffi::c_void, + user_data: &mut __writer as *mut FmtWriter as *const core::ffi::c_void, + vtable_size: core::mem::size_of::() as u16, + }, + }; + let __writer_handle = + &mut __temp as *mut ffier::FfierHandle as *mut core::ffi::c_void; + unsafe { krun_init_error_message(self.handle_ptr(), __writer_handle) }; + Ok(()) + } +} + +impl std::error::Error for ConfigError {} + +unsafe extern "C" { + pub fn krun_init_guest_file_destroy(handle: *mut core::ffi::c_void); + pub fn krun_init_guest_file_path( + handle: *mut core::ffi::c_void, + ) -> <&'static str as FfiType>::CRepr; + pub fn krun_init_guest_file_data( + handle: *mut core::ffi::c_void, + ) -> <&'static [u8] as FfiType>::CRepr; + pub fn krun_init_guest_file_mode(handle: *mut core::ffi::c_void) -> ::CRepr; + pub fn krun_init_guest_file_one_shot( + handle: *mut core::ffi::c_void, + ) -> ::CRepr; +} + +pub struct GuestFile(*mut core::ffi::c_void); + +impl GuestFile { + #[doc(hidden)] + pub fn __from_raw(ptr: *mut core::ffi::c_void) -> Self { + Self(ptr) + } + #[doc(hidden)] + pub fn __into_raw(self) -> *mut core::ffi::c_void { + let this = std::mem::ManuallyDrop::new(self); + this.0 + } +} + +impl FfiHandle for GuestFile { + const C_HANDLE_NAME: &'static str = "GuestFile"; + const TYPE_TAG: u32 = 33554436u32; + unsafe fn as_handle(&self) -> *mut core::ffi::c_void { + self.0 + } + fn __from_raw(handle: *mut core::ffi::c_void) -> Self { + Self(handle) + } +} + +impl FfiType for GuestFile { + type CRepr = *mut core::ffi::c_void; + const C_TYPE_NAME: &'static str = "GuestFile"; + fn into_c(self) -> *mut core::ffi::c_void { + self.__into_raw() + } + unsafe fn from_c(repr: *mut core::ffi::c_void) -> Self { + Self::__from_raw(repr) + } +} + +impl std::fmt::Debug for GuestFile { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("GuestFile").field(&self.0).finish() + } +} + +impl GuestFile { + #[doc = " Path on the guest root filesystem (e.g. `\"/init.krun\"`)."] + pub fn path(&self) -> &str { + let __raw = unsafe { krun_init_guest_file_path(self.0) }; + unsafe { <&str as FfiType>::from_c(__raw) } + } + #[doc = " File contents."] + pub fn data(&self) -> &[u8] { + let __raw = unsafe { krun_init_guest_file_data(self.0) }; + unsafe { <&[u8] as FfiType>::from_c(__raw) } + } + #[doc = " Permission mode bits (e.g. `0o755`)."] + pub fn mode(&self) -> u32 { + let __raw = unsafe { krun_init_guest_file_mode(self.0) }; + unsafe { ::from_c(__raw) } + } + #[doc = " Whether this file is one-shot (removed after first lookup)."] + pub fn one_shot(&self) -> bool { + let __raw = unsafe { krun_init_guest_file_one_shot(self.0) }; + unsafe { ::from_c(__raw) } + } +} + +impl Drop for GuestFile { + fn drop(&mut self) { + unsafe { krun_init_guest_file_destroy(self.0) } + } +} + +unsafe extern "C" { + pub fn krun_init_config_destroy(handle: *mut core::ffi::c_void); + pub fn krun_init_config_builder() -> ::CRepr; + pub fn krun_init_config_from_oci_config_json( + json: <&'static str as FfiType>::CRepr, + err_out: *mut *mut core::ffi::c_void, + ) -> *mut core::ffi::c_void; + pub fn krun_init_config_kernel_init_arg( + handle: *mut core::ffi::c_void, + ) -> <&'static str as FfiType>::CRepr; + pub fn krun_init_config_guest_files(handle: *mut core::ffi::c_void) -> ffier::FfierObjectArray; +} + +pub struct Config(*mut core::ffi::c_void); + +impl Config { + #[doc(hidden)] + pub fn __from_raw(ptr: *mut core::ffi::c_void) -> Self { + Self(ptr) + } + #[doc(hidden)] + pub fn __into_raw(self) -> *mut core::ffi::c_void { + let this = std::mem::ManuallyDrop::new(self); + this.0 + } +} + +impl FfiHandle for Config { + const C_HANDLE_NAME: &'static str = "Config"; + const TYPE_TAG: u32 = 33554437u32; + unsafe fn as_handle(&self) -> *mut core::ffi::c_void { + self.0 + } + fn __from_raw(handle: *mut core::ffi::c_void) -> Self { + Self(handle) + } +} + +impl FfiType for Config { + type CRepr = *mut core::ffi::c_void; + const C_TYPE_NAME: &'static str = "Config"; + fn into_c(self) -> *mut core::ffi::c_void { + self.__into_raw() + } + unsafe fn from_c(repr: *mut core::ffi::c_void) -> Self { + Self::__from_raw(repr) + } +} + +impl std::fmt::Debug for Config { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Config").field(&self.0).finish() + } +} + +impl Config { + #[doc = " Start building a new init configuration."] + pub fn builder() -> ConfigBuilder { + let __raw = unsafe { krun_init_config_builder() }; + unsafe { ::from_c(__raw) } + } + #[doc = " Construct from an OCI runtime-spec config.json string."] + #[doc = ""] + #[doc = " The JSON is expected to use the OCI runtime-spec layout:"] + #[doc = " `{\"process\": {\"args\": [...], \"env\": [...], \"cwd\": \"...\"}, \"mounts\": [...]}`."] + #[doc = ""] + #[doc = " # Errors"] + #[doc = ""] + #[doc = " Returns `Err` if the JSON is syntactically invalid or contains"] + #[doc = " unexpected types."] + pub fn from_oci_config_json(json: &str) -> Result { + let mut __err: *mut core::ffi::c_void = core::ptr::null_mut(); + let __raw = unsafe { + krun_init_config_from_oci_config_json( + <&str as FfiType>::into_c(json), + &mut __err as *mut *mut core::ffi::c_void, + ) + }; + if !__raw.is_null() { + Ok(unsafe { ::from_c(__raw) }) + } else { + let __r = unsafe { krun_init_error_result(__err) }; + Err(ConfigError::from_ffi(__r, __err)) + } + } + #[doc = " Returns the kernel cmdline argument needed to boot with this init"] + #[doc = " (e.g. `\"init=/init.krun\"`). Pass this to `krun_set_kernel_args`."] + pub fn kernel_init_arg(&self) -> &str { + let __raw = unsafe { krun_init_config_kernel_init_arg(self.0) }; + unsafe { <&str as FfiType>::from_c(__raw) } + } + #[doc = " Returns the guest files that need to be injected into the guest"] + #[doc = " root filesystem."] + pub fn guest_files(&self) -> ForeignSlice { + ForeignSlice::from_raw(unsafe { krun_init_config_guest_files(self.0) }) + } +} + +impl Drop for Config { + fn drop(&mut self) { + unsafe { krun_init_config_destroy(self.0) } + } +} + +unsafe extern "C" { + pub fn krun_init_config_builder_destroy(handle: *mut core::ffi::c_void); + pub fn krun_init_config_builder_args( + handle: *mut core::ffi::c_void, + argv: *const ffier::FfierBytes, + argv_len: usize, + ); + pub fn krun_init_config_builder_env( + handle: *mut core::ffi::c_void, + vars: *const ffier::FfierBytes, + vars_len: usize, + ); + pub fn krun_init_config_builder_workdir( + handle: *mut core::ffi::c_void, + dir: <&'static str as FfiType>::CRepr, + ); + pub fn krun_init_config_builder_mount( + handle: *mut core::ffi::c_void, + destination: <&'static str as FfiType>::CRepr, + fs_type: <&'static str as FfiType>::CRepr, + source: <&'static str as FfiType>::CRepr, + ); + pub fn krun_init_config_builder_rlimits( + handle: *mut core::ffi::c_void, + limits: *const ffier::FfierBytes, + limits_len: usize, + ); + pub fn krun_init_config_builder_build( + handle: *mut core::ffi::c_void, + ) -> ::CRepr; +} + +pub struct ConfigBuilder(*mut core::ffi::c_void); + +impl ConfigBuilder { + #[doc(hidden)] + pub fn __from_raw(ptr: *mut core::ffi::c_void) -> Self { + Self(ptr) + } + #[doc(hidden)] + pub fn __into_raw(self) -> *mut core::ffi::c_void { + let this = std::mem::ManuallyDrop::new(self); + this.0 + } +} + +impl FfiHandle for ConfigBuilder { + const C_HANDLE_NAME: &'static str = "ConfigBuilder"; + const TYPE_TAG: u32 = 33554438u32; + unsafe fn as_handle(&self) -> *mut core::ffi::c_void { + self.0 + } + fn __from_raw(handle: *mut core::ffi::c_void) -> Self { + Self(handle) + } +} + +impl FfiType for ConfigBuilder { + type CRepr = *mut core::ffi::c_void; + const C_TYPE_NAME: &'static str = "ConfigBuilder"; + fn into_c(self) -> *mut core::ffi::c_void { + self.__into_raw() + } + unsafe fn from_c(repr: *mut core::ffi::c_void) -> Self { + Self::__from_raw(repr) + } +} + +impl std::fmt::Debug for ConfigBuilder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("ConfigBuilder").field(&self.0).finish() + } +} + +impl ConfigBuilder { + #[doc = " Set the full argv: `args[0]` is the executable, `args[1..]` are arguments."] + pub fn args(self, argv: &[&str]) -> Self { + let mut __handle = { + let this = std::mem::ManuallyDrop::new(self); + this.0 + }; + let __ffi_argv: Vec = argv + .iter() + .map(|s| unsafe { ffier::FfierBytes::from_str(s) }) + .collect(); + unsafe { + krun_init_config_builder_args( + &mut __handle as *mut *mut core::ffi::c_void as *mut core::ffi::c_void, + __ffi_argv.as_ptr(), + __ffi_argv.len(), + ) + }; + Self(__handle) + } + #[doc = " Set environment variables. Each entry should be `\"KEY=value\"`."] + pub fn env(self, vars: &[&str]) -> Self { + let mut __handle = { + let this = std::mem::ManuallyDrop::new(self); + this.0 + }; + let __ffi_vars: Vec = vars + .iter() + .map(|s| unsafe { ffier::FfierBytes::from_str(s) }) + .collect(); + unsafe { + krun_init_config_builder_env( + &mut __handle as *mut *mut core::ffi::c_void as *mut core::ffi::c_void, + __ffi_vars.as_ptr(), + __ffi_vars.len(), + ) + }; + Self(__handle) + } + #[doc = " Set the guest working directory."] + pub fn workdir(self, dir: &str) -> Self { + let mut __handle = { + let this = std::mem::ManuallyDrop::new(self); + this.0 + }; + unsafe { + krun_init_config_builder_workdir( + &mut __handle as *mut *mut core::ffi::c_void as *mut core::ffi::c_void, + <&str as FfiType>::into_c(dir), + ) + }; + Self(__handle) + } + #[doc = " Add a mount specification."] + pub fn mount(self, destination: &str, fs_type: &str, source: &str) -> Self { + let mut __handle = { + let this = std::mem::ManuallyDrop::new(self); + this.0 + }; + unsafe { + krun_init_config_builder_mount( + &mut __handle as *mut *mut core::ffi::c_void as *mut core::ffi::c_void, + <&str as FfiType>::into_c(destination), + <&str as FfiType>::into_c(fs_type), + <&str as FfiType>::into_c(source), + ) + }; + Self(__handle) + } + #[doc = " Set resource limits. Each entry should be `\"id=cur:max\"` (e.g. `\"7=0:0\"`)."] + pub fn rlimits(self, limits: &[&str]) -> Self { + let mut __handle = { + let this = std::mem::ManuallyDrop::new(self); + this.0 + }; + let __ffi_limits: Vec = limits + .iter() + .map(|s| unsafe { ffier::FfierBytes::from_str(s) }) + .collect(); + unsafe { + krun_init_config_builder_rlimits( + &mut __handle as *mut *mut core::ffi::c_void as *mut core::ffi::c_void, + __ffi_limits.as_ptr(), + __ffi_limits.len(), + ) + }; + Self(__handle) + } + #[doc = " Consume the builder, serialize the config, and return the"] + #[doc = " finished [`Config`]."] + pub fn build(self) -> Config { + let mut __handle = { + let this = std::mem::ManuallyDrop::new(self); + this.0 + }; + let __raw = unsafe { + krun_init_config_builder_build( + &mut __handle as *mut *mut core::ffi::c_void as *mut core::ffi::c_void, + ) + }; + unsafe { ::from_c(__raw) } + } +} + +impl Drop for ConfigBuilder { + fn drop(&mut self) { + unsafe { krun_init_config_builder_destroy(self.0) } + } +} + +pub trait PushStr { + fn push(&mut self, s: &str) -> bool; + #[doc(hidden)] + fn __ffier_vtable() -> &'static PushStrVtable + where + Self: Sized, + { + &PushStrVtable { + drop: Some({ + unsafe extern "C" fn __drop_trampoline<__T>(__ud: *mut core::ffi::c_void) { + unsafe { drop(Box::from_raw(__ud as *mut __T)) }; + } + __drop_trampoline:: + }), + push: Some({ + unsafe extern "C" fn __trampoline<__T: PushStr>( + __ud: *mut core::ffi::c_void, + s: <&'static str as FfiType>::CRepr, + ) -> ::CRepr { + let __val = unsafe { &mut *(__ud as *mut __T) }; + let __result = __val.push(unsafe { <&str as FfiType>::from_c(s) }); + ::into_c(__result) + } + __trampoline:: + }), + } + } + #[doc(hidden)] + fn __into_raw_handle(self) -> *mut core::ffi::c_void + where + Self: Sized, + { + let __vtable: &'static PushStrVtable = Self::__ffier_vtable(); + let __user_data = Box::into_raw(Box::new(self)); + let vtable_size: u16 = core::mem::size_of::() + .try_into() + .expect("vtable_size exceeds u16::MAX"); + ffier::ffier_handle_new_with_metadata( + 33554433u32, + 0, + ffier::VtableHandle { + vtable_ptr: __vtable as *const PushStrVtable as *const core::ffi::c_void, + user_data: __user_data as *const core::ffi::c_void, + vtable_size, + }, + ) + } +} + +#[repr(C)] +pub struct PushStrVtable { + pub drop: Option, + pub push: Option< + unsafe extern "C" fn( + *mut core::ffi::c_void, + <&'static str as FfiType>::CRepr, + ) -> ::CRepr, + >, +} + +pub struct VtablePushStr(*mut core::ffi::c_void); + +impl VtablePushStr { + #[doc(hidden)] + pub fn __into_raw(self) -> *mut core::ffi::c_void { + let this = std::mem::ManuallyDrop::new(self); + this.0 + } +} + +impl Drop for VtablePushStr { + fn drop(&mut self) {} +} + +unsafe extern "C" { + pub fn krun_init_error_code(handle: *mut core::ffi::c_void) -> ::CRepr; + pub fn krun_init_error_message(handle: *mut core::ffi::c_void, writer: *mut core::ffi::c_void); + pub fn krun_init_error_result(handle: *mut core::ffi::c_void) -> ::CRepr; + pub fn krun_init_error_destroy(handle: *mut core::ffi::c_void); +} diff --git a/init/init-blob/Cargo.toml b/init/init-blob/Cargo.toml index 1a32151c2..ced8f0951 100644 --- a/init/init-blob/Cargo.toml +++ b/init/init-blob/Cargo.toml @@ -12,5 +12,12 @@ amd-sev = [] tdx = [] timesync = [] +[dependencies] +ffier = { git = "file:///home/mhrica/Dev2/ffier/ffier.git", rev = "9e36967" } +ffier-builtins = { git = "file:///home/mhrica/Dev2/ffier/ffier.git", rev = "9e36967" } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +thiserror = "2" + [lib] path = "src/lib.rs" diff --git a/init/init-blob/src/config.rs b/init/init-blob/src/config.rs new file mode 100644 index 000000000..19d42b19c --- /dev/null +++ b/init/init-blob/src/config.rs @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: Apache-2.0 +// +//! Builder for the `/.krun_config.json` file consumed by the in-guest init. +//! +//! The JSON schema matches the OCI runtime-spec config.json format that +//! `init/init-binary/src/config.rs` expects: +//! +//! ```json +//! { +//! "process": { +//! "args": ["/usr/bin/bash", "--login"], +//! "env": ["HOME=/root", "TERM=xterm-256color"], +//! "cwd": "/home/user" +//! }, +//! "mounts": [{"destination": "/tmp", "type": "tmpfs", "source": "tmpfs"}] +//! } +//! ``` +//! +//! Callers should not rely on the serialization format — it is an internal +//! detail shared between init-blob and the init binary. +//! +//! # Example (Rust) +//! +//! ```ignore +//! use init_blob::Config; +//! +//! let config = Config::builder() +//! .args(&["/usr/bin/bash", "--login"]) +//! .env(&["HOME=/root", "TERM=xterm-256color"]) +//! .workdir("/home/user") +//! .build(); +//! +//! for file in config.guest_files() { +//! // inject file.path, file.data, file.mode, file.one_shot +//! } +//! ``` +//! +//! # Example (C via ffier) +//! +//! ```c +//! KrunInitConfig cfg = krun_init_config_builder_build(&b); +//! uint32_t n = krun_init_config_guest_file_count(cfg); +//! for (uint32_t i = 0; i < n; i++) { +//! KrunInitStr path = krun_init_config_guest_file_path(cfg, i); +//! KrunInitBytes data = krun_init_config_guest_file_data(cfg, i); +//! uint32_t mode = krun_init_config_guest_file_mode(cfg, i); +//! bool one_shot = krun_init_config_guest_file_one_shot(cfg, i); +//! krun_fs_add_overlay_file(ctx, fs_tag, path.data, data.data, data.len, mode, one_shot); +//! } +//! krun_init_config_destroy(cfg); +//! ``` + +use std::borrow::Cow; + +use crate::FfiBorrow; +use crate::FfiType; +use serde::{Deserialize, Serialize}; + +/// Error type for init configuration operations. +#[derive(Clone, Debug, thiserror::Error, ffier::FfiError)] +#[non_exhaustive] +pub enum ConfigError { + /// The JSON string could not be parsed. + #[error("invalid config JSON: {0}")] + #[ffier(code = 1)] + InvalidJson(Box), +} + +/// Guest-side path of the init binary (e.g. for `init=` kernel arg). +pub const INIT_PATH: &str = "/init.krun"; + +/// Kernel cmdline argument to boot with the embedded init. +pub const KERNEL_INIT_ARG: &str = "init=/init.krun"; + +/// A file that the init process expects to find on the guest root filesystem. +/// +/// The caller decides how to materialize these (virtiofs overlay, block +/// device, etc.) — init-blob only describes *what* init needs. +pub struct GuestFile { + /// Path on the guest root filesystem. + pub path: &'static str, + /// File contents. + pub data: Cow<'static, [u8]>, + /// Permission bits (e.g. `0o755` for executables). + pub mode: u32, + /// If true, the file is only needed during early init and can be + /// removed after first use. + pub one_shot: bool, +} + +#[ffier::exportable] +impl GuestFile { + /// Path on the guest root filesystem (e.g. `"/init.krun"`). + pub fn path(&self) -> &str { + self.path + } + + /// File contents. + pub fn data(&self) -> &[u8] { + &self.data + } + + /// Permission mode bits (e.g. `0o755`). + pub fn mode(&self) -> u32 { + self.mode + } + + /// Whether this file is one-shot (removed after first lookup). + pub fn one_shot(&self) -> bool { + self.one_shot + } +} + +/// OCI runtime-spec "process" object (serialization helper). +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[serde(default)] +struct ProcessConfig { + #[serde(skip_serializing_if = "Vec::is_empty")] + args: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + env: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + cwd: Option, +} + +/// A mount specification for the guest init. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Mount { + pub destination: String, + #[serde(rename = "type")] + pub fs_type: String, + pub source: String, +} + +/// Serialization envelope (matches what the init binary parses). +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[serde(default)] +struct ConfigJson { + #[serde(skip_serializing_if = "ProcessConfig::is_empty")] + process: ProcessConfig, + #[serde(skip_serializing_if = "Vec::is_empty")] + mounts: Vec, +} + +impl ProcessConfig { + fn is_empty(&self) -> bool { + self.args.is_empty() && self.env.is_empty() && self.cwd.is_none() + } +} + +/// Built init configuration. Immutable after construction. +/// +/// Holds pre-computed guest files. Methods return borrowed references +/// valid for the lifetime of this value. +pub struct Config { + files: Vec, +} + +#[ffier::exportable] +impl Config { + /// Start building a new init configuration. + pub fn builder() -> ConfigBuilder { + ConfigBuilder::default() + } + + /// Construct from an OCI runtime-spec config.json string. + /// + /// The JSON is expected to use the OCI runtime-spec layout: + /// `{"process": {"args": [...], "env": [...], "cwd": "..."}, "mounts": [...]}`. + /// + /// # Errors + /// + /// Returns `Err` if the JSON is syntactically invalid or contains + /// unexpected types. + pub fn from_oci_config_json(json: &str) -> Result { + let parsed: ConfigJson = serde_json::from_str(json) + .map_err(|e| ConfigError::InvalidJson(e.to_string().into()))?; + Ok(Self::from_config_json(parsed)) + } + + /// Returns the kernel cmdline argument needed to boot with this init + /// (e.g. `"init=/init.krun"`). Pass this to `krun_set_kernel_args`. + pub fn kernel_init_arg(&self) -> &str { + KERNEL_INIT_ARG + } + + /// Returns the guest files that need to be injected into the guest + /// root filesystem. + pub fn guest_files(&self) -> &[GuestFile] { + &self.files + } +} + +impl Config { + fn from_config_json(config: ConfigJson) -> Self { + let config_json = + serde_json::to_vec(&config).expect("ConfigJson serialization cannot fail"); + Self { + files: vec![ + GuestFile { + path: INIT_PATH, + data: Cow::Borrowed(super::INIT_BINARY), + mode: 0o755, + one_shot: true, + }, + GuestFile { + path: "/.krun_config.json", + data: Cow::Owned(config_json), + mode: 0o644, + one_shot: true, + }, + ], + } + } +} + +/// Builder for [`Config`]. +#[derive(Clone, Debug, Default)] +pub struct ConfigBuilder { + inner: ConfigJson, + rlimits: Vec, +} + +#[ffier::exportable] +impl ConfigBuilder { + /// Set the full argv: `args[0]` is the executable, `args[1..]` are arguments. + pub fn args(mut self, argv: &[&str]) -> Self { + self.inner.process.args = argv.iter().map(|s| s.to_string()).collect(); + self + } + + /// Set environment variables. Each entry should be `"KEY=value"`. + pub fn env(mut self, vars: &[&str]) -> Self { + self.inner.process.env = vars.iter().map(|s| s.to_string()).collect(); + self + } + + /// Set the guest working directory. + pub fn workdir(mut self, dir: &str) -> Self { + self.inner.process.cwd = Some(dir.to_string()); + self + } + + /// Add a mount specification. + pub fn mount(mut self, destination: &str, fs_type: &str, source: &str) -> Self { + self.inner.mounts.push(Mount { + destination: destination.to_string(), + fs_type: fs_type.to_string(), + source: source.to_string(), + }); + self + } + + /// Set resource limits. Each entry should be `"id=cur:max"` (e.g. `"7=0:0"`). + pub fn rlimits(mut self, limits: &[&str]) -> Self { + self.rlimits = limits.iter().map(|s| s.to_string()).collect(); + self + } + + /// Consume the builder, serialize the config, and return the + /// finished [`Config`]. + pub fn build(mut self) -> Config { + // Inject rlimits as KRUN_RLIMITS env var. + if !self.rlimits.is_empty() { + let value = self.rlimits.join(","); + self.inner + .process + .env + .retain(|e| !e.starts_with("KRUN_RLIMITS=")); + self.inner.process.env.push(format!("KRUN_RLIMITS={value}")); + } + + Config::from_config_json(self.inner) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn parse_config_json(cfg: &Config) -> serde_json::Value { + let config_file = &cfg.guest_files()[1]; + serde_json::from_slice(&config_file.data).unwrap() + } + + #[test] + fn builder_produces_valid_config() { + let cfg = Config::builder() + .args(&["/usr/bin/bash", "--login"]) + .env(&["HOME=/root", "TERM=xterm-256color"]) + .workdir("/home/user") + .mount("/tmp", "tmpfs", "tmpfs") + .rlimits(&["7=0:0"]) + .build(); + + let json = parse_config_json(&cfg); + assert_eq!( + json["process"]["args"], + serde_json::json!(["/usr/bin/bash", "--login"]) + ); + assert_eq!(json["process"]["cwd"], "/home/user"); + assert_eq!(json["mounts"][0]["type"], "tmpfs"); + + // rlimits injected as env var + let env = json["process"]["env"].as_array().unwrap(); + assert!(env.iter().any(|v| v.as_str() == Some("KRUN_RLIMITS=7=0:0"))); + } + + #[test] + fn from_oci_config_json() { + let json = r#"{"process":{"args":["/bin/sh"],"cwd":"/"}}"#; + let cfg = Config::from_oci_config_json(json).unwrap(); + let parsed = parse_config_json(&cfg); + assert_eq!(parsed["process"]["args"], serde_json::json!(["/bin/sh"])); + } + + #[test] + fn guest_files_contains_init_and_config() { + let cfg = Config::builder().args(&["/bin/sh"]).build(); + let files = cfg.guest_files(); + assert_eq!(files.len(), 2); + assert_eq!(files[0].path, INIT_PATH); + assert!(!files[0].data.is_empty()); + assert_eq!(files[1].path, "/.krun_config.json"); + } +} diff --git a/init/init-blob/src/lib.rs b/init/init-blob/src/lib.rs index 4397da679..347066c85 100644 --- a/init/init-blob/src/lib.rs +++ b/init/init-blob/src/lib.rs @@ -1 +1,17 @@ pub static INIT_BINARY: &[u8] = include_bytes!(env!("KRUN_INIT_BINARY_PATH")); + +pub mod config; +pub use config::{ + Config, ConfigBuilder, ConfigError, GuestFile, Mount, INIT_PATH, KERNEL_INIT_ARG, +}; + +ffier::library_definition!("krun_init", + library_tag = 2, + primitives_prefix = "krun", + trait ffier_builtins::PushStr = 1, + trait ffier_builtins::Error = 2, + crate::config::ConfigError = 3, + crate::config::GuestFile = 4, + crate::config::Config = 5, + crate::config::ConfigBuilder = 6, +); diff --git a/libkrun_init.pc.in b/libkrun_init.pc.in new file mode 100644 index 000000000..0bb992da6 --- /dev/null +++ b/libkrun_init.pc.in @@ -0,0 +1,24 @@ +# Copyright (C) 2023 Red Hat Inc. +# +# 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. + +prefix=@prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: @PACKAGE_NAME@ +Version: @PACKAGE_VERSION@ +Description: Init binary and configuration builder for libkrun guests +Requires: +Cflags: -I${includedir} +Libs: -L${libdir} -lkrun_init From 61afa94dfda39019b30b7db9c2b6aa8dac471506 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 4 Jun 2026 13:43:46 +0200 Subject: [PATCH 33/41] libkrun: add krun_inject_init for init-blob integration Add krun_inject_init(ctx_id, fs_tag, config_handle) C API that takes a KrunInitConfig handle from libkrun-init.so and injects all its guest files into the specified virtiofs device as overlay files. Uses the weak Rust client (krun-init-blob-via-cdylib-weak) to call into libkrun-init.so via dlsym at runtime. Returns -ENOSYS if the library is not loaded. Uses extend_lifetime() instead of raw transmute to extend borrowed data lifetimes. This unsafety will improve once libkrun accepts parameters using references with lifetimes (Rust API), allowing us to encode that InitConfig must outlive the VM. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- Cargo.lock | 1 + include/libkrun.h | 20 +++++++++ src/libkrun/Cargo.toml | 1 + src/libkrun/src/lib.rs | 95 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 751f27204..ef694b533 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1031,6 +1031,7 @@ dependencies = [ "krun-devices", "krun-display", "krun-hvf", + "krun-init-blob-via-cdylib-weak", "krun-input", "krun-polly", "krun-utils", diff --git a/include/libkrun.h b/include/libkrun.h index 7019bc8bd..a6d10c548 100644 --- a/include/libkrun.h +++ b/include/libkrun.h @@ -1057,6 +1057,26 @@ int32_t krun_fs_add_overlay_file(uint32_t ctx_id, const char *fs_tag, const char *path, const uint8_t *data, size_t data_len, uint32_t mode, bool one_shot); +/** + * Inject all guest files from a KrunInitConfig handle (from libkrun-init.so) + * into a virtiofs device as overlay files. + * + * The config_handle is an opaque pointer obtained from + * krun_init_config_builder_build(). This function calls into + * libkrun-init.so via dlsym to iterate the guest files and inject each one. + * + * Arguments: + * "ctx_id" - the configuration context ID. + * "fs_tag" - tag of the virtiofs device (e.g. "/dev/root"). + * "config_handle" - opaque KrunInitConfig handle from libkrun-init. + * + * Returns: + * Zero on success or a negative error number on failure. + * -ENOSYS if libkrun-init.so is not loaded. + */ +int32_t krun_inject_init(uint32_t ctx_id, const char *fs_tag, + void *config_handle); + /** * Add a virtual overlay directory to a virtiofs device. * diff --git a/src/libkrun/Cargo.toml b/src/libkrun/Cargo.toml index e1b367c0e..c765d9ed4 100644 --- a/src/libkrun/Cargo.toml +++ b/src/libkrun/Cargo.toml @@ -24,6 +24,7 @@ timesync = ["devices/timesync", "init-blob/timesync"] [dependencies] crossbeam-channel = ">=0.5.15" env_logger = "0.11" +krun-init-blob-via-cdylib-weak = { path = "../../init/init-blob-via-cdylib-weak" } libc = ">=0.2.39" libloading = "0.8" log = "0.4.0" diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 7ae20a835..bd72287c1 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -67,6 +67,16 @@ use krun_input::{InputConfigBackend, InputEventProviderBackend}; // Value returned on success. We use libc's errors otherwise. const KRUN_SUCCESS: i32 = 0; + +/// Extend a reference's lifetime to `'static`. +/// +/// # Safety +/// +/// The caller must guarantee that the referenced data outlives all uses +/// of the returned reference. +unsafe fn extend_lifetime(r: &T) -> &'static T { + unsafe { core::mem::transmute(r) } +} // Maximum number of arguments/environment variables we allow const MAX_ARGS: usize = 4096; /// Maximum number of virtqueues allowed by virtio spec (16-bit queue index: 0-65535) @@ -2326,6 +2336,91 @@ fn fs_add_overlay_entry(ctx_id: u32, fs_tag: &str, path: &str, entry: VirtualEnt KRUN_SUCCESS } +// --------------------------------------------------------------------------- +// libkrun-init interop: inject GuestFile handles from libkrun-init.so +// +// --------------------------------------------------------------------------- +// libkrun-init interop: inject init config handles from libkrun-init.so +// --------------------------------------------------------------------------- + +use krun_init_blob_via_cdylib_weak as krun_init; + +/// Inject all guest files from a `KrunInitConfig` handle (from libkrun-init.so) +/// into a virtiofs device as overlay files. +/// +/// The `config_handle` is an opaque pointer obtained from +/// `krun_init_config_builder_build()`. This function calls into +/// libkrun-init.so via dlsym to iterate the guest files and inject each +/// one into the specified virtiofs device. +/// +/// Returns `-ENOSYS` if libkrun-init.so is not loaded. +#[allow(clippy::missing_safety_doc)] +#[unsafe(no_mangle)] +#[cfg(not(any(feature = "tee", feature = "aws-nitro")))] +pub unsafe extern "C" fn krun_inject_init( + ctx_id: u32, + c_fs_tag: *const c_char, + config_handle: *mut c_void, +) -> i32 { + use krun_init::Symbol; + + if krun_init::require(&[ + Symbol::KrunInitConfigGuestFiles, + Symbol::KrunInitGuestFilePath, + Symbol::KrunInitGuestFileData, + Symbol::KrunInitGuestFileMode, + Symbol::KrunInitGuestFileOneShot, + ]) + .is_err() + { + return -libc::ENOSYS; + } + + if c_fs_tag.is_null() || config_handle.is_null() { + return -libc::EINVAL; + } + + let fs_tag = match unsafe { CStr::from_ptr(c_fs_tag).to_str() } { + Ok(s) => s, + Err(_) => return -libc::EINVAL, + }; + + use core::mem::ManuallyDrop; + let config = ManuallyDrop::new(krun_init::Config::__from_raw(config_handle)); + let files = config.guest_files(); + + for file in files.iter() { + let path = file.path(); + let mode = file.mode(); + let one_shot = file.one_shot(); + // SAFETY: The data is borrowed from the Config handle. The caller + // must keep the Config alive for the VM lifetime. + let data = unsafe { extend_lifetime(file.data()) }; + + let ret = fs_add_overlay_entry( + ctx_id, + fs_tag, + path, + VirtualEntry { + mode, + one_shot, + content: VirtualEntryContent::File { data }, + }, + ); + if ret != KRUN_SUCCESS { + return ret; + } + } + + // Disable implicit init injection — we just did it explicitly. + match CTX_MAP.lock().unwrap().entry(ctx_id) { + Entry::Occupied(mut ctx_cfg) => ctx_cfg.get_mut().disable_implicit_init = true, + Entry::Vacant(_) => return -libc::ENOENT, + } + + KRUN_SUCCESS +} + #[allow(clippy::missing_safety_doc)] #[unsafe(no_mangle)] #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] From 006d70959694685ac0443b9504385a899b60ae51 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 4 Jun 2026 13:44:23 +0200 Subject: [PATCH 34/41] tests: port to init-blob API via krun_inject_init Migrate integration tests from legacy krun_set_exec/krun_set_env/ krun_set_workdir to the init-blob API: - common.rs: add build_init_config() helper, setup_fs_and_enter now uses krun_inject_init instead of krun_set_exec + krun_set_workdir. - test_augmentfs: replace manual krun_disable_implicit_init + krun_get_default_init + overlay calls with Config::builder() + krun_inject_init. - test_virtiofs_root_ro: use build_init_config + krun_inject_init. - test_root_disk_remount: kept on legacy API (block device root has no virtiofs to inject into). Tests use krun-init-blob-via-cdylib (strong Rust client) to call the init-blob C API. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- tests/Cargo.lock | 53 +++++++++++++++++++ tests/test_cases/Cargo.toml | 3 +- tests/test_cases/src/common.rs | 37 +++++++------ tests/test_cases/src/lib.rs | 3 ++ tests/test_cases/src/test_augmentfs.rs | 43 ++++----------- .../test_cases/src/test_root_disk_remount.rs | 7 ++- tests/test_cases/src/test_virtiofs_root_ro.rs | 19 +++---- 7 files changed, 100 insertions(+), 65 deletions(-) diff --git a/tests/Cargo.lock b/tests/Cargo.lock index fd0a8a588..48c473428 100644 --- a/tests/Cargo.lock +++ b/tests/Cargo.lock @@ -180,6 +180,51 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "ffier" +version = "0.1.0" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=8b06dc3#8b06dc3b86fd66dd11030b7bfd5bacbd026387e8" +dependencies = [ + "ffier-annotations", + "ffier-builtins", + "ffier-rt", +] + +[[package]] +name = "ffier-annotations" +version = "0.1.0" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=8b06dc3#8b06dc3b86fd66dd11030b7bfd5bacbd026387e8" +dependencies = [ + "ffier-meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ffier-builtins" +version = "0.1.0" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=8b06dc3#8b06dc3b86fd66dd11030b7bfd5bacbd026387e8" +dependencies = [ + "ffier-annotations", + "ffier-rt", +] + +[[package]] +name = "ffier-meta" +version = "0.1.0" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=8b06dc3#8b06dc3b86fd66dd11030b7bfd5bacbd026387e8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ffier-rt" +version = "0.1.0" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=8b06dc3#8b06dc3b86fd66dd11030b7bfd5bacbd026387e8" + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -227,6 +272,13 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "krun-init-blob-via-cdylib" +version = "0.1.0-1.18.1" +dependencies = [ + "ffier", +] + [[package]] name = "krun-sys" version = "1.11.1" @@ -497,6 +549,7 @@ name = "test_cases" version = "0.0.0" dependencies = [ "anyhow", + "krun-init-blob-via-cdylib", "krun-sys", "macros", "nix", diff --git a/tests/test_cases/Cargo.toml b/tests/test_cases/Cargo.toml index 599959164..b2d965abc 100644 --- a/tests/test_cases/Cargo.toml +++ b/tests/test_cases/Cargo.toml @@ -3,7 +3,7 @@ name = "test_cases" edition = "2024" [features] -host = ["krun-sys", "serde", "serde_json"] +host = ["krun-sys", "krun-init-blob-via-cdylib", "serde", "serde_json"] guest = [] [lib] @@ -11,6 +11,7 @@ name = "test_cases" [dependencies] krun-sys = { path = "../../krun-sys", optional = true } +krun-init-blob-via-cdylib = { path = "../../init/init-blob-via-cdylib", optional = true } macros = { path = "../macros" } nix = { version = "0.29.0", features = ["fs", "socket", "ioctl"] } anyhow = "1.0.95" diff --git a/tests/test_cases/src/common.rs b/tests/test_cases/src/common.rs index 5f78fad31..69b94952f 100644 --- a/tests/test_cases/src/common.rs +++ b/tests/test_cases/src/common.rs @@ -1,14 +1,13 @@ -//! Common utilities used by multiple test +//! Common utilities used by multiple tests. use anyhow::Context; -use std::ffi::{c_char, CStr, CString}; +use std::ffi::{CStr, CString}; use std::fs; use std::fs::create_dir; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; -use std::ptr::null; -use crate::{krun_call, TestSetup}; +use crate::{krun_call, krun_init, TestSetup}; use krun_sys::*; fn copy_guest_agent(dir: &Path) -> anyhow::Result<()> { @@ -33,6 +32,17 @@ pub fn setup_rootfs(test_setup: &TestSetup) -> anyhow::Result { Ok(root_dir) } +/// Build an init config for running the guest-agent with the given test case. +pub fn build_init_config(test_case: &str, guest_env: &[&str]) -> krun_init::Config { + let mut builder = krun_init::Config::builder() + .args(&["/guest-agent", test_case]) + .workdir("/"); + if !guest_env.is_empty() { + builder = builder.env(guest_env); + } + builder.build() +} + /// Sets up the root filesystem, copies the guest agent into it, and enters the VM. pub fn setup_fs_and_enter(ctx: u32, test_setup: TestSetup) -> anyhow::Result<()> { setup_fs_and_enter_with_env(ctx, test_setup, &[]) @@ -44,13 +54,14 @@ pub fn setup_fs_and_enter_with_env( guest_env: &[&CStr], ) -> anyhow::Result<()> { let root_dir = setup_rootfs(&test_setup)?; - let path_str = CString::new(root_dir.as_os_str().as_bytes()).context("CString::new")?; - let mut envp: Vec<*const c_char> = guest_env + + let env_strs: Vec<&str> = guest_env .iter() - .map(|entry| entry.as_ptr().cast()) + .map(|c| c.to_str().expect("env var not valid UTF-8")) .collect(); - envp.push(null()); + let init_config = build_init_config(&test_setup.test_case, &env_strs); + unsafe { krun_call!(krun_add_virtiofs3( ctx, @@ -59,14 +70,10 @@ pub fn setup_fs_and_enter_with_env( 0, false, ))?; - krun_call!(krun_set_workdir(ctx, c"/".as_ptr()))?; - let test_case_cstr = CString::new(test_setup.test_case).context("CString::new")?; - let argv = [test_case_cstr.as_ptr(), null()]; - krun_call!(krun_set_exec( + krun_call!(krun_inject_init( ctx, - c"/guest-agent".as_ptr(), - argv.as_ptr(), - envp.as_ptr(), + c"/dev/root".as_ptr(), + init_config.__into_raw(), ))?; krun_call!(krun_start_enter(ctx))?; } diff --git a/tests/test_cases/src/lib.rs b/tests/test_cases/src/lib.rs index ee24b0d05..fc96e694b 100644 --- a/tests/test_cases/src/lib.rs +++ b/tests/test_cases/src/lib.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "host")] +extern crate krun_init_blob_via_cdylib as krun_init; + mod test_vm_config; use test_vm_config::TestVmConfig; diff --git a/tests/test_cases/src/test_augmentfs.rs b/tests/test_cases/src/test_augmentfs.rs index 46643b4f3..6fce706c6 100644 --- a/tests/test_cases/src/test_augmentfs.rs +++ b/tests/test_cases/src/test_augmentfs.rs @@ -19,10 +19,10 @@ mod host { use crate::{Test, TestSetup}; use crate::{krun_call, krun_call_u32}; + use crate::krun_init; use krun_sys::*; use std::ffi::CString; use std::os::fd::AsRawFd; - use std::ptr::null_mut; impl Test for TestAugmentFs { fn start_vm(self: Box, test_setup: TestSetup) -> anyhow::Result<()> { @@ -35,12 +35,11 @@ mod host { let guest_agent_bytes: &'static [u8] = Vec::leak(std::fs::read(&guest_agent_path).expect("Failed to read guest-agent")); - // Build JSON config: exec the guest-agent with our test name. - let json = format!( - r#"{{"args": ["/guest-agent", "{}"], "cwd": "/"}}"#, - test_case.to_str().unwrap() - ); - let json_bytes: &'static [u8] = Vec::leak(json.into_bytes()); + // Build init config via libkrun-init. + let init_config = krun_init::Config::builder() + .args(&["/guest-agent", test_case.to_str().unwrap()]) + .workdir("/") + .build(); // Deterministic test payload for range-read tests. let payload: &'static [u8] = Vec::leak(make_test_payload()); @@ -59,14 +58,6 @@ mod host { std::io::stderr().as_raw_fd(), ))?; - // Disable the implicit init — we'll inject it ourselves. - krun_call!(krun_disable_implicit_init(ctx))?; - - // Get the default init binary. - let mut init_data: *const u8 = null_mut(); - let mut init_len: usize = 0; - krun_call!(krun_get_default_init(&mut init_data, &mut init_len))?; - // Set up root with NO host directory (NullFs). krun_call!(krun_add_virtiofs3( ctx, @@ -86,15 +77,11 @@ mod host { ))?; } - // Overlay init.krun (one-shot, executable). - krun_call!(krun_fs_add_overlay_file( + // Inject init binary + config via libkrun-init. + krun_call!(krun_inject_init( ctx, c"/dev/root".as_ptr(), - c"init.krun".as_ptr(), - init_data, - init_len, - 0o100_755, - true, + init_config.__into_raw(), ))?; // Overlay guest-agent (one-shot, executable). After init @@ -109,17 +96,6 @@ mod host { true, ))?; - // Overlay .krun_config.json (one-shot). - krun_call!(krun_fs_add_overlay_file( - ctx, - c"/dev/root".as_ptr(), - c".krun_config.json".as_ptr(), - json_bytes.as_ptr(), - json_bytes.len(), - 0o100_644, - true, - ))?; - // Overlay a persistent marker file. krun_call!(krun_fs_add_overlay_file( ctx, @@ -167,7 +143,6 @@ mod host { false, ))?; - krun_call!(krun_set_workdir(ctx, c"/".as_ptr()))?; krun_call!(krun_start_enter(ctx))?; } Ok(()) diff --git a/tests/test_cases/src/test_root_disk_remount.rs b/tests/test_cases/src/test_root_disk_remount.rs index 388b68bc3..3a6a6d9da 100644 --- a/tests/test_cases/src/test_root_disk_remount.rs +++ b/tests/test_cases/src/test_root_disk_remount.rs @@ -13,14 +13,13 @@ pub struct TestRootDiskRemount; mod host { use super::*; - use crate::{ShouldRun, krun_call, krun_call_u32}; + use crate::{krun_call, krun_call_u32, ShouldRun}; use crate::{Test, TestSetup}; use krun_sys::*; use nix::libc; use std::ffi::CString; use std::os::fd::AsRawFd; use std::process::Command; - use std::ptr::null; type KrunAddDiskFn = unsafe extern "C" fn( ctx_id: u32, @@ -114,8 +113,8 @@ mod host { std::io::stderr().as_raw_fd(), ))?; - let argv = [test_case.as_ptr(), null()]; - let envp = [null()]; + let argv = [test_case.as_ptr(), std::ptr::null()]; + let envp = [std::ptr::null()]; krun_call!(krun_set_exec( ctx, c"/guest-agent".as_ptr(), diff --git a/tests/test_cases/src/test_virtiofs_root_ro.rs b/tests/test_cases/src/test_virtiofs_root_ro.rs index 8e18ac1d5..ef648969d 100644 --- a/tests/test_cases/src/test_virtiofs_root_ro.rs +++ b/tests/test_cases/src/test_virtiofs_root_ro.rs @@ -17,14 +17,13 @@ mod host { use super::*; use crate::common::setup_rootfs; - use crate::{Test, TestSetup}; use crate::{krun_call, krun_call_u32}; + use crate::{Test, TestSetup}; use krun_sys::*; use std::ffi::CString; use std::fs; use std::os::fd::AsRawFd; use std::os::unix::ffi::OsStrExt; - use std::ptr::null; impl Test for TestVirtiofsRootRo { fn start_vm(self: Box, test_setup: TestSetup) -> anyhow::Result<()> { @@ -39,8 +38,6 @@ mod host { fs::write(root_dir.join(TEST_FILE), TEST_CONTENT)?; let root_path = CString::new(root_dir.as_os_str().as_bytes())?; let test_case = CString::new(test_setup.test_case)?; - let argv = [test_case.as_ptr(), null()]; - let envp = [null()]; unsafe { krun_call!(krun_init_log( @@ -67,12 +64,12 @@ mod host { true, ))?; - krun_call!(krun_set_workdir(ctx, c"/".as_ptr()))?; - krun_call!(krun_set_exec( + let init_config = + crate::common::build_init_config(test_case.to_str().unwrap(), &[]); + krun_call!(krun_inject_init( ctx, - c"/guest-agent".as_ptr(), - argv.as_ptr(), - envp.as_ptr(), + c"/dev/root".as_ptr(), + init_config.__into_raw(), ))?; krun_call!(krun_start_enter(ctx))?; } @@ -87,12 +84,12 @@ mod guest { use crate::Test; use nix::errno::Errno; use nix::libc; - use nix::sys::stat::{Mode, SFlag, mknod, stat}; + use nix::sys::stat::{mknod, stat, Mode, SFlag}; use nix::unistd::{mkfifo, truncate}; use std::fs; use std::fs::Permissions; use std::io::ErrorKind; - use std::os::unix::fs::{PermissionsExt, chown, symlink}; + use std::os::unix::fs::{chown, symlink, PermissionsExt}; use std::os::unix::net::UnixListener; use std::path::Path; From 4a96d76d247f54f303426967cbad28ea309aea55 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 4 Jun 2026 14:08:35 +0200 Subject: [PATCH 35/41] examples: port to init-blob API via krun_inject_init Replace legacy krun_set_exec/krun_set_workdir/krun_set_rlimits calls with the init-blob config builder + krun_inject_init: - chroot_vm.c: use ConfigBuilder for args, env, workdir, rlimits - consoles.c: use ConfigBuilder for args - nitro.c: use ConfigBuilder for args and env - gui_vm/main.rs: use krun_init::Config::builder() for args All C examples now link -lkrun_init and include . launch-tee.c is left unchanged (TEE variant has its own init flow). Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- examples/Makefile | 8 +++--- examples/chroot_vm.c | 57 ++++++++++++++++++++++++------------- examples/consoles.c | 26 ++++++++++++++--- examples/gui_vm/Cargo.toml | 1 + examples/gui_vm/src/main.rs | 31 ++++++++++---------- examples/nitro.c | 33 +++++++++++++++++---- 6 files changed, 108 insertions(+), 48 deletions(-) diff --git a/examples/Makefile b/examples/Makefile index 724a9049d..ad3c1c33e 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -1,9 +1,9 @@ ARCH = $(shell uname -m) OS = $(shell uname -s) -LDFLAGS_x86_64_Linux = -lkrun -LDFLAGS_aarch64_Linux = -lkrun -LDFLAGS_riscv64_Linux = -lkrun -LDFLAGS_arm64_Darwin = -L/opt/homebrew/lib -lkrun +LDFLAGS_x86_64_Linux = -lkrun -lkrun_init +LDFLAGS_aarch64_Linux = -lkrun -lkrun_init +LDFLAGS_riscv64_Linux = -lkrun -lkrun_init +LDFLAGS_arm64_Darwin = -L/opt/homebrew/lib -lkrun -lkrun_init LDFLAGS_sev = -lkrun-sev LDFLAGS_tdx = -lkrun-tdx LDFLAGS_nitro = -lkrun-awsnitro diff --git a/examples/chroot_vm.c b/examples/chroot_vm.c index f9dfd9b63..1f3308362 100644 --- a/examples/chroot_vm.c +++ b/examples/chroot_vm.c @@ -5,6 +5,7 @@ * Virtual Machine created and managed by libkrun. */ +#include #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -458,25 +460,42 @@ int main(int argc, char *const argv[]) } } - // Configure the rlimits that will be set in the guest - if (err = krun_set_rlimits(ctx_id, &rlimits[0])) { - errno = -err; - perror("Error configuring rlimits"); - return -1; - } - - // Set the working directory to "/", just for the sake of completeness. - if (err = krun_set_workdir(ctx_id, "/")) { - errno = -err; - perror("Error configuring \"/\" as working directory"); - return -1; - } - - // Specify the path of the binary to be executed in the isolated context, relative to the root path. - if (err = krun_set_exec(ctx_id, cmdline.guest_argv[0], (const char* const*) &cmdline.guest_argv[1], &envp[0])) { - errno = -err; - perror("Error configuring the parameters for the executable to be run"); - return -1; + // Build the init configuration (executable, args, env, workdir, rlimits). + { + KrunInitConfigBuilder builder = krun_init_config_builder(); + + // Count and convert guest_argv to KrunStr array. + int argc_guest = 0; + while (cmdline.guest_argv[argc_guest]) argc_guest++; + KrunStr *args = alloca(argc_guest * sizeof(KrunStr)); + for (int i = 0; i < argc_guest; i++) + args[i] = KRUN_STR(cmdline.guest_argv[i]); + krun_init_config_builder_args(&builder, args, argc_guest); + + // Convert envp to KrunStr array. + int envc = 0; + while (envp[envc]) envc++; + KrunStr *env_strs = alloca(envc * sizeof(KrunStr)); + for (int i = 0; i < envc; i++) + env_strs[i] = KRUN_STR(envp[i]); + krun_init_config_builder_env(&builder, env_strs, envc); + + krun_init_config_builder_workdir(&builder, KRUN_STR("/")); + + // Convert rlimits to KrunStr array. + int rlimitc = 0; + while (rlimits[rlimitc]) rlimitc++; + KrunStr *rlimit_strs = alloca(rlimitc * sizeof(KrunStr)); + for (int i = 0; i < rlimitc; i++) + rlimit_strs[i] = KRUN_STR(rlimits[i]); + krun_init_config_builder_rlimits(&builder, rlimit_strs, rlimitc); + + KrunInitConfig config = krun_init_config_builder_build(&builder); + if (err = krun_inject_init(ctx_id, "/dev/root", config)) { + errno = -err; + perror("Error injecting init configuration"); + return -1; + } } if (err = krun_split_irqchip(ctx_id, false)) { diff --git a/examples/consoles.c b/examples/consoles.c index d78c42592..aa9942142 100644 --- a/examples/consoles.c +++ b/examples/consoles.c @@ -9,6 +9,7 @@ #include #include +#include static int cmd_output(char *output, size_t output_size, const char *prog, ...) { @@ -195,10 +196,27 @@ int main(int argc, char *const argv[]) return 1; } - if ((err = krun_set_exec(ctx_id, command, command_args, envp))) { - errno = -err; - perror("krun_set_exec"); - return 1; + // Build init configuration. + { + KrunInitConfigBuilder builder = krun_init_config_builder(); + + // Build full argv: command + command_args (null-terminated). + int argc_guest = 1; + if (command_args) { + while (command_args[argc_guest - 1]) argc_guest++; + } + KrunStr args[argc_guest]; + args[0] = KRUN_STR(command); + for (int i = 1; i < argc_guest; i++) + args[i] = KRUN_STR(command_args[i - 1]); + krun_init_config_builder_args(&builder, args, argc_guest); + + KrunInitConfig config = krun_init_config_builder_build(&builder); + if ((err = krun_inject_init(ctx_id, "/dev/root", config))) { + errno = -err; + perror("krun_inject_init"); + return 1; + } } if ((err = krun_start_enter(ctx_id))) { diff --git a/examples/gui_vm/Cargo.toml b/examples/gui_vm/Cargo.toml index 4a8e2da0b..eac8f6eb9 100644 --- a/examples/gui_vm/Cargo.toml +++ b/examples/gui_vm/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] gtk_display = { path = "../krun_gtk_display" } krun-sys = { path = "../../krun-sys" } +krun-init = { package = "krun-init-blob-via-cdylib", path = "../../init/init-blob-via-cdylib" } krun_input = { path = "../../src/input", package = "krun-input" } anyhow = "1.0.98" clap = "4.5.39" diff --git a/examples/gui_vm/src/main.rs b/examples/gui_vm/src/main.rs index 4395a273d..dbafd9e87 100644 --- a/examples/gui_vm/src/main.rs +++ b/examples/gui_vm/src/main.rs @@ -8,11 +8,11 @@ use gtk_display::{ use krun_sys::{ krun_add_display, krun_add_input_device, krun_add_input_device_fd, krun_add_virtio_console_default, krun_add_virtiofs3, krun_create_ctx, krun_display_set_dpi, - krun_display_set_physical_size, krun_display_set_refresh_rate, krun_init_log, - krun_set_display_backend, krun_set_exec, krun_set_gpu_options2, krun_set_vm_config, - krun_start_enter, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_LEVEL_WARN, KRUN_LOG_STYLE_ALWAYS, - KRUN_LOG_TARGET_DEFAULT, VIRGLRENDERER_RENDER_SERVER, VIRGLRENDERER_THREAD_SYNC, - VIRGLRENDERER_USE_ASYNC_FENCE_CB, VIRGLRENDERER_USE_EGL, VIRGLRENDERER_VENUS, + krun_display_set_physical_size, krun_display_set_refresh_rate, krun_init_log, krun_inject_init, + krun_set_display_backend, krun_set_gpu_options2, krun_set_vm_config, krun_start_enter, + KRUN_LOG_LEVEL_TRACE, KRUN_LOG_LEVEL_WARN, KRUN_LOG_STYLE_ALWAYS, KRUN_LOG_TARGET_DEFAULT, + VIRGLRENDERER_RENDER_SERVER, VIRGLRENDERER_THREAD_SYNC, VIRGLRENDERER_USE_ASYNC_FENCE_CB, + VIRGLRENDERER_USE_EGL, VIRGLRENDERER_VENUS, }; use log::LevelFilter; use regex::{Captures, Regex}; @@ -25,7 +25,6 @@ use anyhow::Context; use std::os::fd::{AsRawFd, IntoRawFd}; use std::path::PathBuf; use std::process::exit; -use std::ptr::null; use std::str::FromStr; use std::sync::LazyLock; use std::thread; @@ -175,15 +174,17 @@ fn krun_thread( false ))?; - let executable = args.executable.as_ref().unwrap().as_ptr(); - let argv: Vec<_> = args.argv.iter().map(|a| a.as_ptr()).collect(); - let argv_ptr = if argv.is_empty() { - null() - } else { - argv.as_ptr() - }; - let envp = [null()]; - krun_call!(krun_set_exec(ctx, executable, argv_ptr, envp.as_ptr()))?; + // Build init configuration. + let exec = args.executable.as_ref().unwrap().to_str().unwrap(); + let argv_strs: Vec<&str> = args.argv.iter().map(|a| a.to_str().unwrap()).collect(); + let mut full_argv: Vec<&str> = vec![exec]; + full_argv.extend_from_slice(&argv_strs); + let config = krun_init::Config::builder().args(&full_argv).build(); + krun_call!(krun_inject_init( + ctx, + c"/dev/root".as_ptr(), + config.__into_raw(), + ))?; for display in &args.display { let display_id = krun_call_u32!(krun_add_display(ctx, display.width, display.height))?; diff --git a/examples/nitro.c b/examples/nitro.c index a742f1441..91eb8753e 100644 --- a/examples/nitro.c +++ b/examples/nitro.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -216,12 +217,32 @@ int main(int argc, char *const argv[]) return -1; } - // Configure the enclave's execution environment. - if (err = krun_set_exec(ctx_id, default_argv[0], default_argv, - default_envp)) { - errno = -err; - perror("Error configuring enclave execution path"); - return -1; + // Configure the enclave's execution environment via init-blob. + { + KrunInitConfigBuilder builder = krun_init_config_builder(); + + // Count default_argv entries (null-terminated). + int argc_guest = 0; + while (default_argv[argc_guest]) argc_guest++; + KrunStr args[argc_guest]; + for (int i = 0; i < argc_guest; i++) + args[i] = KRUN_STR(default_argv[i]); + krun_init_config_builder_args(&builder, args, argc_guest); + + // Count default_envp entries (null-terminated). + int envc = 0; + while (default_envp[envc]) envc++; + KrunStr env_strs[envc]; + for (int i = 0; i < envc; i++) + env_strs[i] = KRUN_STR(default_envp[i]); + krun_init_config_builder_env(&builder, env_strs, envc); + + KrunInitConfig config = krun_init_config_builder_build(&builder); + if (err = krun_inject_init(ctx_id, "/dev/root", config)) { + errno = -err; + perror("Error injecting init configuration"); + return -1; + } } if (cmdline.net) { From 4e3955ea06e251cf03fcb397095cf00ca725a25a Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 4 Jun 2026 14:17:13 +0200 Subject: [PATCH 36/41] libkrun: propagate kernel init arg from krun_inject_init Have krun_inject_init() retrieve the kernel init argument from the Config handle (e.g. "init=/init.krun") and store it in ContextConfig. krun_start_enter() now uses this stored value instead of hardcoding init={INIT_PATH}. This makes the kernel cmdline init= argument explicit: it comes from the init-blob library, not from libkrun internals. For backwards compatibility, the default still falls back to init=/init.krun when krun_inject_init was not called. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- src/libkrun/src/lib.rs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index bd72287c1..425eb0f48 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -181,6 +181,9 @@ struct ContextConfig { vmm_gid: Option, #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] disable_implicit_init: bool, + /// Kernel init arg set by `krun_inject_init` (e.g. `"init=/init.krun"`). + #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] + kernel_init_arg: Option, } impl ContextConfig { @@ -2365,6 +2368,7 @@ pub unsafe extern "C" fn krun_inject_init( use krun_init::Symbol; if krun_init::require(&[ + Symbol::KrunInitConfigKernelInitArg, Symbol::KrunInitConfigGuestFiles, Symbol::KrunInitGuestFilePath, Symbol::KrunInitGuestFileData, @@ -2412,9 +2416,14 @@ pub unsafe extern "C" fn krun_inject_init( } } - // Disable implicit init injection — we just did it explicitly. + // Store the kernel init arg and disable implicit init injection. + let kernel_init_arg = config.kernel_init_arg().to_string(); match CTX_MAP.lock().unwrap().entry(ctx_id) { - Entry::Occupied(mut ctx_cfg) => ctx_cfg.get_mut().disable_implicit_init = true, + Entry::Occupied(mut ctx_cfg) => { + let cfg = ctx_cfg.get_mut(); + cfg.disable_implicit_init = true; + cfg.kernel_init_arg = Some(kernel_init_arg); + } Entry::Vacant(_) => return -libc::ENOENT, } @@ -2788,8 +2797,18 @@ pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 { return -libc::EINVAL; } + #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] + let default_init_arg = format!("init={INIT_PATH}"); + #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] + let init_arg = ctx_cfg + .kernel_init_arg + .as_deref() + .unwrap_or(&default_init_arg); + #[cfg(any(feature = "tee", feature = "aws-nitro"))] + let init_arg = format!("init={INIT_PATH}"); + let kernel_cmdline = KernelCmdlineConfig { - prolog: Some(format!("{DEFAULT_KERNEL_CMDLINE} init={INIT_PATH}")), + prolog: Some(format!("{DEFAULT_KERNEL_CMDLINE} {init_arg}")), krun_env: Some(format!( " {} {} {} {} {}", ctx_cfg.get_exec_path(), From 08a73e944e6ec01d493c4bde92d9304eda9d6662 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 4 Jun 2026 15:44:55 +0200 Subject: [PATCH 37/41] init: load config before block root pivot Move config::load() before fs::mount_block_root_device() so that /.krun_config.json is read while the initial (virtiofs) root is still accessible. After pivot_root the NullFs is gone and the config file is unreachable. This enables test_root_disk_remount to use krun_inject_init instead of the legacy krun_set_exec/krun_set_workdir path. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- init/init-binary/src/main.rs | 11 +++++++-- .../test_cases/src/test_root_disk_remount.rs | 23 ++++++++++--------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/init/init-binary/src/main.rs b/init/init-binary/src/main.rs index 825e4582d..f89558d63 100644 --- a/init/init-binary/src/main.rs +++ b/init/init-binary/src/main.rs @@ -23,6 +23,15 @@ fn main() -> anyhow::Result<()> { #[cfg(target_os = "linux")] { fs::mount_filesystems()?; + } + + // Load config before block root pivot — /.krun_config.json lives on the + // initial (virtiofs) root and is inaccessible after pivot_root. + #[cfg(target_os = "linux")] + let cfg = config::load(fs::is_mount_point); + + #[cfg(target_os = "linux")] + { fs::mount_block_root_device()?; fs::mount_shared_root()?; } @@ -50,8 +59,6 @@ fn main() -> anyhow::Result<()> { #[cfg(target_os = "freebsd")] let iso_mounted = std::env::var("KRUN_CONFIG").is_err() && freebsd::mount_config_iso(); - #[cfg(target_os = "linux")] - let cfg = config::load(fs::is_mount_point); #[cfg(not(target_os = "linux"))] let cfg = config::load(); diff --git a/tests/test_cases/src/test_root_disk_remount.rs b/tests/test_cases/src/test_root_disk_remount.rs index 3a6a6d9da..7e802b813 100644 --- a/tests/test_cases/src/test_root_disk_remount.rs +++ b/tests/test_cases/src/test_root_disk_remount.rs @@ -13,7 +13,8 @@ pub struct TestRootDiskRemount; mod host { use super::*; - use crate::{krun_call, krun_call_u32, ShouldRun}; + use crate::common; + use crate::{krun_call, krun_call_u32, krun_init, ShouldRun}; use crate::{Test, TestSetup}; use krun_sys::*; use nix::libc; @@ -113,16 +114,8 @@ mod host { std::io::stderr().as_raw_fd(), ))?; - let argv = [test_case.as_ptr(), std::ptr::null()]; - let envp = [std::ptr::null()]; - krun_call!(krun_set_exec( - ctx, - c"/guest-agent".as_ptr(), - argv.as_ptr(), - envp.as_ptr(), - ))?; - - krun_call!(krun_set_workdir(ctx, c"/".as_ptr()))?; + // Disable implicit init — we inject explicitly below. + krun_call!(krun_disable_implicit_init(ctx))?; // Add a block device with the ext4 image. krun_call!(krun_add_disk( @@ -140,6 +133,14 @@ mod host { std::ptr::null(), ))?; + // Inject init config into the NullFs root. + let init_config = common::build_init_config(test_case.to_str().unwrap(), &[]); + krun_call!(krun_inject_init( + ctx, + c"/dev/root".as_ptr(), + init_config.__into_raw(), + ))?; + krun_call!(krun_start_enter(ctx))?; } Ok(()) From 24c7b376f62e51a11ebdb44f2c824298b7e4389b Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 4 Jun 2026 15:46:39 +0200 Subject: [PATCH 38/41] squash! tests: port to init-blob API via krun_inject_init Update tests/Cargo.lock for ffier rev bump. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- tests/Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Cargo.lock b/tests/Cargo.lock index 48c473428..9e2d96ab6 100644 --- a/tests/Cargo.lock +++ b/tests/Cargo.lock @@ -183,7 +183,7 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "ffier" version = "0.1.0" -source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=8b06dc3#8b06dc3b86fd66dd11030b7bfd5bacbd026387e8" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=9e36967#9e36967ad64ae94f540e1e8b84dccbfd397b6fd9" dependencies = [ "ffier-annotations", "ffier-builtins", @@ -193,7 +193,7 @@ dependencies = [ [[package]] name = "ffier-annotations" version = "0.1.0" -source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=8b06dc3#8b06dc3b86fd66dd11030b7bfd5bacbd026387e8" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=9e36967#9e36967ad64ae94f540e1e8b84dccbfd397b6fd9" dependencies = [ "ffier-meta", "proc-macro2", @@ -204,7 +204,7 @@ dependencies = [ [[package]] name = "ffier-builtins" version = "0.1.0" -source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=8b06dc3#8b06dc3b86fd66dd11030b7bfd5bacbd026387e8" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=9e36967#9e36967ad64ae94f540e1e8b84dccbfd397b6fd9" dependencies = [ "ffier-annotations", "ffier-rt", @@ -213,7 +213,7 @@ dependencies = [ [[package]] name = "ffier-meta" version = "0.1.0" -source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=8b06dc3#8b06dc3b86fd66dd11030b7bfd5bacbd026387e8" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=9e36967#9e36967ad64ae94f540e1e8b84dccbfd397b6fd9" dependencies = [ "proc-macro2", "quote", @@ -223,7 +223,7 @@ dependencies = [ [[package]] name = "ffier-rt" version = "0.1.0" -source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=8b06dc3#8b06dc3b86fd66dd11030b7bfd5bacbd026387e8" +source = "git+file:///home/mhrica/Dev2/ffier/ffier.git?rev=9e36967#9e36967ad64ae94f540e1e8b84dccbfd397b6fd9" [[package]] name = "fuchsia-cprng" From 59897d0d7c4490b22679977043845d18423ab71a Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 4 Jun 2026 15:53:06 +0200 Subject: [PATCH 39/41] libkrun: remove implicit init injection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove krun_disable_implicit_init() and krun_get_default_init() — the init binary and config are now injected explicitly via krun_inject_init(). - Remove init_virtual_entry() and DEFAULT_INIT_PAYLOAD - Remove disable_implicit_init field from ContextConfig - krun_add_virtiofs3 no longer injects init.krun for /dev/root - krun_set_root_disk_remount no longer injects init.krun - krun_start_enter only adds init= to kernel cmdline when krun_inject_init was called (TEE path unchanged) - Remove test_disable_implicit_init unit test - Remove krun_disable_implicit_init call from test_root_disk_remount Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- include/libkrun.h | 32 ------ src/libkrun/src/lib.rs | 107 +++--------------- .../test_cases/src/test_root_disk_remount.rs | 3 - 3 files changed, 13 insertions(+), 129 deletions(-) diff --git a/include/libkrun.h b/include/libkrun.h index a6d10c548..510b59027 100644 --- a/include/libkrun.h +++ b/include/libkrun.h @@ -994,38 +994,6 @@ int32_t krun_get_max_vcpus(void); */ int32_t krun_split_irqchip(uint32_t ctx_id, bool enable); -/** - * Do not inject the default init binary (/init.krun) into the root - * filesystem. Must be called before configuring the root filesystem. - * - * Arguments: - * "ctx_id" - the configuration context ID. - * - * Returns: - * Zero on success or a negative error number on failure. - */ -int32_t krun_disable_implicit_init(uint32_t ctx_id); - -/** - * Get a pointer to the built-in default init binary. - * - * This is the same binary that libkrun injects as /init.krun by default. - * Callers that use krun_disable_implicit_init() can use this to inject the - * init binary themselves (e.g. via krun_fs_add_overlay_file with custom - * settings). - * - * The returned pointer is valid for the lifetime of the process (static data). - * - * Arguments: - * "data_out" - receives a pointer to the init binary bytes. - * "len_out" - receives the length in bytes. - * - * Returns: - * Zero on success or a negative error number on failure. - * -EINVAL - data_out or len_out is NULL - */ -int32_t krun_get_default_init(const uint8_t **data_out, size_t *len_out); - /** * Add a virtual overlay file to a virtiofs device. * diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 425eb0f48..dbb246f75 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -96,26 +96,10 @@ const KRUNFW_NAME: &str = "libkrunfw.5.dylib"; #[cfg(feature = "aws-nitro")] static KRUN_NITRO_DEBUG: Mutex = Mutex::new(false); -// Path to the init binary to be executed inside the VM. +// Path to the init binary to be executed inside the VM (used in TEE kernel cmdline). +#[cfg(any(feature = "tee", feature = "aws-nitro"))] const INIT_PATH: &str = "/init.krun"; -#[cfg(not(any(feature = "tee", feature = "aws-nitro")))] -const DEFAULT_INIT_PAYLOAD: &[u8] = init_blob::INIT_BINARY; - -#[cfg(not(any(feature = "tee", feature = "aws-nitro")))] -fn init_virtual_entry() -> VirtualDirEntry { - VirtualDirEntry { - name: CString::new("init.krun").unwrap(), - entry: VirtualEntry { - mode: 0o755, - one_shot: true, - content: VirtualEntryContent::File { - data: DEFAULT_INIT_PAYLOAD, - }, - }, - } -} - static KRUNFW: LazyLock> = LazyLock::new(|| unsafe { libloading::Library::new(KRUNFW_NAME).ok() }); @@ -179,8 +163,6 @@ struct ContextConfig { console_output: Option, vmm_uid: Option, vmm_gid: Option, - #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] - disable_implicit_init: bool, /// Kernel init arg set by `krun_inject_init` (e.g. `"init=/init.krun"`). #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] kernel_init_arg: Option, @@ -621,17 +603,12 @@ pub unsafe extern "C" fn krun_add_virtiofs3( match CTX_MAP.lock().unwrap().entry(ctx_id) { Entry::Occupied(mut ctx_cfg) => { - let cfg = ctx_cfg.get_mut(); - let mut virtual_entries = Vec::new(); - if tag == "/dev/root" && !cfg.disable_implicit_init { - virtual_entries.push(init_virtual_entry()); - } - cfg.vmr.add_fs_device(FsDeviceConfig { + ctx_cfg.get_mut().vmr.add_fs_device(FsDeviceConfig { fs_id: tag.to_string(), shared_dir: path.map(|p| p.to_string()), shm_size: shm, read_only, - virtual_entries, + virtual_entries: Vec::new(), }); } Entry::Vacant(_) => return -libc::ENOENT, @@ -2235,13 +2212,11 @@ pub unsafe extern "C" fn krun_set_root_disk_remount( } // Boot from a block device: the virtiofs root only needs to - // serve init.krun and provide mount points for /dev, /proc, /sys. + // provide mount points for /dev, /proc, /sys. The init binary + // and config are injected separately via krun_inject_init(). // Use a NullFs (no host directory) with the inode overlay. let mut virtual_entries = Vec::new(); - if !ctx_cfg.disable_implicit_init { - virtual_entries.push(init_virtual_entry()); - } - // init.c needs these directories as mount points before + // The init binary needs these directories as mount points before // pivoting to the block device root. for name in ["dev", "proc", "sys", "newroot"] { virtual_entries.push(VirtualDirEntry { @@ -2274,19 +2249,6 @@ pub unsafe extern "C" fn krun_set_root_disk_remount( } } -#[unsafe(no_mangle)] -#[cfg(not(any(feature = "tee", feature = "aws-nitro")))] -pub extern "C" fn krun_disable_implicit_init(ctx_id: u32) -> i32 { - match CTX_MAP.lock().unwrap().entry(ctx_id) { - Entry::Occupied(mut ctx_cfg) => { - ctx_cfg.get_mut().disable_implicit_init = true; - } - Entry::Vacant(_) => return -libc::ENOENT, - } - - KRUN_SUCCESS -} - /// Resolve a path like "a/b/c" into parent directory children + leaf name. /// Errors with a libc errno if any intermediate component is missing or not a Dir. #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] @@ -2416,13 +2378,11 @@ pub unsafe extern "C" fn krun_inject_init( } } - // Store the kernel init arg and disable implicit init injection. + // Store the kernel init arg for krun_start_enter. let kernel_init_arg = config.kernel_init_arg().to_string(); match CTX_MAP.lock().unwrap().entry(ctx_id) { Entry::Occupied(mut ctx_cfg) => { - let cfg = ctx_cfg.get_mut(); - cfg.disable_implicit_init = true; - cfg.kernel_init_arg = Some(kernel_init_arg); + ctx_cfg.get_mut().kernel_init_arg = Some(kernel_init_arg); } Entry::Vacant(_) => return -libc::ENOENT, } @@ -2513,23 +2473,6 @@ pub unsafe extern "C" fn krun_fs_add_overlay_dir( ) } -#[allow(clippy::missing_safety_doc)] -#[unsafe(no_mangle)] -#[cfg(not(any(feature = "tee", feature = "aws-nitro")))] -pub unsafe extern "C" fn krun_get_default_init( - data_out: *mut *const u8, - len_out: *mut size_t, -) -> i32 { - if data_out.is_null() || len_out.is_null() { - return -libc::EINVAL; - } - unsafe { - *data_out = DEFAULT_INIT_PAYLOAD.as_ptr(); - *len_out = DEFAULT_INIT_PAYLOAD.len(); - } - KRUN_SUCCESS -} - #[unsafe(no_mangle)] pub extern "C" fn krun_add_vsock(ctx_id: u32, tsi_features: u32) -> i32 { let tsi_flags = match TsiFlags::from_bits(tsi_features) { @@ -2797,18 +2740,17 @@ pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 { return -libc::EINVAL; } - #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] - let default_init_arg = format!("init={INIT_PATH}"); #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] let init_arg = ctx_cfg .kernel_init_arg .as_deref() - .unwrap_or(&default_init_arg); + .map(|a| format!(" {a}")) + .unwrap_or_default(); #[cfg(any(feature = "tee", feature = "aws-nitro"))] - let init_arg = format!("init={INIT_PATH}"); + let init_arg = format!(" init={INIT_PATH}"); let kernel_cmdline = KernelCmdlineConfig { - prolog: Some(format!("{DEFAULT_KERNEL_CMDLINE} {init_arg}")), + prolog: Some(format!("{DEFAULT_KERNEL_CMDLINE}{init_arg}")), krun_env: Some(format!( " {} {} {} {} {}", ctx_cfg.get_exec_path(), @@ -2920,27 +2862,4 @@ fn krun_start_enter_nitro(ctx_id: u32) -> i32 { } } -#[cfg(all(test, not(feature = "tee")))] -mod test_disable_implicit_init { - use super::*; - #[test] - fn test_disable_implicit_init() { - let ctx = unsafe { krun_create_ctx() } as u32; - unsafe { - krun_disable_implicit_init(ctx); - krun_add_virtiofs3(ctx, c"/dev/root".as_ptr(), c"/tmp".as_ptr(), 0, false); - } - - let ctx_map = CTX_MAP.lock().unwrap(); - let cfg = ctx_map.get(&ctx).unwrap(); - assert_eq!(cfg.vmr.fs.len(), 1); - assert!( - cfg.vmr.fs[0].virtual_entries.is_empty(), - "root virtiofs should not inject init.krun after krun_disable_implicit_init()" - ); - drop(ctx_map); - - assert_eq!(krun_free_ctx(ctx), KRUN_SUCCESS); - } -} diff --git a/tests/test_cases/src/test_root_disk_remount.rs b/tests/test_cases/src/test_root_disk_remount.rs index 7e802b813..7fd2c9234 100644 --- a/tests/test_cases/src/test_root_disk_remount.rs +++ b/tests/test_cases/src/test_root_disk_remount.rs @@ -114,9 +114,6 @@ mod host { std::io::stderr().as_raw_fd(), ))?; - // Disable implicit init — we inject explicitly below. - krun_call!(krun_disable_implicit_init(ctx))?; - // Add a block device with the ext4 image. krun_call!(krun_add_disk( ctx, From 3c069c4bf2d79e85f4b7705fa2559102da64cc17 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 4 Jun 2026 15:57:47 +0200 Subject: [PATCH 40/41] WIP: SQUASHME fix cargo fmt 2024 edition Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- src/libkrun/src/lib.rs | 2 -- tests/test_cases/src/common.rs | 2 +- tests/test_cases/src/common_freebsd.rs | 4 ++-- tests/test_cases/src/test_augmentfs.rs | 9 +++++++-- tests/test_cases/src/test_freebsd_boot.rs | 2 +- .../src/test_freebsd_gvproxy_tcp_guest_connect.rs | 4 ++-- .../src/test_freebsd_gvproxy_tcp_guest_listen.rs | 4 ++-- tests/test_cases/src/test_root_disk_remount.rs | 2 +- tests/test_cases/src/test_virtiofs_root_ro.rs | 6 +++--- tests/test_cases/src/test_vsock_guest_connect.rs | 4 ++-- 10 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index dbb246f75..04b288e8d 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -2861,5 +2861,3 @@ fn krun_start_enter_nitro(ctx_id: u32) -> i32 { } } } - - diff --git a/tests/test_cases/src/common.rs b/tests/test_cases/src/common.rs index 69b94952f..1e86b9f9b 100644 --- a/tests/test_cases/src/common.rs +++ b/tests/test_cases/src/common.rs @@ -7,7 +7,7 @@ use std::fs::create_dir; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; -use crate::{krun_call, krun_init, TestSetup}; +use crate::{TestSetup, krun_call, krun_init}; use krun_sys::*; fn copy_guest_agent(dir: &Path) -> anyhow::Result<()> { diff --git a/tests/test_cases/src/common_freebsd.rs b/tests/test_cases/src/common_freebsd.rs index 5f3cdb0e4..f3ce7ed51 100644 --- a/tests/test_cases/src/common_freebsd.rs +++ b/tests/test_cases/src/common_freebsd.rs @@ -9,8 +9,8 @@ use std::process::Command; use std::time::{SystemTime, UNIX_EPOCH}; use crate::test_net::get_krun_add_net_unixgram; -use crate::test_net::gvproxy::{wait_for_socket, Gvproxy}; -use crate::{krun_call, TestSetup}; +use crate::test_net::gvproxy::{Gvproxy, wait_for_socket}; +use crate::{TestSetup, krun_call}; use krun_sys::*; pub struct FreeBsdAssets { diff --git a/tests/test_cases/src/test_augmentfs.rs b/tests/test_cases/src/test_augmentfs.rs index 6fce706c6..3661bf878 100644 --- a/tests/test_cases/src/test_augmentfs.rs +++ b/tests/test_cases/src/test_augmentfs.rs @@ -17,9 +17,9 @@ fn make_test_payload() -> Vec { mod host { use super::*; + use crate::krun_init; use crate::{Test, TestSetup}; use crate::{krun_call, krun_call_u32}; - use crate::krun_init; use krun_sys::*; use std::ffi::CString; use std::os::fd::AsRawFd; @@ -48,7 +48,12 @@ mod host { let marker: &'static [u8] = b"virtual-file-marker-content-12345"; unsafe { - krun_call!(krun_init_log(KRUN_LOG_TARGET_DEFAULT, KRUN_LOG_LEVEL_TRACE, KRUN_LOG_STYLE_AUTO, 0))?; + krun_call!(krun_init_log( + KRUN_LOG_TARGET_DEFAULT, + KRUN_LOG_LEVEL_TRACE, + KRUN_LOG_STYLE_AUTO, + 0 + ))?; let ctx = krun_call_u32!(krun_create_ctx())?; krun_call!(krun_set_vm_config(ctx, 1, 512))?; krun_call!(krun_add_virtio_console_default( diff --git a/tests/test_cases/src/test_freebsd_boot.rs b/tests/test_cases/src/test_freebsd_boot.rs index 6b669fed9..07fbfae59 100644 --- a/tests/test_cases/src/test_freebsd_boot.rs +++ b/tests/test_cases/src/test_freebsd_boot.rs @@ -7,7 +7,7 @@ mod host { use super::*; use crate::common_freebsd::{freebsd_assets, normalize_serial_output, setup_kernel_and_enter}; - use crate::{krun_call, krun_call_u32, ShouldRun, Test, TestOutcome, TestSetup}; + use crate::{ShouldRun, Test, TestOutcome, TestSetup, krun_call, krun_call_u32}; use krun_sys::*; impl Test for TestFreeBsdBoot { diff --git a/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_connect.rs b/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_connect.rs index bf0994e60..444390fd1 100644 --- a/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_connect.rs +++ b/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_connect.rs @@ -28,8 +28,8 @@ mod host { freebsd_assets, normalize_serial_output, setup_gvproxy_backend, setup_kernel_and_enter, }; use crate::test_net::gvproxy::gvproxy_path; - use crate::{krun_call, krun_call_u32}; use crate::{ShouldRun, Test, TestOutcome, TestSetup}; + use crate::{krun_call, krun_call_u32}; use krun_sys::*; use std::thread; @@ -83,8 +83,8 @@ mod host { #[guest] mod guest { use super::*; - use crate::freebsd_network::configure_virtio_net_ip; use crate::Test; + use crate::freebsd_network::configure_virtio_net_ip; impl Test for TestFreeBsdGvproxyTcpGuestConnect { fn in_guest(self: Box) { diff --git a/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_listen.rs b/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_listen.rs index 7001c5f5b..88b2d1c56 100644 --- a/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_listen.rs +++ b/tests/test_cases/src/test_freebsd_gvproxy_tcp_guest_listen.rs @@ -26,8 +26,8 @@ mod host { freebsd_assets, normalize_serial_output, setup_gvproxy_backend, setup_kernel_and_enter, }; use crate::test_net::gvproxy::{gvproxy_path, setup_gvproxy_port_forward}; - use crate::{krun_call, krun_call_u32}; use crate::{ShouldRun, Test, TestOutcome, TestSetup}; + use crate::{krun_call, krun_call_u32}; use krun_sys::*; use std::net::Ipv4Addr; use std::thread; @@ -94,8 +94,8 @@ mod host { #[guest] mod guest { use super::*; - use crate::freebsd_network::configure_virtio_net_ip; use crate::Test; + use crate::freebsd_network::configure_virtio_net_ip; impl Test for TestFreeBsdGvproxyTcpGuestListen { fn in_guest(self: Box) { diff --git a/tests/test_cases/src/test_root_disk_remount.rs b/tests/test_cases/src/test_root_disk_remount.rs index 7fd2c9234..2ee0de0cb 100644 --- a/tests/test_cases/src/test_root_disk_remount.rs +++ b/tests/test_cases/src/test_root_disk_remount.rs @@ -14,7 +14,7 @@ mod host { use super::*; use crate::common; - use crate::{krun_call, krun_call_u32, krun_init, ShouldRun}; + use crate::{ShouldRun, krun_call, krun_call_u32, krun_init}; use crate::{Test, TestSetup}; use krun_sys::*; use nix::libc; diff --git a/tests/test_cases/src/test_virtiofs_root_ro.rs b/tests/test_cases/src/test_virtiofs_root_ro.rs index ef648969d..9b46d9e98 100644 --- a/tests/test_cases/src/test_virtiofs_root_ro.rs +++ b/tests/test_cases/src/test_virtiofs_root_ro.rs @@ -17,8 +17,8 @@ mod host { use super::*; use crate::common::setup_rootfs; - use crate::{krun_call, krun_call_u32}; use crate::{Test, TestSetup}; + use crate::{krun_call, krun_call_u32}; use krun_sys::*; use std::ffi::CString; use std::fs; @@ -84,12 +84,12 @@ mod guest { use crate::Test; use nix::errno::Errno; use nix::libc; - use nix::sys::stat::{mknod, stat, Mode, SFlag}; + use nix::sys::stat::{Mode, SFlag, mknod, stat}; use nix::unistd::{mkfifo, truncate}; use std::fs; use std::fs::Permissions; use std::io::ErrorKind; - use std::os::unix::fs::{chown, symlink, PermissionsExt}; + use std::os::unix::fs::{PermissionsExt, chown, symlink}; use std::os::unix::net::UnixListener; use std::path::Path; diff --git a/tests/test_cases/src/test_vsock_guest_connect.rs b/tests/test_cases/src/test_vsock_guest_connect.rs index 2c101b008..42747e500 100644 --- a/tests/test_cases/src/test_vsock_guest_connect.rs +++ b/tests/test_cases/src/test_vsock_guest_connect.rs @@ -36,8 +36,8 @@ mod host { use super::*; use crate::common::setup_fs_and_enter; - use crate::{krun_call, krun_call_u32}; use crate::{Test, TestSetup}; + use crate::{krun_call, krun_call_u32}; use krun_sys::*; use std::ffi::CString; use std::io::Write; @@ -99,7 +99,7 @@ mod guest { use crate::Test; use nix::libc::VMADDR_CID_HOST; - use nix::sys::socket::{connect, socket, AddressFamily, SockFlag, SockType, VsockAddr}; + use nix::sys::socket::{AddressFamily, SockFlag, SockType, VsockAddr, connect, socket}; use std::io::Write; use std::os::fd::AsRawFd; From eb4188bcf58438f2db24d0fd775a89747b4d98d0 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 4 Jun 2026 16:03:58 +0200 Subject: [PATCH 41/41] libkrun: remove legacy krun_set_exec/env/workdir/rlimits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gate krun_set_exec, krun_set_env, krun_set_workdir, krun_set_rlimits and their supporting code behind cfg(tee/aws-nitro). These functions populated the kernel cmdline with KRUN_INIT/KRUN_WORKDIR/KRUN_RLIMITS environment variables — the non-TEE path now uses krun_inject_init with .krun_config.json instead. - Gate ContextConfig fields (workdir, exec_path, env, args, rlimits) - Gate setter/getter methods and collapse_str_array helper - Split kernel cmdline construction: non-TEE only emits init= (from krun_inject_init) and block root; TEE keeps the full KRUN_* env var scheme - Remove function declarations from libkrun.h TEE builds (libkrun-sev, libkrun-tdx) retain these functions unchanged — launch-tee.c still uses them. Assisted-by: OpenCode:claude-opus-4.6 Signed-off-by: Matej Hrica --- include/libkrun.h | 59 ------------------------------------------ src/libkrun/src/lib.rs | 30 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 59 deletions(-) diff --git a/include/libkrun.h b/include/libkrun.h index 510b59027..b72673f77 100644 --- a/include/libkrun.h +++ b/include/libkrun.h @@ -714,18 +714,6 @@ int32_t krun_add_vhost_user_device(uint32_t ctx_id, uint16_t num_queues, const uint16_t *queue_sizes); -/** - * Configures a map of rlimits to be set in the guest before starting the isolated binary. - * - * Arguments: - * "ctx_id" - the configuration context ID. - * "rlimits" - an array of string pointers with format "RESOURCE=RLIM_CUR:RLIM_MAX". - * - * Returns: - * Zero on success or a negative error number on failure. - */ -int32_t krun_set_rlimits(uint32_t ctx_id, const char *const rlimits[]); - /** * Sets the SMBIOS OEM Strings. * @@ -738,39 +726,6 @@ int32_t krun_set_rlimits(uint32_t ctx_id, const char *const rlimits[]); */ int32_t krun_set_smbios_oem_strings(uint32_t ctx_id, const char *const oem_strings[]); -/** - * Sets the working directory for the executable to be run inside the microVM. - * - * Arguments: - * "ctx_id" - the configuration context ID. - * "workdir_path" - the path to the working directory, relative to the root filesystem. - * - * Returns: - * Zero on success or a negative error number on failure. - */ -int32_t krun_set_workdir(uint32_t ctx_id, - const char *workdir_path); - -/** - * Sets the path to the executable to be run inside the microVM, the arguments to be passed to the - * executable, and the environment variables to be configured in the context of the executable. - * - * Arguments: - * "ctx_id" - the configuration context ID. - * "exec_path" - the path to the executable, relative to the root filesystem. - * "argv" - an array of string pointers to be passed as arguments. - * "envp" - an array of string pointers to be injected as environment variables into the - * context of the executable. If NULL, it will auto-generate an array collecting the - * the variables currently present in the environment. - * - * Returns: - * Zero on success or a negative error number on failure. - */ -int32_t krun_set_exec(uint32_t ctx_id, - const char *exec_path, - const char *const argv[], - const char *const envp[]); - /** * Sets the path to the firmware to be loaded into the microVM. * @@ -809,20 +764,6 @@ int32_t krun_set_kernel(uint32_t ctx_id, const char *initramfs, const char *cmdline); -/** - * Sets environment variables to be configured in the context of the executable. - * - * Arguments: - * "ctx_id" - the configuration context ID. - * "envp" - an array of string pointers to be injected as environment variables into the - * context of the executable. If NULL, it will auto-generate an array collecting the - * the variables currently present in the environment. - * - * Returns: - * Zero on success or a negative error number on failure. - */ -int32_t krun_set_env(uint32_t ctx_id, const char *const envp[]); - /** * Sets the file path to the TEE configuration file. Only available in libkrun-sev. * diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 04b288e8d..4cbb244cf 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -140,10 +140,15 @@ impl KrunfwBindings { struct ContextConfig { krunfw: Option, vmr: VmResources, + #[cfg(any(feature = "tee", feature = "aws-nitro"))] workdir: Option, + #[cfg(any(feature = "tee", feature = "aws-nitro"))] exec_path: Option, + #[cfg(any(feature = "tee", feature = "aws-nitro"))] env: Option, + #[cfg(any(feature = "tee", feature = "aws-nitro"))] args: Option, + #[cfg(any(feature = "tee", feature = "aws-nitro"))] rlimits: Option, net_index: u8, tsi_port_map: Option>, @@ -169,10 +174,12 @@ struct ContextConfig { } impl ContextConfig { + #[cfg(any(feature = "tee", feature = "aws-nitro"))] fn set_workdir(&mut self, workdir: String) { self.workdir = Some(workdir); } + #[cfg(any(feature = "tee", feature = "aws-nitro"))] fn get_workdir(&self) -> String { match &self.workdir { Some(workdir) => format!("KRUN_WORKDIR={workdir}"), @@ -180,10 +187,12 @@ impl ContextConfig { } } + #[cfg(any(feature = "tee", feature = "aws-nitro"))] fn set_exec_path(&mut self, exec_path: String) { self.exec_path = Some(exec_path); } + #[cfg(any(feature = "tee", feature = "aws-nitro"))] fn get_exec_path(&self) -> String { match &self.exec_path { Some(exec_path) => format!("KRUN_INIT={exec_path}"), @@ -219,10 +228,12 @@ impl ContextConfig { "".to_string() } + #[cfg(any(feature = "tee", feature = "aws-nitro"))] fn set_env(&mut self, env: String) { self.env = Some(env); } + #[cfg(any(feature = "tee", feature = "aws-nitro"))] fn get_env(&self) -> String { match &self.env { Some(env) => env.clone(), @@ -230,10 +241,12 @@ impl ContextConfig { } } + #[cfg(any(feature = "tee", feature = "aws-nitro"))] fn set_args(&mut self, args: String) { self.args = Some(args); } + #[cfg(any(feature = "tee", feature = "aws-nitro"))] fn get_args(&self) -> String { match &self.args { Some(args) => args.clone(), @@ -241,10 +254,12 @@ impl ContextConfig { } } + #[cfg(any(feature = "tee", feature = "aws-nitro"))] fn set_rlimits(&mut self, rlimits: String) { self.rlimits = Some(rlimits); } + #[cfg(any(feature = "tee", feature = "aws-nitro"))] fn get_rlimits(&self) -> String { match &self.rlimits { Some(rlimits) => format!("KRUN_RLIMITS={rlimits}"), @@ -1056,6 +1071,7 @@ pub unsafe extern "C" fn krun_set_port_map(ctx_id: u32, c_port_map: *const *cons #[allow(clippy::missing_safety_doc)] #[unsafe(no_mangle)] +#[cfg(any(feature = "tee", feature = "aws-nitro"))] pub unsafe extern "C" fn krun_set_rlimits(ctx_id: u32, c_rlimits: *const *const c_char) -> i32 { unsafe { let rlimits = if c_rlimits.is_null() { @@ -1092,6 +1108,7 @@ pub unsafe extern "C" fn krun_set_rlimits(ctx_id: u32, c_rlimits: *const *const #[allow(clippy::missing_safety_doc)] #[unsafe(no_mangle)] +#[cfg(any(feature = "tee", feature = "aws-nitro"))] pub unsafe extern "C" fn krun_set_workdir(ctx_id: u32, c_workdir_path: *const c_char) -> i32 { unsafe { let workdir_path = match CStr::from_ptr(c_workdir_path).to_str() { @@ -1110,6 +1127,7 @@ pub unsafe extern "C" fn krun_set_workdir(ctx_id: u32, c_workdir_path: *const c_ } } +#[cfg(any(feature = "tee", feature = "aws-nitro"))] unsafe fn collapse_str_array(array: &[*const c_char]) -> Result { unsafe { let mut strvec = Vec::new(); @@ -1130,6 +1148,7 @@ unsafe fn collapse_str_array(array: &[*const c_char]) -> Result i32 { unsafe { let env = if !c_envp.is_null() { @@ -2749,6 +2769,16 @@ pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 { #[cfg(any(feature = "tee", feature = "aws-nitro"))] let init_arg = format!(" init={INIT_PATH}"); + #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] + let kernel_cmdline = KernelCmdlineConfig { + prolog: Some(format!( + "{DEFAULT_KERNEL_CMDLINE}{init_arg} {}", + ctx_cfg.get_block_root(), + )), + krun_env: None, + epilog: None, + }; + #[cfg(any(feature = "tee", feature = "aws-nitro"))] let kernel_cmdline = KernelCmdlineConfig { prolog: Some(format!("{DEFAULT_KERNEL_CMDLINE}{init_arg}")), krun_env: Some(format!(