<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
    <channel>
        <title>Thomas Barabosch</title>
        <description>Systems security, reverse engineering, threat research, incident response, and AI Engineering.</description>
        <link>https://tbarabosch.com/</link>
        <atom:link href="https://tbarabosch.com/feed.xml" rel="self" type="application/rss+xml"/>
        <pubDate>Thu, 10 Sep 2026 14:24:48 +0000</pubDate>
        <lastBuildDate>Thu, 10 Sep 2026 14:24:48 +0000</lastBuildDate>
        <generator>Jekyll v3.10.0</generator>
        <dc:creator>Thomas Barabosch</dc:creator>
        
            <item>
                <title>Running NetBSD OCI images with Apple Container</title>
                <description>&lt;p&gt;Finally, the public &lt;a href=&quot;https://github.com/tbarabosch/container-runtime-netbsd/pkgs/container/netbsd&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ghcr.io/tbarabosch/netbsd:11&lt;/code&gt;&lt;/a&gt; image I built runs with Apple’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;container&lt;/code&gt; tool on Apple silicon. The &lt;a href=&quot;https://github.com/tbarabosch/container-runtime-netbsd/tree/v0.3.0&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;container-runtime-netbsd&lt;/code&gt; proof of concept&lt;/a&gt; pulls the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netbsd/arm64&lt;/code&gt; image, turns its OCI layers into a bootable FFS disk, starts NetBSD 11 with Virtualization.framework and runs the requested program through a small guest agent.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/running-netbsd-oci-images-with-apple-container/netbsd-container-uname.gif&quot; alt=&quot;Animated terminal showing container netbsd pulling the GHCR image, assembling its disk, booting NetBSD 11 and executing uname&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The current CLI only prints the final command output. I added the four status lines to make its otherwise silent work visible.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ghcr.io/tbarabosch/netbsd:11
              |
 OCI layers -&amp;gt; GPT / EFI / FFS
              |
     Apple VZ -&amp;gt; NetBSD 11
              |
     NVZA agent -&amp;gt; execve(2)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is not NetBSD support in an unmodified Apple Container release. I had to use OpenAI’s Codex to patch Apple Container 1.3.0 and use the NetBSD platform kit from the earlier experiments. With those two pieces installed, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;container netbsd run&lt;/code&gt; behaves like a real container command: it fetches the image, boots the guest, connects the process streams and returns its exit status.&lt;/p&gt;

&lt;!--more--&gt;

&lt;nav class=&quot;post-toc&quot; aria-labelledby=&quot;contents-heading&quot;&gt;
  &lt;p id=&quot;contents-heading&quot; class=&quot;manual-label&quot;&gt;CONTENTS&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#reusing-the-earlier-netbsd-vm&quot; id=&quot;markdown-toc-reusing-the-earlier-netbsd-vm&quot;&gt;Reusing the earlier NetBSD VM&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#what-i-had-to-patch&quot; id=&quot;markdown-toc-what-i-had-to-patch&quot;&gt;What I had to patch&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#turn-oci-layers-into-a-bootable-ffs-disk&quot; id=&quot;markdown-toc-turn-oci-layers-into-a-bootable-ffs-disk&quot;&gt;Turn OCI layers into a bootable FFS disk&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#use-a-serial-device-as-the-process-api&quot; id=&quot;markdown-toc-use-a-serial-device-as-the-process-api&quot;&gt;Use a serial device as the process API&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#run-it-and-what-remains&quot; id=&quot;markdown-toc-run-it-and-what-remains&quot;&gt;Run it and what remains&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/nav&gt;

&lt;h2 id=&quot;reusing-the-earlier-netbsd-vm&quot;&gt;Reusing the earlier NetBSD VM&lt;/h2&gt;

&lt;p&gt;Apple Container already puts every Linux container in its own lightweight virtual machine. In &lt;a href=&quot;/detecting-apple-container-from-inside-the-guest/&quot;&gt;Detecting Apple Container from inside the guest&lt;/a&gt;, I followed a request from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;container&lt;/code&gt; CLI through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;container-apiserver&lt;/code&gt;, the Linux runtime plugin and Virtualization.framework to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vminitd&lt;/code&gt; inside the guest. Each container gets its own VM instead of sharing one Linux kernel with the other containers.&lt;/p&gt;

&lt;p&gt;In &lt;a href=&quot;/porting-netbsd-to-apple-vz/&quot;&gt;Porting NetBSD to Apple VZ&lt;/a&gt;, I replaced that Linux guest with NetBSD 11. The patched kernel booted on the virtual ARM machine, used its Virtio console, block, entropy and network devices, mounted an FFS root filesystem and shut down cleanly. However, a purpose-built Swift program still had to start a prepared kernel and disk. It was a NetBSD VM, not a container runtime.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/moving-netbsd-on-apple-vz-from-direct-boot-to-efi/&quot;&gt;Moving NetBSD on Apple VZ from direct boot to EFI&lt;/a&gt; removed another custom part. Virtualization.framework’s generic EFI firmware starts NetBSD’s unmodified &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bootaa64.efi&lt;/code&gt;, the loader starts a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GENERIC64&lt;/code&gt; kernel and ACPI describes the hardware. Only the three generic Virtio patches remained in the platform kit.&lt;/p&gt;

&lt;p&gt;That left the OCI image. As described in &lt;a href=&quot;/oci-fundamentals-images-layers-and-registries/&quot;&gt;OCI fundamentals: images, layers and registries&lt;/a&gt;, an OCI image normally contains a userspace filesystem and process configuration, not a kernel. The GHCR image therefore provides NetBSD userspace. The runtime adds the kernel, EFI loader, disk format and process-control agent.&lt;/p&gt;

&lt;h2 id=&quot;what-i-had-to-patch&quot;&gt;What I had to patch&lt;/h2&gt;

&lt;p&gt;Apple Container supports CLI and runtime plugins. I use both. The CLI plugin adds &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;container netbsd&lt;/code&gt;; the runtime plugin starts and controls one NetBSD VM. The &lt;a href=&quot;https://github.com/tbarabosch/container-runtime-netbsd/blob/v0.3.0/runtime/Sources/NetBSDCLI/main.swift&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netbsd&lt;/code&gt; CLI plugin&lt;/a&gt; asks Apple’s image service for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netbsd/arm64&lt;/code&gt; manifest, assembles a RAW disk and creates a container handled by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;container-runtime-netbsd&lt;/code&gt;. It passes the disk path to the runtime as opaque data. CPU and memory still use the normal container resource settings.&lt;/p&gt;

&lt;p&gt;There was one problem: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;container-apiserver&lt;/code&gt; assumed that every runtime needed the Linux kernel, initial filesystem and image-root snapshot prepared by Apple Container. The NetBSD runtime cannot use any of them. Its EFI disk already contains the loader, kernel and root filesystem.&lt;/p&gt;

&lt;p&gt;The included &lt;a href=&quot;https://github.com/tbarabosch/container-runtime-netbsd/blob/v0.3.0/compat/apple-container-runtime-owned-resources.patch&quot;&gt;Apple Container compatibility patch&lt;/a&gt; adds one explicit service capability:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[[servicesConfig.services]]
type = &quot;runtime&quot;
capabilities = [&quot;runtime-owned-resources&quot;]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When a runtime declares this capability, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;container-apiserver&lt;/code&gt; requires &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtimeData&lt;/code&gt; and skips the Linux kernel, initial filesystem and root snapshot. Nothing changes for runtimes without the capability, and the Linux runtime still rejects a request without its Linux resources.&lt;/p&gt;

&lt;p&gt;NetBSD still needs two corrections for the devices used by the runtime. PCI memory decoding must be enabled for modern Virtio BARs, and a Virtio 1.0 reset must wait until the device reports status zero. The earlier MTU negotiation patch is also present in the kernel, although the runtime currently does not attach a network device. None of the NetBSD changes names Apple or adds a VZ-specific platform. Neither patch set is upstream, so the stock releases of NetBSD 11 and Apple Container 1.3.0 cannot run this setup on their own.&lt;/p&gt;

&lt;h2 id=&quot;turn-oci-layers-into-a-bootable-ffs-disk&quot;&gt;Turn OCI layers into a bootable FFS disk&lt;/h2&gt;

&lt;p&gt;The public image contains NetBSD userspace, but no kernel, EFI loader or guest agent. The CLI checks that the image declares &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netbsd/arm64&lt;/code&gt; and a NetBSD 11 release. It then reads each layer from Apple’s local content store and passes it to the &lt;a href=&quot;https://github.com/tbarabosch/container-runtime-netbsd/blob/v0.3.0/runtime/Sources/NetBSDOCI/OCILayerApplier.swift&quot;&gt;OCI layer applier&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Layers may be plain tar archives or use gzip or zstd compression. They are applied in manifest order, including ordinary and opaque whiteouts, hardlinks, symbolic links and file modes. The applier rejects absolute paths, parent traversal, paths through existing symlinks, extended attributes and special files. Device nodes therefore do not come from an untrusted image layer.&lt;/p&gt;

&lt;p&gt;After all layers have been staged, the &lt;a href=&quot;https://github.com/tbarabosch/container-runtime-netbsd/blob/v0.3.0/runtime/Sources/NetBSDOCI/NetBSDDiskAssembler.swift&quot;&gt;disk assembler&lt;/a&gt; verifies that the result looks like a complete NetBSD root. At minimum, it expects &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt;, a shell, the password database and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MAKEDEV&lt;/code&gt;. Only then does it overlay the runtime-owned files:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the patched &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GENERIC64&lt;/code&gt; kernel and unmodified &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bootaa64.efi&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;the statically linked NetBSD guest agent and its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rc.d&lt;/code&gt; service&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fstab&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rc.conf&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ttys&lt;/code&gt; and the EFI &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;boot.cfg&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;device metadata generated by NetBSD’s own &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MAKEDEV&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also lock the root password, disable interactive gettys on both Virtio ports and leave DHCP, SSH, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inetd&lt;/code&gt; and Postfix switched off. The agent starts the container process. There is no reason to log in to the guest.&lt;/p&gt;

&lt;p&gt;NetBSD’s Darwin-hosted &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nbmakefs&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nbgpt&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nbpwd_mkdb&lt;/code&gt; tools do the filesystem-specific work. The resulting RAW disk has a GPT, a 64 MiB FAT32 EFI System Partition and an FFSv1 root partition labeled &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netbsd-root&lt;/code&gt;. The ESP contains the AArch64 removable-media path &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EFI/BOOT/BOOTAA64.EFI&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;boot.cfg&lt;/code&gt; selects &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/netbsd&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root=NAME=netbsd-root&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;use-a-serial-device-as-the-process-api&quot;&gt;Use a serial device as the process API&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/tbarabosch/container-runtime-netbsd/blob/v0.3.0/runtime/Sources/ContainerRuntimeNetBSD/RuntimeService.swift&quot;&gt;runtime service&lt;/a&gt; is an XPC helper launched once per container. It creates a generic VZ platform with persistent machine identity and EFI variable state, the cloned root disk, an entropy device, a small Virtio GPU for EFI output and two Virtio console ports. There is no virtual NIC.&lt;/p&gt;

&lt;p&gt;Port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ttyVI00&lt;/code&gt; is the boot console. Its output is appended to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;boot.log&lt;/code&gt;, but its input comes from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/null&lt;/code&gt; and no getty listens on it. Port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ttyVI10&lt;/code&gt; is a private, root-owned connection between the macOS runtime and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/sbin/netbsd-vz-agent&lt;/code&gt; in the guest.&lt;/p&gt;

&lt;p&gt;That second port carries the &lt;a href=&quot;https://github.com/tbarabosch/container-runtime-netbsd/blob/v0.3.0/protocol/PROTOCOL.md&quot;&gt;NetBSD VZ Agent protocol&lt;/a&gt;, or NVZA. Every frame has a fixed 32-byte header containing the magic, protocol version, frame type, request identifier, process identifier and payload length. Control messages are bounded JSON objects; standard input, standard output, standard error and file contents remain opaque bytes.&lt;/p&gt;

&lt;p&gt;After a version handshake, the runtime translates Apple Container operations into agent requests. It supports process creation, start, wait, signal, terminal resize, standard-input closure, deletion and regular-file copy in both directions. Non-terminal processes keep stdout and stderr separate; a PTY merges them. The agent drains the output and sends EOF before reporting the final exit event. Otherwise, the last bytes from a short-lived command could disappear.&lt;/p&gt;

&lt;p&gt;There is no hidden shell. The CLI combines the OCI entrypoint and command, adds environment overrides and sends the executable and its literal argument vector to the guest. The agent calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;execve(2)&lt;/code&gt; and only searches &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATH&lt;/code&gt; when the executable contains no slash. If you want shell operators, use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/bin/sh -c&lt;/code&gt; yourself. Every process currently runs as root; OCI user selection is not implemented.&lt;/p&gt;

&lt;p&gt;Virtio console works, but its tty input queue is small. NVZA permits larger stream frames, while the serial adapter sends at most 768 data bytes at a time and waits for an acknowledgement before continuing. This prevents terminal input and file transfers from overrunning the queue. A vsock transport should eventually make this workaround unnecessary.&lt;/p&gt;

&lt;p&gt;When the main process exits, the runtime asks the agent to shut down NetBSD, waits briefly and force-stops the VZ machine only if the guest remains alive. A direct stop first signals the process and gives it a bounded wait. The process exit status then travels back through NVZA and Apple Container to the invoking CLI.&lt;/p&gt;

&lt;h2 id=&quot;run-it-and-what-remains&quot;&gt;Run it and what remains&lt;/h2&gt;

&lt;p&gt;Building the runtime requires an Apple silicon Mac with macOS 26 or newer, Xcode, network access and Apple Container 1.3.0. The repository pins the Apple source revision and the NetBSD platform-kit archive. The following commands keep the locally built Apple binaries and both NetBSD plugins under the repository’s ignored &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.build&lt;/code&gt; directory:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone &lt;span class=&quot;nt&quot;&gt;--branch&lt;/span&gt; v0.3.0 &lt;span class=&quot;nt&quot;&gt;--depth&lt;/span&gt; 1 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  https://github.com/tbarabosch/container-runtime-netbsd.git
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;container-runtime-netbsd

make compatible-container
&lt;span class=&quot;nv&quot;&gt;CONTAINER_INSTALL_ROOT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PWD&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/.build/apple-container-compat&quot;&lt;/span&gt; make &lt;span class=&quot;nb&quot;&gt;install

&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CLI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PWD&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/.build/apple-container-compat/bin/container&quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$CLI&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; system stop
&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$CLI&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; system start &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--install-root&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PWD&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/.build/apple-container-compat&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--disable-kernel-install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;system stop&lt;/code&gt; stops the active Apple Container service and its containers, so check existing work before running it. The locally built API server is necessary because the stock 1.3.0 server does not understand &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtime-owned-resources&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The NetBSD image is public and does not require a GHCR login:&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$CLI&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; netbsd run ghcr.io/tbarabosch/netbsd:11 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;    --name hello --remove -- /usr/bin/uname -a
&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;NetBSD container-runtime-netbsd 11.0 NetBSD 11.0 (GENERIC64) #&lt;/span&gt;0: Thu Aug 27 21:53:13 CEST 2026  tbarabosch@Thomass-MacBook-Neo.local:/Users/tbarabosch/code/netbsd-vz/.build/obj/sys/arch/evbarm/compile/GENERIC64 evbarm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I captured this run on an Apple silicon Mac with macOS 26.6.2, Xcode 26.6, Swift 6.3.3 and the patched Apple Container 1.3.0 source. The output is exact, including the cross-builder identity and object path embedded in the kernel version string. The command exited successfully, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--remove&lt;/code&gt; option deleted its container state and the runtime shut the guest down.&lt;/p&gt;

&lt;p&gt;The important part is that the custom Swift VM runner from the first article is no longer involved. Apple Container fetched the public image, the CLI assembled the disk, Virtualization.framework booted NetBSD through EFI and NVZA started &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uname&lt;/code&gt;. Its output and exit status reached the host as expected.&lt;/p&gt;

&lt;p&gt;There is still plenty to do:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Submit the generic Virtio corrections to NetBSD and the external-runtime changes to Apple Container.&lt;/li&gt;
  &lt;li&gt;Add guest networking, mounts, published ports and socket APIs.&lt;/li&gt;
  &lt;li&gt;Replace or complement the serial transport with vsock and handle crashed runtimes more cleanly.&lt;/li&gt;
  &lt;li&gt;Support non-root processes, recursive copy and more complete OCI filesystem metadata.&lt;/li&gt;
  &lt;li&gt;Rehash cached disks, serialize concurrent cache writes and expand negative and integration testing.&lt;/li&gt;
  &lt;li&gt;Replace the one-off base-image publisher with a reproducible, automated and attested GHCR pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;container netbsd run ghcr.io/tbarabosch/netbsd:11&lt;/code&gt; now boots NetBSD 11 and runs a real NetBSD program. It still needs patched components and lacks several everyday container features. But it works, and the image is available on GHCR for others who want to try it.&lt;/p&gt;
</description>
                <pubDate>Thu, 03 Sep 2026 19:30:00 +0000</pubDate>
                <link>https://tbarabosch.com/running-netbsd-oci-images-with-apple-container/</link>
                <guid isPermaLink="true">https://tbarabosch.com/running-netbsd-oci-images-with-apple-container/</guid>
                <dc:creator>Thomas Barabosch</dc:creator>
                
                <category>systems security</category>
                
                <category>Apple Containers</category>
                
                <category>NetBSD</category>
                
                <category>OCI</category>
                
                <category>virtualization</category>
                
                <category>macOS</category>
                
            </item>
        
            <item>
                <title>Earning the BSD Specialist certification</title>
                <description>&lt;p&gt;On 31 August 2026, I earned the &lt;a href=&quot;https://www.credly.com/badges/5bcfb19e-b19e-4168-a041-554545aeaa0c&quot;&gt;LPI BSD Specialist certification&lt;/a&gt;. The exam covers administration across FreeBSD, NetBSD and OpenBSD: installation, storage, networking, security, system maintenance and the Unix tools shared between them.&lt;/p&gt;

&lt;p&gt;I am pleased to have passed, but the credential does not mark the beginning of my work with BSD. It is a formal checkpoint after years of approaching these systems from several directions.&lt;/p&gt;

&lt;!--more--&gt;

&lt;figure class=&quot;credential-badge&quot;&gt;
  &lt;a href=&quot;https://www.credly.com/badges/5bcfb19e-b19e-4168-a041-554545aeaa0c&quot; aria-label=&quot;View my LPI BSD Specialist credential on Credly&quot;&gt;
    &lt;img src=&quot;/assets/images/posts/earning-the-bsd-specialist-certification/bsd-specialist-badge.png&quot; alt=&quot;LPI BSD Specialist Certification badge issued to Thomas Barabosch&quot; width=&quot;600&quot; height=&quot;600&quot; /&gt;
  &lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Much of that work began on the security side. A malformed ELF header led to a FreeBSD kernel crash and memory disclosure fixed as &lt;a href=&quot;https://www.freebsd.org/security/advisories/FreeBSD-SA-18:12.elf.asc&quot;&gt;CVE-2018-6924&lt;/a&gt;. Trap fuzzing exposed the OpenBSD denial of service later assigned &lt;a href=&quot;https://www.openbsd.org/errata63.html&quot;&gt;CVE-2018-14775&lt;/a&gt;. Together with Maxime Villard, I designed KLEAK, the kernel memory-disclosure detector credited with finding thirteen issues in &lt;a href=&quot;https://mail-index.netbsd.org/current-users/2019/02/06/msg035047.html&quot;&gt;NetBSD-SA2019-001&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;BSD projects also accepted reports and patches that became fixes in their source trees. Some crossed project boundaries: related mistakes in KAME-derived networking tools were corrected in both NetBSD and OpenBSD. I described that work in &lt;a href=&quot;/one-bsds-fix-is-another-bsds-bug/&quot;&gt;One BSD’s fix is another BSD’s bug&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;More recently, I moved from finding faults in BSD software to maintaining a small part of its supply chain. I updated FreeBSD’s &lt;a href=&quot;https://cgit.freebsd.org/ports/commit/?id=4fe2fada927d063c57cf7006de4f0dfacb8913aa&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;misc/xdelta3&lt;/code&gt; port&lt;/a&gt; to version 3.2.0 and became its maintainer. Keeping build metadata, dependencies and packaging correct is the sort of quiet maintenance an operating system ecosystem needs!&lt;/p&gt;

&lt;p&gt;The certification does not make FreeBSD, NetBSD and OpenBSD interchangeable. It confirms a useful, independent administrative baseline across all three and gives this part of my career a tidy marker. For anyone preparing for the same exam, I published the &lt;a href=&quot;/2026/08/27/preparing-for-bsd-specialist-with-a-one-page-cheat-sheet.html&quot;&gt;one-page BSD Specialist cheat sheet&lt;/a&gt; that I used for my final review.&lt;/p&gt;
</description>
                <pubDate>Mon, 31 Aug 2026 10:00:00 +0000</pubDate>
                <link>https://tbarabosch.com/earning-the-bsd-specialist-certification/</link>
                <guid isPermaLink="true">https://tbarabosch.com/earning-the-bsd-specialist-certification/</guid>
                <dc:creator>Thomas Barabosch</dc:creator>
                
                <category>systems security</category>
                
                <category>FreeBSD</category>
                
                <category>NetBSD</category>
                
                <category>OpenBSD</category>
                
            </item>
        
            <item>
                <title>From forty dots to an entropy map</title>
                <description>&lt;p&gt;In 2020, one of my triage tools printed a file’s entropy as forty colored dots. I mainly used it on executables that might be packed. It was fast, pleasantly colorful and easy to overread.&lt;/p&gt;

&lt;p&gt;The dots never proved packing. What helped was seeing where the byte distribution changed. Six years later, I wanted the same quick view with real offsets and without the fake verdict.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;the-original-forty-dots&quot;&gt;The original forty dots&lt;/h2&gt;

&lt;p&gt;The core of the old tool was small. It divided the file into forty chunks and colored each chunk by its Shannon entropy:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;entropy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Counter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()))&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;visualize_compression&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bin_count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;rb&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bin_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bins&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;current_chunk&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_chunk&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;ent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entropy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_chunk&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;bins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;colored&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;.&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;green&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;bins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;colored&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;.&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;yellow&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;bins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;colored&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;.&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;red&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;current_chunk&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bins&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It did the job in the usual quick-and-dirty RE fashion: read the complete file, chop it into chunks and print some dots. It also gets stuck on files shorter than forty bytes because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chunk_size&lt;/code&gt; becomes zero. The bigger problem is the color scheme. Two arbitrary thresholds look surprisingly authoritative when they are green, yellow and red.&lt;/p&gt;

&lt;p&gt;Forty bins were another shortcut. A tiny executable and a multi-gigabyte image got the same number of dots, even though each dot represented a wildly different amount of data.&lt;/p&gt;

&lt;p&gt;A companion &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;packed()&lt;/code&gt; helper went further and returned &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Packed&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Possibly packed&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Not packed&lt;/code&gt;. Its score included sections without raw data and sections with entropy below 1 or above 7. Those are all reasons to look closer at a PE file. They are not specific to packers. Compilers, linkers, installers and ordinary resources can produce the same measurements.&lt;/p&gt;

&lt;p&gt;The measurement itself is still useful. &lt;a href=&quot;https://onlinelibrary.wiley.com/doi/abs/10.1002/j.1538-7305.1948.tb00917.x&quot;&gt;Claude Shannon defined entropy&lt;/a&gt; from the probabilities of symbols. For bytes, it is calculated as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;H(X) = -sum(p(x) * log2(p(x)))&lt;/code&gt;. One repeated byte gives 0 bits per byte. An even distribution of all 256 byte values gives 8.&lt;/p&gt;

&lt;p&gt;Bin size really matters. A 128-byte bin cannot contain all 256 byte values, so it cannot reach 8 bits per byte. Larger bins give steadier numbers but hide small transitions. The output needs to show that resolution.&lt;/p&gt;

&lt;p&gt;I kept the new display to one strip:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;0x00000000 │▁▁▂▂▃▄▅▆▇█│ 0x00090000
            low entropy                    high entropy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;a-sparkline-with-byte-offsets&quot;&gt;A sparkline with byte offsets&lt;/h2&gt;

&lt;p&gt;The new &lt;a href=&quot;https://github.com/tbarabosch/re-scripts/blob/main/entropy/entropy_map.py&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;entropy-map&lt;/code&gt;&lt;/a&gt; is still one Python file and uses only the standard library. It scans the selected range once through a bounded buffer. Every byte belongs to exactly one bin, and the output prints both the bin size and the half-open file range.&lt;/p&gt;

&lt;p&gt;For a predictable demo, I asked OpenAI’s Codex to generate a file with nine 64 KiB regions. The first region repeats one byte, the second alternates two values, and the last cycles through all 256 values. Their entropies run from exactly 0 to 8 bits per byte.&lt;/p&gt;

&lt;p&gt;Using eight bins per region makes the staircase obvious in both the block height and the color:&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;python3 entropy/entropy_map.py &lt;span class=&quot;nt&quot;&gt;--bins&lt;/span&gt; 72 entropy-demo.bin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/from-forty-dots-to-an-entropy-map/entropy-map-terminal.png&quot; alt=&quot;Terminal output from entropy-map showing nine colored entropy plateaus from 0 through 8 bits per byte&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The same output without ANSI color remains readable:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&apos;entropy-demo.bin&apos; · 589824 bytes · range [0x00000000, 0x00090000)
72 bins · 8192 bytes/bin · H(range)=5.520 bits/byte

0x00000000 │▁▁▁▁▁▁▁▁▂▂▂▂▂▂▂▂▃▃▃▃▃▃▃▃▄▄▄▄▄▄▄▄▅▅▅▅▅▅▅▅▆▆▆▆▆▆▆▆▇▇▇▇▇▇▇▇████████████████│ 0x00090000
            0%                                 50%                              100%
                                ▁ 0.0   Shannon entropy   8.0 █
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;H(range)&lt;/code&gt; is 5.520, not the average of the nine local values. It describes the combined byte distribution. The strip shows what happens inside that range. To zoom in, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--offset 0x20000 --length 0x30000 --bins 24&lt;/code&gt; selects exactly &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0x20000, 0x50000)&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;where-it-fits&quot;&gt;Where it fits&lt;/h2&gt;

&lt;p&gt;There are already existing entropy visualizers. &lt;a href=&quot;https://github.com/Piyush-Bhor/entroplot/blob/29373b5378cc5dc1b39b13cf1da2b880a9b80540/src/main.rs&quot;&gt;Entroplot&lt;/a&gt; uses fixed 1,024-byte blocks and writes a PNG line chart. &lt;a href=&quot;https://github.com/ReFirmLabs/binwalk/wiki/Generating-Entropy-Graphs&quot;&gt;Binwalk&lt;/a&gt; combines an offset-based graph with firmware signatures and extraction. &lt;a href=&quot;https://github.com/WerWolv/Documentation/blob/db4b4adfa81e2f4158801a58c86e1a79051df649/imhex/views/data-information.md#entropy&quot;&gt;ImHex&lt;/a&gt; has an interactive graph and a local-entropy minimap in its hex editor. The archived &lt;a href=&quot;https://github.com/codilime/veles&quot;&gt;Veles&lt;/a&gt; offers a much broader set of interactive statistical views.&lt;/p&gt;

&lt;p&gt;I wanted something simpler: run it in the shell (including using it in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ranger&lt;/code&gt; or a similar terminal file manager), get a quick map and continue working. There is no GUI, plotting dependency or output file. Give it a file or a byte range and it prints the offsets, resolution and entropy strip.&lt;/p&gt;

&lt;p&gt;I read the strip as a map, not a diagnosis. Low entropy may be padding, text or another repetitive structure. High entropy may be compression, encryption, random data or media that was already compressed. A sharp edge may mark an embedded object or an entirely normal format boundary. Entropy gives me a byte range to inspect next. It still cannot prove packing. Good. That was the point of replacing the old dots.&lt;/p&gt;
</description>
                <pubDate>Sun, 30 Aug 2026 10:00:00 +0000</pubDate>
                <link>https://tbarabosch.com/from-forty-dots-to-an-entropy-map/</link>
                <guid isPermaLink="true">https://tbarabosch.com/from-forty-dots-to-an-entropy-map/</guid>
                <dc:creator>Thomas Barabosch</dc:creator>
                
                <category>malware analysis</category>
                
                <category>reverse engineering</category>
                
            </item>
        
            <item>
                <title>OCI fundamentals: images, layers and registries</title>
                <description>&lt;p&gt;While working on the ongoing &lt;a href=&quot;/porting-netbsd-to-apple-vz/&quot;&gt;NetBSD on Apple Virtualization series&lt;/a&gt;, I eventually had to look beyond boot loaders and Virtio drivers. I needed to understand how to package NetBSD as an OCI image for Apple’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;container&lt;/code&gt; tool. The actual NetBSD image deserves a separate future post. This short article is the reference material for it.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://opencontainers.org/&quot;&gt;OCI&lt;/a&gt; stands for Open Container Initiative. Canonical’s presentation &lt;a href=&quot;https://www.youtube.com/watch?v=RBAQyqgFl4w&quot;&gt;What is the Open Container Initiative?&lt;/a&gt; provides a good introduction to this technology.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;three-specifications-not-one-runtime&quot;&gt;Three specifications, not one runtime&lt;/h2&gt;

&lt;p&gt;The OCI project maintains three specifications. The &lt;a href=&quot;https://specs.opencontainers.org/image-spec/&quot;&gt;Image Specification&lt;/a&gt; describes the packaged content. The &lt;a href=&quot;https://specs.opencontainers.org/distribution-spec/&quot;&gt;Distribution Specification&lt;/a&gt; defines how registries push and pull that content. The &lt;a href=&quot;https://specs.opencontainers.org/runtime-spec/&quot;&gt;Runtime Specification&lt;/a&gt; describes the filesystem bundle, configuration and lifecycle used to start a container.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;build input
    |
    v
OCI image ------ Image Specification
    |
    v
registry API --- Distribution Specification
    |
    v
runtime bundle - Runtime Specification
    |
    v
container process
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;OCI does not build or run anything on its own. Its specifications let a tool build an image, a registry store it and another compatible tool fetch and run it.&lt;/p&gt;

&lt;h2 id=&quot;an-image-is-a-graph-of-blobs&quot;&gt;An image is a graph of blobs&lt;/h2&gt;

&lt;p&gt;An image manifest references a configuration and an ordered list of filesystem layers. Each descriptor records a media type, size and content digest. An optional image index points to several manifests, usually one per operating-system and CPU-architecture combination.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;image index (optional)
`-- manifest: linux/arm64
    |-- config: command, environment, layer order
    `-- filesystem changes
        |-- layer 2: /hello.txt
        `-- layer 1: Alpine root filesystem
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Layers are changesets, not complete copies of the filesystem. A runtime applies them from the base upward to produce one root filesystem. The configuration holds properties such as the entry point, default arguments, environment and working directory. A digest identifies immutable content; a convenient tag such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.0&lt;/code&gt; is only a movable name that resolves to a manifest.&lt;/p&gt;

&lt;p&gt;A normal container image does not include a kernel. It supplies userspace files and process settings; the runtime supplies the execution environment. That distinction is one reason the NetBSD experiment needs more than a different &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FROM&lt;/code&gt; line.&lt;/p&gt;

&lt;h2 id=&quot;push-an-image-to-github-container-registry&quot;&gt;Push an image to GitHub Container Registry&lt;/h2&gt;

&lt;p&gt;This minimal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Containerfile&lt;/code&gt; adds one filesystem layer and one command:&lt;/p&gt;

&lt;div class=&quot;language-dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; alpine:3.22&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;hello from OCI\n&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;/hello.txt
&lt;span class=&quot;k&quot;&gt;CMD&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; [&quot;cat&quot;, &quot;/hello.txt&quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry&quot;&gt;GitHub Container Registry&lt;/a&gt; accepts OCI images at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ghcr.io&lt;/code&gt;. With Apple’s &lt;a href=&quot;https://github.com/apple/container/blob/main/docs/command-reference.md&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;container&lt;/code&gt; commands&lt;/a&gt;, a local round trip looks like this:&lt;/p&gt;

&lt;p&gt;The reference &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ghcr.io/OWNER/oci-demo:1.0&lt;/code&gt; contains the registry, account namespace, image name and tag. During a push, the client uploads missing blobs and then the manifest. A pull resolves the tag and downloads the referenced configuration and layers.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;container system start

&lt;span class=&quot;nv&quot;&gt;GHCR_OWNER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;your-lowercase-github-name
&lt;span class=&quot;nv&quot;&gt;IMAGE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ghcr.io/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GHCR_OWNER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/oci-demo:1.0&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rsp&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;GHCR token: &apos;&lt;/span&gt; CR_PAT
&lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;\n&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;%s&apos;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$CR_PAT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | container registry login &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--username&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$GHCR_OWNER&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--password-stdin&lt;/span&gt; ghcr.io
&lt;span class=&quot;nb&quot;&gt;unset &lt;/span&gt;CR_PAT

container build &lt;span class=&quot;nt&quot;&gt;--tag&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$IMAGE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
container image push &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$IMAGE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The classic personal access token needs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write:packages&lt;/code&gt;. New packages are private by default. After repeating the login on another Mac, fetch and run the image with:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;container system start
container image pull &lt;span class=&quot;s2&quot;&gt;&quot;ghcr.io/your-lowercase-github-name/oci-demo:1.0&quot;&lt;/span&gt;
container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ghcr.io/your-lowercase-github-name/oci-demo:1.0&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Public packages can be pulled without authentication. This example publishes only the builder’s platform; multi-platform NetBSD images are a subject for the follow-up.&lt;/p&gt;
</description>
                <pubDate>Sat, 29 Aug 2026 10:00:00 +0000</pubDate>
                <link>https://tbarabosch.com/oci-fundamentals-images-layers-and-registries/</link>
                <guid isPermaLink="true">https://tbarabosch.com/oci-fundamentals-images-layers-and-registries/</guid>
                <dc:creator>Thomas Barabosch</dc:creator>
                
                <category>systems security</category>
                
                <category>Apple Containers</category>
                
                <category>NetBSD</category>
                
                <category>OCI</category>
                
            </item>
        
            <item>
                <title>What lives after the last PE section?</title>
                <description>&lt;p&gt;A Portable Executable does not necessarily end with its last mapped section. Trailing bytes form an &lt;em&gt;overlay&lt;/em&gt;: data that Windows does not map as part of the image, but which still belongs to the file.&lt;/p&gt;

&lt;p&gt;Removing an overlay is easy. Doing it without discarding useful information is the interesting part. This post revisits an old helper I wrote a couple of years ago and adds the missing guardrails using OpenAI’s Codex.&lt;/p&gt;

&lt;p&gt;This somehow continues my work on &lt;a href=&quot;/malware-analyst-guide-to-pe-timestamps/&quot;&gt;PE timestamps&lt;/a&gt; and &lt;a href=&quot;/fix-pe-magic-numbers-with-malduck/&quot;&gt;PE magic numbers&lt;/a&gt;, but at the other end of the file.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;an-old-helper&quot;&gt;An old helper&lt;/h2&gt;

&lt;p&gt;Years ago, I wrote a small script to remove PE overlays. It is from 2021 and simple enough to show in full:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;sys&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pefile&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Usage: pe_strip_overlay.py PATH_TO_PE_WITH_OVERLAY&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;rb&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;pe&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pefile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;.bin&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;_stripped.bin&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;wb&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Could not trim PE file: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;__main__&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, the heavy lifting is done mostly by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pefile&lt;/code&gt;. It looked like a good idea for a short post, so I used Codex to review its assumptions and turn it into the safer public helper described below as this is a common problem you might stumble upon while reversing Windows malware.&lt;/p&gt;

&lt;p&gt;The old script discarded the overlay, invented a name with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;str.replace()&lt;/code&gt;, caught every exception and reported no hashes. Its filename could collide with another file. More importantly, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pefile.PE.trim()&lt;/code&gt; does not ask whether the suffix contains a PE Certificate Table. So in summary, a classical RE script!&lt;/p&gt;

&lt;h2 id=&quot;the-section-table-is-not-the-whole-file&quot;&gt;The section table is not the whole file&lt;/h2&gt;

&lt;p&gt;The raw end of a section is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PointerToRawData + SizeOfRawData&lt;/code&gt;. The highest relevant end offset gives an overlay candidate:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;file offset -&amp;gt;

+---------+---------+---------+------------------+
| headers | .text   | .idata  | appended bytes   |
+---------+---------+---------+------------------+
                              ^ overlay candidate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That boundary needs a parser. The &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/debug/pe-format&quot;&gt;Microsoft PE/COFF specification&lt;/a&gt; has one awkward exception: the Certificate Table contains a file offset, not an RVA, because the loader does not map its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WIN_CERTIFICATE&lt;/code&gt; records.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Optional Header
  Certificate Table ---------------------+
                                          v
+---------+----------+-------------------+----------+
| headers | sections | WIN_CERTIFICATE   | more data|
+---------+----------+-------------------+----------+
                     outside mapped sections
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The pinned &lt;a href=&quot;https://github.com/erocarrera/pefile/blob/v2024.8.26/pefile.py#L7903-L7966&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pefile&lt;/code&gt; 2024.8.26 implementation&lt;/a&gt; skips the Security directory while finding the overlay. Its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trim()&lt;/code&gt; therefore removes certificate bytes too. They are outside the mapped sections, but not undescribed junk.&lt;/p&gt;

&lt;h2 id=&quot;preserve-the-suffix-before-trimming-it&quot;&gt;Preserve the suffix before trimming it&lt;/h2&gt;

&lt;p&gt;The updated script uses explicit paths, exclusive output creation, SHA-256 and a hard stop for a nonempty or malformed Certificate Table. The &lt;a href=&quot;https://github.com/tbarabosch/re-scripts/blob/06c38cccce956731244703f68c8239426759d0e6/pe/trim_pe_overlay.py&quot;&gt;complete helper and reusable Apple Container environment live in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;re-scripts&lt;/code&gt; at commit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;06c38cc&lt;/code&gt;&lt;/a&gt;. The abbreviated excerpts below cover only its two important decisions.&lt;/p&gt;

&lt;p&gt;First, the Security data-directory entry is either empty or a stop sign. A malformed pair of offset and size is not a reason to guess:&lt;/p&gt;

&lt;div data-language=&quot;PYTHON EXCERPT&quot; class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;VirtualAddress&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Size&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OverlayError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;malformed PE Certificate Table entry; refusing to trim&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OverlayError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;PE Certificate Table present; refusing to trim&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Only after that check does the helper split the bytes at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pefile&lt;/code&gt;’s overlay offset. It writes the evidence first and the derived executable second:&lt;/p&gt;

&lt;div data-language=&quot;PYTHON EXCERPT&quot; class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;overlay_offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_overlay_data_start_offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;overlay_offset&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;overlay_offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OverlayError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;invalid overlay offset&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;overlay_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;overlay_offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;trimmed_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;overlay_offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;write_new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;overlay_out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;overlay_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;write_new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;trimmed_out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trimmed_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I built this no-op with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x86_64-w64-mingw32-gcc 13-win32&lt;/code&gt; in an Ubuntu 24.04 Apple Container:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I put &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OVERLAY-DEMO.txt&lt;/code&gt; into a ZIP, appended it and ran the helper with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pefile&lt;/code&gt; 2024.8.26:&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;x86_64-w64-mingw32-gcc &lt;span class=&quot;nt&quot;&gt;-Os&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; hello.exe hello.c
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;zip &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; payload.zip OVERLAY-DEMO.txt
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cp &lt;/span&gt;hello.exe hello-overlay.exe
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dd &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;payload.zip &lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;hello-overlay.exe &lt;span class=&quot;nv&quot;&gt;oflag&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;append &lt;span class=&quot;nv&quot;&gt;conv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;notrunc &lt;span class=&quot;nv&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;none
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;python3 trim_pe_overlay.py hello-overlay.exe &lt;span class=&quot;nt&quot;&gt;--overlay-out&lt;/span&gt; recovered.zip &lt;span class=&quot;nt&quot;&gt;--trimmed-out&lt;/span&gt; hello-trimmed.exe
&lt;span class=&quot;go&quot;&gt;input:   hello-overlay.exe (15021 bytes, sha256=223aaf2145a3d03ae375c72251a320320b3728c83ee1440221ffcf2253b6ed7a)
overlay: recovered.zip (173 bytes at 0x3a00, sha256=06dfd3404725f7152381d9e44a3fbb9021cdddd8e508b743efe3c1313d18e5dc)
trimmed: hello-trimmed.exe (14848 bytes, sha256=7c71c75a9d692a7c3436be34b487d96c1a21c6ad3868079d060675af2b070623)

&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;cmp payload.zip recovered.zip
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;cmp hello.exe hello-trimmed.exe
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Both comparisons succeeded: the suffix was the exact ZIP and the derived PE was the pre-append executable.&lt;/p&gt;

&lt;h2 id=&quot;a-certificate-table-is-a-stop-sign&quot;&gt;A certificate table is a stop sign&lt;/h2&gt;

&lt;p&gt;I locally signed the executable with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;osslsigncode&lt;/code&gt;. The helper found its Certificate Table and refused before creating output:&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;python3 trim_pe_overlay.py hello-signed.exe &lt;span class=&quot;nt&quot;&gt;--overlay-out&lt;/span&gt; signed.overlay &lt;span class=&quot;nt&quot;&gt;--trimmed-out&lt;/span&gt; hello-signed-trimmed.exe
&lt;span class=&quot;gp&quot;&gt;error: PE Certificate Table present;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;refusing to trim &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0x3a00, &lt;span class=&quot;nv&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1488&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A Certificate Table proves neither signature validity nor trust. It only describes certificate data at a file offset and probably not the job of this script. Authenticode verification is a separate job and shall be addressed in another blog post.&lt;/p&gt;
</description>
                <pubDate>Fri, 28 Aug 2026 10:00:00 +0000</pubDate>
                <link>https://tbarabosch.com/what-lives-after-the-last-pe-section/</link>
                <guid isPermaLink="true">https://tbarabosch.com/what-lives-after-the-last-pe-section/</guid>
                <dc:creator>Thomas Barabosch</dc:creator>
                
                <category>malware analysis</category>
                
                <category>reverse engineering</category>
                
                <category>Windows</category>
                
            </item>
        
            <item>
                <title>Moving NetBSD on Apple VZ from direct boot to EFI</title>
                <description>&lt;p&gt;The first version of &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netbsd-vz&lt;/code&gt;&lt;/a&gt; booted NetBSD 11 directly through Apple’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VZLinuxBootLoader&lt;/code&gt;. It worked, but the host had to prepare a padded AArch64 kernel image and pass a flattened device tree (FDT) to a project-specific &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VZ64&lt;/code&gt; kernel. That experiment needed four NetBSD patch files, including changes for the early bootstrap and console.&lt;/p&gt;

&lt;p&gt;The same guest now boots through Virtualization.framework’s generic EFI platform. EFI starts NetBSD’s vanilla &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bootaa64.efi&lt;/code&gt;, the loader enters a vanilla &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GENERIC64&lt;/code&gt; kernel configuration, and ACPI describes the virtual hardware. Three generic Virtio fixes remain. Everything specific to direct kernel booting is gone.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;direct boot                         EFI boot
-----------                         --------
host loads kernel + FDT             firmware loads stock NetBSD loader
four patch files                    three generic Virtio patches
custom VZ64 configuration           stock GENERIC64 configuration
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is not just one patch fewer, it is less Apple VZ related code that needs to be added to the upstream NetBSD kernel.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;efi-on-apple-vz-in-a-nutshell&quot;&gt;EFI on Apple VZ in a nutshell&lt;/h2&gt;

&lt;p&gt;EFI is the firmware interface between the virtual machine and its operating system loader. With Virtualization.framework, &lt;a href=&quot;https://developer.apple.com/documentation/virtualization/vzefibootloader&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VZEFIBootLoader&lt;/code&gt;&lt;/a&gt; provides that environment in Apple VZ Framework. A &lt;a href=&quot;https://developer.apple.com/documentation/virtualization/vzefivariablestore&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VZEFIVariableStore&lt;/code&gt;&lt;/a&gt; keeps the firmware’s NVRAM-style state separately from the guest disk.&lt;/p&gt;

&lt;p&gt;The disk contains a FAT32 EFI System Partition (ESP). When no more specific boot entry exists, the &lt;a href=&quot;https://uefi.org/specs/UEFI/2.11/03_Boot_Manager.html#removable-media-boot-behavior&quot;&gt;UEFI removable-media rules&lt;/a&gt; give AArch64 firmware a standard loader path: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EFI/BOOT/BOOTAA64.EFI&lt;/code&gt;. The disk builder copies NetBSD 11’s unmodified &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bootaa64.efi&lt;/code&gt; there. Firmware loads it, the loader finds the kernel, and the kernel takes over.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;VZEFIBootLoader
      |
      +-- EFI variable store
      |
      `-- GPT disk
            |
            +-- FAT32 ESP
            |     `-- EFI/BOOT/BOOTAA64.EFI
            |
            `-- FFS root
                  `-- /netbsd (GENERIC64)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;EFI does not know anything special about this NetBSD-on-VZ experiment. It knows how to find and start an EFI application. NetBSD’s loader knows how to start NetBSD.&lt;/p&gt;

&lt;p&gt;After the loader hands over control, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GENERIC64&lt;/code&gt; uses the ACPI tables exposed by VZ to discover the CPUs, GICv3 interrupt controller, timer, PCI host bridge, and the devices below it. Direct boot obtained similar information from the FDT supplied with the raw kernel image. Changing the discovery mechanism is what allows the standard kernel configuration to replace the custom &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VZ64&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;from-direct-boot-to-efi&quot;&gt;From direct boot to EFI&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;/porting-netbsd-to-apple-vz/&quot;&gt;earlier direct-boot version&lt;/a&gt; used an API named for Linux, although the interface accepted the raw AArch64 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Image&lt;/code&gt; format that a cross-compiled NetBSD could also produce. The runner supplied that image and a command line. VZ placed an FDT in memory and passed its address in register &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x0&lt;/code&gt; when it entered the kernel.&lt;/p&gt;

&lt;p&gt;That short route made the project responsible for details normally handled by firmware and the operating-system loader. The build padded the kernel to an 8 MiB reservation, regenerated its AArch64 image header, and rejected a kernel that grew into adjacent boot data. The kernel configuration enabled the FDT path and removed EFI and ACPI.&lt;/p&gt;

&lt;p&gt;The EFI path moves those jobs into firmware and NetBSD’s normal arm64 loader:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;DIRECT

VZLinuxBootLoader
        |
        v
padded raw AArch64 Image + command line
        |
        v
host-supplied FDT -&amp;gt; VZ64 kernel


EFI

VZEFIBootLoader
        |
        v
GPT + ESP -&amp;gt; stock bootaa64.efi
        |
        v
ACPI handoff -&amp;gt; stock GENERIC64 kernel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The result is closer to an ordinary bootable NetBSD disk. The host configures a generic machine and attaches devices; it no longer prepares the kernel’s entry format or describes the platform through a direct-boot FDT.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/moving-netbsd-on-apple-vz-from-direct-boot-to-efi/netbsd-vz-efi-boot.gif&quot; alt=&quot;Animated condensed terminal transcript of NetBSD 11 booting through EFI on Apple VZ and shutting down cleanly&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;less-patches-less-issues&quot;&gt;Less Patches, Less Issues&lt;/h2&gt;

&lt;p&gt;The direct-boot tree carried four patch files with 378 lines between them. The &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz/tree/main/patches&quot;&gt;current patch directory&lt;/a&gt; contains three files with 70 lines.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;DIRECT BOOT: 4 files / 378 lines
        |
        +-- viocon-console.patch ------- removed
        +-- vz64-config.patch ----------- removed
        +-- vz-platform.patch
        |      |-- FDT/bootstrap -------- removed
        |      |-- no-match console ----- removed
        |      |-- PCI memory decode ---- kept, split out
        |      `-- Virtio reset wait ---- kept, split out
        `-- vioif-mtu.patch ------------- kept
                    |
                    v
EFI BOOT: 3 files / 70 lines
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The old &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz/blob/8da4fae201966eb433a84dd35cd2aeda0a9e038c/patches/viocon-console.patch&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viocon-console.patch&lt;/code&gt;&lt;/a&gt; promoted Virtio console port zero to the kernel console. EFI lets NetBSD keep its normal GOP console instead, while a vanilla getty provides the interactive serial login later. The old &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz/blob/8da4fae201966eb433a84dd35cd2aeda0a9e038c/patches/vz64-config.patch&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vz64-config.patch&lt;/code&gt;&lt;/a&gt; defined exactly the direct-boot hardware subset. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GENERIC64&lt;/code&gt; already includes the EFI, ACPI, PCI, and Virtio paths needed by the new machine.&lt;/p&gt;

&lt;p&gt;The old &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz/blob/8da4fae201966eb433a84dd35cd2aeda0a9e038c/patches/vz-platform.patch&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vz-platform.patch&lt;/code&gt;&lt;/a&gt; mixed platform-specific and generic work. Its FDT mapping and empty-console accommodations disappear with direct boot. Its two reusable corrections survive as focused patches: enable PCI memory decoding for modern Virtio BARs, and wait until a Virtio 1.0 reset actually completes before configuring queues.&lt;/p&gt;

&lt;p&gt;The current &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz/blob/main/patches/virtio-pci-memory.patch&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;virtio-pci-memory.patch&lt;/code&gt;&lt;/a&gt; enables memory decoding alongside PCI bus mastering and I/O decoding. &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz/blob/main/patches/virtio-reset.patch&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;virtio-reset.patch&lt;/code&gt;&lt;/a&gt; polls the device status after writing zero, as required before queue setup continues. &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz/blob/main/patches/vioif-mtu.patch&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vioif-mtu.patch&lt;/code&gt;&lt;/a&gt; negotiates the MTU feature advertised by VZ’s network device and applies its value to the interface.&lt;/p&gt;

&lt;p&gt;None of these three patches names Apple, changes EFI or ACPI, or defines a VZ platform. They are generic driver corrections and therefore much better upstream candidates than the old patches.&lt;/p&gt;

&lt;h2 id=&quot;less-complexity-during-vm-creation-and-booting&quot;&gt;Less Complexity during VM Creation and Booting&lt;/h2&gt;

&lt;p&gt;EFI did not make the machine smaller. It moved complexity out of the kernel delta and into conventional firmware, storage, and display interfaces.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1,088 MiB RAW disk
+------------------------------------------------------+
| GPT | 64 MiB FAT32 ESP | FFSv1 root              | GPT |
+------------------------------------------------------+
          |                   |
          |                   `-- /netbsd + base system
          `-- BOOTAA64.EFI + boot.cfg

separate host state
+----------------------+       +----------------------+
| machine identifier   |       | EFI variable store   |
+----------------------+       +----------------------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The runner now creates or reuses a generic machine identifier and EFI variable store. The VM also needs a Virtio GPU because EFI and early kernel output use the Graphics Output Protocol (GOP). The VZ Virtio serial device is not an EFI console, so a headless terminal stays quiet until NetBSD starts &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getty&lt;/code&gt; on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/ttyVI00&lt;/code&gt;. Earlier messages remain available through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dmesg&lt;/code&gt; after login.&lt;/p&gt;

&lt;p&gt;The ESP contains a small &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;boot.cfg&lt;/code&gt; that asks the loader to start &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netbsd&lt;/code&gt; verbosely with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root=NAME=netbsd-root&lt;/code&gt;. Using the GPT and FFS label avoids coupling the root filesystem to whichever device number autoconfiguration assigns. The default runner boots a copy-on-write clone of the published disk and creates temporary EFI state. Supplying explicit disk and state paths makes both persistent.&lt;/p&gt;

&lt;p&gt;Firmware and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bootaa64.efi&lt;/code&gt; also add work before the kernel starts. Direct boot remains attractive when a firmware-free path and a small boot chain matter more than matching ordinary hardware. For this proof of concept, however, boot speed was not the main requirement. Reducing NetBSD-specific changes was.&lt;/p&gt;

&lt;h2 id=&quot;wrapping-it-up&quot;&gt;Wrapping It Up&lt;/h2&gt;

&lt;p&gt;Moving to EFI made &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netbsd-vz&lt;/code&gt; less of a special NetBSD platform and more of a normal arm64 guest. Stock firmware conventions select the vanilla loader. The vanilla loader starts &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GENERIC64&lt;/code&gt;. ACPI is responsible for the hardware description. The remaining changes sit in Virtio drivers without anything specific to Apple VZ.&lt;/p&gt;

&lt;p&gt;In the end, NetBSD 11 is not unmodified yet, but the platform-specific patch set is now gone. The repository’s &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz/blob/main/docs/TECHNICAL.md&quot;&gt;technical documentation&lt;/a&gt; documents the disk geometry, runner state, build inputs, and current patches.&lt;/p&gt;
</description>
                <pubDate>Thu, 27 Aug 2026 20:00:00 +0000</pubDate>
                <link>https://tbarabosch.com/moving-netbsd-on-apple-vz-from-direct-boot-to-efi/</link>
                <guid isPermaLink="true">https://tbarabosch.com/moving-netbsd-on-apple-vz-from-direct-boot-to-efi/</guid>
                <dc:creator>Thomas Barabosch</dc:creator>
                
                <category>NetBSD</category>
                
                <category>virtualization</category>
                
                <category>macOS</category>
                
            </item>
        
            <item>
                <title>Preparing for BSD Specialist with a one-page cheat sheet</title>
                <description>&lt;p&gt;FreeBSD, NetBSD and OpenBSD share an ancestry, but an exam question can turn on the command or configuration file they do not share. Package tools, rc service control, persistent network configuration and kernel handling are exactly where Linux muscle memory becomes unhelpful. I condensed those differences into a printable one-page &lt;a href=&quot;/assets/files/bsd-specialist-cheat-sheet.pdf&quot;&gt;BSD Specialist Cheat Sheet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The guide covers the five weighted objective areas, common administration workflows, cross-BSD command differences and the Unix tools expected by the exam. It follows version 1.0 of the &lt;a href=&quot;https://www.lpi.org/our-certifications/exam-702-objectives/&quot;&gt;official LPI BSD Specialist objectives for exam 702-100&lt;/a&gt; and is intended for a final pass after hands-on preparation, not as a replacement for the objectives or the BSD manuals, which can change.&lt;/p&gt;

&lt;figure class=&quot;guide-preview&quot;&gt;
  &lt;a class=&quot;guide-preview-link&quot; href=&quot;/assets/files/bsd-specialist-cheat-sheet.pdf&quot; aria-label=&quot;Open the BSD Specialist Cheat Sheet PDF&quot;&gt;
    &lt;img src=&quot;/assets/images/posts/preparing-for-bsd-specialist-with-a-one-page-cheat-sheet/bsd-specialist-cheat-sheet-preview.png&quot; alt=&quot;Preview of the one-page BSD Specialist Cheat Sheet showing four columns on base-system software, storage and access, operations and networking, and Unix tools across FreeBSD, NetBSD and OpenBSD&quot; width=&quot;842&quot; height=&quot;596&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;BSD Specialist Cheat Sheet (PDF)&lt;/figcaption&gt;
&lt;/figure&gt;
</description>
                <pubDate>Thu, 27 Aug 2026 10:00:00 +0000</pubDate>
                <link>https://tbarabosch.com/2026/08/27/preparing-for-bsd-specialist-with-a-one-page-cheat-sheet.html</link>
                <guid isPermaLink="true">https://tbarabosch.com/2026/08/27/preparing-for-bsd-specialist-with-a-one-page-cheat-sheet.html</guid>
                <dc:creator>Thomas Barabosch</dc:creator>
                
                <category>systems security</category>
                
                <category>FreeBSD</category>
                
                <category>NetBSD</category>
                
                <category>OpenBSD</category>
                
            </item>
        
            <item>
                <title>Preparing for GH-500 with a final review guide</title>
                <description>&lt;p&gt;GitHub Advanced Security is several products and workflows wearing one security label. The distinctions worth retaining are practical: which suite owns a control, where push protection ends and credential remediation begins, what the dependency graph feeds, and when CodeQL default setup stops being enough. I condensed those decision points into a printable one-page &lt;a href=&quot;/assets/files/gh-500-final-review-guide.pdf&quot;&gt;GH-500 Final Review Guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The guide covers Secret Protection, supply chain security, Code Security and CodeQL, administration, vulnerability terminology, abbreviations, and current high-signal limits. It follows the July 2026 blueprint and is intended for a final pass after hands-on preparation, not as a replacement for the &lt;a href=&quot;https://learn.microsoft.com/en-us/credentials/certifications/resources/study-guides/gh-500&quot;&gt;official GH-500 study guide&lt;/a&gt;, whose objectives can change.&lt;/p&gt;

&lt;figure class=&quot;guide-preview&quot;&gt;
  &lt;a class=&quot;guide-preview-link&quot; href=&quot;/assets/files/gh-500-final-review-guide.pdf&quot; aria-label=&quot;Open the GH-500 Final Review Guide PDF&quot;&gt;
    &lt;img src=&quot;/assets/images/posts/preparing-for-gh-500-with-a-final-review-guide/gh-500-final-review-guide-preview.png&quot; alt=&quot;Preview of the one-page GH-500 Final Review Guide showing four columns on GitHub security suites, secret and supply chain protection, CodeQL, operations, abbreviations, and current limits&quot; width=&quot;842&quot; height=&quot;596&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;GH-500 Final Review Guide (PDF)&lt;/figcaption&gt;
&lt;/figure&gt;
</description>
                <pubDate>Wed, 26 Aug 2026 10:00:00 +0000</pubDate>
                <link>https://tbarabosch.com/preparing-for-gh-500-with-a-final-review-guide/</link>
                <guid isPermaLink="true">https://tbarabosch.com/preparing-for-gh-500-with-a-final-review-guide/</guid>
                <dc:creator>Thomas Barabosch</dc:creator>
                
                <category>systems security</category>
                
                <category>GitHub</category>
                
                <category>GitHub Advanced Security</category>
                
                <category>software supply chain</category>
                
            </item>
        
            <item>
                <title>Preparing for AWS SAA-C03 with a final review guide</title>
                <description>&lt;p&gt;The last hours before an architecture exam are a bad time to rediscover whether a standby serves reads or which endpoint avoids a NAT gateway. I condensed the distinctions I wanted in front of me into a printable one-page &lt;a href=&quot;/assets/files/aws-saa-c03-final-review-guide.pdf&quot;&gt;AWS SAA-C03 Final Review Guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The guide focuses on service boundaries, abbreviations, current limits, resilience and cost tradeoffs, and the traps hidden in plausible answers. It is for a final pass after hands-on preparation, not a replacement for the &lt;a href=&quot;https://docs.aws.amazon.com/aws-certification/latest/solutions-architect-associate-03/solutions-architect-associate-03.html&quot;&gt;official AWS Certified Solutions Architect - Associate exam guide&lt;/a&gt;, whose objectives and service scope can change.&lt;/p&gt;

&lt;figure class=&quot;guide-preview&quot;&gt;
  &lt;a class=&quot;guide-preview-link&quot; href=&quot;/assets/files/aws-saa-c03-final-review-guide.pdf&quot; aria-label=&quot;Open the AWS SAA-C03 Final Review Guide PDF&quot;&gt;
    &lt;img src=&quot;/assets/images/posts/preparing-for-aws-saa-c03-with-a-final-review-guide/aws-saa-c03-final-review-guide-preview.png&quot; alt=&quot;Preview of the one-page AWS SAA-C03 Final Review Guide showing four columns of architecture comparisons, constants, and abbreviations&quot; width=&quot;842&quot; height=&quot;596&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;AWS SAA-C03 Final Review Guide (PDF)&lt;/figcaption&gt;
&lt;/figure&gt;
</description>
                <pubDate>Sun, 23 Aug 2026 10:00:00 +0000</pubDate>
                <link>https://tbarabosch.com/preparing-for-aws-saa-c03-with-a-final-review-guide/</link>
                <guid isPermaLink="true">https://tbarabosch.com/preparing-for-aws-saa-c03-with-a-final-review-guide/</guid>
                <dc:creator>Thomas Barabosch</dc:creator>
                
                <category>systems security</category>
                
                <category>AWS</category>
                
                <category>cloud architecture</category>
                
            </item>
        
            <item>
                <title>If NetBSD runs on a toaster, it must run on Apple VZ: Porting NetBSD to yet another platform</title>
                <description>&lt;p&gt;In 2005, Technologic Systems put a TS-7200 ARM board inside a standard two-slice toaster and ran NetBSD on it. &lt;a href=&quot;https://www.netbsd.org/foundation/reports/2005Q3Q4.html&quot;&gt;The appliance still made toast&lt;/a&gt;. &lt;a href=&quot;https://www.netbsd.org/releases/formal-3/NetBSD-3.0.html&quot;&gt;NetBSD 3.0 subsequently listed the TS-7200&lt;/a&gt; as the “NetBSD Controlled Toaster,” turning the old portability joke into supported hardware.&lt;/p&gt;

&lt;p&gt;Twenty-one years later, I had a less thermally ambitious target: Apple’s Virtualization.framework on an Apple silicon Mac. If NetBSD could run a toaster, it had no excuse for avoiding a virtual ARM machine. The result is &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netbsd-vz&lt;/code&gt;&lt;/a&gt;, a reproducible build that boots NetBSD 11.0 from a direct AArch64 kernel image, mounts an FFS root disk, drives the VZ Virtio devices and shuts the virtual machine down cleanly.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;                           |\
                           | \~~~~~~~~~~~~~~~~\
                           |  \     NetBSD      \
                           |   \          /~~~~~/
                           |    \~~~~~~~~/
                           |
              .-&quot;&quot;&quot;&quot;-.    |
           .-&apos;////////`-._ |
         .&apos;/////////////  `|
        ////////////    _.-|
        \///////   _..-&apos;   |
         `--...---&apos;        |
             __..---.      |      .---..__
          .-&apos;::::::::`-.__.|.__.-&apos;::::::::`-.
        .&apos;::::::::::::::::::::::::::::::::::::`.
       /::::::::::::::::::::::::::::::::::::::::\
      /::::::....::::::::::::::::::::::::::::::::\
     |::::..    ..:::::::::::::::::::::::::::::::|
     |:::..      ..::::::::::::::::::::::::::::::|
     |::::..    ..:::::::::::::::::::::::::::::::|
      \::::::....:::::::::::::::::::::::::::::::/
       \:::::::::::::::::::::::::::::::::::::::/
        `.:::::::::::::::::::::::::::::::::::.&apos;
          `-._:::::::::::::::::::::::::::_.-&apos;
              `--..__::::::::::::__..--&apos;
                     `--.::::.--&apos;
                         `--&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;!--more--&gt;

&lt;nav class=&quot;post-toc&quot; aria-labelledby=&quot;contents-heading&quot;&gt;
  &lt;p id=&quot;contents-heading&quot; class=&quot;manual-label&quot;&gt;CONTENTS&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#start-with-the-virtual-hardware-contract&quot; id=&quot;markdown-toc-start-with-the-virtual-hardware-contract&quot;&gt;Start with the virtual hardware contract&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#freebsd-took-a-different-route-through-vz&quot; id=&quot;markdown-toc-freebsd-took-a-different-route-through-vz&quot;&gt;FreeBSD took a different route through VZ&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#first-things-first-prepare-the-kernel-image-before-touching-a-driver&quot; id=&quot;markdown-toc-first-things-first-prepare-the-kernel-image-before-touching-a-driver&quot;&gt;First Things First: Prepare the Kernel Image before touching a driver&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#make-the-fdt-mapping-agree-with-the-kernel&quot; id=&quot;markdown-toc-make-the-fdt-mapping-agree-with-the-kernel&quot;&gt;Make the FDT mapping agree with the kernel&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#enable-the-pci-path-and-finish-resetting-virtio&quot; id=&quot;markdown-toc-enable-the-pci-path-and-finish-resetting-virtio&quot;&gt;Enable the PCI path and finish resetting Virtio&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#turn-a-virtio-port-into-the-kernel-console&quot; id=&quot;markdown-toc-turn-a-virtio-port-into-the-kernel-console&quot;&gt;Turn a Virtio port into the kernel console&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#put-an-ffs-root-behind-virtio-block&quot; id=&quot;markdown-toc-put-an-ffs-root-behind-virtio-block&quot;&gt;Put an FFS root behind Virtio block&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#negotiate-the-mtu-that-vz-advertises&quot; id=&quot;markdown-toc-negotiate-the-mtu-that-vz-advertises&quot;&gt;Negotiate the MTU that VZ advertises&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#companion-code&quot; id=&quot;markdown-toc-companion-code&quot;&gt;Companion Code&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#summing-up&quot; id=&quot;markdown-toc-summing-up&quot;&gt;Summing Up&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/nav&gt;

&lt;p&gt;The complete boot is easier to recognize in motion. The following GIF animates
a condensed transcript from an actual offline &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make smoke&lt;/code&gt; run. It omits
repetitive &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dmesg&lt;/code&gt; output and machine-local paths, but retains the device
attachment, root mount, login, userspace marker, clean FFS unmount and guest
poweroff.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/porting-netbsd-to-apple-vz/netbsd-vz-boot.gif&quot; alt=&quot;Animated condensed terminal transcript of NetBSD 11 booting on Apple VZ and shutting down cleanly&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;start-with-the-virtual-hardware-contract&quot;&gt;Start with the virtual hardware contract&lt;/h2&gt;

&lt;p&gt;I described the host side of Virtualization.framework in &lt;a href=&quot;/build-a-tiny-linux-vm-with-apple-vz/&quot;&gt;Build a tiny Linux VM with Apple VZ&lt;/a&gt;. The NetBSD runner uses the same small part of the framework: a generic ARM platform, one virtual CPU, 512 MiB of memory, a direct kernel loader and selected Virtio devices. Console and entropy are always present. Block storage and VZ NAT are explicit additions.&lt;/p&gt;

&lt;p&gt;The interesting work is therefore in the guest, not another tour through the Swift configuration. Apple’s class is named &lt;a href=&quot;https://developer.apple.com/documentation/virtualization/vzlinuxbootloader&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VZLinuxBootLoader&lt;/code&gt;&lt;/a&gt;, and Apple documents it as a Linux kernel loader. In practice, the interface accepts the uncompressed AArch64 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Image&lt;/code&gt; format expected by that boot path.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;macOS
  |
  `-- Virtualization.framework
        |-- direct AArch64 Image loader
        |-- flattened device tree
        |-- Virtio console
        |-- Virtio entropy
        |-- Virtio block ---- 1 GiB RAW disk
        `-- optional NIC ---- VZ NAT
                    |
              NetBSD VZ64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is close to hardware that NetBSD already understands. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;evbarm-aarch64&lt;/code&gt; port handles AArch64, FDT, PSCI, GICv3 and generic timers. NetBSD also has drivers for Virtio console, block, entropy and network devices. Nevertheless, there were still some issues that needed to be resolved: how VZ reserved memory around the kernel image, how the FDT was mapped, which PCI address spaces were enabled, when a Virtio reset had completed, which console became the kernel console and which network features the guest accepted.&lt;/p&gt;

&lt;h2 id=&quot;freebsd-took-a-different-route-through-vz&quot;&gt;FreeBSD took a different route through VZ&lt;/h2&gt;

&lt;p&gt;This was not the first BSD bring-up on Virtualization.framework. Thomas Fontaine’s &lt;a href=&quot;https://github.com/tjfontaine/freebsd-apple-virtualization&quot;&gt;FreeBSD-on-Apple-Virtualization work&lt;/a&gt; booted a prepared FreeBSD 16.0-CURRENT/aarch64 guest to multiuser, persisted data on UFS, configured &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vtnet0&lt;/code&gt; through VZ NAT, wrote through a Virtio console and shut down cleanly.&lt;/p&gt;

&lt;p&gt;The two experiments reached a similar device set through different boot contracts. The FreeBSD work uses VZ’s generic EFI platform. EFI starts the stock FreeBSD &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loader.efi&lt;/code&gt;, which follows the ACPI handoff into the kernel. The NetBSD experiment bypasses EFI and enters a raw AArch64 Image with an FDT. That difference decides where the investigation starts: EFI media and ACPI for FreeBSD, kernel-image placement and FDT bootstrap mappings for NetBSD.&lt;/p&gt;

&lt;h2 id=&quot;first-things-first-prepare-the-kernel-image-before-touching-a-driver&quot;&gt;First Things First: Prepare the Kernel Image before touching a driver&lt;/h2&gt;

&lt;p&gt;The build starts with the official NetBSD 11.0 &lt;a href=&quot;https://cdn.netbsd.org/pub/NetBSD/NetBSD-11.0/source/sets/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gnusrc&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sharesrc&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;syssrc&lt;/code&gt; sets&lt;/a&gt;. &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz/blob/main/scripts/build.sh&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scripts/build.sh&lt;/code&gt;&lt;/a&gt; pins and verifies the SHA-512 digest of every archive, extracts the source, applies four tracked patches and uses NetBSD’s own &lt;a href=&quot;https://www.netbsd.org/docs/guide/en/chap-build.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build.sh&lt;/code&gt;&lt;/a&gt; to create Darwin-hosted AArch64 cross tools. This is actually one of my favorite NetBSD features: apart from it’s portability, it can be cross-compiled on several platforms. Personally, I can confirm this for Linux and macOS.&lt;/p&gt;

&lt;p&gt;I did not keep the full &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GENERIC64&lt;/code&gt; configuration as it included far more evbarm hardware than VZ exposes. The project-specific &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz/blob/main/patches/vz64-config.patch&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VZ64&lt;/code&gt; configuration&lt;/a&gt; keeps the most important modules while also retaining DDB, symbols and tracing for failures. For instance, it removes modules, memory disks, compatibility ABIs, EFI, ACPI and drivers for physical storage, USB, display and audio hardware.&lt;/p&gt;

&lt;p&gt;VZ placed adjacent boot data close enough to the reduced kernel image to stop the kernel from starting. The builder now reserves exactly 8 MiB, pads the payload and regenerates its AArch64 header with the final declared size and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ARM\x64&lt;/code&gt; magic. It also rejects a kernel that grows into the reservation. This prevents the boot inputs from colliding.&lt;/p&gt;

&lt;h2 id=&quot;make-the-fdt-mapping-agree-with-the-kernel&quot;&gt;Make the FDT mapping agree with the kernel&lt;/h2&gt;

&lt;p&gt;VZ passes the flattened device tree to the kernel in register &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x0&lt;/code&gt;. NetBSD maps that FDT during the early AArch64 bootstrap. On the observed VZ layout, the FDT can share a 2 MiB L2 block with an existing kernel bootstrap mapping.&lt;/p&gt;

&lt;p&gt;The original FDT mapping requested both unprivileged execute-never (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UXN&lt;/code&gt;) and privileged execute-never (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PXN&lt;/code&gt;) attributes. The existing block used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UXN&lt;/code&gt;. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pmapboot_enter&lt;/code&gt; could not reuse one block for two mappings with different attributes, so boot stopped before any useful console existed. The &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz/blob/main/patches/vz-platform.patch&quot;&gt;platform patch&lt;/a&gt; makes the attributes compatible:&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; mov x2, #L2_SIZE
 mov x3, #L2_SIZE
 mov x4, #LX_BLKPAG_ATTR_NORMAL_WB | LX_BLKPAG_AP_RW
&lt;span class=&quot;gd&quot;&gt;-orr x4, x4, #LX_BLKPAG_UXN | LX_BLKPAG_PXN
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+/* The FDT may share an L2 block with the kernel bootstrap mapping. */
+orr x4, x4, #LX_BLKPAG_UXN
&lt;/span&gt; mov x5, x26
 bl  pmapboot_enter
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is a tiny diff at a particularly problematic point in the boot. There is no filesystem, debugger transport or working console yet. The useful fact came from the mapping geometry: the FDT and kernel were not independently broken; they asked the same page-table block to describe memory in incompatible ways.&lt;/p&gt;

&lt;h2 id=&quot;enable-the-pci-path-and-finish-resetting-virtio&quot;&gt;Enable the PCI path and finish resetting Virtio&lt;/h2&gt;

&lt;p&gt;Once the kernel reached device discovery, the next boundary was PCI. VZ presents modern Virtio devices with their capabilities in memory BARs (Base Address Registers). NetBSD’s Virtio PCI attach path enabled bus mastering and I/O decoding, but not PCI memory decoding. The first half of the fix is exactly as plain as it sounds:&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; csr = pci_conf_read(pc, tag, PCI_COMMAND_STATUS_REG);
&lt;span class=&quot;gd&quot;&gt;-csr |= PCI_COMMAND_MASTER_ENABLE | PCI_COMMAND_IO_ENABLE;
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+csr |= PCI_COMMAND_MASTER_ENABLE | PCI_COMMAND_MEM_ENABLE |
+    PCI_COMMAND_IO_ENABLE;
&lt;/span&gt; pci_conf_write(pc, tag, PCI_COMMAND_STATUS_REG, csr);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That made the BARs accessible. It did not make the queues reliable. The second half concerns time rather than address space.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html&quot;&gt;Virtio 1.0 specification&lt;/a&gt; requires a driver resetting a device to write status zero and then wait until a subsequent read returns zero. VZ completes that reset asynchronously. NetBSD wrote zero and continued into queue configuration before VZ had reported completion. The writes appeared to happen, but the queue state did not survive.&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; bus_space_write_1(iot, ioh, VIRTIO_CONFIG1_DEVICE_STATUS,
     status | old);
&lt;span class=&quot;gi&quot;&gt;+if (status == 0) {
+    /* Virtio 1.0 requires the driver to wait for reset completion. */
+    while (bus_space_read_1(iot, ioh,
+        VIRTIO_CONFIG1_DEVICE_STATUS) != 0)
+        DELAY(1);
+}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Waiting for the status byte closed the race. Console, block, entropy and network devices all use the same Virtio PCI transport, so this one correction sits below most of the useful machine.&lt;/p&gt;

&lt;h2 id=&quot;turn-a-virtio-port-into-the-kernel-console&quot;&gt;Turn a Virtio port into the kernel console&lt;/h2&gt;

&lt;p&gt;VZ connects the host terminal to a Virtio console device. NetBSD 11’s &lt;a href=&quot;https://man.netbsd.org/viocon.4&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viocon(4)&lt;/code&gt;&lt;/a&gt; attaches that device as a tty, but it does not promote port zero to the kernel console. A tty appearing during autoconfiguration is not enough for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;printf&lt;/code&gt;, DDB and the polling console operations used during shutdown or panic handling.&lt;/p&gt;

&lt;p&gt;The patch is substantially derived from kernel-console support originally written by Taylor R. Campbell and carried as patch 2 in Emile “iMil” Heitor’s &lt;a href=&quot;https://mail-index.netbsd.org/port-amd64/2026/01/22/msg003793.html&quot;&gt;ongoing full VirtIO console patch series&lt;/a&gt;. I adapted its late-console portion to NetBSD 11 and the VZ configuration. The project does not include the series’ early Virtio-MMIO console or multiport work.&lt;/p&gt;

&lt;p&gt;After &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viocon(4)&lt;/code&gt; has created and filled port zero’s queues, the &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz/blob/main/patches/viocon-console.patch&quot;&gt;console patch&lt;/a&gt; installs its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;consdev&lt;/code&gt; operations:&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gi&quot;&gt;+if (cn_tab == NULL || cn_tab-&amp;gt;cn_dev == NODEV ||
+    cn_tab-&amp;gt;cn_pri &amp;lt; CN_NORMAL) {
+    sc-&amp;gt;sc_ports[0]-&amp;gt;vp_cntab = (struct consdev) {
+        .cn_pollc = viocon_cnpollc,
+        .cn_getc = viocon_cngetc,
+        .cn_putc = viocon_cnputc,
+        .cn_dev = VIOCONDEV(device_unit(self), 0),
+        .cn_pri = CN_REMOTE,
+    };
+    cn_set_tab(&amp;amp;sc-&amp;gt;sc_ports[0]-&amp;gt;vp_cntab);
+    printf(&quot;NetBSD/VZ viocon console attached\n&quot;);
+}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The distinction between &lt;em&gt;late&lt;/em&gt; and &lt;em&gt;early&lt;/em&gt; matters. Live output begins only after FDT, PCI, Virtio PCI and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viocon(4)&lt;/code&gt; have attached. Messages from kernel entry through that point remain invisible on the terminal. The smoke test runs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dmesg&lt;/code&gt; after login to recover them. This limitation makes early boot issues harder to diagnose.&lt;/p&gt;

&lt;h2 id=&quot;put-an-ffs-root-behind-virtio-block&quot;&gt;Put an FFS root behind Virtio block&lt;/h2&gt;

&lt;p&gt;A kernel reaching a root-device prompt proves less than it first appears to. I wanted the normal NetBSD base system to reach &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt;, start &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getty&lt;/code&gt;, accept a console login and shut itself down.&lt;/p&gt;

&lt;p&gt;The disk builder downloads the official NetBSD 11.0 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;evbarm-aarch64&lt;/code&gt; &lt;a href=&quot;https://cdn.netbsd.org/pub/NetBSD/NetBSD-11.0/evbarm-aarch64/binary/sets/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;base&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;etc&lt;/code&gt; sets&lt;/a&gt; and verifies their SHA-512 hashes. NetBSD’s cross-built &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nbmakefs&lt;/code&gt; creates a little-endian FFSv1 filesystem. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nbgpt&lt;/code&gt; wraps it in a fixed 1 GiB RAW disk with a protective MBR, primary and backup GPT, and one NetBSD FFS partition beginning at LBA 2048.&lt;/p&gt;

&lt;p&gt;Both the GPT wedge and the filesystem use the label &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netbsd-root&lt;/code&gt;. The kernel command line selects &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root=NAME=netbsd-root&lt;/code&gt;, avoiding a dependency on whichever unit number autoconfiguration assigns. In the accepted run, Virtio block attached as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ld0&lt;/code&gt;, wedge discovery created &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dk0&lt;/code&gt;, and the kernel mounted FFS from that wedge.&lt;/p&gt;

&lt;p&gt;The root overlay changes only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fstab&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rc.conf&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ttys&lt;/code&gt;. It enables a secure console login, disables ordinary network services and starts DHCP only when a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vioif&lt;/code&gt; interface exists. The release set’s empty root password is retained for this isolated console proof. Networking remains opt-in, and enabling remote services without first setting a password is not recommended.&lt;/p&gt;

&lt;h2 id=&quot;negotiate-the-mtu-that-vz-advertises&quot;&gt;Negotiate the MTU that VZ advertises&lt;/h2&gt;

&lt;p&gt;At this point, the offline guest has no NIC. Adding a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VZVirtioNetworkDeviceConfiguration&lt;/code&gt; with VZ NAT exposes another Virtio PCI function, but the first attempts did not produce a usable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vioif&lt;/code&gt; interface.&lt;/p&gt;

&lt;p&gt;VZ advertises the Virtio network &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VIRTIO_NET_F_MTU&lt;/code&gt; feature. In this configuration, feature negotiation failed until the NetBSD driver accepted that feature and read the advertised MTU. The &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz/blob/main/patches/vioif-mtu.patch&quot;&gt;network patch&lt;/a&gt; adds the bit to the requested feature set and applies the device value to the interface:&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gi&quot;&gt;+#define VIRTIO_NET_F_MTU        __BIT(3)
&lt;/span&gt;
 req_features =
&lt;span class=&quot;gd&quot;&gt;-    VIRTIO_NET_F_MAC | VIRTIO_NET_F_STATUS | VIRTIO_NET_F_CTRL_VQ |
-    VIRTIO_NET_F_CTRL_RX | VIRTIO_F_NOTIFY_ON_EMPTY;
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+    VIRTIO_NET_F_MTU | VIRTIO_NET_F_MAC | VIRTIO_NET_F_STATUS |
+    VIRTIO_NET_F_CTRL_VQ | VIRTIO_NET_F_CTRL_RX |
+    VIRTIO_F_NOTIFY_ON_EMPTY;
&lt;/span&gt;
+if (features &amp;amp; VIRTIO_NET_F_MTU)
&lt;span class=&quot;gi&quot;&gt;+    ifp-&amp;gt;if_mtu = virtio_read_device_config_2(vsc,
+        VIRTIO_NET_CONFIG_MTU);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With that change, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vioif0&lt;/code&gt; attached with an MTU of 1500, reported active carrier, acquired an IPv4 address by DHCP and installed the VZ NAT gateway as its default route.&lt;/p&gt;

&lt;h2 id=&quot;companion-code&quot;&gt;Companion Code&lt;/h2&gt;

&lt;p&gt;The companion code separates building from booting. An Apple silicon Mac, the selected Xcode or Command Line Tools, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make&lt;/code&gt;, network access to the official NetBSD archives and roughly 10 GiB of free space are enough for the first build. No prebuilt NetBSD cross compiler is assumed.&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;git clone https://github.com/tbarabosch/netbsd-vz.git
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;netbsd-vz
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;make build
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;make disk
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;make smoke
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make build&lt;/code&gt; verifies the source archives, builds the cross tools and produces the 8 MiB VZ64 Image. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make disk&lt;/code&gt; verifies the binary sets and assembles the GPT/FFSv1 root disk. The default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make smoke&lt;/code&gt; invocation does not attach a network device. It proves the console, root mount, userspace and guest-driven poweroff without giving the guest an external path.&lt;/p&gt;

&lt;p&gt;Networking is a separate test:&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;make smoke-network
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This adds VZ NAT, waits for DHCP and checks both the local gateway and public IPv4 reachability (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8.8.8.8&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Both automated targets boot a copy-on-write clone of the default RAW disk and delete it after success. Interactive &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make run&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make run-network&lt;/code&gt; do the same. Supplying an explicit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DISK=/absolute/path/root.raw&lt;/code&gt; instead attaches that file directly and makes guest changes persistent.&lt;/p&gt;

&lt;h2 id=&quot;summing-up&quot;&gt;Summing Up&lt;/h2&gt;

&lt;p&gt;I tested the result on Apple silicon with macOS 26.6.1, Xcode 26.6 and Swift 6.3.3. The runner used one virtual CPU and 512 MiB of RAM. The following are selected, non-contiguous lines from fresh offline and network runs; device unit numbers, addresses and timings can change between boots.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;NetBSD/VZ viocon console attached
ld0 at virtio1: features: 0x110002244&amp;lt;V1,INDIRECT_DESC,DISCARD,FLUSH,BLK_SIZE,SEG_MAX&amp;gt;
viornd0 at virtio2: features: 0x110000000&amp;lt;V1,INDIRECT_DESC&amp;gt;
dk0 at ld0: &quot;netbsd-root&quot;, 2093056 blocks at 2048, type: ffs
root on dk0
root file system type: ffs

NetBSD 11.0 (VZ64) #0: Tue Aug 18 20:55:23 CEST 2026
NETBSD_VZ_USERSPACE_OK

vioif0 at virtio0: features: 0x130010028&amp;lt;V1,EVENT_IDX,INDIRECT_DESC,STATUS,MAC,MTU&amp;gt;
vioif0: flags=0x8b43&amp;lt;UP,BROADCAST,RUNNING,PROMISC,ALLMULTI,SIMPLEX,MULTICAST&amp;gt; mtu 1500
        status: active
        inet 192.168.65.12/24 broadcast 192.168.65.255 flags 0
NETBSD_VZ_NETWORK_GATEWAY_192.168.65.1
NETBSD_VZ_NETWORK_PUBLIC_8.8.8.8
NETBSD_VZ_NETWORK_OK

unmounted /dev/dk0 on / type ffs
Observed NETBSD_VZ_USERSPACE_OK and NETBSD_VZ_NETWORK_OK and guest poweroff; smoke test passed.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The offline test separately rejects an unexpected &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vioif&lt;/code&gt; attachment. The network variant requires an active interface, a non-link-local DHCP address, a default route, one successful ping to the VZ gateway and one to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8.8.8.8&lt;/code&gt;. Both variants log in through the Virtio console, execute a userspace marker, issue &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shutdown -p now&lt;/code&gt;, wait for a clean FFS unmount and require the VZ virtual machine to reach its stopped state.&lt;/p&gt;

&lt;p&gt;This shows that NetBSD support yet another platform. All in all, getting a NetBSD kernel to boot was easier than expected. First, NetBSD’s stellar cross-compilation feature and portability capabilities made it easy to achieve that directly on my macOS machine. Second, partnering with Codex to overcome the obstacles during boot and later stages helped me to overcome technical isseus that would have taken days to solve in a couple of hours only.&lt;/p&gt;

&lt;p&gt;The patched NetBSD 11 kernel and root disk boot on the VZ platform and drive console, entropy, block and optional network devices through shutdown. I did not enable an early console, EFI boot, graphics, shared directories, suspend and resume, multiple volumes or support in an unmodified NetBSD release. The full build, patch rationale and disk layout are documented in the companion repository’s &lt;a href=&quot;https://github.com/tbarabosch/netbsd-vz/blob/main/docs/TECHNICAL.md&quot;&gt;technical account&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As a next step, I’d like to engage with the NetBSD community and see which patches could actually reach NetBSD-current. The console work should remain aligned with the existing VirtIO work mentioned before, while the reset, PCI and network changes probably need testing beyond this single VZ device model.&lt;/p&gt;

&lt;p&gt;Furthermore, there is already an &lt;a href=&quot;https://github.com/apple/containerization/issues/226&quot;&gt;open request for FreeBSD support&lt;/a&gt; in Apple’s Containerization project. The next experiment is to find out whether this NetBSD guest can be integrated into &lt;a href=&quot;https://github.com/apple/container&quot;&gt;Apple Container&lt;/a&gt;, but that belongs to another article.&lt;/p&gt;
</description>
                <pubDate>Tue, 18 Aug 2026 19:30:00 +0000</pubDate>
                <link>https://tbarabosch.com/porting-netbsd-to-apple-vz/</link>
                <guid isPermaLink="true">https://tbarabosch.com/porting-netbsd-to-apple-vz/</guid>
                <dc:creator>Thomas Barabosch</dc:creator>
                
                <category>NetBSD</category>
                
                <category>virtualization</category>
                
                <category>macOS</category>
                
            </item>
        
    </channel>
</rss>
