|
| 1 | ++++ |
| 2 | +title = "Sealed images: the security chain from firmware to filesystem" |
| 3 | +date = 2026-04-27 |
| 4 | +slug = "2026-apr-27-sealed-images-security-chain" |
| 5 | + |
| 6 | +[extra] |
| 7 | +author = "jeckersb" |
| 8 | ++++ |
| 9 | + |
| 10 | +# Sealed images: the security chain from firmware to filesystem |
| 11 | + |
| 12 | +This is the first in a series of posts about bootc sealed images. In |
| 13 | +this post we'll take a tour through the full security chain that makes |
| 14 | +sealed images work, from the moment your system powers on through |
| 15 | +every file read at runtime. Future posts will cover key management, |
| 16 | +building sealed images, and deploying them to various environments. |
| 17 | + |
| 18 | +## The gap |
| 19 | + |
| 20 | +Typically a default Linux install comes "unlocked", allowing you to |
| 21 | +be root and make persistent changes. While many distributions |
| 22 | +support Secure Boot, it's typically only the kernel that's signed. |
| 23 | +The cryptographic chain of trust from firmware doesn't cover the |
| 24 | +root filesystem or applications. Every library loaded, every binary |
| 25 | +executed, every configuration file read after the kernel boots runs |
| 26 | +on trust. |
| 27 | + |
| 28 | +This is the gap. The boot chain may be verified. The operating |
| 29 | +system is not. |
| 30 | + |
| 31 | +Sealed images close this gap by extending cryptographic verification |
| 32 | +from the firmware all the way through every file in the operating |
| 33 | +system. Not as a one-time check at boot, but continuously, at every |
| 34 | +file read, for the entire lifetime of the system. |
| 35 | + |
| 36 | +Let's walk through how each piece fits together. |
| 37 | + |
| 38 | +## Secure Boot |
| 39 | + |
| 40 | +Secure Boot is the foundation of the chain. It's a UEFI firmware |
| 41 | +feature that ensures only signed code runs during the boot process. |
| 42 | + |
| 43 | +The firmware maintains a database of trusted keys. When the system |
| 44 | +powers on, the firmware loads the bootloader and checks its signature |
| 45 | +against those keys. If the signature is valid, the bootloader runs. |
| 46 | +If not, the system refuses to boot. |
| 47 | + |
| 48 | +This is well-established technology and not new to sealed images. |
| 49 | +What matters for our purposes is that Secure Boot gives us a root of |
| 50 | +trust: a guarantee that the first piece of software to execute after |
| 51 | +the firmware is something we explicitly chose to trust. |
| 52 | + |
| 53 | +## Unified Kernel Images |
| 54 | + |
| 55 | +Traditionally, the boot chain after Secure Boot looks something like |
| 56 | +this: the signed bootloader loads a kernel, an initramfs, and a kernel |
| 57 | +command line, often from separate files on a boot partition. The |
| 58 | +bootloader may verify the kernel's signature, but the initramfs and |
| 59 | +command line are typically not covered by any signature. |
| 60 | + |
| 61 | +A [Unified Kernel Image (UKI)](https://uapi-group.org/specifications/specs/unified_kernel_image/) |
| 62 | +changes this by bundling the kernel, the initramfs, and the kernel |
| 63 | +command line into a single EFI binary. The entire bundle is signed |
| 64 | +as one unit. This means the firmware (or a signed bootloader like |
| 65 | +systemd-boot) can verify the whole thing in one step. |
| 66 | + |
| 67 | +This is important for sealed images because the kernel command line |
| 68 | +is no longer an unverified input. Anything embedded in the command |
| 69 | +line is covered by the same signature that covers the kernel itself. |
| 70 | +As we'll see next, this is where the composefs digest lives. |
| 71 | + |
| 72 | +## composefs: EROFS + overlayfs + fs-verity |
| 73 | + |
| 74 | +Here's where things get interesting. composefs brings together |
| 75 | +three kernel technologies to provide cryptographic verification of |
| 76 | +the entire filesystem. |
| 77 | + |
| 78 | +When a sealed image is built, the operating system filesystem is |
| 79 | +processed into an |
| 80 | +[EROFS](https://docs.kernel.org/filesystems/erofs.html) metadata |
| 81 | +image. This image describes every file, directory, symlink, and |
| 82 | +permission in the tree, but it does not contain the actual file |
| 83 | +contents. Instead, each file entry in the EROFS image records the |
| 84 | +[fs-verity](https://docs.kernel.org/filesystems/fsverity.html) |
| 85 | +digest of that file's contents. The actual file data is stored |
| 86 | +separately in a content-addressed object store, where each file has |
| 87 | +fs-verity enabled by the kernel. |
| 88 | + |
| 89 | +At mount time, the kernel stitches these pieces together using |
| 90 | +[overlayfs](https://docs.kernel.org/filesystems/overlayfs.html). |
| 91 | +The EROFS image serves as a metadata layer, and the object store |
| 92 | +provides the file data. The overlayfs `verity=require` mount option |
| 93 | +tells the kernel to enforce that every file served through the mount |
| 94 | +has an fs-verity digest matching what the EROFS metadata expects. |
| 95 | + |
| 96 | +fs-verity itself is a kernel feature that provides per-file integrity |
| 97 | +verification. When fs-verity is enabled on a file, the kernel |
| 98 | +computes a cryptographic hash of the file's contents and stores a |
| 99 | +hash tree alongside the file on disk. From that point on, every |
| 100 | +read is verified at the block level -- the kernel checks only the |
| 101 | +blocks being read, not the entire file, and caches the results so |
| 102 | +repeated reads don't pay the cost again. |
| 103 | + |
| 104 | +A key behavior to understand is what happens when verification |
| 105 | +fails: the kernel returns an I/O error. The corrupted or tampered |
| 106 | +data is never served to any process. The `open()` call on the file |
| 107 | +succeeds (the file exists, after all), but `read()` on the corrupted |
| 108 | +portion returns `EIO`. The data is simply unreadable. |
| 109 | + |
| 110 | +The EROFS metadata image itself also has an fs-verity digest: a |
| 111 | +single cryptographic hash that covers the complete filesystem |
| 112 | +description. This is the composefs digest. It is embedded in the |
| 113 | +UKI's kernel command line: |
| 114 | + |
| 115 | +``` |
| 116 | +composefs=abc123def456... (128-character SHA-512 hex digest) |
| 117 | +``` |
| 118 | + |
| 119 | +Because the UKI is signed, this digest is now part of the verified |
| 120 | +boot chain. The firmware verifies the UKI signature, and the UKI |
| 121 | +carries within it a cryptographic commitment to the exact filesystem |
| 122 | +that should be mounted as the operating system. |
| 123 | + |
| 124 | +## Putting it all together |
| 125 | + |
| 126 | +The full chain looks like this. Each step must succeed before the |
| 127 | +next can proceed: |
| 128 | + |
| 129 | +1. The firmware verifies the signed bootloader. |
| 130 | +2. The bootloader verifies the signed Unified Kernel Image (UKI). |
| 131 | +3. The initramfs (inside the verified UKI) reads the composefs |
| 132 | + digest from the kernel command line (also inside the verified UKI). |
| 133 | +4. The initramfs mounts the composefs image and verifies the EROFS |
| 134 | + image's fs-verity digest matches the digest from the command line. |
| 135 | +5. Every subsequent file read from the operating system is |
| 136 | + individually verified by the kernel against the fs-verity digest |
| 137 | + recorded in the EROFS metadata. |
| 138 | + |
| 139 | +There is no point after firmware where unverified code executes or |
| 140 | +unverified data is served. The chain is continuous. |
| 141 | + |
| 142 | +## What sealed images cover |
| 143 | + |
| 144 | +It's worth being precise about scope. The seal covers every file in |
| 145 | +the immutable operating system image: the base OS, any packages you |
| 146 | +added, your monitoring agents, security tools, application binaries. |
| 147 | +Everything that was part of the image when it was built is sealed. |
| 148 | + |
| 149 | +By default, `/etc` and `/var` are mutable and persistent. It is |
| 150 | +possible to make `/etc` transient today, and this is a good idea if |
| 151 | +it matches your use case. More suggestions on this will be in the |
| 152 | +documentation. |
| 153 | + |
| 154 | +## What's next |
| 155 | + |
| 156 | +This post covered the architecture. In the next posts, we'll get |
| 157 | +hands-on: |
| 158 | + |
| 159 | +- **Key management**: generating Secure Boot keys, enrolling them, |
| 160 | + and understanding the signing chain |
| 161 | +- **Building sealed images**: writing a Containerfile, producing a |
| 162 | + signed UKI, and verifying the result |
| 163 | +- **Deploying sealed images**: installing to bare metal, virtual |
| 164 | + machines, and cloud environments |
| 165 | + |
| 166 | +The signing chain is fully under your control. You generate the keys, |
| 167 | +you sign the images, you decide what your systems trust. The next |
| 168 | +post will show you how. |
0 commit comments