Tag: initramfs

  • The Real Struggle of Encrypted Storage on OpenWRT: Targets, Initramfs, and the Device Mapper Problem

    Encrypting storage on OpenWRT sounds straightforward — install cryptsetup, run luksOpen, done. Reality is a different story. OpenWRT’s storage model, build system, and boot chain were never designed with encrypted-at-rest workflows in mind. Every step from kernel config to key management requires decisions that upstream tooling simply does not make for you.

    Why OpenWRT Is Not a Normal Linux Distribution

    Standard Linux distributions store the root filesystem on a block device. systemd-cryptsetup unlocks it at boot via an initrd, Plymouth asks for a passphrase, and LUKS handles the rest. On OpenWRT none of that infrastructure exists.

    OpenWRT’s default storage layout is a layered read-only/writable design:

    • SquashFS — the read-only base system, compressed and immutable after flashing
    • JFFS2 or UBIFS overlay — a writable layer that holds configuration changes and installed packages
    • OverlayFS — merges both into a single unified root

    The consequence: you cannot encrypt the root filesystem the way you would on a desktop. The base SquashFS lives in flash, the overlay lives in a separate MTD partition or UBI volume, and the bootloader (almost always U-Boot) hands control to a kernel that does not know how to pause and ask for a passphrase before mounting anything.

    What You Actually Need: Packages and Kernel Modules

    OpenWRT’s default builds strip everything not needed for basic routing. Encryption requires explicitly enabling a chain of kernel modules and userspace tools absent from every stock image.

    Userspace package:

    CONFIG_PACKAGE_cryptsetup=y

    Kernel modules — all must be built together with your kernel or they will be rejected by vermagic validation:

    CONFIG_PACKAGE_kmod-dm=y
    CONFIG_PACKAGE_kmod-dm-crypt=y
    CONFIG_PACKAGE_kmod-crypto-aes=y
    CONFIG_PACKAGE_kmod-crypto-xts=y
    CONFIG_PACKAGE_kmod-crypto-cbc=y
    CONFIG_PACKAGE_kmod-crypto-hash=y
    CONFIG_PACKAGE_kmod-crypto-iv=y
    CONFIG_PACKAGE_kmod-crypto-manager=y
    CONFIG_PACKAGE_kmod-crypto-misc=y
    CONFIG_PACKAGE_kmod-crypto-core=y

    The critical trap is kmod-dm-crypt. It is not included in CONFIG_ALL_KMODS=y builds by default on all targets. Any attempt to insmod a pre-built kmod against a custom kernel fails immediately — OpenWRT’s vermagic system embeds the exact kernel build hash and rejects mismatches with no useful error message beyond “invalid module format”.

    Defining a Custom Target With Encryption Support

    The cleanest approach is to define a custom profile in the OpenWRT build system. Profiles live under target/linux/<arch>/ and control which packages and kernel options are baked in. For an x86_64 target with encrypted data storage:

    # target/linux/x86/image/Makefile — add a custom profile block
    
    define Profile/encrypted-storage
      NAME:=x86 Encrypted Storage Profile
      PACKAGES:= 
        cryptsetup 
        kmod-dm 
        kmod-dm-crypt 
        kmod-crypto-aes 
        kmod-crypto-xts 
        kmod-crypto-cbc 
        kmod-crypto-hash 
        kmod-crypto-iv 
        kmod-crypto-manager 
        block-mount 
        e2fsprogs
    endef
    $(eval $(call Profile,encrypted-storage))

    On ARM targets (MediaTek, Qualcomm IPQ), kernel config fragments must be placed in target/linux/<arch>/<subarch>/config-*. Add the dm-crypt specific options there:

    # config-5.15 (match your target kernel version)
    CONFIG_DM_CRYPT=m
    CONFIG_CRYPTO_AES=m
    CONFIG_CRYPTO_XTS=m
    CONFIG_CRYPTO_SHA256=m
    CONFIG_BLK_DEV_DM=m

    If not modifying the build tree, the Image Builder approach is simpler — pass PACKAGES to add userspace tools, and overlay custom preinit files. The constraint: Image Builder cannot add kernel modules absent from the original build. This is why vermagic is the wall you always hit first.

    The Initramfs and Initramdisk Problem

    On standard Linux, the initrd is a temporary root that runs before the real root is mounted: it unlocks LUKS, then does a switch_root pivot. OpenWRT’s initramfs exists but serves a different purpose — it is an image for RAM-only deployments, not a pre-mount unlock stage.

    OpenWRT’s preinit framework handles what happens just before procd starts. Preinit scripts live in /lib/preinit/ and are sourced in lexicographic order. There is no standardized hook for “pause, unlock block device, then pivot”. You write it yourself:

    #!/bin/sh
    # /lib/preinit/30_unlock_encrypted_storage
    
    unlock_storage() {
        # Load required modules
        insmod /lib/modules/$(uname -r)/dm-mod.ko
        insmod /lib/modules/$(uname -r)/dm-crypt.ko
        insmod /lib/modules/$(uname -r)/aes_generic.ko
        insmod /lib/modules/$(uname -r)/xts.ko
    
        # Retrieve passphrase — serial console, USB key, or network call
        echo -n "Storage passphrase: "
        read -r passphrase
    
        echo "$passphrase" | cryptsetup luksOpen /dev/sda2 encdata
    
        if [ $? -ne 0 ]; then
            echo "LUKS unlock failed. Dropping to emergency shell."
            /bin/sh
        fi
    
        mkdir -p /mnt/encdata
        mount /dev/mapper/encdata /mnt/encdata
    }
    
    boot_hook_add preinit_main unlock_storage

    boot_hook_add preinit_main registers the function to run before overlay assembly. If you need to replace the root entirely (pivot into the decrypted volume rather than mount a secondary partition), the complexity multiplies: you must perform the full switch_root before OpenWRT’s overlay system assembles, which means partially reimplementing the preinit framework itself. No upstream support exists for this. Community patches modify package/base-files/ and none are merged into mainline.

    Device Mapper: The dm-crypt Target

    Once kmod-dm and kmod-dm-crypt are loaded, the device mapper exposes the dm-crypt target. The raw table format:

    # <start> <length> crypt <cipher> <key> <iv_offset> <device> <device_offset>
    echo "0 2097152 crypt aes-xts-plain64 <hex-key> 0 /dev/sda2 0" | 
      dmsetup create encdata

    cryptsetup luksOpen builds this table automatically from the LUKS header. The embedded-specific failure mode: device mapper relies on udev to create /dev/mapper/ entries. OpenWRT uses mdev or hotplug2, not udev. Without explicit device node creation after dmsetup create, /dev/mapper/encdata does not appear and every subsequent mount fails silently:

    # Create the device node manually when udev is absent
    DEVINFO=$(dmsetup info encdata)
    MAJOR=$(echo "$DEVINFO" | awk '/Major/ {print $2}')
    MINOR=$(echo "$DEVINFO" | awk '/Minor/ {print $2}')
    mkdir -p /dev/mapper
    mknod /dev/mapper/encdata b $MAJOR $MINOR

    Recent cryptsetup versions handle this via --disable-udev or the libdevmapper fallback. Verify which version your build ships — older OpenWRT package feeds lag the upstream releases, and the --disable-udev flag was not always present.

    Key Management: The Genuinely Unsolved Problem

    This is where OpenWRT encryption becomes a product design question, not just a software one. The standard ecosystem — systemd-cryptenroll for TPM PCR binding, Clevis/Tang for NBDE, FIDO2 keys — is entirely absent.

    • Hardcoded passphrase in initramfs — shifts the threat model from “attacker reads the storage” to “attacker reads the firmware”. Only useful against targeted flash chip extraction without the device board.
    • Serial console input — requires a human at boot. Viable for gateway hardware in a locked cabinet, impossible for autonomous field devices.
    • USB key file — works, but now the key management problem lives in protecting the USB drive. Cryptsetup supports --key-file directly from a mounted USB path.
    • TPM 2.0 — available via CONFIG_PACKAGE_tpm2-tools and kmod-tpm, but consumer router SoCs almost never include a TPM. Industrial ARM SBCs (NXP i.MX8, TI AM64x) sometimes do. PCR-based sealing requires a measured boot chain that U-Boot does not provide without explicit Measured Boot configuration.
    • Network-bound decryption (NBDE) — a Tang server holds a key fragment; the device reconstructs the key at boot using Clevis over the network. Most operationally scalable for fleet deployments. No official OpenWRT package. Community builds exist, with the fundamental constraint that the network must be reachable before storage is mounted — the reverse of the usual boot order.

    The Entropy Problem

    LUKS key derivation (PBKDF2 or Argon2) must be slow by design — it resists brute force. On a 600 MHz MIPS or a low-clocked Cortex-A7, this already hurts. The deeper issue is entropy quality.

    OpenWRT ships urngd, a timing-jitter entropy daemon. Published research has demonstrated that urngd‘s entropy quality degrades on systems with predictable timing — particularly at cold boot before network interfaces are active. Running cryptsetup luksFormat on a freshly booted device with no I/O may produce a key derived from insufficient entropy, compromising the entire encryption scheme before it starts.

    Practical mitigations:

    • Load haveged or rng-tools before luksFormat; feed from /dev/hwrng if the SoC exposes one
    • Verify entropy before key generation: cat /proc/sys/kernel/random/entropy_avail — target above 256
    • On platforms with a hardware TRNG (NXP i.MX, Qualcomm IPQ, TI Sitara), ensure the driver is loaded and /dev/hwrng is active
    • Pass --iter-time 1000 to reduce PBKDF time on constrained hardware — but acknowledge the brute-force resistance tradeoff explicitly

    What a Production Implementation Actually Requires

    1. A full source build — no Image Builder shortcut when custom kernel modules are required
    2. A defined target profile with all crypto kmods as required packages, versioned to your kernel
    3. A preinit hook or dedicated initramfs stage that runs before overlay assembly, loads modules, and unlocks the LUKS container
    4. A key management architecture decided at device design time — TPM, NBDE, or operational. Retrofitting after production is expensive
    5. Entropy validation before any luksFormat invocation
    6. Encrypting a separate data partition on eMMC or SD, not the JFFS2/UBIFS overlay on NOR/NAND flash, unless you fully understand MTD/UBI wear leveling interactions with a dm-crypt layer

    EOTICS specializes in BSP development, OpenWRT customization, and embedded security implementations across Yocto, Buildroot, and OpenWRT targets. If you are working through these problems on a real project, get in touch.