Docker: AppArmor is off, and kernel and userspace have to land together #70

Closed
opened 2026-08-30 11:38:49 +00:00 by tiagoagueda · 1 comment
Owner

Deliberately excluded from the Tier 0 kernel change. Recording why, so it does not get
"fixed" later without the other half.

Measured

  • apparmor_parser is not installed on the board
  • /etc/apparmor.d/ exists with two leftover profiles (local, usr.sbin.dhclient)
  • CONFIG_SECURITY was n, so no LSM at all
  • CONFIG_LSM="landlock,lockdown,yama,loadpin,safesetid,ipe,bpf" - no apparmor in it

Why it was left out

Turning on CONFIG_SECURITY_APPARMOR alone is worse than leaving it off. If the kernel
reports AppArmor as enabled and the userspace cannot load the docker-default profile,
every container start fails with a confusing error. The kernel option and the
apparmor package have to arrive in the same change.

To do it properly

  1. CONFIG_SECURITY=y, CONFIG_SECURITY_APPARMOR=y
  2. add apparmor to CONFIG_LSM - compiling it in is not enough, it must be in the
    ordered list or it stays inactive
  3. apt install apparmor in provision.sh
  4. reboot, and confirm docker info no longer reports AppArmor as absent

Meanwhile

Containers are unconfined by LSM. Seccomp still applies - CONFIG_SECCOMP and
CONFIG_SECCOMP_FILTER are both on, so the default seccomp profile works. This is a
defence-in-depth gap, not an open door.

Deliberately excluded from the Tier 0 kernel change. Recording why, so it does not get "fixed" later without the other half. ## Measured - `apparmor_parser` is **not installed** on the board - `/etc/apparmor.d/` exists with two leftover profiles (`local`, `usr.sbin.dhclient`) - `CONFIG_SECURITY` was `n`, so no LSM at all - `CONFIG_LSM="landlock,lockdown,yama,loadpin,safesetid,ipe,bpf"` - no `apparmor` in it ## Why it was left out Turning on `CONFIG_SECURITY_APPARMOR` alone is worse than leaving it off. If the kernel reports AppArmor as enabled and the userspace cannot load the `docker-default` profile, **every container start fails** with a confusing error. The kernel option and the `apparmor` package have to arrive in the same change. ## To do it properly 1. `CONFIG_SECURITY=y`, `CONFIG_SECURITY_APPARMOR=y` 2. add `apparmor` to `CONFIG_LSM` - compiling it in is not enough, it must be in the ordered list or it stays inactive 3. `apt install apparmor` in `provision.sh` 4. reboot, and confirm `docker info` no longer reports AppArmor as absent ## Meanwhile Containers are unconfined by LSM. **Seccomp still applies** - `CONFIG_SECCOMP` and `CONFIG_SECCOMP_FILTER` are both on, so the default seccomp profile works. This is a defence-in-depth gap, not an open door.
Author
Owner

Done, and both halves landed together as this issue required.

What changed

patches/configs/apparmor.config, kept as its own fragment rather than folded into
docker.config so the reasoning stays attached to it:

CONFIG_SECURITY=y
CONFIG_SECURITY_APPARMOR=y
CONFIG_LSM="landlock,lockdown,yama,loadpin,safesetid,ipe,bpf,apparmor"

The CONFIG_LSM line is the one that is easy to get wrong. Compiling AppArmor in is not enough -
it is an ordered list, and a module missing from it is present, visible in /sys/module, and
enforces nothing.

The apparmor package went onto the board before the kernel that carries the LSM, which is
the whole point of this issue: a kernel advertising AppArmor while apparmor_parser is missing
makes every container start fail. apparmor_parser 4.1.0, and it is in packages.txt so a
provisioned rootfs gets it.

Verified, not assumed

/sys/kernel/security/lsm             capability,apparmor
/sys/module/apparmor/parameters/enabled   Y
aa-status                            107 profiles loaded, docker-default in enforce
docker info Security Options         apparmor
                                     seccomp (Profile: builtin)

and the check that actually matters - is a container confined, rather than merely running on a
kernel that has the module:

$ docker run --rm alpine cat /proc/self/attr/current
docker-default (enforce)

Containers still start (container OK), 0 failed units, 4 GiB and all five cgroup controllers
intact.

Guarded

provision.sh gained five assertions covering both halves and the enforce state, because "the
module is loaded" and "containers are confined" are different claims and only the second one is
worth anything:

ok   apparmor_parser installed
ok   apparmor is an active LSM
ok     and enabled
ok   docker-default is loaded
ok   docker reports apparmor

Still 0 failures on the board.

One note for #72's benefit

The kernel is now 8.58 MiB, up ~170 KiB. It would not have booted before SYS_BOOTM_LEN
was raised - it would have reset with no console output, looking exactly like an instant panic.
Nice to have that cost paid already rather than rediscovered here.

Done, and both halves landed together as this issue required. ## What changed `patches/configs/apparmor.config`, kept as its own fragment rather than folded into `docker.config` so the reasoning stays attached to it: ``` CONFIG_SECURITY=y CONFIG_SECURITY_APPARMOR=y CONFIG_LSM="landlock,lockdown,yama,loadpin,safesetid,ipe,bpf,apparmor" ``` The `CONFIG_LSM` line is the one that is easy to get wrong. Compiling AppArmor in is not enough - it is an ordered list, and a module missing from it is present, visible in `/sys/module`, and enforces nothing. The `apparmor` package went onto the board **before** the kernel that carries the LSM, which is the whole point of this issue: a kernel advertising AppArmor while `apparmor_parser` is missing makes every container start fail. `apparmor_parser 4.1.0`, and it is in `packages.txt` so a provisioned rootfs gets it. ## Verified, not assumed ``` /sys/kernel/security/lsm capability,apparmor /sys/module/apparmor/parameters/enabled Y aa-status 107 profiles loaded, docker-default in enforce docker info Security Options apparmor seccomp (Profile: builtin) ``` and the check that actually matters - is a container *confined*, rather than merely running on a kernel that has the module: ``` $ docker run --rm alpine cat /proc/self/attr/current docker-default (enforce) ``` Containers still start (`container OK`), 0 failed units, 4 GiB and all five cgroup controllers intact. ## Guarded `provision.sh` gained five assertions covering both halves and the enforce state, because "the module is loaded" and "containers are confined" are different claims and only the second one is worth anything: ``` ok apparmor_parser installed ok apparmor is an active LSM ok and enabled ok docker-default is loaded ok docker reports apparmor ``` Still **0 failures** on the board. ## One note for #72's benefit The kernel is now **8.58 MiB**, up ~170 KiB. It would **not** have booted before `SYS_BOOTM_LEN` was raised - it would have reset with no console output, looking exactly like an instant panic. Nice to have that cost paid already rather than rediscovered here.
Sign in to join this conversation.
No description provided.