What Lockdown refused after root already had a shell

On Ubuntu AppArmor and Rocky SELinux, root read another application’s file until extra policy was added, then turned that policy off without a reboot. Under Lockdown the same ledger path could not be created, and the allowlist write was refused.

SSH as root. The job is to read a file that belongs to another program.

/var/lib/vaultapp/customer-ledger.secret

vaultapp is a demo program. Its ledger is the file the other tools should not read.

Three hosts: Ubuntu 24.04 with AppArmor, Rocky Linux 9 with SELinux Enforcing, Debian 12 running Root Lock by HeartSuite with Lockdown on. No reboot.

On Ubuntu and Rocky the file was already on disk. Default policy left root cat of it allowed. Those hosts then got extra policy that denied cat and still let vaultapp read the ledger. The same root shell then tried to turn that policy off.

The same job, in order

AppArmor

Ubuntu AppArmor does not confine cat. After that profile, root cat failed. vaultapp still printed the ledger.

cat: /var/lib/vaultapp/customer-ledger.secret: Permission denied
apparmor="DENIED" operation="open" profile="demo-agent-cat"
 name="/var/lib/vaultapp/customer-ledger.secret" requested_mask="r"

AppArmor denies cat; vaultapp still reads

Root then ran aa-disable:

aa-disable /etc/apparmor.d/demo-agent-tools

cat printed:

VAULTAPP-LEDGER
customer=acme-healthcare
token=HS-DEMO-LEDGER-2026-09-11

AppArmor itself was still loaded. Only that profile was gone.

AppArmor profile disabled, ledger in the clear

Root can unload an AppArmor profile without rebooting.

SELinux

Rocky followed the same steps. Root maps to unconfined_u. Red Hat documents that unconfined users, including administrators, are only minimally restricted.

After the custom type, the same cat failed under Enforcing. vaultapp still printed the ledger.

cat: /var/lib/vaultapp/customer-ledger.secret: Permission denied
avc: denied { read } for comm="cat" name="customer-ledger.secret"
 scontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
 tcontext=unconfined_u:object_r:vaultapp_secret_t:s0
 tclass=file permissive=0

SELinux denies cat; vaultapp still reads

Root then set SELinux to permissive (setenforce 0):

setenforce 0

getenforce showed Enforcing. After setenforce 0, Permissive. The config file still said enforcing. cat printed the same three ledger lines. Policy stayed on disk.

SELinux set permissive from Enforcing, ledger in the clear

Root can set SELinux to permissive without rebooting.

Root Lock

The same job on the third host. Lockdown was already on. SSH as root still worked.

The ledger is not on disk here. The first move is to create the path:

# mkdir -p /var/lib/vaultapp
mkdir: cannot create directory '/var/lib/vaultapp': Unknown error 242
# /usr/local/bin/vaultapp
bash: /usr/local/bin/vaultapp: Permission denied

Unknown error 242 is the kernel refusing mkdir under Lockdown. The ledger never exists on this host. Lockdown refuses the create, so there is no second cat to compare.

Root Lock refuses the ledger path

setenforce and aa-disable are not on Debian 12. A missing binary is not a kernel deny. The disable that exists here is a write to the allowlist:

MoveResult under Lockdown
add a cat grant for the ledgerwrite refused
chattr -i on the allowlistPermission denied while reading flags
cat of the ledgerNo such file or directory

Root Lock refuses the allowlist write

The allowlist is sealed. The files are immutable, and the kernel refuses the write.

A file that is already on disk is /opt/heartsuite/src/main.py. cat is denied. /usr/bin/python3 is the Dashboard interpreter and is granted that tree:

# cat /opt/heartsuite/src/main.py
cat: /opt/heartsuite/src/main.py: Permission denied
# python3 -c "print(open('/opt/heartsuite/src/main.py').readline())"
# SPDX-License-Identifier: BUSL-1.1

Grants follow the program.

cat denied; python3 reads the same file

What the kernel refused

StepAppArmorSELinuxRoot Lock in Lockdown
Default policy vs root cat of the ledgerAllowedAllowedmkdir /var/lib/vaultapp refused
After a deny for cat on that pathDeniedDeniedCreate already refused
vaultappstill readsstill readscould not be executed
Root disableaa-disable the profilesetenforce 0Allowlist add refused; chattr refused
Second catLedger in the clearLedger in the clearNo file
Same file, different programvaultapp still readsvaultapp still readscat denied; python3 reads

AppArmor and SELinux can be unloaded from a root shell. Root Lock is in the kernel, and Lockdown seals the allowlist.

Setup Mode and the console are still the remaining paths:

  1. Setup Mode over-granted cat on /etc, so host keys in /etc/ssh were readable. That is allowlist work from Setup Mode, not a Lockdown hole.
  2. Unsealing takes physical or serial-console access.

See Lockdown.