From c4012449f3192e34b07e3850b887e852bc223d25 Mon Sep 17 00:00:00 2001 From: Jan Noha Date: Wed, 3 Dec 2025 13:21:13 +0100 Subject: [PATCH 1/2] init.c: port to FreeBSD Makefile: add optional BSD build target Signed-off-by: Jan Noha --- .gitignore | 1 + Makefile | 53 +++++++++++++++++- init/init.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 201 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index c0b4e6771..825c31f76 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ examples/consoles examples/rootfs_fedora test-prefix /linux-sysroot +/freebsd-sysroot diff --git a/Makefile b/Makefile index 8e078721b..0258183d8 100644 --- a/Makefile +++ b/Makefile @@ -81,10 +81,14 @@ ifeq ($(TIMESYNC),1) INIT_DEFS += -D__TIMESYNC__ endif +CLANG = /usr/bin/clang + OS = $(shell uname -s) ARCH = $(shell uname -m) DEBIAN_DIST ?= bookworm ROOTFS_DIR = linux-sysroot +FREEBSD_VERSION ?= 14.3-RELEASE +FREEBSD_ROOTFS_DIR = freebsd-sysroot KRUN_BINARY_Linux = libkrun$(VARIANT).so.$(FULL_VERSION) KRUN_SONAME_Linux = libkrun$(VARIANT).so.$(ABI_VERSION) @@ -121,7 +125,7 @@ else SYSROOT_TARGET = endif # Cross-compile on macOS with the LLVM linker (brew install lld) - CC_LINUX=/usr/bin/clang -target $(ARCH)-linux-gnu -fuse-ld=lld -Wl,-strip-debug --sysroot $(SYSROOT_LINUX) -Wno-c23-extensions + CC_LINUX=$(CLANG) -target $(ARCH)-linux-gnu -fuse-ld=lld -Wl,-strip-debug --sysroot $(SYSROOT_LINUX) -Wno-c23-extensions else # Build on Linux host CC_LINUX=$(CC) @@ -134,6 +138,28 @@ $(INIT_BINARY): $(INIT_SRC) $(SYSROOT_TARGET) $(CC_LINUX) -O2 -static -Wall $(INIT_DEFS) -o $@ $(INIT_SRC) $(INIT_DEFS) endif +ifeq ($(OS),Darwin) +# If SYSROOT_BSD is not set and we're on macOS, generate sysroot automatically +ifeq ($(SYSROOT_BSD),) + SYSROOT_BSD = $(FREEBSD_ROOTFS_DIR) + SYSROOT_BSD_TARGET = $(FREEBSD_ROOTFS_DIR)/.sysroot_ready +else + SYSROOT_BSD_TARGET = +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) +else + # Build on FreeBSD host + CC_BSD=$(CC) + SYSROOT_BSD_TARGET = +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 $(INIT_DEFS) -lutil -o $@ $(INIT_SRC) $(INIT_DEFS) +endif + NITRO_INIT_BINARY= init/nitro/init $(NITRO_INIT_BINARY): $(NITRO_INIT_SRC) $(CC) -O2 -static -Wall $(NITRO_INIT_LD_FLAGS) -o $@ $(NITRO_INIT_SRC) $(NITRO_INIT_LD_FLAGS) @@ -166,11 +192,27 @@ $(PACKAGES_FILE): @mkdir -p $(ROOTFS_TMP) @curl -fL -o $@ https://deb.debian.org/debian/dists/$(DEBIAN_DIST)/main/binary-$(ARCH)/Packages.xz +# FreeBSD sysroot preparation rules for cross-compilation on macOS +FREEBSD_BASE_TXZ = $(FREEBSD_ROOTFS_DIR)/base.txz + +.INTERMEDIATE: $(FREEBSD_BASE_TXZ) + +$(FREEBSD_ROOTFS_DIR)/.sysroot_ready: $(FREEBSD_BASE_TXZ) + @echo "Extracting FreeBSD base to $(FREEBSD_ROOTFS_DIR)..." + @cd $(FREEBSD_ROOTFS_DIR) && tar xJf base.txz 2>/dev/null || true + @touch $@ + +$(FREEBSD_BASE_TXZ): + @echo "Downloading FreeBSD $(FREEBSD_VERSION) base for $(ARCH)..." + @mkdir -p $(FREEBSD_ROOTFS_DIR) + @curl -fL -o $@ https://download.freebsd.org/releases/$(ARCH)/$(FREEBSD_VERSION)/base.txz + clean-sysroot: rm -rf $(ROOTFS_DIR) + rm -rf $(FREEBSD_ROOTFS_DIR) -$(LIBRARY_RELEASE_$(OS)): $(INIT_BINARY) +$(LIBRARY_RELEASE_$(OS)): $(INIT_BINARY) $(INIT_BINARY_BSD) cargo build --release $(FEATURE_FLAGS) ifeq ($(SEV),1) mv target/release/libkrun.so target/release/$(KRUN_BASE_$(OS)) @@ -189,7 +231,7 @@ endif endif cp target/release/$(KRUN_BASE_$(OS)) $(LIBRARY_RELEASE_$(OS)) -$(LIBRARY_DEBUG_$(OS)): $(INIT_BINARY) +$(LIBRARY_DEBUG_$(OS)): $(INIT_BINARY) $(INIT_BINARY_BSD) cargo build $(FEATURE_FLAGS) ifeq ($(SEV),1) mv target/debug/libkrun.so target/debug/$(KRUN_BASE_$(OS)) @@ -221,7 +263,12 @@ install: libkrun.pc cd $(DESTDIR)$(PREFIX)/$(LIBDIR_$(OS))/ ; ln -sf $(KRUN_BINARY_$(OS)) $(KRUN_SONAME_$(OS)) ; ln -sf $(KRUN_SONAME_$(OS)) $(KRUN_BASE_$(OS)) clean: +ifeq ($(BUILD_INIT),1) rm -f $(INIT_BINARY) +endif +ifeq ($(BUILD_BSD_INIT),1) + rm -f $(INIT_BINARY_BSD) +endif cargo clean rm -rf test-prefix cd tests; cargo clean diff --git a/init/init.c b/init/init.c index 855b3778c..f9eb9ab5e 100644 --- a/init/init.c +++ b/init/init.c @@ -17,12 +17,21 @@ #include #include #include +#if __FreeBSD__ +#include +#include +#include +#include +#else #include +#endif #include #include #include +#if __linux__ #include +#endif #include "jsmn.h" @@ -50,6 +59,92 @@ static char *snp_get_luks_passphrase(char *, char *, char *, int *); char DEFAULT_KRUN_INIT[] = "/bin/sh"; +#if __FreeBSD__ + +#define b64_ntop __b64_ntop +#define b64_pton __b64_pton +/* There are no header files for these functions. */ + +int b64_ntop(unsigned char const *src, size_t srclength, char *target, + size_t targsize); +int b64_pton(char const *src, unsigned char *target, size_t targsize); + +static char *get_kenv(const char *name) +{ + static char kenv_value[KENV_MVALLEN + 1]; + if (kenv(KENV_GET, name, kenv_value, KENV_MVALLEN + 1) < 0) { + return NULL; + } + return kenv_value; +} + +static int get_krun_init_argv_flat(char *buf, int buf_len) +{ + int len_total = 0; + int len_part; + char *buf_ptr = buf; + char name_buf[32]; + int idx = 0; + + while (true) { + snprintf(name_buf, sizeof(name_buf), "KRUN_INIT_ARGV%d", idx++); + char *argv_b64 = get_kenv(name_buf); + if (!argv_b64) { + break; + } + len_part = + b64_pton(argv_b64, (unsigned char *)buf_ptr, buf_len - len_total); + buf_ptr += len_part; + len_total += len_part; + } + + return len_total; +} + +#define getenv get_kenv + +#define _PATH_CONSOLE "/dev/console" +#define _PATH_DEVNULL "/dev/null" +#define _PATH_INITLOG "/init.log" +/* + * Start a session and allocate a controlling terminal. + * Only called by children of init after forking. + */ +static void open_console(void) +{ + int fd; + + /* + * Try to open /dev/console. Open the device with O_NONBLOCK to + * prevent potential blocking on a carrier. + */ + revoke(_PATH_CONSOLE); + if ((fd = open(_PATH_CONSOLE, O_RDWR | O_NONBLOCK)) != -1) { + (void)fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK); + if (login_tty(fd) == 0) + return; + close(fd); + } + + /* No luck. Log output to file if possible. */ + if ((fd = open(_PATH_DEVNULL, O_RDWR)) == -1) { + _exit(1); + } + if (fd != STDIN_FILENO) { + dup2(fd, STDIN_FILENO); + close(fd); + } + fd = open(_PATH_INITLOG, O_WRONLY | O_APPEND | O_CREAT, 0644); + if (fd == -1) + dup2(STDIN_FILENO, STDOUT_FILENO); + else if (fd != STDOUT_FILENO) { + dup2(fd, STDOUT_FILENO); + close(fd); + } + dup2(STDOUT_FILENO, STDERR_FILENO); +} +#endif + static void set_rlimits(const char *rlimits) { unsigned long long int lim_id, lim_cur, lim_max; @@ -395,6 +490,7 @@ static int chroot_luks() static int mount_filesystems() { +#if __linux__ char *const DIRS_LEVEL1[] = {"/dev", "/proc", "/sys"}; char *const DIRS_LEVEL2[] = {"/dev/pts", "/dev/shm"}; int i; @@ -451,7 +547,7 @@ static int mount_filesystems() /* May fail if already exists and that's fine. */ symlink("/proc/self/fd", "/dev/fd"); - +#endif return 0; } @@ -1003,6 +1099,7 @@ void set_exit_code(int code) close(fd); } +#if __linux__ int try_mount(const char *source, const char *target, const char *fstype, unsigned long mountflags, const void *data) { @@ -1037,11 +1134,19 @@ int try_mount(const char *source, const char *target, const char *fstype, return mount_status; } +#endif + +char *clone_str(const char *str) +{ + if (str == NULL) { + return NULL; + } + return strdup(str); +} int main(int argc, char **argv) { struct ifreq ifr; - int fd; int sockfd; int status; int saved_errno; @@ -1051,14 +1156,21 @@ int main(int argc, char **argv) char *krun_home; char *krun_term; char *krun_init; +#if __linux__ + int fd; char *krun_root; char *krun_root_fstype; char *krun_root_options; +#endif char *env_init_pid1; char *config_workdir, *env_workdir; char *rlimits; char **config_argv, **exec_argv; +#if __FreeBSD__ + open_console(); +#endif + #ifdef TDX if (mkdir("/tmp", 0755) < 0 && errno != EEXIST) { perror("mkdir(/tmp)"); @@ -1092,21 +1204,25 @@ int main(int argc, char **argv) exit(-2); } - krun_root = getenv("KRUN_BLOCK_ROOT_DEVICE"); +#if __linux__ + krun_root = clone_str(getenv("KRUN_BLOCK_ROOT_DEVICE")); if (krun_root) { if (mkdir("/newroot", 0755) < 0 && errno != EEXIST) { perror("mkdir(/newroot)"); exit(-1); } - krun_root_fstype = getenv("KRUN_BLOCK_ROOT_FSTYPE"); - krun_root_options = getenv("KRUN_BLOCK_ROOT_OPTIONS"); + krun_root_fstype = clone_str(getenv("KRUN_BLOCK_ROOT_FSTYPE")); + krun_root_options = clone_str(getenv("KRUN_BLOCK_ROOT_OPTIONS")); if (try_mount(krun_root, "/newroot", krun_root_fstype, 0, krun_root_options) < 0) { perror("mount KRUN_BLOCK_ROOT_DEVICE"); exit(-1); } + free(krun_root); + free(krun_root_fstype); + free(krun_root_options); chdir("/newroot"); @@ -1137,10 +1253,15 @@ int main(int argc, char **argv) perror("Couldn't set shared propagation on the root mount"); exit(-1); } +#endif setsid(); ioctl(0, TIOCSCTTY, 1); +#if __FreeBSD__ + setlogin("root"); +#endif + sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd >= 0) { memset(&ifr, 0, sizeof ifr); @@ -1184,16 +1305,35 @@ int main(int argc, char **argv) chdir(config_workdir); } +#if __FreeBSD__ + exec_argv = malloc(MAX_ARGS * sizeof(char *)); +#else exec_argv = argv; +#endif krun_init = getenv("KRUN_INIT"); if (krun_init) { - exec_argv[0] = krun_init; + exec_argv[0] = clone_str(krun_init); } else if (config_argv) { exec_argv = config_argv; } else { exec_argv[0] = &DEFAULT_KRUN_INIT[0]; } +#if __FreeBSD__ + int i = 1; + static char argv_flat[512]; + int argv_flat_len = get_krun_init_argv_flat(argv_flat, sizeof(argv_flat)); + + int j = 0; + while (j < argv_flat_len) { + exec_argv[i++] = &argv_flat[j]; + for (; j < argv_flat_len && argv_flat[j] != 0; j++) { + } + j++; + } + exec_argv[i] = NULL; +#endif + env_init_pid1 = getenv("KRUN_INIT_PID1"); if (env_init_pid1 && *env_init_pid1 == '1') { init_pid1 = true; @@ -1219,9 +1359,13 @@ int main(int argc, char **argv) } if (child == 0) { // child exec_init: +#if __FreeBSD__ + open_console(); +#else if (setup_redirects() < 0) { exit(125); } +#endif if (execvp(exec_argv[0], exec_argv) < 0) { saved_errno = errno; printf("Couldn't execute '%s' inside the vm: %s\n", exec_argv[0], From a4232cb97ace5be06558c5b4bdb9c54c366cea64 Mon Sep 17 00:00:00 2001 From: Jan Noha Date: Wed, 3 Dec 2025 13:29:05 +0100 Subject: [PATCH 2/2] krun_set_exec now also preserves raw argv (passed as base64-encoded variables) which is needed when booting FreeBSD Signed-off-by: Jan Noha --- Cargo.lock | 1 + src/libkrun/Cargo.toml | 1 + src/libkrun/src/lib.rs | 33 ++++++++++++++++++++++++++++++++- src/vmm/src/builder.rs | 8 +++++++- 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c713e34f3..747cb5e8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -946,6 +946,7 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" name = "libkrun" version = "1.16.0" dependencies = [ + "base64", "crossbeam-channel", "devices", "env_logger", diff --git a/src/libkrun/Cargo.toml b/src/libkrun/Cargo.toml index cba704732..2b0a3f0a6 100644 --- a/src/libkrun/Cargo.toml +++ b/src/libkrun/Cargo.toml @@ -33,6 +33,7 @@ polly = { path = "../polly" } utils = { path = "../utils" } vmm = { path = "../vmm" } rand = "0.9.2" +base64 = "0.22.1" [target.'cfg(target_os = "macos")'.dependencies] hvf = { path = "../hvf" } diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 2df75efc1..6b689947f 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -1,6 +1,7 @@ #[macro_use] extern crate log; +use base64::prelude::*; use crossbeam_channel::unbounded; #[cfg(feature = "blk")] use devices::virtio::block::ImageType; @@ -1191,6 +1192,24 @@ unsafe fn collapse_str_array(array: &[*const c_char]) -> Result Vec { + let mut bytevec = Vec::new(); + + for item in array.iter().take(MAX_ARGS) { + if item.is_null() { + break; + } else { + let cs = CStr::from_ptr(*item); + bytevec.extend_from_slice(cs.to_bytes()); + bytevec.push(0); + } + } + + bytevec +} + #[allow(clippy::format_collect)] #[allow(clippy::missing_safety_doc)] #[no_mangle] @@ -1221,7 +1240,14 @@ pub unsafe extern "C" fn krun_set_exec( "".to_string() }; - let env = if !c_envp.is_null() { + let args_raw = if !c_argv.is_null() { + let argv_array: &[*const c_char] = slice::from_raw_parts(c_argv, MAX_ARGS); + flatten_cstr_array(argv_array) + } else { + vec![] + }; + + let mut env = if !c_envp.is_null() { let envp_array: &[*const c_char] = slice::from_raw_parts(c_envp, MAX_ARGS); match collapse_str_array(envp_array) { Ok(s) => s, @@ -1236,6 +1262,11 @@ pub unsafe extern "C" fn krun_set_exec( .collect() }; + // KRUN_INIT_ARGVXX="" must have at most 128 bytes (KENV_MVALLEN on FreeBSD) + for (i, args_part) in args_raw.chunks(78).enumerate() { + env += &format!(" KRUN_INIT_ARGV{}={}", i, BASE64_STANDARD.encode(args_part)); + } + match CTX_MAP.lock().unwrap().entry(ctx_id) { Entry::Occupied(mut ctx_cfg) => { let cfg = ctx_cfg.get_mut(); diff --git a/src/vmm/src/builder.rs b/src/vmm/src/builder.rs index 7dc8f3849..d9119f7cb 100644 --- a/src/vmm/src/builder.rs +++ b/src/vmm/src/builder.rs @@ -578,11 +578,15 @@ pub fn build_microvm( )?; let vcpu_config = vm_resources.vcpu_config(); + let mut kernel_is_freebsd = false; // Clone the command-line so that a failed boot doesn't pollute the original. #[allow(unused_mut)] let mut kernel_cmdline = Cmdline::new(arch::CMDLINE_MAX_SIZE); if let Some(cmdline) = payload_config.kernel_cmdline { + if cmdline.starts_with("FreeBSD:") { + kernel_is_freebsd = true; + } kernel_cmdline.insert_str(cmdline.as_str()).unwrap(); } else if let Some(cmdline) = &vm_resources.kernel_cmdline.prolog { kernel_cmdline.insert_str(cmdline).unwrap(); @@ -1063,7 +1067,9 @@ pub fn build_microvm( } if let Some(s) = &vm_resources.kernel_cmdline.epilog { - vmm.kernel_cmdline.insert_str(s).unwrap(); + if !kernel_is_freebsd { + vmm.kernel_cmdline.insert_str(s).unwrap(); + } }; // Write the kernel command line to guest memory. This is x86_64 specific, since on