Preparing mainnet launch Follow the progress ↗
GuidesProtected execution

PROTECTED EXECUTION 8 MIN READ

Assembling and checking the guest boot image

Assemble and check the kernel and boot filesystem.

Emerald crystal sculpture with natural light and prismatic reflections
On this page

scripts/build-guest-image.py assembles a Linux kernel and complete in-memory boot filesystem containing the pinned guest evidence services, Node, Python, age and the Cannabis control-plane modules. The current entry point checks those components and powers off. It has no customer-job intake, operating worker endpoint or confidential-hardware authorization.

This image is a step toward the measured worker described in worker deployment. It is not a mainnet service release. Assignment initialization and kernel resource enforcement are integrated into its checks. Hardware acceptance, the isolated workload launcher, protected storage, authenticated transport and independent review remain required.

Inputs and assembly

Use Linux x86_64, Python 3.12+, dpkg-deb, depmod and internet access for the first input download. The input lock pins 39 Ubuntu 24.04 package archives, the kernel release, Node and age distributions, the JavaScript dependency lock and both guest-service binary hashes. The package pins were obtained from Ubuntu's signed package indices with an empty installation state and separate empty archive cache, so already cached host packages were not omitted from the dependency closure.

Build the evidence services first. Then run from this repository:

python3 scripts/build-guest-image.py \
  --guest-services /work/cannabis-agent-build/guest-agent-artifacts-first \
  --cache /work/cannabis-image-downloads \
  --output /work/cannabis-image-first

python3 scripts/build-guest-image.py \
  --guest-services /work/cannabis-agent-build/guest-agent-artifacts-second \
  --cache /work/cannabis-image-downloads \
  --output /work/cannabis-image-second

Use fresh, separate output directories outside the public repository. An existing output is refused. Every package/download hash and the service hashes are checked before use. Package maintainer scripts and npm lifecycle scripts are not executed. The builder restores the merged /usr directory layout after package extraction and computes module dependencies for the pinned kernel.

The output contains vmlinuz, initramfs.cpio.gz, cmdline, build.json, the unpacked rootfs/ and private build-tool staging. The initramfs uses a deterministic newc archive with fixed ownership, timestamps, ordering and gzip metadata. Compare the final kernel, initramfs and command-line file hashes across builds. The command-line file's terminating newline is removed when passing its contents to QEMU.

The embedded file manifest checks regular-file contents during the boot check. This is local integrity evidence, not a hardware signature or proof against an operator controlling an ordinary VM. There is no persistent root disk or host filesystem mount. Customer execution will additionally require enforced isolation and protected temporary storage; the boot image does not establish those properties by itself.

The JavaScript runtime carries ethers and jose with locked transitive dependencies. Escrow inspection uses a checked-in ABI instead of loading the Solidity compiler inside the guest. Automated checks compare that ABI with the current compiled contract. Contract source, deployed-code hash checks and payment semantics are unchanged.

Run the actual VM boot check

Install qemu-system-x86_64, then run:

python3 scripts/verify-guest-boot.py \
  --image /work/cannabis-image-first \
  --output /work/cannabis-image-first-boot

This uses QEMU's ordinary TCG CPU emulation, two vCPUs and 2 GiB of RAM. It attaches no NIC, disk, shared filesystem, monitor, QMP or debugger. The default deadline is five minutes; failure or timeout does not count as a successful boot. Keep the serial log and verification JSON private.

Inside the VM, the entry point mounts ephemeral runtime storage, disables core dumps and further kernel-module loading, checks embedded files, exercises the Python extensions and age executable, and starts the actual attestation agent and REST bridge on loopback. Both production CPU probes must reject the unsupported sample profile with their exact structured refusal. Import errors, missing libraries and unexpected success fail the check. No hardware evidence, channel key, job key or customer computation is requested. The services are terminated and the VM powers off.

The boot check also runs the initialization gate with both CPU profiles. It requires exact refusal on unsupported hardware and verifies that child services and sockets are removed. A cancelled startup, changed service binary and occupied loopback port must fail. An existing listener is never queried as if it were the spawned bridge.

The check deliberately does not treat execution on real SNP/TDX hardware as an accepted deployment: that requires the separate initialization, measurement, negative-case and full workload acceptance run. Do not turn an ordinary boot result into an attestation policy.

Resource enforcement inside the guest

compute/guest_resources.py supplies a cgroup-v2 boundary for the trusted launcher. It creates one fresh group beneath an already configured, empty parent, without changing ancestor limits or moving other services. It requires the real kernel filesystem, root-owned paths without writable ancestors or symlinks, and enabled CPU, memory and process controllers. Ordinary files cannot stand in for enforcement.

The boundary sets a CPU quota, aggregate memory ceiling, process/thread ceiling, whole-group out-of-memory handling, and zero swap allowance. It disables compressed swap where that control exists and prevents child cgroups. Controls are read back before admission. A single-threaded trusted helper moves itself into the group before starting customer execution, so its descendants inherit the limits. Normal scheduling is required; the isolated runtime must remove capabilities and real-time scheduling privileges that could bypass these controls.

The live supervisor waits for completion, cancellation or a monotonic wall deadline. Expiry and cancellation take precedence even when the group is already empty; late observation cannot establish that the job finished on time. Cleanup uses the kernel's tree-wide kill operation, checks that no processes remain, then removes only the group it created. A new process session does not escape that cleanup. Failure to prove cleanup is an error requiring guest retirement. The independent guardian below handles supervisor death and stopped supervisors. These semantics follow the Linux cgroup-v2 interface.

The boot check executes bounded owned programs to verify CPU throttling, whole-group memory exhaustion, fork refusal, successful completion, cancellation and deadline cleanup. It also rejects changed limits, missing controllers, ordinary directories and intermediate symlinks. CI runs the same kernel checks on Linux. They do not use customer data, job keys or payments. To run them on a disposable Linux system whose root hierarchy already enables CPU, memory and process controllers:

sudo python3 -I -B scripts/check-guest-resources.py

These controls are one part of runtime isolation. They do not hide the control plane, implement a root filesystem or network policy, reserve provider capacity, or bound scratch storage and output logs. Memory already charged outside the group is not transferred by moving a process; staging buffers and input storage need their own protected budgets. Kernel counters are operational evidence, not trusted billing or proof of useful work. Admission must bind the actual loaded adapter, all resource limits, devices, network peers and funded assignment before any customer program or content key is exposed to it.

Independent process guardian

compute/guest_guardian.py adds a separate trusted process to enforce cleanup even when the supervisor exits, receives SIGKILL, or stops responding. Arm GuardedResources(parent, limits) before key release or starting a runtime, retain it throughout the job, and call assert_alive() before admission. It does not execute a customer program or establish confidential hardware.

While work runs, monitor guard.fileno() alongside the runtime's process handle in the supervisor's selector. Readability means call wait() to collect a terminal result or handle unexpected guardian failure. Once the runtime has exited, request wait(action='finish'). Do not leave a guardian failure unobserved while waiting indefinitely for the runtime. The control socket is non-inheritable and must never be passed to customer processes.

The owner opens a Linux process handle before spawning the guardian. Both retain descriptors for the same newly created resource group. Startup data is queued on a private bounded socket before spawn, and the owner waits for an armed response. Only descriptor numbers enter the helper command line; the helper inherits no customer file handles, environment, terminal or log output. It runs outside the workload resource group. The guardian watches the process handle rather than a reusable PID or socket EOF alone, so a leaked copy of the owner's socket cannot conceal owner death. This uses Python's Linux pidfd interface.

The guardian independently enforces the retained monotonic deadline, kills the complete process tree, verifies it empty and removes the original group. An early wait(action='finish') while work remains terminates the job and reports not-empty; it never disables protection for a live job. Late completion remains a deadline failure. wait(action='cancel') terminates and cleans up. The owner checks actual kernel removal before accepting a terminal response. If the guardian unexpectedly dies, the owner uses its retained group descriptors for cleanup and raises a fatal error instead of restarting protection around an uncertain job.

Ten kernel checks cover normal finish, cancellation, refused busy finish, owner SIGKILL, owner exit without finalizers, a stopped owner's deadline, a deliberately leaked control socket, guardian failure, independent expiry and late finish. The same checks run in CI and in the pinned boot image:

sudo python3 -I -B scripts/check-guest-guardian.py

This guardian contains workload processes; it does not erase staging data, retire keys, or power off a VM. The deployment controller must still dispose of the guest on fatal failure. Simultaneous loss of owner and guardian, kernel failure and a privileged attacker inside the guest require that outer lifecycle boundary. Filesystem, network and device isolation must keep workloads away from both control processes and their handles before customer execution is admitted.

Connect these artifacts to measured launch

For SNP, the reviewed launch chain must cover firmware, kernel, initramfs, kernel arguments and initial vCPU state. QEMU's SNP launch controls include initialization host data; the sev-snp-measure tool calculates expected digests using the corresponding firmware, kernel, initrd and CPU configuration. Pin and review the actual firmware, VMM and measurement-tool revisions before using their outputs as trusted reference values.

For TDX, firmware and runtime measurement registers must cover the entire boot chain. Contrast's reference-value documentation illustrates this distinction, including binding an external root filesystem through its integrity root in measured kernel arguments. Cannabis's current image has its userspace inside the initramfs; it does not yet have a reviewed firmware/TDX measurement calculation. Do not substitute a firmware-only measurement or an ordinary SHA256 file digest for that evidence.

The separate firmware and SNP measurement tools calculate candidate reference values from pinned firmware and verified private copies of these boot artifacts. They do not establish a hardware-approved reference. The initialization gate binds the retained assignment to HOST_DATA/MRCONFIGID through the pinned agent; actual hardware mismatch/success cases and enforcement of the loaded workload policy still require the worker acceptance run.

The published artifacts are build source and instructions. Before distributing guest binaries, complete the dependency/source and license review, preserve upstream and distribution notices, and satisfy the source-availability obligations of included components. A repeat build on one machine is not independent reconstruction or a security audit.