Skip to content

v1.7.0 — drive permissions

Choose a tag to compare

@doug445 doug445 released this 24 Aug 10:00
· 71 commits to main since this release
Immutable release. Only release title and notes can be modified.

Security fix — chmod has been silently doing nothing on FAT deployment drives

chmod(2) on vfat, exfat and ntfs returns success and changes nothing. Those filesystems have no permission bits; the mode you see comes from the mount's fmask/dmask, not from the inode. So a script that runs chmod 0600 recovery-key.txt on a FAT stick is told it succeeded, logs nothing, and leaves the file readable by anyone who plugs the drive in.

The deployment drive is very often exactly such a stick. Measured on a vfat loop image:

chmod 0700 <dir>       rc=0   mode=755
install -m 600 <file>  rc=0   mode=755
chmod 0400 <file>      rc=0   mode=755

Three things were wrong, and they differ in severity depending on how the deployment drive is formatted.

  • The recovery key. install -m 600 was already being used, so on ext4/btrfs it was correct. On FAT it was a no-op and the key sat world-readable. This is the serious one: the recovery key is 64 plaintext hex characters that unlock the volume outright. There is nothing to crack.
  • The LUKS header backup copied to the drive was created with plain cp, which uses 0666 & ~umask0644 by default. It was world-readable on every filesystem, FAT or not. A header backup carries your keyslots. They are argon2id-protected rather than plaintext, but it hands someone an offline target that needs no further access to your machine.
  • pre-luks-state-*/ itself was created by a bare mkdir -p and left at 0755, so the directory holding both of the above was world-listable.
ext4 / btrfs drive FAT / exFAT drive
recovery-key.txt 0600 — correct 0755plaintext key exposed
luks-header-backup.img 0644exposed 0755exposed
pre-luks-state-*/ 0755 0755

What changed

  • New harden_path helper. It sets the mode, reads it back with stat, and warns once if the mode did not take. It never returns non-zero — a drive that cannot hold permissions is a reason to warn loudly, not to abort an encryption that is already under way.
  • The warning fires before encryption starts. The first harden_path call locks pre-luks-state-*/ to 0700, immediately after it is created, so an unsuitable deployment drive is reported while aborting still costs nothing. Locking the directory also covers the layout dumps, crypttab and the BLS entries copied into it.
  • The header backup is copied with install -m 0400, not cp. install sets the mode as it creates the destination, so the header is never briefly world-readable between the copy and a chmod — and never permanently so if that chmod is a no-op.
  • The recovery key and its README now verify their modes rather than assuming them.

The directory-plus-explicit-mode pattern is the one bin/save-luks-recovery-bundle.sh already used; this brings luks-deploy.sh in line with it.

A global umask 077 would have fixed all of it in one line. It was rejected: the same script writes /etc/fstab, the BLS entries and the initramfs config on the target, and mode-restricting those is a boot-breaking change hiding inside a tidy one-liner.

If you have already deployed

Check what your deployment drive actually holds:

ls -la <drive>/pre-luks-state-*/

If the modes are 0700 / 0600 / 0400, nothing was exposed beyond that drive. Move the recovery key and header backup to secure offline storage — which the deployment already told you to do — and you are done.

If they are 0755 or 0644, treat the contents as having been readable by anything with access to that drive, and judge the exposure by where the drive has been. A stick that never left a desk drawer is a very different situation from one that was plugged into a shared machine.

One thing worth knowing before you reach for the obvious fix. If a header backup genuinely leaked, changing your passphrase does not neutralise it. luksChangeKey re-wraps the same master key; a header backup taken beforehand still contains a keyslot that unwraps that master key, and restoring it re-enables the old passphrase against your current data. The only real remedy is a new master key — full re-encryption, or a reinstall. A leaked recovery key is different: revoking that keyslot with cryptsetup luksKillSlot is sufficient, provided no header backup from before the revocation is also loose.

Not affected

bin/save-luks-recovery-bundle.sh, which already set 0700 on its bundle directory and 0400 on every header it wrote.