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/.gitignore b/.gitignore index 8a68e65a4..3eba43934 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ __pycache__ *.pyc *~ /libkrun.pc +/libkrun_init.pc +init/init init/nitro/init examples/chroot_vm examples/launch-tee @@ -16,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 3847ccbde..ef694b533 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", @@ -382,15 +382,107 @@ 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.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 +513,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 +590,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 +613,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 +633,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", ] @@ -530,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" @@ -560,9 +683,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 +696,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 +717,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 +755,7 @@ dependencies = [ "linux-loader", "tdx", "vm-memory", - "vmm-sys-util 0.14.0", + "vmm-sys-util", ] [[package]] @@ -657,7 +782,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 +807,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 +822,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 +838,52 @@ dependencies = [ "log", ] +[[package]] +name = "krun-init" +version = "0.1.0-1.18.1" +dependencies = [ + "anyhow", + "libc", + "nix 0.30.1", + "serde", + "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" dependencies = [ "bindgen", - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "log", "static_assertions", @@ -753,7 +918,7 @@ dependencies = [ "pkg-config", "remain", "thiserror 1.0.69", - "vmm-sys-util 0.14.0", + "vmm-sys-util", "winapi", "zerocopy", ] @@ -775,7 +940,7 @@ dependencies = [ "libc", "log", "nix 0.30.1", - "vmm-sys-util 0.14.0", + "vmm-sys-util", "windows-sys", ] @@ -784,7 +949,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 +975,7 @@ dependencies = [ "serde_json", "tdx", "vm-memory", - "vmm-sys-util 0.14.0", + "vmm-sys-util", "zstd", ] @@ -824,23 +989,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 +1016,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" @@ -866,6 +1031,7 @@ dependencies = [ "krun-devices", "krun-display", "krun-hvf", + "krun-init-blob-via-cdylib-weak", "krun-input", "krun-polly", "krun-utils", @@ -890,18 +1056,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 +1073,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 +1132,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 +1145,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 +1170,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 +1179,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 +1236,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "plain" -version = "0.2.3" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "portable-atomic" @@ -1100,9 +1248,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 +1306,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 +1350,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 +1392,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 +1411,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 +1426,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 +1462,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 +1490,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 +1518,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 +1577,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 +1588,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 +1643,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 +1683,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 +1713,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 +1735,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 +1771,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 +1783,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 +1806,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 +1824,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 +1834,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 +1847,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 +1882,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 +1934,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 +1989,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 +2031,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..314e8fdd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,11 @@ [workspace] members = [ + "init/init-binary", "src/libkrun", - "src/init-blob", + "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 6f6a3c4fa..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 \ @@ -20,8 +32,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 @@ -61,6 +71,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 @@ -96,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 @@ -140,6 +164,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),) @@ -150,16 +175,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 = 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/init-binary/Cargo.toml \ + --target $(FREEBSD_RUST_TARGET) + cp target/$(FREEBSD_RUST_TARGET)/release/krun-init $@ endif # Sysroot preparation rules for cross-compilation on macOS @@ -227,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) @@ -237,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 @@ -248,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) @@ -265,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/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/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/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/boot_efi.c b/examples/boot_efi.c index 5105d46df..5b84e2c56 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"); @@ -191,13 +191,19 @@ 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"); 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/chroot_vm.c b/examples/chroot_vm.c index b0ab0a05e..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 @@ -308,6 +310,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 @@ -357,14 +365,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, @@ -405,7 +407,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; @@ -419,8 +421,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; @@ -450,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 30a17a492..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, ...) { @@ -119,18 +120,12 @@ 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(); 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; @@ -195,16 +190,33 @@ 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; } - 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/external_kernel.c b/examples/external_kernel.c index 14649881d..9b2b96f6c 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; @@ -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/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 660a2e21a..dbafd9e87 100644 --- a/examples/gui_vm/src/main.rs +++ b/examples/gui_vm/src/main.rs @@ -6,26 +6,25 @@ 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_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, 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_set_vm_config, krun_start_enter, + 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; 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; use std::str::FromStr; use std::sync::LazyLock; use std::thread; @@ -150,6 +149,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 @@ -160,17 +166,25 @@ 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(); - 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/launch-tee.c b/examples/launch-tee.c index 063cdd5f3..cc64a6c48 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"); @@ -67,8 +67,14 @@ 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_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 +120,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; diff --git a/examples/nitro.c b/examples/nitro.c index 2a80e02fd..91eb8753e 100644 --- a/examples/nitro.c +++ b/examples/nitro.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -180,7 +181,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"); @@ -203,25 +204,45 @@ 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; } // 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; } - // 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) { diff --git a/include/libkrun.h b/include/libkrun.h index 9a71e946d..b72673f77 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 @@ -103,60 +85,12 @@ 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); - -/** - * 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 +117,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 +185,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`. * @@ -283,22 +211,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. * @@ -371,7 +283,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 @@ -492,57 +404,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. * @@ -565,8 +426,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[]); @@ -852,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. * @@ -876,40 +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 configured with - * "krun_set_root". - * - * 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 configured with "krun_set_root". - * "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. * @@ -948,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. * @@ -1005,10 +807,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. * @@ -1034,22 +832,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. * @@ -1153,59 +935,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 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 - * filesystem. Must be called before krun_set_root(). - * - * 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. * @@ -1237,6 +966,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. * @@ -1262,20 +1011,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. * @@ -1293,9 +1028,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` @@ -1323,9 +1056,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. @@ -1348,8 +1079,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/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/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/init-binary/Cargo.toml b/init/init-binary/Cargo.toml new file mode 100644 index 000000000..1748ddfeb --- /dev/null +++ b/init/init-binary/Cargo.toml @@ -0,0 +1,23 @@ +[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] +anyhow = "1" +libc = "0.2" +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/init-binary/src/config.rs b/init/init-binary/src/config.rs new file mode 100644 index 000000000..aed73827c --- /dev/null +++ b/init/init-binary/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/init-binary/src/dhcp.rs b/init/init-binary/src/dhcp.rs new file mode 100644 index 000000000..89e4d62aa --- /dev/null +++ b/init/init-binary/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/init-binary/src/env.rs b/init/init-binary/src/env.rs new file mode 100644 index 000000000..a242ae4cb --- /dev/null +++ b/init/init-binary/src/env.rs @@ -0,0 +1,176 @@ +use std::env; +#[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}"); + } + } +} + +/// 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); +} + +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) }; + } +} diff --git a/init/init-binary/src/exec.rs b/init/init-binary/src/exec.rs new file mode 100644 index 000000000..577f41308 --- /dev/null +++ b/init/init-binary/src/exec.rs @@ -0,0 +1,125 @@ +#[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(); + #[cfg(target_os = "freebsd")] + crate::freebsd::open_console(); + + 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/init-binary/src/freebsd.rs b/init/init-binary/src/freebsd.rs new file mode 100644 index 000000000..13a52d742 --- /dev/null +++ b/init/init-binary/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) }; +} diff --git a/init/init-binary/src/fs.rs b/init/init-binary/src/fs.rs new file mode 100644 index 000000000..5c0fab924 --- /dev/null +++ b/init/init-binary/src/fs.rs @@ -0,0 +1,186 @@ +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; + +/// 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 .") +} + +/// 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")?; + + 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") +} diff --git a/init/init-binary/src/main.rs b/init/init-binary/src/main.rs new file mode 100644 index 000000000..f89558d63 --- /dev/null +++ b/init/init-binary/src/main.rs @@ -0,0 +1,106 @@ +mod config; +#[cfg(target_os = "linux")] +mod dhcp; +mod env; +mod exec; +#[cfg(target_os = "freebsd")] +mod freebsd; +#[cfg(target_os = "linux")] +mod fs; +#[cfg(feature = "timesync")] +mod timesync; + +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()?; + + #[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()?; + } + + unsafe { + libc::setsid(); + 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 = "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(); + + #[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)?; + } + + 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()] + }; + + #[cfg(feature = "timesync")] + timesync::run(); + + exec::run_workload(&argv); +} diff --git a/init/init-binary/src/timesync.rs b/init/init-binary/src/timesync.rs new file mode 100644 index 000000000..128365112 --- /dev/null +++ b/init/init-binary/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/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 new file mode 100644 index 000000000..ced8f0951 --- /dev/null +++ b/init/init-blob/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "init-blob" +version = "0.1.0-1.18.1" +edition = "2021" +description = "Default init binary blob for libkrun guests" +license = "Apache-2.0" +repository = "https://github.com/containers/libkrun" +build = "build.rs" + +[features] +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/build.rs b/init/init-blob/build.rs new file mode 100644 index 000000000..4f3e9b547 --- /dev/null +++ b/init/init-blob/build.rs @@ -0,0 +1,163 @@ +use std::env; +use std::path::PathBuf; +use std::process::Command; + +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(); + + // 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/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. + let init_target_dir = out_dir.join("init-target"); + let init_bin = out_dir.join("init"); + + 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={}", + 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. + 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 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"); + } + + 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 cfg!(feature = "timesync") { + 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 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 = env::var_os("KRUN_INIT_BINARY_PATH") + .map(PathBuf::from) + .unwrap_or_else(build_rust_init); + println!( + "cargo:rustc-env=KRUN_INIT_BINARY_PATH={}", + init_binary_path.display() + ); + println!("cargo:rerun-if-env-changed=KRUN_INIT_BINARY_PATH"); +} 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 new file mode 100644 index 000000000..347066c85 --- /dev/null +++ b/init/init-blob/src/lib.rs @@ -0,0 +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/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/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 */ 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 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..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] @@ -33,7 +34,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 +52,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 deleted file mode 100644 index 6be3f306c..000000000 --- a/src/init-blob/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "init-blob" -version = "0.1.0-1.18.1" -edition = "2021" -description = "Default init binary blob for libkrun guests" -license = "Apache-2.0" -repository = "https://github.com/containers/libkrun" -build = "build.rs" - -[lib] -path = "src/lib.rs" diff --git a/src/init-blob/build.rs b/src/init-blob/build.rs deleted file mode 100644 index 49a4346d2..000000000 --- a/src/init-blob/build.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::ffi::OsStr; -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"); - - let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); - 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() - ); - println!( - "cargo:rerun-if-changed={}", - libkrun_root.join("init/dhcp.h").display() - ); - - 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 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 !status.success() { - panic!("failed to compile init/init.c: {status}"); - } - init_bin -} - -fn main() { - let init_binary_path = std::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 - }); - println!( - "cargo:rustc-env=KRUN_INIT_BINARY_PATH={}", - init_binary_path.display() - ); - println!("cargo:rerun-if-env-changed=KRUN_INIT_BINARY_PATH"); -} diff --git a/src/init-blob/src/lib.rs b/src/init-blob/src/lib.rs deleted file mode 100644 index 4397da679..000000000 --- a/src/init-blob/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub static INIT_BINARY: &[u8] = include_bytes!(env!("KRUN_INIT_BINARY_PATH")); 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..c765d9ed4 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"] @@ -19,10 +19,12 @@ 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" 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" @@ -31,7 +33,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" } @@ -40,11 +42,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/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 696642325..4cbb244cf 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) @@ -86,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() }); @@ -142,36 +136,26 @@ impl KrunfwBindings { } } -#[derive(Clone)] -#[cfg(feature = "net")] -enum LegacyNetworkConfig { - VirtioNetPasst(RawFd), - VirtioNetGvproxy(PathBuf), -} - #[derive(Default)] 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, - #[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, #[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, @@ -179,18 +163,23 @@ 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, + /// Kernel init arg set by `krun_inject_init` (e.g. `"init=/init.krun"`). #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] - disable_implicit_init: bool, + kernel_init_arg: Option, } 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}"), @@ -198,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}"), @@ -237,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(), @@ -248,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(), @@ -259,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}"), @@ -275,36 +272,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() - } - } - - #[cfg(feature = "net")] - fn set_net_mac(&mut self, mac: [u8; 6]) { - self.legacy_mac = Some(mac); + self.block_cfgs.clone() } fn set_port_map(&mut self, new_port_map: HashMap) -> Result<(), ()> { @@ -461,26 +431,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; @@ -529,6 +479,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 } } @@ -596,44 +554,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")))] @@ -698,17 +618,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, @@ -718,16 +633,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")] @@ -876,74 +781,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. @@ -972,19 +809,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 @@ -1188,79 +1013,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 { @@ -1319,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() { @@ -1355,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() { @@ -1373,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(); @@ -1393,6 +1148,7 @@ unsafe fn collapse_str_array(array: &[*const c_char]) -> Result i32 { unsafe { let env = if !c_envp.is_null() { @@ -1949,27 +1706,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() { @@ -1992,6 +1734,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 { @@ -2471,13 +2232,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 { @@ -2510,19 +2269,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")))] @@ -2575,6 +2321,95 @@ 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::KrunInitConfigKernelInitArg, + 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; + } + } + + // 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) => { + ctx_cfg.get_mut().kernel_init_arg = Some(kernel_init_arg); + } + Entry::Vacant(_) => return -libc::ENOENT, + } + + KRUN_SUCCESS +} + #[allow(clippy::missing_safety_doc)] #[unsafe(no_mangle)] #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] @@ -2658,49 +2493,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_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) { - 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) { @@ -2968,8 +2760,27 @@ pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 { return -libc::EINVAL; } + #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] + let init_arg = ctx_cfg + .kernel_init_arg + .as_deref() + .map(|a| format!(" {a}")) + .unwrap_or_default(); + #[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={INIT_PATH}")), + 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!( " {} {} {} {} {}", ctx_cfg.get_exec_path(), @@ -2985,22 +2796,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 } => { @@ -3013,33 +2808,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() && ctx_cfg.legacy_net_cfg.is_none(); - #[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 { @@ -3049,10 +2817,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 { @@ -3127,28 +2891,3 @@ 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_set_root(ctx, c"/tmp".as_ptr()); - } - - 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/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" } 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..639cca4b2 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")] @@ -131,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 @@ -189,16 +188,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 +354,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,8 +392,6 @@ 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}; @@ -440,11 +430,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/Cargo.lock b/tests/Cargo.lock index fd0a8a588..9e2d96ab6 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=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-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-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 = "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 810347791..1e86b9f9b 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::{CStr, CString, c_char}; +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::{TestSetup, krun_call}; +use crate::{TestSetup, krun_call, krun_init}; 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,23 +54,26 @@ 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_set_root(ctx, path_str.as_ptr()))?; - 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_add_virtiofs3( + ctx, + c"/dev/root".as_ptr(), + path_str.as_ptr(), + 0, + false, + ))?; + 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/common_freebsd.rs b/tests/test_cases/src/common_freebsd.rs index 274806fd4..f3ce7ed51 100644 --- a/tests/test_cases/src/common_freebsd.rs +++ b/tests/test_cases/src/common_freebsd.rs @@ -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/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 24032033c..3661bf878 100644 --- a/tests/test_cases/src/test_augmentfs.rs +++ b/tests/test_cases/src/test_augmentfs.rs @@ -17,11 +17,12 @@ 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 krun_sys::*; use std::ffi::CString; - use std::ptr::null_mut; + use std::os::fd::AsRawFd; impl Test for TestAugmentFs { fn start_vm(self: Box, test_setup: TestSetup) -> anyhow::Result<()> { @@ -34,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()); @@ -48,17 +48,20 @@ 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))?; - - // 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))?; + 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(), + ))?; // Set up root with NO host directory (NullFs). krun_call!(krun_add_virtiofs3( @@ -79,15 +82,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 @@ -102,17 +101,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, @@ -160,7 +148,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_freebsd_boot.rs b/tests/test_cases/src/test_freebsd_boot.rs index 866007f74..07fbfae59 100644 --- a/tests/test_cases/src/test_freebsd_boot.rs +++ b/tests/test_cases/src/test_freebsd_boot.rs @@ -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..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 @@ -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)?; 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..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 @@ -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)?; diff --git a/tests/test_cases/src/test_multiport_console.rs b/tests/test_cases/src/test_multiport_console.rs index 8fcbf6033..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_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))?; - // Add a default console (as with other tests this uses stdout for writing "OK") krun_call!(krun_add_virtio_console_default( ctx, diff --git a/tests/test_cases/src/test_net/mod.rs b/tests/test_cases/src/test_net/mod.rs index 394464115..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_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))?; // 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 11a445a81..2ee0de0cb 100644 --- a/tests/test_cases/src/test_root_disk_remount.rs +++ b/tests/test_cases/src/test_root_disk_remount.rs @@ -13,13 +13,14 @@ pub struct TestRootDiskRemount; mod host { use super::*; - use crate::{ShouldRun, krun_call, krun_call_u32}; + use crate::common; + use crate::{ShouldRun, krun_call, krun_call_u32, krun_init}; 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, @@ -98,21 +99,21 @@ 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))?; - - let argv = [test_case.as_ptr(), null()]; - let envp = [null()]; - krun_call!(krun_set_exec( + krun_call!(krun_add_virtio_console_default( ctx, - c"/guest-agent".as_ptr(), - argv.as_ptr(), - envp.as_ptr(), + std::io::stdin().as_raw_fd(), + std::io::stdout().as_raw_fd(), + std::io::stderr().as_raw_fd(), ))?; - krun_call!(krun_set_workdir(ctx, c"/".as_ptr()))?; - // Add a block device with the ext4 image. krun_call!(krun_add_disk( ctx, @@ -129,6 +130,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(()) 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..3369371e7 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,21 @@ 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_add_vsock(ctx, KRUN_TSI_HIJACK_INET))?; 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 c1a8d8721..61c3d9b78 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,14 +34,26 @@ 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(); 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( + 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 189ba6297..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_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))?; + 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 31cbd1cb2..9b46d9e98 100644 --- a/tests/test_cases/src/test_virtiofs_root_ro.rs +++ b/tests/test_cases/src/test_virtiofs_root_ro.rs @@ -22,8 +22,8 @@ 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; impl Test for TestVirtiofsRootRo { fn start_vm(self: Box, test_setup: TestSetup) -> anyhow::Result<()> { @@ -38,13 +38,22 @@ 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_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))?; + 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( @@ -55,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))?; } diff --git a/tests/test_cases/src/test_vm_config.rs b/tests/test_cases/src/test_vm_config.rs index 207f2b460..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_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))?; + 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 686d15654..42747e500 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}; @@ -65,14 +66,26 @@ 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(ctx, 0))?; krun_call!(krun_add_vsock_port( ctx, VSOCK_PORT, 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(())