diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 09fd3aa51..cd82ea2a6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,6 +49,8 @@ jobs: # Install dependencies for build & test sudo apt-get update -y sudo apt-get install -y faketime protobuf-compiler libsgx-dcap-ql-dev clang-18 musl-tools gcc-multilib + # ukify tool for elf2uki + sudo apt install systemd-ukify - name: Setup Rust toolchain run: | @@ -132,3 +134,6 @@ jobs: - name: snmalloc correntness test run: cd ./examples/mem-correctness-test && cargo run + + - name: Run elf2uki example + run: ./examples/elf2uki/convert_hello_world.sh diff --git a/Cargo.lock b/Cargo.lock index 203ccf57b..5b3540f7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -260,7 +260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9138ac237191fcb830a6c5fb45b93573ee670956bef2b2dd1ee8609daa59a4a" dependencies = [ "libc", - "log 0.4.28", + "log 0.4.29", "nix 0.20.2", "serde", "serde_bytes", @@ -641,7 +641,7 @@ dependencies = [ "clang-sys", "lazy_static", "lazycell", - "log 0.4.28", + "log 0.4.29", "peeking_take_while", "prettyplease", "proc-macro2 1.0.95", @@ -713,7 +713,7 @@ dependencies = [ "hyper-named-pipe", "hyper-util", "hyperlocal-next", - "log 0.4.28", + "log 0.4.29", "pin-project-lite", "serde", "serde_derive 1.0.204 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1003,6 +1003,14 @@ dependencies = [ "rustc-std-workspace-core", ] +[[package]] +name = "confidential-vm-blobs" +version = "0.1.0" +dependencies = [ + "anyhow", + "tempfile", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -1220,7 +1228,7 @@ dependencies = [ "dcap-ql", "env_logger", "lazy_static", - "log 0.4.28", + "log 0.4.29", "report-test", "reqwest", "rustc-serialize", @@ -1360,7 +1368,7 @@ dependencies = [ "clap 2.34.0", "elf", "env_logger", - "log 0.4.28", + "log 0.4.29", "nitro-cli", ] @@ -1390,6 +1398,19 @@ dependencies = [ "byteorder 0.5.3", ] +[[package]] +name = "elf2uki" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap 4.4.18", + "confidential-vm-blobs", + "env_logger", + "fortanix-vme-initramfs", + "log 0.4.29", + "tempfile", +] + [[package]] name = "em-app" version = "0.5.1" @@ -1493,7 +1514,7 @@ dependencies = [ "clap 4.4.18", "flate2", "futures 0.3.31", - "log 0.4.28", + "log 0.4.29", "serde", "serde_json", "serde_yaml", @@ -1521,7 +1542,7 @@ checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" dependencies = [ "atty", "humantime", - "log 0.4.28", + "log 0.4.29", "regex", "termcolor", ] @@ -1592,7 +1613,7 @@ dependencies = [ "glob", "is-terminal", "lazy_static", - "log 0.4.28", + "log 0.4.29", "nu-ansi-term", "regex", "thiserror", @@ -1706,7 +1727,7 @@ dependencies = [ "fnv", "fortanix-vme-abi", "fortanix-vme-eif", - "log 0.4.28", + "log 0.4.29", "nitro-cli", "nix 0.22.2", "rand 0.7.3", @@ -2188,7 +2209,7 @@ dependencies = [ "futures-util", "http 0.2.12", "hyper 0.14.32", - "log 0.4.28", + "log 0.4.29", "rustls 0.21.12", "rustls-native-certs", "tokio", @@ -2297,7 +2318,7 @@ dependencies = [ "clap 2.34.0", "env_logger", "lazy_static", - "log 0.4.28", + "log 0.4.29", "mbedtls", "percent-encoding 2.3.2", "pkix", @@ -2652,14 +2673,14 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" dependencies = [ - "log 0.4.28", + "log 0.4.29", ] [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru-cache" @@ -2833,7 +2854,7 @@ checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" dependencies = [ "lazy_static", "libc", - "log 0.4.28", + "log 0.4.29", "openssl", "openssl-probe", "openssl-sys", @@ -2877,7 +2898,7 @@ dependencies = [ "inotify", "lazy_static", "libc", - "log 0.4.28", + "log 0.4.29", "nix 0.26.4", "openssl", "page_size", @@ -2998,7 +3019,7 @@ version = "0.1.0" source = "git+https://github.com/aws/aws-nitro-enclaves-nsm-api#9ddb589875baf345d085a980aa96cf3a4e480ea8" dependencies = [ "libc", - "log 0.4.28", + "log 0.4.29", "nix 0.20.2", "nsm-io", "serde_cbor", @@ -3009,7 +3030,7 @@ name = "nsm-io" version = "0.1.0" source = "git+https://github.com/aws/aws-nitro-enclaves-nsm-api#9ddb589875baf345d085a980aa96cf3a4e480ea8" dependencies = [ - "log 0.4.28", + "log 0.4.29", "serde", "serde_bytes", "serde_cbor", @@ -3518,7 +3539,7 @@ checksum = "96cb37955261126624a25b5e6bda40ae34cf3989d52a783087ca6091b29b5642" dependencies = [ "anyhow", "indexmap 1.9.3", - "log 0.4.28", + "log 0.4.29", "protobuf", "protobuf-support", "tempfile", @@ -3786,7 +3807,7 @@ dependencies = [ "hyper-util", "ipnet", "js-sys", - "log 0.4.28", + "log 0.4.29", "mime 0.3.16", "native-tls", "once_cell", @@ -3913,7 +3934,7 @@ version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ - "log 0.4.28", + "log 0.4.29", "ring", "rustls-webpki 0.101.7", "sct", @@ -3925,7 +3946,7 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ - "log 0.4.28", + "log 0.4.29", "ring", "rustls-pki-types", "rustls-webpki 0.102.8", @@ -4075,7 +4096,7 @@ dependencies = [ "bitflags 1.2.1", "chrono", "hyper 0.10.16", - "log 0.4.28", + "log 0.4.29", "rustc-serialize", "serde", "serde_json", @@ -4360,7 +4381,7 @@ dependencies = [ "env_logger", "fnv", "lazy_static", - "log 0.4.28", + "log 0.4.29", "num", "openssl", "petgraph", @@ -4738,7 +4759,7 @@ checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" dependencies = [ "bytes 0.4.12", "futures 0.1.30", - "log 0.4.28", + "log 0.4.29", ] [[package]] @@ -5129,7 +5150,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.28", + "log 0.4.29", "try-lock", ] diff --git a/Cargo.toml b/Cargo.toml index e1778d229..a41d8b3e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ members = [ "fortanix-vme/fortanix-vme-initramfs", "fortanix-vme/fortanix-vme-runner", "fortanix-vme/fortanix-vme-tools", + "fortanix-vme/tools/confidential-vm-blobs", + "fortanix-vme/tools/elf2uki", "fortanix-vme/vme-pkix", "intel-sgx/aesm-client", "intel-sgx/async-usercalls", @@ -39,6 +41,7 @@ members = [ ] exclude = [ "examples/backtrace_panic", + "examples/elf2uki", "examples/mpsc-crypto-mining", "examples/tls", "examples/unit_tests", diff --git a/examples/.gitignore b/examples/.gitignore index f078e5ae6..4bb577dbc 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -1,5 +1,6 @@ **/.cargo/* !**/.cargo/config +!**/.cargo/config.toml **/Cargo.lock /target target/ diff --git a/examples/elf2uki/convert_hello_world.sh b/examples/elf2uki/convert_hello_world.sh new file mode 100755 index 000000000..6287fcea5 --- /dev/null +++ b/examples/elf2uki/convert_hello_world.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -euo pipefail + +source_dir=$(dirname "${BASH_SOURCE[0]}") +app_dir="$source_dir/hello_world" + +pushd "$app_dir" +#builds statically linked bin for `musl` +cargo -q build --release +popd + +# build UKI and write it to `/dev/null` +cargo -q run -p elf2uki -- \ + --app "$app_dir/target/x86_64-unknown-linux-musl/release/hello_world" \ + --cmdline "console=ttyS0 earlyprintk=serial" \ + --output /dev/null diff --git a/examples/elf2uki/hello_world/.cargo/config.toml b/examples/elf2uki/hello_world/.cargo/config.toml new file mode 100644 index 000000000..d67b66e2b --- /dev/null +++ b/examples/elf2uki/hello_world/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "x86_64-unknown-linux-musl" diff --git a/examples/elf2uki/hello_world/Cargo.toml b/examples/elf2uki/hello_world/Cargo.toml new file mode 100644 index 000000000..2a078603c --- /dev/null +++ b/examples/elf2uki/hello_world/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "hello_world" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/examples/elf2uki/hello_world/src/main.rs b/examples/elf2uki/hello_world/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/examples/elf2uki/hello_world/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/fortanix-vme/fortanix-vme-initramfs/Cargo.toml b/fortanix-vme/fortanix-vme-initramfs/Cargo.toml index 4b0449384..47b0e3136 100644 --- a/fortanix-vme/fortanix-vme-initramfs/Cargo.toml +++ b/fortanix-vme/fortanix-vme-initramfs/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "fortanix-vme-initramfs" version = "0.1.0" -edition = "2024" +edition = "2021" [dependencies] cpio = "0.2" diff --git a/fortanix-vme/tools/confidential-vm-blobs/Cargo.toml b/fortanix-vme/tools/confidential-vm-blobs/Cargo.toml new file mode 100644 index 000000000..bae84ee2c --- /dev/null +++ b/fortanix-vme/tools/confidential-vm-blobs/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "confidential-vm-blobs" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.100" +tempfile = "3" diff --git a/fortanix-vme/tools/confidential-vm-blobs/blobs/OVMF.amdsev.fd b/fortanix-vme/tools/confidential-vm-blobs/blobs/OVMF.amdsev.fd new file mode 100644 index 000000000..3f9935c46 Binary files /dev/null and b/fortanix-vme/tools/confidential-vm-blobs/blobs/OVMF.amdsev.fd differ diff --git a/fortanix-vme/tools/confidential-vm-blobs/blobs/OVMF.fd b/fortanix-vme/tools/confidential-vm-blobs/blobs/OVMF.fd new file mode 100644 index 000000000..52e16fb16 Binary files /dev/null and b/fortanix-vme/tools/confidential-vm-blobs/blobs/OVMF.fd differ diff --git a/fortanix-vme/tools/confidential-vm-blobs/blobs/README.md b/fortanix-vme/tools/confidential-vm-blobs/blobs/README.md new file mode 100644 index 000000000..8c3448fac --- /dev/null +++ b/fortanix-vme/tools/confidential-vm-blobs/blobs/README.md @@ -0,0 +1,27 @@ + +# Blobs + +These blobs have been vendored from the following locations + +## init +Tweaked the code to build the `init` executable from [here](https://github.com/aws/aws-nitro-enclaves-sdk-bootstrap/blob/f718dea60a9d9bb8b8682fd852ad793912f3c5db) and placed it under `build_init/`. +`build.rs` uses the shell script `build_init/update_init.sh` to place the generated `init` artifact in this folder and keep it up-to-date. + +Changes made are as follows: +1. Remove function `init_nsm_driver`: initialization of Nitro Secure Module driver +2. Remove function `enclave_ready`: sending signal to nitro-cli that the enclave has started +3. Remove global vars for above two functions. + +To see the diff: +```sh +diff -c10 <(curl https://raw.githubusercontent.com/aws/aws-nitro-enclaves-sdk-bootstrap/f718dea60a9d9bb8b8682fd852ad793912f3c5db/init/init.c) blobs/build_init/init.c +``` + +## vmlinuz-{version} +Ubuntu kernel, extracted from noble package `linux-image-{version}`. + +## OVMF +Defaults copied from noble installation, taken from `/usr/share/ovmf/OVMF.fd` and `/usr/share/ovmf/OVMF.amdsev.fd` respectively + +## EFI boot stub +Defaults copied from noble package `sytemd-boot-efi` version `255.4-1ubuntu8.12`, installed under path `/usr/lib/systemd/boot/efi/linuxx64.efi.stub` by default diff --git a/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/Dockerfile b/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/Dockerfile new file mode 100644 index 000000000..38aac1345 --- /dev/null +++ b/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/Dockerfile @@ -0,0 +1,14 @@ +FROM nixos/nix:2.21.4 AS build +ARG TARGET=all +ENV TARGET=${TARGET} + +RUN mkdir /build +ADD ./ /build/ +WORKDIR /build + +RUN nix-build -A ${TARGET} + +FROM scratch AS artifacts +COPY --from=build /build/result/* /blobs/ +# Without a CMD we can not create a container from this to extract the content +CMD ["dummy"] diff --git a/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/default.nix b/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/default.nix new file mode 100644 index 000000000..403249962 --- /dev/null +++ b/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/default.nix @@ -0,0 +1,12 @@ +{ pkgs ? (import ./nixpkgs.nix) { } }: +let + arch = pkgs.stdenv.hostPlatform.uname.processor; +in +rec { + init = pkgs.pkgsStatic.callPackage ./init.nix { }; + + all = pkgs.runCommandNoCC "enclaves-blobs-${arch}" { } '' + mkdir -p $out/${arch} + cp -r ${init}/* $out/${arch}/ + ''; +} diff --git a/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/init.c b/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/init.c new file mode 100644 index 000000000..8ce358a52 --- /dev/null +++ b/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/init.c @@ -0,0 +1,398 @@ +/* + * MIT License + * + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Code from OpenGCS (https://github.com/microsoft/opengcs) is used in this file. + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * + * 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 + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +_Noreturn void die(const char *msg); +#define die_on(CONDITION, ...) \ + do { \ + if (CONDITION) { \ + die(__VA_ARGS__); \ + } \ + } while (0) + +#define DEFAULT_PATH_ENV "PATH=/sbin:/usr/sbin:/bin:/usr/bin" +#define TIMEOUT 20000 // millis + +const char *const default_envp[] = { + DEFAULT_PATH_ENV, + NULL, +}; + +const char *const default_argv[] = { "sh", NULL }; + +struct Mount { + const char *source, *target, *type; + unsigned long flags; + const void *data; +}; + +struct Mkdir { + const char *path; + mode_t mode; +}; + +struct Mknod { + const char *path; + mode_t mode; + int major, minor; +}; + +struct Symlink { + const char *linkpath, *target; +}; + +enum OpType { + OpMount, + OpMkdir, + OpMknod, + OpSymlink, +}; + +struct InitOp { + enum OpType op; + union { + struct Mount mount; + struct Mkdir mkdir; + struct Mknod mknod; + struct Symlink symlink; + }; +}; + +const struct InitOp ops[] = { + // mount /proc (which should already exist) + { OpMount, .mount = { "proc", "/proc", "proc", MS_NODEV | MS_NOSUID | MS_NOEXEC } }, + + // add symlinks in /dev (which is already mounted) + { OpSymlink, .symlink = { "/dev/fd", "/proc/self/fd" } }, + { OpSymlink, .symlink = { "/dev/stdin", "/proc/self/fd/0" } }, + { OpSymlink, .symlink = { "/dev/stdout", "/proc/self/fd/1" } }, + { OpSymlink, .symlink = { "/dev/stderr", "/proc/self/fd/2" } }, + + // mount tmpfs on /run and /tmp (which should already exist) + { OpMount, .mount = { "tmpfs", "/run", "tmpfs", MS_NODEV | MS_NOSUID | MS_NOEXEC, "mode=0755" } }, + { OpMount, .mount = { "tmpfs", "/tmp", "tmpfs", MS_NODEV | MS_NOSUID | MS_NOEXEC } }, + + // mount shm and devpts + { OpMkdir, .mkdir = { "/dev/shm", 0755 } }, + { OpMount, .mount = { "shm", "/dev/shm", "tmpfs", MS_NODEV | MS_NOSUID | MS_NOEXEC } }, + { OpMkdir, .mkdir = { "/dev/pts", 0755 } }, + { OpMount, .mount = { "devpts", "/dev/pts", "devpts", MS_NOSUID | MS_NOEXEC } }, + + // mount /sys (which should already exist) + { OpMount, .mount = { "sysfs", "/sys", "sysfs", MS_NODEV | MS_NOSUID | MS_NOEXEC } }, + { OpMount, .mount = { "cgroup_root", "/sys/fs/cgroup", "tmpfs", MS_NODEV | MS_NOSUID | MS_NOEXEC, "mode=0755" } }, +}; + +void warn(const char *msg) { + int error = errno; + perror(msg); + errno = error; +} + +void warn2(const char *msg1, const char *msg2) { + int error = errno; + fputs(msg1, stderr); + fputs(": ", stderr); + errno = error; + warn(msg2); +} + +_Noreturn void dien() { + exit(errno); +} + +_Noreturn void die(const char *msg) { + warn(msg); + dien(); +} + +_Noreturn void die2(const char *msg1, const char *msg2) { + warn2(msg1, msg2); + dien(); +} + +void init_dev() { + if (mount("dev", "/dev", "devtmpfs", MS_NOSUID | MS_NOEXEC, NULL) < 0) { + warn2("mount", "/dev"); + // /dev will be already mounted if devtmpfs.mount = 1 on the kernel + // command line or CONFIG_DEVTMPFS_MOUNT is set. Do not consider this + // an error. + if (errno != EBUSY) { + dien(); + } + } +} + +void init_fs(const struct InitOp *ops, size_t count) { + for (size_t i = 0; i < count; i++) { + switch (ops[i].op) { + case OpMount: { + const struct Mount *m = &ops[i].mount; + if (mount(m->source, m->target, m->type, m->flags, m->data) < 0) { + die2("mount", m->target); + } + break; + } + case OpMkdir: { + const struct Mkdir *m = &ops[i].mkdir; + if (mkdir(m->path, m->mode) < 0) { + warn2("mkdir", m->path); + if (errno != EEXIST) { + dien(); + } + } + break; + } + case OpMknod: { + const struct Mknod *n = &ops[i].mknod; + if (mknod(n->path, n->mode, makedev(n->major, n->minor)) < 0) { + warn2("mknod", n->path); + if (errno != EEXIST) { + dien(); + } + } + break; + } + case OpSymlink: { + const struct Symlink *sl = &ops[i].symlink; + if (symlink(sl->target, sl->linkpath) < 0) { + warn2("symlink", sl->linkpath); + if (errno != EEXIST) { + dien(); + } + } + break; + } + } + } +} + +void init_cgroups() { + const char *fpath = "/proc/cgroups"; + FILE *f = fopen(fpath, "r"); + if (f == NULL) { + die2("fopen", fpath); + } + // Skip the first line. + for (;;) { + int c = fgetc(f); + if (c == EOF || c == '\n') { + break; + } + } + for (;;) { + static const char base_path[] = "/sys/fs/cgroup/"; + char path[sizeof(base_path) - 1 + 65]; + char* name = path + sizeof(base_path) - 1; + int hier, groups, enabled; + int r = fscanf(f, "%64s %d %d %d\n", name, &hier, &groups, &enabled); + if (r == EOF) { + break; + } + if (r != 4) { + errno = errno ? : EINVAL; + die2("fscanf", fpath); + } + if (enabled) { + memcpy(path, base_path, sizeof(base_path) - 1); + if (mkdir(path, 0755) < 0) { + die2("mkdir", path); + } + if (mount(name, path, "cgroup", MS_NODEV | MS_NOSUID | MS_NOEXEC, name) < 0) { + die2("mount", path); + } + } + } + fclose(f); +} + +void init_console() { + // init process needs to set up a tty for the container and this is likely + // not the correct way to do this, although it works when compiling with + // musl-gcc. + const char *console_path = "/dev/console"; + die_on(freopen(console_path, "r", stdin) == NULL, + "freopen failed for stdin"); + die_on(freopen(console_path, "w", stdout) == NULL, + "freopen failed for stdout"); + die_on(freopen(console_path, "w", stderr) == NULL, + "freopen failed for stderr"); +} + +pid_t launch(char **argv, char **envp) { + int pid = fork(); + if (pid != 0) { + die_on(pid < 0, "fork"); + + return pid; + } + + if (argv == NULL) + argv = (char **) default_argv; + + if (envp == NULL) + envp = (char **) default_envp; + + // Unblock signals before execing. + sigset_t set; + sigfillset(&set); + sigprocmask(SIG_UNBLOCK, &set, 0); + + // Create a session and process group. + setsid(); + setpgid(0, 0); + + // Terminate the arguments and exec. + die_on(putenv(DEFAULT_PATH_ENV), "putenv"); // Specify the PATH used for execvpe + execvpe(argv[0], argv, envp); + die2("execvpe", argv[0]); +} + +int reap_until(pid_t until_pid) { + for (;;) { + int status; + pid_t pid = wait(&status); + die_on(pid < 0, "wait"); + + if (pid == until_pid) { + // The initial child process died. Pass through the exit status. + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + fputs("child exited with error\n", stderr); + } + return WEXITSTATUS(status); + } + fputs("child exited by signal\n", stderr); + return 128 + WTERMSIG(status); + } + } +} + +char **read_config(FILE *env_file) { + if (env_file == NULL) { + warn("Could not open /env file"); + return NULL; + } + + char **env = NULL; + + const size_t env_increment = 10; + size_t env_size = 0; + + for (size_t i = 0; !feof(env_file); i++) { + char *line = NULL; + size_t len = 0; + ssize_t read = getline(&line, &len, env_file); + if (read == -1) + break; + + if (line[read - 1] == '\n') + line[read - 1] = 0; + + if (i + 1 >= env_size) { + env_size += env_increment; + env = realloc(env, env_size * sizeof(char *)); + die_on(env == NULL, "not enough mem for env variables"); + } + + env[i] = line; + env[i + 1] = NULL; + } + return env; +} + +int main() { + // Block all signals in init. SIGCHLD will still cause wait() to return. + sigset_t set; + sigfillset(&set); + sigprocmask(SIG_BLOCK, &set, 0); + + // Set up the minimal dependencies to start a container + // Init /dev and start /dev/console for early debugging + init_dev(); + init_console(); + + FILE *env_file = fopen("/env", "r"); + FILE *cmd_file = fopen("/cmd", "r"); + + // env should be an array of "VAR1=string1", "VAR2=string2", ... + // The array should end with NULL + char **env = read_config(env_file); + // cmd should be an array of "command", "param1", "param2", ... + // The array should end with NULL + char **cmd = read_config(cmd_file); + + fclose(env_file); + fclose(cmd_file); + + unlink("/env"); + unlink("/cmd"); + + // Turn /rootfs into a mount point so it can be used with mount --move + die_on(mount("/rootfs", "/rootfs", NULL, MS_BIND, NULL) != 0, + "mount --bind /rootfs /rootfs"); + die_on(chdir("/rootfs") != 0, "chdir /rootfs"); + // Change the root directory of the mount namespace to the root directory + // by overmounting / with /rootfs + die_on(mount(".", "/", NULL, MS_MOVE, NULL) != 0, + "mount --move . /"); + die_on(chroot(".") != 0, "chroot ."); + die_on(chdir("/") != 0, "chdir /"); + + // At this point, we need to make sure the container /dev is initialized + // as well. + init_dev(); + init_fs(ops, sizeof(ops) / sizeof(ops[0])); + init_cgroups(); + + pid_t pid = launch(cmd, env); + + //// Reap until the initial child process dies. + reap_until(pid); + reboot(RB_AUTOBOOT); +} diff --git a/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/init.nix b/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/init.nix new file mode 100644 index 000000000..6d51a211f --- /dev/null +++ b/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/init.nix @@ -0,0 +1,16 @@ +{ pkgs ? (import ./nixpkgs.nix) { } }: +pkgs.stdenv.mkDerivation rec { + name = "amd-sev-snp-init"; + + src = ./.; + + buildPhase = '' + $CC -Wall -Wextra -Werror -O2 -o init init.c -flto + $STRIP --strip-all init + ''; + + installPhase = '' + mkdir -p $out + cp init $out/ + ''; +} diff --git a/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/nixpkgs.nix b/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/nixpkgs.nix new file mode 100644 index 000000000..6ac53739f --- /dev/null +++ b/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/nixpkgs.nix @@ -0,0 +1,4 @@ +import (fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/refs/tags/24.05.tar.gz"; + sha256 = "sha256:1lr1h35prqkd1mkmzriwlpvxcb34kmhc9dnr48gkm8hh089hifmx"; +}) diff --git a/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/update_init.sh b/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/update_init.sh new file mode 100755 index 000000000..d527f9897 --- /dev/null +++ b/fortanix-vme/tools/confidential-vm-blobs/blobs/build_init/update_init.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# update the `init` executable stored in the parent directory + +script_folder=$(dirname "$(realpath "${BASH_SOURCE[0]}")") + +image_name=blobs_all +container_name="${image_name}_extract" +output=$(dirname "$script_folder") #store in parent by default +while [ $# -gt 0 ]; do + case "$1" in + --image_name) + shift + image_name=$1 + ;; + --container_name) + shift + container_name=$1 + ;; + --output) + usage + exit 0 + ;; + --*) + echo "Error: bad option $1" + exit 1 + ;; + *) + echo "Error: bad argument $1" + exit 1 + ;; + esac + shift +done + +docker build -t "$image_name" "$script_folder" + +docker create --name "$container_name" "$image_name" +docker cp "${container_name}:/blobs/." "$output" +docker rm "$container_name" diff --git a/fortanix-vme/tools/confidential-vm-blobs/blobs/init b/fortanix-vme/tools/confidential-vm-blobs/blobs/init new file mode 100755 index 000000000..62bf4e7d5 Binary files /dev/null and b/fortanix-vme/tools/confidential-vm-blobs/blobs/init differ diff --git a/fortanix-vme/tools/confidential-vm-blobs/blobs/linuxx64.efi.stub b/fortanix-vme/tools/confidential-vm-blobs/blobs/linuxx64.efi.stub new file mode 100644 index 000000000..f7046b944 Binary files /dev/null and b/fortanix-vme/tools/confidential-vm-blobs/blobs/linuxx64.efi.stub differ diff --git a/fortanix-vme/tools/confidential-vm-blobs/blobs/vmlinuz-6.14.0-36-generic b/fortanix-vme/tools/confidential-vm-blobs/blobs/vmlinuz-6.14.0-36-generic new file mode 100755 index 000000000..66d49e4e7 Binary files /dev/null and b/fortanix-vme/tools/confidential-vm-blobs/blobs/vmlinuz-6.14.0-36-generic differ diff --git a/fortanix-vme/tools/confidential-vm-blobs/build.rs b/fortanix-vme/tools/confidential-vm-blobs/build.rs new file mode 100644 index 000000000..e322d3ba2 --- /dev/null +++ b/fortanix-vme/tools/confidential-vm-blobs/build.rs @@ -0,0 +1,19 @@ +use std::process::Command; + +// Build `init` executable, to be used in constructing the initramfs +fn main() { + println!("cargo::rerun-if-changed=blobs/build_init"); + + let output = Command::new("./blobs/build_init/update_init.sh") + .output() + .unwrap(); + + if !output.status.success() { + panic!( + "failed to compile init script: {} \n\n sterr : {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); + } +} + diff --git a/fortanix-vme/tools/confidential-vm-blobs/src/lib.rs b/fortanix-vme/tools/confidential-vm-blobs/src/lib.rs new file mode 100644 index 000000000..5359109e2 --- /dev/null +++ b/fortanix-vme/tools/confidential-vm-blobs/src/lib.rs @@ -0,0 +1,25 @@ +// TODO: cannot place in lfs - decide on final versions, store these somewhere and download them +pub mod maybe_vendored; + +pub const INIT: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/blobs/init")); + +/// Fallback for the used kernel, specified if the user does not provide one +pub const KERNEL: &[u8] = include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/blobs/vmlinuz-6.14.0-36-generic" +)); + +/// Fallback for the AMD OVMF firmware, specified if the user does not provide one +pub const AMD_SEV_OVMF: &[u8] = + include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/blobs/OVMF.amdsev.fd")); + +/// Fallback for the regular OVMF firmware, specified if the user does not provide one in +/// simulation mode +pub const VANILLA_OVMF: &[u8] = + include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/blobs/OVMF.fd")); + +/// Blob for the efi boot stub +pub const EFI_BOOT_STUB: &[u8] = include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/blobs/linuxx64.efi.stub" +)); diff --git a/fortanix-vme/tools/confidential-vm-blobs/src/maybe_vendored.rs b/fortanix-vme/tools/confidential-vm-blobs/src/maybe_vendored.rs new file mode 100644 index 000000000..7a4a08892 --- /dev/null +++ b/fortanix-vme/tools/confidential-vm-blobs/src/maybe_vendored.rs @@ -0,0 +1,40 @@ +//! Utility type for blobs that are either passed in as an argument by a user or vendored + +use anyhow::{Context as _, Result}; +use std::{ + io::Write as _, + path::{Path, PathBuf}, +}; + +use tempfile::NamedTempFile; + +pub enum MaybeVendoredImage { + External(PathBuf), + /// Unfortunately `ukify` receives its input as a file, so we store fallback blobs in temporary named + /// files before passing them + Vendored(NamedTempFile), +} + +impl MaybeVendoredImage { + pub fn path(&self) -> &Path { + match self { + MaybeVendoredImage::External(path_buf) => path_buf, + MaybeVendoredImage::Vendored(named_temp_file) => named_temp_file.path(), + } + } + + /// Load a vendored blob to a temp file and create a instance of `Self` from that + pub fn from_vendored(blob: &[u8]) -> Result { + let temp_file = NamedTempFile::new() + .and_then(|mut tempfile| tempfile.write_all(blob).map(|_| tempfile)) + .and_then(|mut tempfile| tempfile.flush().map(|_| tempfile)) + .context("failed to write backup kernel image to file")?; + Ok(MaybeVendoredImage::Vendored(temp_file)) + } +} + +impl From for MaybeVendoredImage { + fn from(value: PathBuf) -> Self { + MaybeVendoredImage::External(value) + } +} diff --git a/fortanix-vme/tools/elf2uki/Cargo.lock b/fortanix-vme/tools/elf2uki/Cargo.lock new file mode 100644 index 000000000..cfb5a8d14 --- /dev/null +++ b/fortanix-vme/tools/elf2uki/Cargo.lock @@ -0,0 +1,545 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "clap_lex" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "confidential-vm-blobs" +version = "0.1.0" + +[[package]] +name = "cpio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27e77cfc4543efb4837662cb7cd53464ae66f0fd5c708d71e0f338b1c11d62d3" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "elf2uki" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "confidential-vm-blobs", + "env_logger", + "fortanix-vme-initramfs", + "log", + "tempfile", +] + +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fortanix-vme-initramfs" +version = "0.1.0" +dependencies = [ + "cpio", + "derivative", + "flate2", + "normalize-path", + "thiserror", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "jiff" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "normalize-path" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5438dd2b2ff4c6df6e1ce22d825ed2fa93ee2922235cc45186991717f0a892d" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "portable-atomic" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "proc-macro2" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" diff --git a/fortanix-vme/tools/elf2uki/Cargo.toml b/fortanix-vme/tools/elf2uki/Cargo.toml new file mode 100644 index 000000000..cd30a00f4 --- /dev/null +++ b/fortanix-vme/tools/elf2uki/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "elf2uki" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.100" +clap = { version = "4.4", features = ["cargo", "derive"] } +confidential-vm-blobs = { path = "../confidential-vm-blobs/" } +env_logger = "0.9" +fortanix-vme-initramfs = { path = "../../fortanix-vme-initramfs" } +log = "0.4.29" +tempfile = "3" diff --git a/fortanix-vme/tools/elf2uki/src/initramfs.rs b/fortanix-vme/tools/elf2uki/src/initramfs.rs new file mode 100644 index 000000000..66ea0428b --- /dev/null +++ b/fortanix-vme/tools/elf2uki/src/initramfs.rs @@ -0,0 +1,37 @@ +use anyhow::Result; +use fortanix_vme_initramfs::FsTree; +use fortanix_vme_initramfs::{Initramfs, ReadSeek}; +use std::io::{Cursor, Read, Write}; + +const RUN_APP_CONTENT: &str = "/bin/app"; +const EMPTY_ENV: &str = ""; +pub const APP_PATH: &str = "rootfs/bin/app"; +pub const ENV_PATH: &str = "env"; +pub const CMD_PATH: &str = "cmd"; +pub const INIT_PATH: &str = "init"; + +pub fn build_fs_tree( + application: R, + init: S, +) -> FsTree { + FsTree::new() + .add_file(ENV_PATH, Box::new(Cursor::new(EMPTY_ENV.as_bytes()))) + .add_file(CMD_PATH, Box::new(Cursor::new(RUN_APP_CONTENT.as_bytes()))) + .add_executable(INIT_PATH, Box::new(init)) + .add_executable(APP_PATH, Box::new(application)) + .add_directory("rootfs/dev") + .add_directory("rootfs/proc") + .add_directory("rootfs/run") + .add_directory("rootfs/sys") + .add_directory("rootfs/tmp") +} + +pub fn build( + application: R, + init: S, + output: U, +) -> Result { + let fs_tree = build_fs_tree(application, init); + let initramfs = Initramfs::from_fs_tree(fs_tree, output)?; + Ok(initramfs.into_inner()) +} diff --git a/fortanix-vme/tools/elf2uki/src/main.rs b/fortanix-vme/tools/elf2uki/src/main.rs new file mode 100644 index 000000000..813698c2e --- /dev/null +++ b/fortanix-vme/tools/elf2uki/src/main.rs @@ -0,0 +1,176 @@ +use std::io::Cursor; +use std::path::Path; +use std::process::Command; +use std::{fs::File, path::PathBuf}; + +use anyhow::{anyhow, Context as _, Result}; +use clap::{Args, Parser}; +use confidential_vm_blobs::{maybe_vendored::MaybeVendoredImage, EFI_BOOT_STUB, INIT, KERNEL}; +use tempfile::NamedTempFile; + +mod initramfs; + +// TODO (RTE-740): deal with measurement/ID block/author key as part of CLI +/// Entry point for CLI application. +/// +/// # Example +/// +/// Under the following conditions: +/// * the user wants to use our vendored kernel image and efi boot stub rather than their own +/// * a statically compiled application is available at `/tmp/application` +/// * the `ukify` binary is available in the user's `PATH` +/// +/// the following invocation will create a UKI image at the specified output path: +/// +/// ```sh +/// elf2uki \ +/// --app /tmp/application \ +/// --output image-to-test.efi +/// ``` +#[derive(Parser, Debug)] +#[command(name = "Elf2Uki")] +#[command(version, author)] +#[command( + about = "Assemble UKI files from their constituents", + long_about = "Receive paths to the different building blocks of a UKI file as input, and output the resulting UKI file" +)] +struct Cli { + #[command(flatten)] + non_defaulted_args: NonDefaultedArgs, + + /// Path where the newly created UKI file will be written. + /// + /// Equal to the kernel image path appended with `.efi` if not specified + #[arg(short, long = "output", value_name = "FILE")] + output_path: Option, + + /// Path to the kernel image file, defaulting to the vendored kernel blob if not provided. + #[arg(long = "kernel", value_name = "FILE")] + kernel_image_path: Option, + + /// Path to the EFI boot stub file, defaulting to the vendored boot stub blob if not provided + #[arg(long = "efi-stub", value_name = "FILE")] + efi_stub_path: Option, +} + +struct ValidatedCli { + non_defaulted_args: NonDefaultedArgs, + output_path: PathBuf, + kernel_image: MaybeVendoredImage, + efi_stub_image: MaybeVendoredImage, +} + +#[derive(Args, Debug)] +struct NonDefaultedArgs { + /// Path to the application elf file + #[arg(long = "app", value_name = "FILE")] + application_elf_path: PathBuf, + + /// String to pass as the kernel command line + #[arg(long = "cmdline", value_name = "STRING")] + kernel_cmdline: Option, +} + +pub fn open_file(path: &Path) -> Result { + File::open(path).with_context(|| format!("failed to open file at path {}", path.display())) +} + +impl Cli { + /// Validate the provided values, filling in defaults where necessary + fn validate(self) -> Result { + let Cli { + output_path, + non_defaulted_args, + kernel_image_path, + efi_stub_path, + } = self; + + let kernel_image = match kernel_image_path { + Some(path) => MaybeVendoredImage::from(path), + None => MaybeVendoredImage::from_vendored(KERNEL)?, + }; + + let efi_stub_image = match efi_stub_path { + Some(path) => MaybeVendoredImage::from(path), + None => MaybeVendoredImage::from_vendored(EFI_BOOT_STUB)?, + }; + + // Check susceptible to TOCTOU, but try to error out early and clearly if files + // cannot be opened + for path in [ + kernel_image.path(), + &non_defaulted_args.application_elf_path, + efi_stub_image.path(), + ] { + let _ = open_file(path)?; + } + + let output_path = output_path.unwrap_or_else(|| kernel_image.path().join(".efi")); + + Ok(ValidatedCli { + non_defaulted_args, + output_path, + kernel_image, + efi_stub_image, + }) + } +} + +/// Run the `ukify` tool after validation of the passed-in arguments +fn build_uki(cli: &ValidatedCli, initramfs_path: &Path) -> Result<()> { + const UKIFY_EXECUTABLE: &str = "ukify"; + let mut command = Command::new(UKIFY_EXECUTABLE); + command + .arg("build") + .arg("--linux") + .arg(cli.kernel_image.path()) + .arg("--initrd") + .arg(initramfs_path) + .arg("--stub") + .arg(&cli.efi_stub_image.path()) + .arg("--output") + .arg(&cli.output_path); + + if let Some(cmdline) = &cli.non_defaulted_args.kernel_cmdline { + command.arg("--cmdline").arg(cmdline); + } + + let output = command.output().map_err(|e| { + let error_kind = e.kind(); + anyhow::Error::new(e).context(match error_kind { + std::io::ErrorKind::NotFound => { + "`ukify` tool not found in PATH; make it available or install `systemd-ukify`" + } + _ => "spawning ukify process failed", + }) + })?; + if !output.status.success() { + return Err(anyhow!( + "ukify exited with non-zero status code and stdout : {} \n\n stderr : {}", + String::from_utf8(output.stdout).context("ukify stdout is non-utf8")?, + String::from_utf8(output.stderr).context("ukify stderr is non-utf8")?, + )); + } + Ok(()) +} + +fn main() -> Result<()> { + let args = Cli::parse(); + let validated_args = args.validate()?; + + let application_elf = open_file(&validated_args.non_defaulted_args.application_elf_path)?; + let init = Cursor::new(INIT); + + // Unfortunately `ukify` forces us to have data in files. + let mut initramfs_file = NamedTempFile::new().context("failed to create initramfs file")?; + initramfs_file = initramfs::build(application_elf, init, initramfs_file) + .context("failed to create initramfs")?; + + build_uki(&validated_args, initramfs_file.path())?; + + println!( + "Enclave Image successfully created at path: `{}`", + validated_args.output_path.display() + ); + Ok(()) +}