Mastering advanced AppArmor profile features: mount rules, ptrace and signals, pivot_root, finer network filtering, transitions between profiles with child profiles and attach, and understanding policy versioning between the 4.x and 5.x formats.

In episode 16 you understood where AppArmor stands in the kernel: one LSM in a stack, mediating alongside other modules. Now we turn back to policy — but to the parts rarely explored. So far your profiles have been dominated by file, network, and capability rules. Yet AppArmor can mediate far deeper: mount, ptrace and signals, pivot_root, finer network filtering, and transitions between profiles.
This episode covers those features, then closes with policy versioning: how the 4.x and 5.x formats coexist, and what that means for the profiles you write today.
Processes allowed to mount can be controlled with mount rules — source, target, and options. This matters for daemons that need additional filesystems, for example container services or applications that mount media, while also preventing other daemons from mounting anything at all:
mount options=(ro) -> /mnt/readonly/,
mount /dev/sr0 -> /mnt/media/,
deny mount -> /proc/sys/**,
deny mount -> /etc/**,The first line allows a read-only mount to a single target point — an important note: specifying the ro option here isn't just documentation, the kernel mediates it. The third and fourth lines are denies that close sensitive areas: no process in this profile may mount anything to /proc/sys or /etc. As always, deny wins over allow.
Inter-process communication is a dimension often overlooked. ptrace governs debugging and injection (like gdb), while signal governs signals such as kill, TERM, and HUP. Both rules can be restricted to specific peers:
ptrace (read) peer=myapp_helper,
signal (send) peer=myapp_helper,
deny ptrace peer=unconfined,
deny signal (receive) peer=other,The first rule allows a process in this profile to read the state of processes owned by myapp_helper; the second allows sending signals to the same peer. The last two denies close off interaction with the outside world: no tracing of unconfined processes, and no receiving signals from other processes. This limits lateral movement — an attacker who seizes one process can't use it to influence others.
Tip
If your application does debugging or inter-process communication and suddenly fails after the profile is activated, suspect the ptrace and signal rules first. Both are often invisible denies because they aren't recorded as file denies.
pivot_root is the syscall that switches the root filesystem — the core mechanism when a container or chroot-like runtime prepares its new filesystem. In AppArmor, access to it can be allowed fully or restricted by path:
pivot_root,
deny pivot_root -> /var/lib/**,The first line allows pivot_root with no target restriction. The second line refuses pivot_root into the /var/lib tree — for example protecting data volumes from a process that tries to use them as a new root. For container workloads that build their own rootfs, allowing pivot_root with a clear target is better than granting sys_chroot capability wildly.
The network rules from episode 6 can be refined by explicitly combining domain, type, and protocol. The deny patterns below show how to narrow network behavior:
network inet tcp,
network inet6 tcp,
network unix stream,
deny network inet raw,
deny network netlink,Here the profile allows IPv4/IPv6 TCP and UNIX stream — then explicitly forbids raw sockets and netlink. The raw deny matters for daemons that don't need to capture packets; the netlink deny prevents a process from manipulating network configuration. Note that mediation details, including support for more granular unix rules, can differ depending on the kernel version and the policy ABI — back to the versioning section below.
So far one profile guards one binary. But AppArmor supports transitions: when a binary executes another program, confinement can move to a different profile. The most common transition is px (discrete profile transition):
profile myapp /opt/myapp/bin/myapp {
#include <abstractions/base>
/opt/myapp/bin/myapp mr,
/opt/myapp/bin/helper px -> myapp_helper,
}
profile myapp_helper {
#include <abstractions/base>
/opt/myapp/bin/helper mr,
/opt/myapp/data/ r,
/opt/myapp/data/** r,
}When myapp executes helper, the process moves to the stricter myapp_helper profile — it no longer inherits all of its parent's permissions. This is the cleanest way to restrict risky parts of an application: separate its functions into its own binary, then confine each binary with a narrow profile.
For the case of one binary with several modes — for example an admin mode and a worker mode — AppArmor provides hats (child profiles prefixed with ^), activated through the change_hat call from inside the application:
profile myapp /opt/myapp/bin/myapp {
#include <abstractions/base>
/opt/myapp/bin/myapp mr,
^admin {
/etc/myapp/admin.conf r,
}
}There's also the attach keyword on transitions, which binds execution to certain conditions (for example extended attributes on a binary). It's useful for scenarios where one executable is served by more than one profile. The full syntax details are in the apparmor.d man page, but the concept you should carry away is this: AppArmor doesn't lock a process to a single profile for life — it can move between profiles in a controlled way.
All the features above depend on the policy ABI — the agreement between the policy format, the parser, and the kernel. Every profile can declare the ABI it uses:
# top of profile
abi <abi/4.0>,
#include <tunables/global>
#include <abstractions/base>A profile that doesn't declare an ABI is considered to use the legacy ABI pinned by the parser configuration. The parser performs rule downgrade: features the kernel doesn't support are downgraded or dropped, and you can ask the parser to tell you when that happens:
apparmor_parser --skip-kernel-load --warn=rule-not-enforced /etc/apparmor.d/myapp
aa-features-abi -xapparmor_parser --warn=rule-not-enforced warns about rules that can't be enforced; aa-features-abi -x prints the current kernel's feature capabilities in ABI format — redirect its output to a file to compare easily across hosts.
The core of what you need to understand about versioning:
unix rules — and a number of its new features aren't backward compatible with the 4.x format.Warning
When raising the parser to a new version, don't forget the compiled profile cache. A cache built by an old parser can hold a format the new kernel doesn't recognize — or vice versa. Make a habit of re-verifying all profiles with apparmor_parser --skip-cache --skip-kernel-load whenever you upgrade the userspace.
In this episode you've opened new dimensions of AppArmor profiles: mount rules restricted by source, target, and options; ptrace and signal rules that limit inter-process interaction; pivot_root for container needs; finer network filtering; and transitions between profiles — child profiles via px, hats via ^, and the attach keyword — that let a single process switch confinement in a controlled way. You also understood policy versioning: ABI declarations, rule downgrade, and the compatibility boundaries between the 4.x and 5.x formats.
The core takeaways:
px transitions separate risky parts of an application into narrower profiles.^ split one binary into isolated modes.In episode 18 next we turn all this knowledge into a repeatable workflow: batch mode switching with aa-enforce and aa-complain, profile merging with aa-mergeprof, validation with apparmor_parser -Q, and integrating profile linting and compile checks into a CI pipeline with a multi-distro test matrix. See you in episode 18!