Using squashfs for fast, encrypted Torizon Secure Offline Updates (Lockboxes)
September 09, 2026
Written by Jon Oster, Principal Product Security Architect
Torizon Offline Updates fire when a lockbox appears at a configured path. That works, but USB media is awkward in two ways:
- Mount paths depend on the volume label (
/media/<label>-…), so update sticks need a pre-agreed label, or the lockbox won’t appear at the correct path. - OSTree lockboxes are many small files, which can perform poorly on removable media—especially older filesystems like FAT.
We can instead ship the lockbox as a single squashfs image with a fixed name. All we need to do is add a small systemd path unit that finds it on any mounted USB volume and loop-mounts it to where Aktualizr already looks.
As a bonus, using squashfs makes it easy for us to encrypt the lockbox.
How it works
Torizon’s usermount already mounts USB partitions under /media (/var/rootdirs/media). A systemd path unit watches for torizon-lockbox.squashfs; when it appears, a oneshot service loop-mounts the image at a stable path (/mnt/signed-squashfs). We point Aktualizr’s offline-update path configuration there. A udev rule stops the service (and thus unmounts) when the USB stick is removed.
A couple of minor details:
- The mount script always exits 0, even on verify/mount failure. We use an
EXITtrap to ensure this. This was the easiest way to deal with the problem of a script failure causing systemd to either retry in a loop, or end up in a failed state that wasn’t cleared when removing the USB stick. - The udev rule technically fires when any USB mass storage is removed. If you unplug a different USB stick while your lockbox-containing stick is present, it will briefly unmount the squashfs image (and then immediately remount it).
- Similarly, if you have two USB sticks plugged in that each contain a
torizon-lockbox.squashfsfile, the first one you plug in will be mounted. If you unplug the one that’s mounted, the systemd path unit will immediately mount the other one, most likely triggering another update.
Implementation
This was initially small enough to publish as small code snippets inline, but I decided to publish it to github. The initial version is here (.path unit, systemd oneshot service, mount and umount scripts, udev rule). I also included a small shell script you can use to install it–download the release tarball and you can run the install script to put all the files in the right place.
Security
This basic architecture shouldn’t have major security risks. Lockboxes are still thoroughly validated before installation, and we’re only changing the delivery mechanism slightly. However, there is one change in security posture compared to stock hardened Torizon OS that this brings: squashfs has, in the past, had kernel CVEs that were triggerable by malicious images. There aren’t any CVEs like this that affect Torizon OS as of the date of publication, but of course there could be in the future. This is the same class of risk when allowing any type of filesystem to auto-mount.
If you want to mitigate that risk, you could also add a signature check before the image is mounted. I’ve implemented a basic version of that in the signed directory of the repo.
Encryption
Finally, there’s one big headline feature that using squashfs makes a lot easier: we can encrypt the image using a LUKS wrapper. The changes required are relatively minor, so I won’t rehash them here. You can read the LUKS variant docs and implementation on github.
A couple things to watch out for:
- This is a basic, demo implementation. It uses a 64-character random password, but that password needs to exist on the device. So if your threat model includes, for example, someone desoldering the eMMC chip on the module and reading it, or using recovery mode to load an arbitrary kernel, you should add other protections. I have a short section on this in the readme, but it boils down to “use Torizon’s already-excellent support for secure boot and encryption”.
- LUKS uses a key derivation function called Argon2 by default. Argon2 is designed to be resistant to cracking by being memory-hard: deriving the key from the password takes a configurable amount of memory, but the default for LUKS is usually around 200MiB. That’s a bit too much for a typical embedded device, so you might want to either switch to a smaller allocation, or use a CPU-bound KDF. This is also documented in the LUKS README in the repo.
Final thoughts
This was easy enough to implement, but it does have some flaws. I’ve proposed to the teams working on TorizonCore Builder and Torizon OS to add some official support for it, but as of now it’s going to live as a demo repository. That being said, I think it’s a fairly straightforward, robust implementation, and would be willing to use it in a production device (subject to its risk assessment and security posture, of course!). Have fun!