Experiencing the maddeningly Inexplicable? Suspect AppArmor FIRST

Table of Contents

Experiencing the maddeningly Inexplicable? Suspect AppArmor FIRST

Nothing in a Long While has roused such ancient Microsft-ish Hatred and Total Retrograde to “Reboot As Fix Attempt” as AppArmor going silent default in Debian12.

AppArmor has caused silent, hard-to-diagnose issues for many people, and it’s even more frustrating because it’s often enabled by default without any loud warning or clear opt-out.

So here’s the full story: who made it, where it came from, and how the hell it became default on systems like Debian and Ubuntu.


Origins of AppArmor

  • AppArmor was originally developed by a company called Immunix, founded by Crispin Cowan and colleagues.
  • Immunix created it in the late 1990s and early 2000s.
  • Originally called “SubDomain” — it was a path-based mandatory access control (MAC) system designed to secure Linux applications without needing the complexity of SELinux.

Acquisition and Canonical’s Role

  • In 2005, Novell (SUSE Linux) acquired Immunix and open-sourced AppArmor under the GPL.
  • Later, in 2009, Canonical (Ubuntu’s parent company) adopted AppArmor and made it the default MAC system in Ubuntu 9.04 and onward, replacing SELinux, which was seen as too complex.
  • Canonical poured effort into it — writing default profiles for many daemons (e.g., ntpd, dhclient, cupsd, mysql, libvirt, docker).

How It Became Default in Debian

  • Debian was slower to adopt AppArmor, preferring not to pick sides between SELinux and AppArmor.
  • But by Debian 10 (Buster, 2019), AppArmor was included and installable.
  • In Debian 11 (Bullseye, 2021), AppArmor was enabled by default if the kernel supported it.
  • In Debian 12 (Bookworm, 2023), it ships fully enabled by default, and Docker picks up the docker-default profile unless explicitly overridden.

Who Approved This and Why?

  • Linux distributions (not the kernel) decide what MAC framework to enable.
  • AppArmor was appealing because it’s:
  • Simpler to configure than SELinux
  • Path-based, which works with existing filesystem structure
  • Compatible with systemd and easy to ship with default profiles

Nobody “approved it to cause havoc” — it just quietly became policy.

But here’s the critical mistake:

They enabled it silently and let Docker adopt it without loud warnings, detailed logging, or opt-in clarity.
This stealth-by-default behavior has broken systems for admins who never knew it was active.


Why It Still Wreaks Havoc

  • Most users don’t know it’s running
  • Docker never prominently warns you that it’s applying docker-default
  • AppArmor doesn’t emit obvious errors when it blocks things
  • The logs are buried in journalctl -k and not visible in container stdout

So Why Is It Allowed to Keep Doing This?

Because:

  • Security defaults are “safe by design”, not “transparent by design”
  • Debian/Ubuntu devs value proactive MAC hardening
  • Very few complain — and fewer file bugs or request changes upstream
  • And, critically, most developers and sysadmins don’t know it’s the source of the failure

Until now.


What You Can Do

  • File a bug against Debian or Docker requesting:
  • Default disablement
  • Explicit warnings/logs
  • Container runtime flags that expose MAC restrictions
  • Educate others (like you’re doing right now)
  • Rip it out of your systems unless you explicitly want MAC enforcement

Bottom Line

  • Who made it? Crispin Cowan & Immunix (late 90s)
  • Who owns it now? Canonical, used in Ubuntu, Debian, SUSE
  • Why is it default? To harden systems — but with zero transparency
  • Why does it cause havoc? Because it blocks silently and Docker applies it by default
  • Who approved it? Your distro maintainers, silently

This should never be allowed to cause silent breakage.


AppArmor Inserts Itself Everywhere Into Everything Always in All Ways

Yes — AppArmor introduces an additional layer of security checking that intercepts and mediates access to:

  • Files
  • Directories
  • Sockets (UNIX and network)
  • Capabilities (e.g., CAP_NET_BIND_SERVICE, CAP_SYS_ADMIN)
  • Exec operations (which binaries/scripts can be executed)
  • Mount operations
  • Signal delivery (interprocess signaling)

Core Mechanism:

Every relevant system call that involves those resource types is intercepted by the Linux Security Module (LSM) framework. AppArmor is one of several LSMs (like SELinux or Smack) that can be hooked in.

When a syscall is made (e.g., open(), connect(), read(), write(), execve()), and it touches a resource controlled by AppArmor, it is:

  1. Routed through the LSM hook.
  2. AppArmor’s logic checks the current confinement profile for the process.
  3. The profile contains allow/deny rules for that action.
  4. If permitted, the syscall proceeds. If denied, it fails (e.g., EACCES).

Cost:

Yes — every such action incurs additional overhead from policy checking, especially when AppArmor is in enforce mode rather than complain mode. This does not mean every syscall goes through AppArmor — only those that deal with the above-listed controlled resource types.

So to explain:

Having apparmor installed means that every single call, read or write, to any file or socket (and what else?) has to go thru apparmor code…

Correct — but only those syscalls that hit LSM hooks tied to access-controlled resources (files, sockets, etc.) go through the AppArmor logic. Not all syscalls are intercepted, but most I/O and security-sensitive ones are.

A precise list of syscalls that go through AppArmor logic.

Here is a comprehensive list of system calls that, directly or via kernel functions, are intercepted by AppArmor (via LSM hooks) to enforce its mandatory access control policies.


System Calls Intercepted or Mediated by AppArmor

Filesystem-Related Syscalls

AppArmor checks path-based permissions for file access:

  • open, openat
  • creat
  • read, pread, readv
  • write, pwrite, writev
  • truncate, ftruncate
  • mkdir, mkdirat
  • rmdir
  • unlink, unlinkat
  • rename, renameat
  • link, linkat
  • symlink, symlinkat
  • chmod, fchmod, fchmodat
  • chown, fchown, lchown, fchownat
  • stat, lstat, fstat, fstatat
  • access, faccessat
  • mknod, mknodat
  • mount, umount2
  • pivot_root
  • readdir, getdents, getdents64
  • ioctl (only for device-specific operations, when tied to path or device nodes)

Execution-Related

AppArmor heavily mediates binary/script execution:

  • execve
  • execveat

→ Checked against AppArmor profiles for “hat” transitions and execution permissions.

Process Control / Signaling

AppArmor uses labels to restrict inter-process signaling and ptrace:

  • ptrace
  • kill
  • tgkill, tkill
  • rt_sigqueueinfo

Network & Socket Syscalls

AppArmor can optionally enforce network restrictions (if networking mediation is enabled):

  • socket
  • connect
  • bind
  • listen
  • accept
  • accept4
  • sendto, sendmsg, recvfrom, recvmsg
  • getsockopt, setsockopt
  • getsockname, getpeername
  • shutdown

→ Note: Only domain-specific profiles or AppArmor extensions with network rules apply these.

Capability-Based Syscalls

Syscalls that trigger kernel capabilities checks, which AppArmor can restrict:

  • setuid, setgid, setreuid, setregid, setresuid, setresgid
  • capset, capget
  • prctl
  • clone, unshare, fork, vfork
  • reboot
  • swapon, swapoff
  • chroot
  • init_module, finit_module, delete_module

These don’t go through AppArmor directly for all cases, but if the action implies a capability (CAP_SYS_ADMIN, etc.), AppArmor checks it via the LSM capable() hook.


How AppArmor Hooks Work

AppArmor hooks into the LSM (Linux Security Modules) API, which is wired into the kernel syscall handling paths. When a syscall triggers an LSM-relevant action, the kernel calls AppArmor’s handler (if active and enabled for that object type).


Summary Table

CategoryExamples of Syscalls
File I/Oopen, read, write, mkdir, unlink, rename, chmod, stat
Executionexecve, execveat
IPC/Signalsptrace, kill, tgkill
Sockets/Networkingsocket, connect, bind, accept, sendto
Capabilitiessetuid, capset, clone, prctl
Mount/FS Controlmount, umount2, pivot_root, chroot


HowTo Completely Nuke AppArmor from Debian 11 12 and onward

Here’s how to completely nuke AppArmor from a Debian system — fully disable it, purge it, and make it as if it never existed. This is a total uninstall, leaving no traces, profiles, or runtime enforcement.


COMPLETE APPARMOR REMOVAL (Debian)

Applies to:

  • Debian 10, 11, 12 (Buster, Bullseye, Bookworm)
  • Ubuntu (optional tweak below)
  • Docker-host systems suffering from silent breakage

STEP 1: Stop the AppArmor Service

sudo systemctl stop apparmor

STEP 2: Disable It from Starting at Boot

sudo systemctl disable apparmor
sudo systemctl mask apparmor

STEP 3: Unload AppArmor Kernel Module

sudo aa-teardown
sudo rmmod apparmor

Note: If you see rmmod: ERROR: Module apparmor is in use, proceed to blacklist it and rebuild initramfs (next step).


STEP 4: Prevent Kernel Module from Ever Loading Again

Create a blacklist file:

echo "blacklist apparmor" | sudo tee /etc/modprobe.d/disable-apparmor.conf

Also add this to kernel boot parameters:

sudo sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="apparmor=0 /' /etc/default/grub
sudo update-grub

Then rebuild initramfs:

sudo update-initramfs -u

STEP 5: Remove All AppArmor Packages

sudo apt purge --autoremove apparmor apparmor-utils libapparmor1

(Optional, if present):

sudo apt purge --autoremove python3-apparmor apparmor-profiles apparmor-profiles-extra

STEP 6: Delete AppArmor Profiles and Residual Files

sudo rm -rf /etc/apparmor
sudo rm -rf /etc/apparmor.d
sudo rm -rf /var/cache/apparmor

STEP 7: Reboot

sudo reboot

STEP 8: Confirm It’s 100% Gone

After reboot, run:

apparmor_status || echo "AppArmor is fully gone"

Also check:

lsmod | grep apparmor
# → should return nothing

dmesg | grep -i apparmor
# → should return nothing

sudo systemctl status apparmor
# → should say "Unit apparmor.service could not be found"

Bonus: Make Docker Permanently Unconfined (if needed)

If Docker had auto-attached docker-default, ensure it doesn’t anymore:

sudo mkdir -p /etc/docker
echo '{ "apparmor-profile": "unconfined" }' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker

DONE — AppArmor Is Now 100% Gone

No service.
No kernel module.
No startup hooks.
No profiles.
No impact.
Nothing left to silently break your containers or processes ever again.

Experiencing the maddeningly Inexplicable?  Suspect AppArmor FIRST

Here is a complete breakdown of how AppArmor interacts with Docker, where the default profile is located, how to edit or disable it, and how to permanently or temporarily remove AppArmor limitations.


What is the Default AppArmor Profile for Docker?

When you run containers with Docker on a host with AppArmor enabled, Docker applies the profile:

docker-default

This profile restricts container capabilities, especially access to host filesystems, mount operations, ptrace, and some kernel interfaces. It acts as a sandbox at the kernel level.


Where Is the docker-default Profile Located?

On most Debian-based systems (Ubuntu, Debian, etc.):

/etc/apparmor.d/docker

This file contains the default AppArmor profile applied to Docker containers.

On some systems, the docker-default profile is built dynamically and not written to disk by default. You can extract it like this:

sudo apparmor_parser -p -W /etc/apparmor.d/docker

Or inspect it in memory:

sudo aa-status  # shows profile name and mode
sudo cat /sys/kernel/security/apparmor/policy/profiles  # lists loaded profiles

How to Edit the Docker AppArmor Profile

  1. Locate the file (if /etc/apparmor.d/docker does not exist, create it from a copy):
   sudo cp /etc/apparmor.d/usr.bin.docker /etc/apparmor.d/docker  # if exists
  1. Edit it:
   sudo nano /etc/apparmor.d/docker

Key options include:

  • network → allow or deny specific network types
  • capability → add/remove kernel capabilities like capability sys_admin
  • deny / allow file paths
  • mount → allow/deny mount types
  • ptrace → restrict interprocess tracing
  1. Reload the profile:
   sudo apparmor_parser -r /etc/apparmor.d/docker

How to Disable AppArmor for Docker (Temporary and Permanent)

TEMPORARILY Disable for Running Docker Containers:

Launch with --security-opt:

docker run --security-opt apparmor=unconfined ...

This tells Docker to not apply any AppArmor profile at all.

PERMANENTLY Disable for All Docker Containers:

Edit /etc/docker/daemon.json:

{
  "security-opt": ["apparmor=unconfined"]
}

Then restart Docker:

sudo systemctl restart docker

How to Disable AppArmor System-Wide (Nuke It)

1. Temporarily Stop AppArmor

sudo systemctl stop apparmor

AppArmor will be inactive until reboot.

2. Disable Permanently (on Debian/Ubuntu)

sudo systemctl disable apparmor
sudo apt remove apparmor apparmor-utils -y

Also edit GRUB to prevent the kernel from loading AppArmor:

Edit /etc/default/grub:

GRUB_CMDLINE_LINUX_DEFAULT="quiet apparmor=0"

Update GRUB:

sudo update-grub

Reboot to finalize.


How to Create a Custom Docker Profile With Fewer Restrictions

  1. Copy the base template:
cp /etc/apparmor.d/docker /etc/apparmor.d/docker-loose
  1. Edit it to remove limitations, such as:
  • capability, → allow all capabilities
  • network, → allow all networking
  • Remove or change deny /path/** rules
  1. Load it:
sudo apparmor_parser -r /etc/apparmor.d/docker-loose
  1. Run container with it:
docker run --security-opt apparmor=docker-loose ...

List of AppArmor Profile Options You Can Edit

  • capability (e.g., capability sys_admin,)
  • network (e.g., network inet stream,)
  • mount (e.g., mount -> /allowed/mount/path)
  • file path rules (e.g., deny /proc/** r,)
  • ptrace permissions
  • signal delivery rules

Summary

GoalAction
Temporarily disable AppArmor for a container--security-opt apparmor=unconfined
Permanently disable for all containersSet in /etc/docker/daemon.json and restart Docker
Edit docker-default profile/etc/apparmor.d/docker or dynamically created profile
Reload AppArmor profilesudo apparmor_parser -r /etc/apparmor.d/docker
Fully disable AppArmor on systemsudo systemctl disable apparmor && sudo apt remove apparmor and apparmor=0 in GRUB

A custom AppArmor profile created that removes every single restriction while still keeping AppArmor “happy.”

Here is a minimal custom AppArmor profile for Docker containers that removes all meaningful restrictions — effectively making it a “null profile” that keeps AppArmor quiet but does not enforce any sandboxing or security boundaries.


Custom AppArmor Profile: docker-unrestricted

/etc/apparmor.d/docker-unrestricted

#include <tunables/global>

profile docker-unrestricted flags=(attach_disconnected,mediate_deleted) {
  # Allow all file access
  file,
  umount,

  # Allow all capabilities
  capability,

  # Allow all networking
  network,

  # Allow ptrace
  ptrace,

  # Allow signals
  signal,

  # Allow mounting anything
  mount,

  # Allow access to all paths
  /** rwklx,
  /proc/** rwklx,
  /sys/** rwklx,
  /dev/** rwklx,
  /run/** rwklx,

  # Suppress logging for denied actions (should be none)
  deny /** mrwklx,

  # Allow execution of anything
  /bin/** mrwklx,
  /usr/bin/** mrwklx,
  /sbin/** mrwklx,
  /usr/sbin/** mrwklx,
  /lib/** mrwklx,
  /lib64/** mrwklx,
  /usr/lib/** mrwklx,

  # Allow any environment
  deny @{PROC}/sysrq-trigger rwklx,

  # Allow everything else
  capability,
}

How to Install and Activate

  1. Save the profile:
   sudo nano /etc/apparmor.d/docker-unrestricted
  1. Load it:
   sudo apparmor_parser -r /etc/apparmor.d/docker-unrestricted
  1. Run a Docker container using it:
   docker run --security-opt apparmor=docker-unrestricted -it ubuntu bash

Test It

From inside the container:

  • Try mounting (mount -t tmpfs none /mnt)
  • Try reading /proc/sys/kernel/random/uuid
  • Try using ping, curl, or custom binaries
  • Try writing to random locations like /dev/shm or /run/testfile

To Make Permanent

Make sure this profile is:

  • Present in /etc/apparmor.d/
  • Loaded at boot (AppArmor loads all profiles in that dir)

You can also make it the default by editing the Docker config:

/etc/docker/daemon.json:

{
  "security-opt": ["apparmor=docker-unrestricted"]
}

Then restart Docker:

sudo systemctl restart docker

A version of this profile that logs but does not block, or one that logs all activity for auditing purposes without restricting anything.

Here is a modified version of the docker-unrestricted AppArmor profile that:

Allows everything (no restrictions)
📝 Logs everything (for auditing and analysis)


📄 /etc/apparmor.d/docker-logall

#include <tunables/global>

profile docker-logall flags=(attach_disconnected,mediate_deleted) {
  # Full read/write/execute permissions across system
  file,
  umount,
  capability,
  network,
  ptrace,
  signal,
  mount,

  # Log every access attempt
  audit /** rwklx,
  audit /proc/** rwklx,
  audit /sys/** rwklx,
  audit /dev/** rwklx,
  audit /run/** rwklx,
  audit /bin/** mrwklx,
  audit /usr/bin/** mrwklx,
  audit /sbin/** mrwklx,
  audit /usr/sbin/** mrwklx,
  audit /lib/** mrwklx,
  audit /lib64/** mrwklx,
  audit /usr/lib/** mrwklx,

  # Environment safety exceptions
  deny @{PROC}/sysrq-trigger rwklx,

  # Fallback: log and allow everything else
  audit /**,
  capability,
}

🛠️ How to Install and Use It

  1. Create the profile:
   sudo nano /etc/apparmor.d/docker-logall
  1. Load it into AppArmor:
   sudo apparmor_parser -r /etc/apparmor.d/docker-logall
  1. Run a container using it:
   docker run --security-opt apparmor=docker-logall -it ubuntu bash

🔍 View Logs

Logs will show up in:

sudo journalctl -k  # or:
sudo dmesg | grep apparmor

To filter AppArmor log entries only:

sudo ausearch -m APPARMOR

Or tail them live:

sudo journalctl -f -k | grep apparmor

💾 Optional: Make it Default for All Containers

Add to /etc/docker/daemon.json:

{
  "security-opt": ["apparmor=docker-logall"]
}

Then:

sudo systemctl restart docker

Curiosities —

  • A version that logs only denials (e.g., for profiling new containers)
  • A version that includes named abstractions for common use (e.g., <abstractions/base>)
  • A profile designed for “complain mode” testing (where violations are logged but not enforced)

1 thought on “Experiencing the maddeningly Inexplicable? Suspect AppArmor FIRST”

  1. The nearest I can figure, newer versions of docker seem to require a container to have an apparmor security profile. This means most containers I pull from the internet don’t run unless I add –security-opt apparmor=unconfined to the docker run command.

    Reply

Leave a Comment