v1.7.0 — drive permissions
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 600was 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 uses0666 & ~umask—0644by 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 baremkdir -pand left at0755, so the directory holding both of the above was world-listable.
| ext4 / btrfs drive | FAT / exFAT drive | |
|---|---|---|
recovery-key.txt |
0600 — correct |
0755 — plaintext key exposed |
luks-header-backup.img |
0644 — exposed |
0755 — exposed |
pre-luks-state-*/ |
0755 |
0755 |
What changed
- New
harden_pathhelper. It sets the mode, reads it back withstat, 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_pathcall lockspre-luks-state-*/to0700, 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,crypttaband the BLS entries copied into it. - The header backup is copied with
install -m 0400, notcp.installsets the mode as it creates the destination, so the header is never briefly world-readable between the copy and achmod— and never permanently so if thatchmodis 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.