I moved a KVM lab from the home directory onto a separate encrypted data volume, and the virtual machines with a virtual TPM stopped starting. Everything else migrated cleanly.

The TPM emulator failed to create its socket. The error said the socket could not be created; the directory existed, was writable, and had the correct ownership.

The confirmation is never in the failing tool’s output. It is in the kernel log:

sudo dmesg | grep -i apparmor | tail
sudo journalctl -k --grep=DENIED -n 20

Which shows a DENIED line naming the profile, the operation and the path. The shipped profile permits the emulator to create sockets under @{HOME} and a small number of system directories. /mnt/data/... is not one of them, so the write is refused before the filesystem is ever consulted, which is exactly why the permissions look fine.

The fix belongs in the local override file, not in the distribution’s profile, so a package update does not discard it:

# /etc/apparmor.d/local/usr.bin.swtpm
/mnt/data/src/** rwk,
sudo apparmor_parser -r /etc/apparmor.d/usr.bin.swtpm

The k permission is the one that is easy to omit and hard to diagnose: it grants file locking, which the emulator needs, and which is a separate permission from read and write.

The generalisable habit: when a permission error does not match the permissions, check mandatory access control before re-checking ownership. AppArmor and SELinux both fail in this shape: a correct-looking filesystem, a refused operation, and the actual reason in a log the failing process never sees. Grepping the kernel log for DENIED should be the second thing tried, not the last.