Skip to content

Releases: hyperlight-dev/hyperlight-unikraft

dev (0.17.0+dev.3df47f6)

Pre-release

Choose a tag to compare

@github-actions github-actions released this 02 Oct 15:53
3df47f6

The latest build of main that passed CI: 0.17.0+dev.3df47f6, from 3df47f6 site: stop narrow phones from scrolling sideways.

Replaced on every push to main. It pulls the images of its own commit, :initrd-dev-3df47f6 (:dev-3df47f6 to build FROM).

curl -fsSL https://raw.githubusercontent.com/hyperlight-dev/hyperlight-unikraft/main/install.sh | HLUK_VERSION=dev sh
$env:HLUK_VERSION = 'dev'; irm https://raw.githubusercontent.com/hyperlight-dev/hyperlight-unikraft/main/install.ps1 | iex

v0.17.0

Choose a tag to compare

@github-actions github-actions released this 29 Sep 06:35

Added

  • arm64 guests: hluk runs on arm64 hosts, macOS on Apple silicon (Hypervisor.framework) and Linux arm64 (KVM), with an arm64 kernel, kernel/elfloader_hyperlight-arm64, embedded in place of the x86_64 one on an arm64 host. just build-kernel and just verify-kernel build and check both (the arm64 one cross-compiled). Every runtime runs on it, warm snapshots included; Rust programs need Rust 1.99 or later, the first to link a static PIE for arm64, and are built on Linux.

  • HLUK_ROOTFS_PLATFORM=linux/arm64 just build-rootfs <runtime> builds an arm64 rootfs on an x86_64 host (into build-elfloader/<runtime>-rootfs-arm64.cpio). Every runtime image builds for either architecture; powershell on arm64 is Microsoft's glibc build, since there is no musl one.

  • Published images are multi-platform (linux/amd64 and linux/arm64): each architecture is built on a native runner and the tags joined into one index (just publish ... <arch>, just publish-index). The kernel image carries both kernels.

  • Releases carry hluk for Linux arm64 and macOS (Apple silicon) too, and a SHA256SUMS. install.sh installs them (and signs the macOS binary for Hypervisor.framework); install.ps1 installs on Windows: irm https://raw.githubusercontent.com/hyperlight-dev/hyperlight-unikraft/main/install.ps1 | iex.

  • The go, dotnet-aot and http-dotnet templates build for the host's CPU, whatever it is (they name only the OS, Linux), and hluk build's Docker builds name the host's platform. dotnet-aot now publishes in Alpine's .NET SDK image (Docker), a musl toolchain: linked by a glibc one, an arm64 binary did not load in the guest. hluk run says so when a mounted program is a macOS or Windows binary, or one for another architecture.

  • CI builds, lints and unit-tests on macOS and Linux arm64. A new arm64 workflow builds the arm64 images and test binaries and runs the integration tests on hyperlight-dev's self-hosted arm64 runners (Linux/KVM and macOS/Hypervisor.framework): on main, on demand, and on pull requests labelled ci/arm64. The Linux runner, a small VM with nested virtualization, runs only the suites with small guests; macOS runs them all.

  • GetRandomBytes host function: the arm64 kernel seeds its CSPRNG from the host, at boot and on every restore, since Apple's cores have no random-number instructions.

  • On macOS, cargo run and cargo test sign binaries with the hypervisor entitlement (dev/macos-sign-and-run.sh), and an unsigned hluk says how to sign itself. Tests run one at a time there (RUST_TEST_THREADS overrides it): Hypervisor.framework runs one sandbox of a process at a time.

  • /dev/maps (LIBUKVMEM_DEVFS_MAPS, both kernels) lists the address space in /proc/self/maps format. The powershell image links /proc/self/maps to it: glibc reads it to find the main thread's stack, and PowerShell's glibc build on arm64 does not start without it.

Changed

  • Snapshots saved by an earlier release are refused (the kernel and the host functions changed); save them again.
  • The c template's [build] command takes CC, e.g. a Linux cross compiler on macOS.
  • Projects made by an earlier hluk pin amd64 (GOARCH=amd64 or -r linux-musl-x64 in [build], or in an http-dotnet Dockerfile); drop it to build them on arm64.

Fixed

  • An entry-point program that slept before anything else happened was reported deadlocked: the boot's first idle dropped its timer.
  • Generic Unikraft arm64 bugs that kept Linux binaries from running, each reproduced and fixed on QEMU/KVM: execve started the program at a stale address with stale registers and FP state, system calls ran with interrupts masked (so any that blocked crashed), struct stat and struct epoll_event had the x86_64 layout, uname reported arm64 rather than aarch64, the build failed without SMP or with signalfd, and chmod, chown and symlinks failed with ENOSYS (the *at system calls arm64 has in their place were missing, so pip could not install a package). A jump into a non-executable page raises SIGSEGV instead of crashing the kernel (arm64 took instruction faults for reads). An SVE or SME instruction raises SIGILL, as on Linux, instead of SIGFPE or a kernel crash, so Node.js (whose OpenSSL probes for SVE at startup) runs on CPUs with SVE and on Apple M4.
  • hluk pull takes the image for the host's architecture from an index, and refuses a single image built for another one.
  • A signal the CPU raises (SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGTRAP) carries the si_code and si_addr Linux would report (the faulting address or instruction where Linux gives one); it looked like a kill(2) from the process itself. Runtimes read these to turn a null dereference into an exception. Both architectures.
  • macOS: sockets are close-on-exec and don't raise SIGPIPE, host errors reach the guest as Linux errno values, a datagram socket can be disconnected and connected again, and a refused connect completes.

v0.16.0

Choose a tag to compare

@github-actions github-actions released this 26 Sep 21:47

Added

  • hluk run --profile (also HLUK_PROFILE=1 and SandboxBuilder::profile) prints where a sandbox's time goes, host side: each VM entry split into guest and host function time with its VM exits, each host function, and boot and restore. See docs/profiling.md.

Changed

  • Snapshot restore takes about 0.9 ms instead of 1.9–2.7 ms, now about the same for every runtime. After a restore the kernel's frame allocator takes its memory in 512 KiB chunks as it needs them, next to the copy-on-write pages, instead of writing its metadata over most of the scratch memory. Only the memory exception delivery writes is pre-faulted, and the exception stacks get fresh pages instead of copies. The new host function GetResumeState brings the clock, mounts and resolver in one exit instead of three.
  • A guest function call costs one VM exit instead of five: its start and return (with the result) ride on the entry's Yield, and the kernel refetches the environment only when the embedder changed it. CallResult is gone; CallStarted and CallDone(status, result) remain for an entry that ends without a Yield.
  • The rootfs is no longer copied into guest memory at boot: files extracted from the initrd reference it until they are changed (CONFIG_LIBVFSCORE_AUTOMOUNT_EXTRACT_BORROW). Python boots in 178 ms instead of 296, its snapshot is 74 MiB instead of 114, and it runs in 48 MiB of scratch instead of 128. The initrd is now mapped with 4 KiB pages, since Hyperlight's snapshot does not take large pages.
  • Snapshots saved by an earlier release are refused (the host functions changed); save them again.

Fixed

  • The copy-on-write page fault handler no longer changes SSE registers. It copied the page with an SSE memcpy, and exception entry doesn't save those registers, so the vectorised store that faulted could write the wrong data.
  • The buddy frame allocator cleared its bitmaps with an SSE memset when memory was added from a page fault, changing the registers of the code that faulted. Generic Unikraft bug, reproduced on QEMU/KVM.
  • A ramfs file shrunk and then grown again (by truncate or a write past its end) read back the bytes the shrink had cut instead of zeros. Generic Unikraft bug, reproduced on QEMU/KVM.

v0.15.0

Choose a tag to compare

@github-actions github-actions released this 25 Sep 19:52

Added

  • quickjs and wasmtime runtime images (tier 3), with hluk init templates.
    • quickjs: QuickJS (quickjs-ng 0.17) with qjs:std and qjs:os, in a 2 MiB rootfs. Scripts and ES modules both run, and globals persist between calls. A call returns once the jobs and timers it started have run.
    • wasmtime: Wasmtime 49 with Cranelift. Runs WebAssembly text, .wasm and .cwasm modules and components under WASI 0.1, 0.2 and 0.3 (0.3 is experimental in Wasmtime).
  • Guest function calls: AppSandbox::call(function, input) runs a function the guest defined and returns its result, JSON in and out. Served by quickjs, node, python, python-shell, agent, dotnet-jit and wasmtime. hluk run --call FUNCTION --input JSON does it from the CLI. See docs/calls.md.
  • Host function calls: SandboxBuilder::host_function(name, f) gives the guest one of your functions. It's host.call in JavaScript (and host: modules in quickjs), hyperlight.call in Python and Host.Call in C#. In wasmtime, any import outside WASI is linked to one, so a component can import a WIT interface you implement.
  • Kernel: a driver's write() can carry a result after the status (sent as CallResult), and HLCALL_IOC_HOSTCALL calls the embedder's functions through one host function, HostCall(name, args). hl_driver.h has hl_set_result and hl_host_call.
  • Two hluk init templates: python-shell, a Python script that runs shell commands through subprocess on the python-shell rootfs (the one runtime that had only http-python), and bash-repl, an interactive shell whose read-eval loop takes commands from hluk run's stdin until Ctrl-D.
  • hluk init --template takes a template of your own: a directory on disk, or github.com/OWNER/REPO[/PATH][@REF] (a browser's …/tree/REF/PATH URL too), downloaded as the repository's tarball through the GitHub API with no git on the host; GITHUB_TOKEN reaches a private repository and lifts the anonymous rate limit. The template's runtime is checked against the published ones, a template is held to 256 text files and 4 MiB, and init prints the [build] command and Dockerfile hluk build would run from a template that is not built in. tier is optional in such a template's template.toml. docs/templates.md is the guide to writing one, with examples/templates/word-count to copy from.

Changed

  • Exec has a new variant, Call, so an exhaustive match on it needs another arm.

Fixed

  • Programs, most often .NET, could resume at a wild address after a snapshot restore (dotnet_jit_snapshot_round_trip failed about half the time). A page fault inside a page fault handler reused the top of the exception stack and overwrote the outer fault's registers. Exception handlers now stay on the stack they were entered on, so a nested fault lands below the outer frame. The fix is in the native x86_64 platform, shared with KVM.
  • Reading stdin past its end deadlocked the guest: poll() and select() waited forever for more input, so a second shell read or Python's input() after EOFError hung. The end of input is now final on Hyperlight (CONFIG_LIBPOSIX_TTY_SERIAL_EOF_FINAL).
  • openat() from a directory fd never crossed into a mount below that directory, so WASI programs couldn't see mounts. Generic Unikraft bug, reproduced on QEMU/KVM.
  • A relative path starting with a dotfile (.profile) was glued to its directory without a /. Generic Unikraft bug, reproduced on QEMU/KVM.

v0.14.2

Choose a tag to compare

@github-actions github-actions released this 24 Sep 07:46
93430ff

Changed

  • A guest with no memory size asked for gets the size its runtime image is tested with, from a table the library carries (RUNTIME_SCRATCH_MB: c and rust 64 MiB, go 128, bash, python, python-shell and dotnet-aot 256, node 512, dotnet-jit 768, powershell 1024, agent 1536), instead of a flat 256 MiB: SandboxBuilder::boot looks the driver in the initrd up (default_scratch_mb), hluk run --scratch-mb is optional, and a manifest's scratch_mb is an override, with the runtime read from the image's name when the rootfs is a published one. A rootfs that has grown past half of that memory is reported by hluk build and hluk run with a size to set.

Added

  • Projects. hluk init starts a project from a template: it writes a hluk.toml manifest and starter files, and pulls the published rootfs the template runs on from GHCR into a local cache (~/.cache/hluk; HLUK_CACHE_DIR overrides), pinned to the tag of this hluk release (<runtime>:initrd-v<version>) so a project keeps running under the hluk that made it. hluk run with no --initrd runs the project the manifest describes, its flags standing in for the manifest's keys where given; hluk build runs its [build] command and builds its [rootfs] dockerfile with Docker into .hluk/rootfs.cpio; hluk pull refreshes the rootfs image; hluk templates lists the templates; hluk cache lists and removes what was pulled and snapshotted. With no arguments init asks for the template and the name. The pull speaks the registry API itself (anonymous token, manifest, one digest-checked layer), so a project runs on any host hluk runs on with no Docker. See docs/manifest.md.
  • hluk run --runtime <name|image> runs a published runtime image by name (python, node, agent, …) or full reference with no project and no local build: pulled into the cache when missing, warm by default. hluk snapshot save --runtime saves one the same way, and its --warm-exec CODE runs code before the snapshot is taken.
  • --log-level info timings are printed to a tenth of a millisecond.
  • Thirteen templates, one per runtime, compiled into the binary: python, agent, node, bash, dotnet (C# compiled in the guest by Roslyn), powershell, go, rust, c, dotnet-aot (compiled on the host with a [build] command and mounted into the guest), and http-python, http-node, http-dotnet (a Flask, Express or Kestrel server on a rootfs the project's Dockerfile extends with pip, npm or the .NET SDK, FROM the published <runtime>:v<version> base).
  • Warm starts. A manifest with warm = true (every template's default), hluk run --warm, or hluk run --runtime has the first run snapshot the booted guest before the workload runs, and every later run restore it instead of booting; --cold boots fresh. Snapshots live in the cache, one per distinct guest and shared by every project that runs it; a rebuilt rootfs replaces the snapshots of its old file. warm_exec is code the runtime driver runs once before that snapshot, so what it loads is in it (http-python sets import flask, http-node require('express')). The snapshot is stamped with the rootfs file, scratch_mb, entry, warm_exec and the build's snapshot key, and is taken again when any of them changes; mounts, network policy and environment are supplied on restore and need no new snapshot.
  • install.sh: curl -fsSL https://raw.githubusercontent.com/hyperlight-dev/hyperlight-unikraft/main/install.sh | sh installs the latest release's Linux binary into ~/.local/bin with no Rust toolchain (HLUK_VERSION, HLUK_INSTALL_DIR), verifying it against a SHA256SUMS when the release has one.
  • hluk init --image-version X.Y.Z (or HLUK_IMAGE_VERSION) pins another release's images than this build's, for a build between releases.
  • A program the exec driver runs from a mount is checked to be position-independent before the boot (hluk build after its command, hluk run before booting), and refused with the build flag that makes it one, instead of the guest's "Image format not recognized".
  • hluk build converts a Docker image's filesystem to the initrd CPIO in Rust (docker export streamed through a tar-to-newc converter that lays entries out as find . | cpio -o -H newc does and restores the /etc/hosts, /etc/nsswitch.conf and /etc/resolv.conf that docker export empties), so the host needs Docker and nothing else, on Windows and macOS too.

v0.14.1

Choose a tag to compare

@github-actions github-actions released this 23 Sep 04:31
2b4790d

Added

  • --port all lets the guest bind any port and --port LOW-HIGH a range, next to single ports; the library gains ListenPorts::all() and ListenPorts::with_range. all is for a container runtime, whose network namespace already scopes what the guest exposes, the way docker run -P publishes every port.
  • hluk --version.
  • --resolv-conf FILE installs a resolver configuration as the guest's /etc/resolv.conf, at boot and again on a restore, so a snapshot resolves names where it now runs rather than where it was taken; the library has SandboxBuilder::resolv_conf. Without it the rootfs's own file stands, and options single-request is added unless present. Under an allow list, the file's nameservers are exempt on port 53 like the host's own. Kernel: the new GetResolvConf host function is read once the rootfs is mounted and on resume.

Changed

  • A restored guest gets the mounts the restore names: on resume the kernel fetches the host's mount table (the new GetMounts host function) and makes its own match, mounting what is new, unmounting what is gone and remounting an entry whose index or read-only flag changed. A warm snapshot saved without mounts serves any mount set, so an embedder restores it to run a script over host directories, and hluk snapshot run --mount mounts what it is given. hluk bench takes --mount, and the new mount workload measures a mounted restore. A mount kept busy by a file open across the snapshot stays until a restore finds it free, its operations failing with ESTALE meanwhile; a mount table is at most 32 mounts and 3.5 KiB of entries (MOUNTS_MAX, FSTAB_ENTRIES_MAX).
  • A snapshot is named by its release and a snapshot key, <release>-k<kernel>-c<contract>: the embedded kernel's hash and a host contract number, the two things a snapshot depends on. A load matches the key, so a release that changes neither keeps every saved snapshot; one that does refuses them with Error::SnapshotRelease, which names the release that saved the snapshot and says to save it again, in hluk snapshot run, hluk bench and the library alike. hluk snapshot key prints this build's key; SNAPSHOT_KEY is the library's. Snapshots from 0.14.0, named by release alone, are refused the same way.
  • The net_* host functions exist on every path, refusing every socket() with EACCES when there is no network policy (before, they were absent and a guest without a policy got EIO). So a snapshot saved under a policy restores without one, its sockets dying on resume, and a snapshot saved without a policy restores under one; before, the first failed the restore for the missing host functions.
  • The urunc demo image (demos/urunc, published as hello-urunc) bakes its workload at /app/hello.py and declares it as the image's CMD, so docker run needs no command after the image name and the driver's fallback entrypoint stays out of the image contract. Its greeting now names Hyperlight.

Fixed

  • connect(2) with AF_UNSPEC dissolves a datagram socket's association, as on Linux, instead of failing. glibc's getaddrinfo relies on it to probe every candidate of a dual-stack answer through one IPv6 socket, so a passive lookup -- socket.getaddrinfo(None, port, AF_UNSPEC, SOCK_STREAM, 0, AI_PASSIVE), what Python's http.server does to bind -- no longer aborts the guest with a glibc assertion on the source address. Kernel: hostsock forwards it as the new net_disconnect host function.
  • A script, --exec or --guest-exec on a rootfs with no runtime driver is an error that names --entry, instead of a guest that exits with status 0 having run nothing.

v0.14.0

Choose a tag to compare

@github-actions github-actions released this 20 Sep 22:52
36b9406

Added

  • Cooperative step model for long-running guests. The kernel hands the vCPU back to the host when the guest goes idle, reporting when its next timer is due, instead of spinning in the VM; the host waits on that timer and on the guest's sockets, then re-enters. A guest with nothing left to wake it is reported deadlocked instead of hung forever, and a guest can be snapshotted at any boundary -- even mid-call -- and resumed later, in the same process or from disk. Library: SandboxBuilder::boot returns an AppSandbox; run dispatches a call and waits, submit dispatches without waiting, step advances the guest one boundary, join drives an entry-point workload to exit, and snapshot / from_snapshot (or restore in place) checkpoint and resume it, sockets included. Yield says why a step returned.
  • A plain Linux binary can be the guest's entry point (--entry "/bin/server --flag") with no runtime driver; hluk run drives it until it exits, and the library exposes it as AppSandbox::join. This is how a container runtime or an actor host runs a server in the guest.
  • asyncio now works in the Python guest, whose event loop needs the Unix-domain sockets the kernel now enables (examples/python/asyncio_demo.py).
  • The host learns how a call and the guest ended: a failed call carries a status (Yield::CallFailed { status } -- the program's own exit code, 1 for an uncaught exception, -1 when the driver could not run it), and a process exit carries the guest's status (Yield::Exited { status }, returned by join; hluk run exits with it). A guest that halts without reporting is an error, not an exit of unknown status.
  • A restored guest is re-seeded and reconnected: the kernel reseeds its CSPRNG on restore (two clones no longer draw the same os.urandom, UUIDs or TLS nonces) and re-establishes its sockets (listeners rebind; connections whose peers died read as closed), so a checkpointed server keeps serving after a restore with nothing saved beside the snapshot.
  • Runtime drivers receive calls through /dev/hlcall, each on its own thread. The device's HLCALL_IOC_MAXLEN ioctl reports the largest call the host can send, and HLCALL_IOC_GETENV hands a driver the host's current environment to refresh before each call.
  • AppSandbox::snapshot_to(dir), SandboxBuilder::from_snapshot_dir(dir) and AppSandbox::restore_from(dir) move a snapshot through disk. It is named by the crate version inside the directory; a load by another version fails and names the versions present.
  • New docs: docs/execution.md (the step model and sandbox lifecycle), docs/driver.md (the /dev/hlcall driver contract and how to write a driver), docs/clock.md and docs/random.md (where the guest's clocks and random bytes come from, and what a restore does to them).

Changed

  • Network-policy hostnames are enforced at the DNS question and the destination. Under an allow list, only listed names may be looked up and only listed, resolved-at-build, or DNS-learned addresses may be reached -- the host no longer resolves names for the guest, so an address it was never given is refused. Under a block list, a blocked name is refused and re-checked at each connect (250 ms deadline; refused if the lookup fails or runs late). Malformed or non-query traffic to port 53 is refused, and the allow list's DNS exemption is UDP-only. Before, a name was enforced only through addresses the host re-resolved at every connect, so a blocked name that moved was reachable until the resolver caught up, and an allow-listed guest could query any name.
  • hluk run and hluk snapshot run exit with the guest's status when the guest is what failed: sys.exit(3) is exit code 3 with nothing added, as running the script directly would be. A malformed --env (no =) or --mount (no :) is now an error instead of being silently ignored, and snapshot run drives a restored entry-point guest to its exit like run.
  • The crate has its own error type, hyperlight_unikraft::Error, and every public function returns hyperlight_unikraft::Result, with a variant per condition (CallFailed, GuestExited, Deadlocked, NoDriver, CallInFlight, and so on) plus Hyperlight for the hypervisor layer, so an embedder matches a failed call or a deadlock instead of parsing text. set_env_vars, which cannot fail, no longer returns a Result.
  • A program the guest runs no longer inherits a driver's call device or pipes (both are close-on-exec).
  • The .NET JIT driver sets the GC hard limit as a share of the guest's memory (DOTNET_GCHeapHardLimitPercent) instead of a fixed 768 MB that never engaged, so an allocation the guest cannot serve is now a catchable OutOfMemoryException instead of a SIGSEGV.
  • SandboxBuilder::boot returns an AppSandbox instead of a (MultiUseSandbox, GuestConfig) tuple; GuestConfig is no longer public and the free run is now AppSandbox::run. run fails if the guest exits before the call returns, join refuses a driver image with no call in flight, and boot refuses kernel/initrd/entry/scratch_mb on a from_snapshot builder or a mount path the kernel's vfs.fstab cannot carry (whitespace, :, brackets, or a relative path), instead of ignoring them.
  • Breaking, guest side: the driver protocol changed (see Removed), so rootfs images built for 0.13.0 must be rebuilt with just build-rootfs.
  • Kernel: guest sockets are more robust -- a recv/send on a connection whose peer died or whose host socket is gone (after a restore, or a reset while parked) returns instead of hanging, a select/accept loop no longer livelocks, and a server polling more than 64 sockets no longer stops waking.
  • Kernel: transfer buffers are sized from the host's I/O stacks instead of hard-coded literals, so a socket send carries the full 64 KiB, a directory listing or symlink target is no longer cut at 8 KiB / 1 KiB, and an environment over 4 KiB no longer vanishes.
  • Kernel: the periodic CSPRNG reseed timer is off, so an idle guest no longer wakes the host every 300 s, and a guest with nothing to wake it is reported deadlocked instead of waited on forever. The CSPRNG is still seeded at boot and on every restore.
  • AppSandbox::set_env_vars no longer rejects keys starting with HL_; the kernel reserves no keys now.
  • AllowList::from_hosts and BlockList::from_hosts fail with a ResolveError (naming the entry and the resolver error) instead of a String; several host-side policy internals are no longer public.

Removed

  • The net_resolve host function (unused; it ran a blocking, unfiltered resolver lookup on the vCPU thread).
  • The host_nanosleep host function (unused; it stalled the embedder's thread up to 30 s). net_poll now refuses a non-zero timeout: waiting is the host's job between entries.
  • The callback-and-halt driver protocol: drivers no longer write a callback pointer and halt the VM themselves, and the HL_* dispatch and env addresses are gone from the guest environment. HLCALL_IOC_GETENV replaces the raw env-refresh function pointer, and hl_driver_init no longer takes envp.
  • SNAPSHOT_TAG and the OciTag re-export: embedders no longer name snapshots themselves.

Fixed

  • Interactive programs no longer echo every character twice: the serial terminal now honors the ECHO flag and stores the termios a program sets, so a shell (hluk run --entry /bin/sh) can turn echo off, while a program that leaves ECHO on still has its input echoed once.
  • The Python drivers set PATH=/usr/local/bin:/usr/bin:/bin at startup, so subprocess.run(["python3", ...]) and other bare-name lookups find the interpreter (a host --env PATH still overrides it).
  • AWS's IPv6 instance-metadata address (fd00:ec2::254) is refused under every network policy, like the link-local metadata addresses already were.
  • Snapshotting a guest whose process had already exited produced an unresumable image; AppSandbox::snapshot (and snapshot_to, hluk snapshot save) now refuses with Error::GuestExited.
  • Listing a mounted directory too large for one host call (about 3,000 entries) poisoned the sandbox; the host now refuses that one listing with EOVERFLOW and the guest goes on. Other listing errors now reach the guest with the right errno instead of EIO.
  • The driver FunctionCall reader (hl_fc.h) bounds-checks every offset, so a malformed call fails instead of reading out of bounds.
  • Kernel: signal-handling fixes (sigaltstack) for a runtime that manages its own alternate signal stacks, so the .NET workers no longer crash the kernel when the guest runs out of memory.
  • The exec driver (C, C++, Rust, Go, .NET AOT) and the PowerShell driver report the program's exit status: a non-zero exit, a signal, a C++ std::terminate or exit 3 now fails the call. Before, they took the closed exit pipe for success whatever the program did.
  • Kernel: a program that returns from main() while another of its threads sleeps no longer hangs the guest.
  • The Node driver reads the next call asynchronously, so its event loop keeps turning: an unref()ed timer or handle left behind makes progress between calls, and the child no longer exits (and deadlocks the next call) after leaving only unref'd work.
  • Kernel: a guest kernel crash now ends the call with a GuestAborted error and the crash dump in the output, instead of the vCPU running on and the guest spinning at 100% CPU.
  • An exit inside a call ends that call with its status and leaves the runtime ready for the next, in every runtime: Python catches SystemExit; Node turns process.exit(), process.exitCode and uncaught errors into the status and stops the timers, servers and sockets the call left; the .NET JIT and bash drivers take a child's exit status and respawn for the next call. sys.exit(0) / process.exit(0) succeed; a non-zero code fails the call. Before, an exit could take down the whole guest or deadlock the next call.
  • The Python dr...
Read more

v0.13.0

Choose a tag to compare

@github-actions github-actions released this 12 Sep 20:56
d41b90a

0.13.0 is a ground-up rewrite of the project since 0.12.1: a single hluk CLI plus a library, a typed guest-to-host boundary, one reproducible embedded Unikraft kernel, and a formal runtime support-tier policy. It is not CLI- or API-compatible with 0.12.x; see Removed and the behavioral notes under Changed.

Added

  • Single hluk CLI with subcommands run, snapshot save / snapshot run, and bench (cold / cold-snap / warm-restore / warm-stateful / parallel). Flags: --mount HOST:GUEST[:ro], --net / --net-allow / --net-block / --port, --exec, --guest-exec (urunc-style, runs a binary baked into the initrd), --entry, --env KEY=VALUE, and --scratch-mb.
  • Library API: a SandboxBuilder — from_initrd(rootfs) / from_kernel / from_snapshot, then .boot() to get a running sandbox — plus the run dispatch call, Mount, Exec, GuestConfig, and re-exported Snapshot / OciTag. hluk is built on the same API.
  • 11 guest runtimes on a 3-tier support policy (docs/guest-support-tiers.md), a shared C driver toolkit, and a CPython conformance suite. .NET runs both ahead-of-time (AOT) and in-guest JIT (Roslyn source compilation).
  • Typed fs_* / net_* host functions with new filesystem operations (rename, symlink, readlink, hard link, chmod), multi-mount support, net_resolve (host DNS), real guest stdin (ReadStdin + EOF), and host environment-variable passthrough (--env, re-applied across snapshot restore).
  • --kernel <path> (advanced) to boot an external kernel instead of the embedded one, and a reproducible native-kernel test fixture (just build-native-kernel / just verify-native-kernel).
  • Snapshot save/restore as OCI-layout directories; macOS (hvf) support; parallel multi-VM benchmarking; on-demand Windows surrogates.

Changed

  • Guest-to-host boundary: a single JSON-RPC __dispatch function plus a host-side ToolRegistry became many small, typed FlatBuffer host functions returning real -errno (Linux numbers, with a Win32/Winsock-to-Linux translation table).
  • Boot metadata: pushed as magic-tagged TLVs prepended to the initrd became pulled via host functions at boot (GetCmdLine, GetInitrdBase/Size, GetWallClockNs, GetEnvVars, …), re-answerable after a snapshot restore.
  • Filesystem sandbox: a hand-rolled path resolver became cap-std with kernel-enforced openat2(RESOLVE_BENEATH) on Linux (component-by-component resolution on Windows). Networking: blocking socket2 became non-blocking rustix with a net_poll readiness model.
  • Guest memory: a no-paging design with a bespoke lib/cpiovfs and lib/ukmmap became the standard Unikraft paging/vmem/mmap stack with a ramfs initrd.
  • Kernel provisioning: per-example kraft builds became one reproducible, Docker-built Unikraft kernel embedded in the binary via include_bytes! and verified in CI (kraftkit dropped).
  • VMM: hyperlight-host 0.16 to 0.17. On Linux the default build is KVM-only (for the fast MADV_DONTNEED snapshot-restore path); MSHV support is behind --features mshv.
  • Behavioral (breaking): in 0.12.x --port implied --net; now --port requires an explicit --net (or --net-allow / --net-block) and errors on its own. Under --net, the default AllowAll policy permits the host loopback interface (needed for intra-guest server+client patterns); --net-allow / --net-block still block loopback.
  • PowerShell runtime moved from a 7.6.x tarball to the 7.4 (LTS) Alpine image.
  • The Unikraft kernel is now embedded by default (0.12.x took a positional kernel path); use --kernel to override it.

Removed

  • Wasm custom host tools (--tool, --tool-wasi-*), the public ToolRegistry / SandboxBuilder::tool() extension API, and the WASIp1 host-function sandbox.
  • The pyhl Python tool (image pull from GHCR, warm-then-snapshot, --deterministic) and the multifn-test / pydriver-run dev binaries; the snapshot workflow folded into hluk snapshot.
  • The --memory / --stack knobs (replaced by --scratch-mb), the reserved-mountpoint rejection list (/, /bin, /dev, /proc, /sys, /usr), and the implicit default /host mount path (mounts now require an explicit guest path).
  • Kernel-internal: the bespoke lib/cpiovfs, the /dev/hcall userspace device, the per-syscall TSC profiler, and the trace ports. File-backed mmap and demand paging move from the custom lib/ukmmap/cow.c to the standard ukvmem fault handler. (mmap's mremap is currently ENOSYS; glibc tolerates it.)
  • The app-elfloader and kraftkit forks; the platform builds against upstream app-elfloader and a single unikraft kernel fork.
  • Docs: the old host_functions.md (dispatch wire format / attack surface) and python-packages.md.

Fixed

  • Host-mount path-escape resolution is now OS-enforced (openat2 RESOLVE_BENEATH / RESOLVE_NO_MAGICLINKS on Linux; component-by-component on Windows), with defense-in-depth :ro enforcement on both the VFS and host sides.
  • Guest output is captured via a HostPrint host function (one VM exit per buffer instead of per byte) and exposed programmatically through GuestConfig::drain_output.

Pull requests

Full Changelog: v0.12.1...v0.13.0

v0.12.1

Choose a tag to compare

@danbugs danbugs released this 06 Jul 23:03
71c543b

Fix: bump pyhl::install() heap from 1.25 GiB to 2.5 GiB to match the v0.12.0 kernel requirements (subprocess support kconfig flags need more memory).

v0.12.0

Choose a tag to compare

@github-actions github-actions released this 03 Jul 20:15

What's Changed

  • ci: trigger cargo publish from release workflow by @danbugs in #103
  • feat: subprocess support, networking idle-poll fix, and CI improvements by @danbugs in #108

Full Changelog: v0.11.0...v0.12.0