|
|
Log in / Subscribe / Register

A set of AppArmor vulnerabilities

Qualys has sent out a somewhat breathless advisory describing a number of vulnerabilities in the AppArmor security module, which is used in a number of Debian-based distributions (among others).

This "CrackArmor" advisory exposes a confused-deputy flaw allowing unprivileged users to manipulate security profiles via pseudo-files, bypass user-namespace restrictions, and execute arbitrary code within the kernel. These flaws facilitate local privilege escalation to root through complex interactions with tools like Sudo and Postfix, alongside denial-of-service attacks via stack exhaustion and Kernel Address Space Layout Randomization (KASLR) bypasses via out-of-bounds reads.


to post comments

Technical writeup

Posted Mar 13, 2026 16:36 UTC (Fri) by geofft (subscriber, #59789) [Link] (11 responses)

Buried at the end of this is a link to a technical writeup more reflective of Qualys's reputation: https://cdn2.qualys.com/advisory/2026/03/10/crack-armor.txt

I have to suspect that the blog post was written by a combination of LLMs and marketing people without any domain knowledge of what they're writing about. The blog post talks about impacts of vastly different severity - disabling AppArmor, local DoS, bypassing user namespace restrictions (which is well-established as possible thanks to previous Qualys research), and kernel-space arbitrary code execution - as if they're all equally concerning, which made me wonder if that last bit was even real, or a hypothetical about what could go wrong without AppArmor's additional layer of protection. The technical writeup very clearly distinguishes these impacts.

Specifically, the writeup first demonstrates that an unprivileged user to load and unload AppArmor profiles, and then escalates that into a) a userspace privesc by confining sudo so that it fails to drop privileges (resuscitating a classic CVE from 2002) and b) a kernelspace privesc by exploiting bugs in the profile parser. That's very concerning!

The flaw is pretty straightforward and well described in the technical writeup: the pseudo-files in /sys/kernel/security/apparmor/ can be open()ed by a non-root user, and permissions are checked on write(), which means that if you pass an open file handle as stdout/stderr to a setuid binary, you can effectively manage to do those writes.

Technical writeup

Posted Mar 13, 2026 23:28 UTC (Fri) by jreiser (subscriber, #11027) [Link] (1 responses)

> ... if you pass an open file handle as stdout/stderr to a setuid binary, you can effectively manage to do those writes.

Does this mean that the badness can be avoided by shell-level re-direction such as ">&-" and "2>&-", which close stdout and stderr respectively, or is there more to it?

Technical writeup

Posted Mar 14, 2026 1:13 UTC (Sat) by geofft (subscriber, #59789) [Link]

The setup for the attack is you have a local user who can already run arbitrary code but only as a non-root user. (So, for instance, this might be a machine shared between several real users, like an academic server or traditional shared host, or a machine running untrusted code as a throwaway user account for sandboxing.) So you, the defender, aren't in a position to insist that they use a particular type of shell redirection - they have full shell access.

In particular, even if you close stdout and stderr before running the untrusted code, the attack involves their code running a setuid subprocess, and so they can re-open stdout and stderr for that subprocess in whatever way they like.

Writable apparmor files

Posted Mar 14, 2026 7:46 UTC (Sat) by geuder (subscriber, #62854) [Link] (3 responses)

Thanks for the link! Truly amazing reading what pile of bugs we are using ;)

Not really familiar with AppArmor and did not read the patches. Is there any real reason to have those files world-writable? Of course the deeper bugs would still need to be fixed even if an unprivileged user could not open them anymore.

Writable apparmor files

Posted Mar 16, 2026 2:17 UTC (Mon) by jrjohansen (subscriber, #75010) [Link] (2 responses)

Yes, it is a virtualized interface used by multiple users. Non-root users use the interface to load policy into their own policy namespace. This use case (none root users being able to load policy to a namespace) can be mitigated via /proc/sys/kernel/unprivileged_userns_apparmor_policy.

Writable apparmor files

Posted Mar 16, 2026 22:26 UTC (Mon) by geofft (subscriber, #59789) [Link] (1 responses)

Is that sufficient? It looks like one of the fixes (8e135b8aee5a) is for code that executed before policy_update(), the function that transitively checks kernel.unprivileged_userns_apparmor_policy, so I'd be worried that either for this bug or another similar type of bug, a non-root user could exploit it before the permission check. As much as it pains me to say it as a fan of unprivileged user namespaces, it feels like the right answer probably is to chmod the policy files to 0644 or 0600 unless you know you need this functionality.

Writable apparmor files

Posted Mar 16, 2026 23:44 UTC (Mon) by jrjohansen (subscriber, #75010) [Link]

This is a set of attacks against apparmor via policy, everyone of these in some way relate to policy, and management of policy. If policy management can be blocked, the attack can be stopped. The sysctl is enough to block the attack from user namespaces, regardless of whether the task is root in the user namespace. It is not sufficient to stop the attack from the root namespace, 6601e13e82841 is needed for that.

Generally speaking the bug set can be broken down into several groups:

1. policy management LPE via confused deputy, this in and of it self only allows managing apparmor policy, and does not directly allow a full LPE escalation, but it can be chained with other exploits to achieve that goal. This allows DOS via loading policy to cause denials, removing policy so that mediation isn't being applied etc.
- 6601e13e82841 - confused deputy

2. resource DOS - Requiring rights to administer policy, can be exploited via policy management LPE
- 306039414932c - create deeply nested namespaces to exhaust resources
- ab09264660f9d - potential to overflow stack with deeply nested namespaces, and then mass free
- e38c55d9f834e - memory leak

3. verifier attack via manipulating policy, bad addr access, via policy manipulation: requires rights to administer policy, can be exploited via policy management LPE
- 9063d7e2615f4 - out of bounds read
- d352873bbefa7 - out of bounds read
- 5df0c44e8f5f6 - use after free
- 39440b137546a - out of bounds read

4. valid policy attack, via trying to race the fs
- a0b7091c4de45 & 8e135b8aee5a0 (use after free) - does not require corrupted, requires rights to administer policy, can be exploited via policy management LPE

5. attack on valid policy feature with a bug, requires rights to administer policy, can be exploited via policy management LPE
- 8756b68edae37 - causes invalid mediation, potential for denial DOS, or allowing things that shouldn't be allowed, but if you can replace policy there are easier ways to do that then manipulate the policy binary the way this bug requires.

Technical writeup

Posted Mar 15, 2026 19:46 UTC (Sun) by mattdm (subscriber, #18) [Link]

OMG yes. Thank you for the link to the actual obfuscated report.

Technical writeup

Posted Mar 16, 2026 10:57 UTC (Mon) by epa (subscriber, #39769) [Link] (2 responses)

The flaw is pretty straightforward and well described in the technical writeup: the pseudo-files in /sys/kernel/security/apparmor/ can be open()ed by a non-root user, and permissions are checked on write(), which means that if you pass an open file handle as stdout/stderr to a setuid binary, you can effectively manage to do those writes.
That's an understandable muddle. But, I wonder, has it been explicitly clarified that "The Linux security model is to check file permissions on open(), and once you have a file descriptor with write access, any process can write to it."

If that is indeed the model, then any access control on write() is a code smell. (If, on the other hand, the security model is to check on both opening and writing, then there are probably lots of missing checks in the kernel, or cases where the permissions applied aren't consistent between the two calls. In principle any write() call would have to go through the same checks that would have been applied on open().)

A quick experiment shows that "check on open" appears to be the model:

% (while sleep 1; do date; done) >foo &
% chmod u-w foo
% tail -f foo
Even once write permission is "revoked", the process with an open file descriptor to the file can keep on writing.

Technical writeup

Posted Mar 16, 2026 22:14 UTC (Mon) by geofft (subscriber, #59789) [Link]

In the traditional UNIX/POSIX model, yes, file permissions are checked on open(), not write(). Doing otherwise would mean that you couldn't e.g. run a command in a terminal that dropped privileges and have it successfully display stdout/stderr to the terminal, at least not without temporarily chowning the terminal to the unprivileged user, which probably defeats the purpose. Or you couldn't have root open a log file in /var/log/ and then drop privileges to a daemon account like httpd.

Looking at the code a bit I have a guess about how this happened: AppArmor is a MAC framework, and this distinction is one of the ways of expressing the difference between MAC and DAC. In DAC, the owner of a resource has the ability to control permissions over that resource. This could be by changing the permissions on the resource, or by passing around an already-authorized open file handle. In MAC, some system-wide policy controls access, and the owner of a resource cannot grant any more access than that. Because AppArmor itself is MAC, it arguably doesn't make sense to allow DAC-based access to reconfiguring AppArmor itself. You want to say "Only the root user can change policies," not "The root user can set file permissions on the AppArmor policy pseudo-files to control who can change policies." Otherwise you aren't in a position to make any sort of MAC statements e.g. "The web server cannot overwrite logs, even if those logs are accidentally world-writable" because it's all contingent on the confined user not being able to change their own policies.

So, the code works by waiting until an attempt to write a policy (in one write() operation, it seems) and then evaluating whether the current user has the authority to change the policy in the requested way. I can see how this is the natural way to implement this if you've just finished implementing an entire MAC system and are adding the config knobs. Also, because it works this way, it's not immediately obvious from reading the code that this amounts to a permission check on write(); the write() handler translates this into the combined check of whether the current user can make this specific policy change (as opposed to having any authority to change policies at all), and some other function evaluates that without any reference to the open file.

The fix that was implemented (6601e13e8284) appears to handle this by making sure the user doing the write() is no more privileged than the user who opened the file, but then continuing to check the permissions of the user actually doing the write() against the MAC framework.

Technical writeup

Posted Mar 17, 2026 0:03 UTC (Tue) by jrjohansen (subscriber, #75010) [Link]

Technical writeup

Posted Mar 16, 2026 10:57 UTC (Mon) by epa (subscriber, #39769) [Link] (1 responses)

The flaw is pretty straightforward and well described in the technical writeup: the pseudo-files in /sys/kernel/security/apparmor/ can be open()ed by a non-root user, and permissions are checked on write(), which means that if you pass an open file handle as stdout/stderr to a setuid binary, you can effectively manage to do those writes.

That's an understandable muddle. But, I wonder, has it been explicitly clarified that "The Linux security model is to check file permissions on open(), and once you have a file descriptor with write access, any process can write to it."

If that is indeed the model, then any access control on write() is a code smell. (If, on the other hand, the security model is to check on both opening and writing, then there are probably lots of missing checks in the kernel, or cases where the permissions applied aren't consistent between the two calls. In principle any write() call would have to go through the same checks that would have been applied on open().)

It is not just at open(), but it can get messy. There is the potential to check on open, most writes (there is caching and other considerations), passing of the file via scm rights, inheritance, and several other operations.

> A quick experiment shows that "check on open" appears to be the model:

yes this is the DAC model. That doesn't mean that, that is the only check. The LSM frame work adds the potential for additional checks.

> Even once write permission is "revoked", the process with an open file descriptor to the file can keep on writing.

Linux doesn't do revocation of open fds, but that also doesn't mean writes to an open fd will succeed, or do what you think either. Both selinux and apparmor will replace open fds with null device backed files during inheritance, and can also return errors for operations on fds that are open. But once an fd is open it does get a lot messier.

Without going into to detail on the technical write-up, the write to the apparmorfs virtual filesystem, are indeed different than talking about mediation of generic writes to open fds.

Technical writeup

Posted Mar 16, 2026 17:17 UTC (Mon) by epa (subscriber, #39769) [Link]

From the writeup:
To fully exploit this vulnerability (in particular, to load arbitrary AppArmor profiles), we must find a privileged program that can be forced into write()ing completely controlled strings, including null bytes, to its stdout or stderr.
I think that by default Linux distributions should include a suid root /bin/echo. That should remind everyone that this class of vulnerability exists, and that file permissions cannot just be checked at the moment you read or write.

Who's going to watch the watchers?

Posted Mar 14, 2026 0:09 UTC (Sat) by alip (subscriber, #170176) [Link]

Nice moment to take a breather and consider unprivileged alternatives such as sydbox, gvisor, nsjail and so on.

Trivial mitigation?

Posted Mar 15, 2026 11:00 UTC (Sun) by cypherpunks2 (guest, #152408) [Link] (6 responses)

Wouldn't the simplest mitigation be to "chmod 700 /sys/kernel/security"?

Trivial mitigation?

Posted Mar 15, 2026 20:58 UTC (Sun) by NYKevin (subscriber, #129325) [Link] (4 responses)

sysfs is not a regular filesystem. In simple terms, it is a figment of the kernel's imagination. If the kernel does not feel like imagining that /sys/kernel/security has a mode of 0700 (read: if the kernel's implementation of sysfs does not track modes explicitly), then it will not let you chmod it (even if you are root). So, trying this on my WSL system, I unsurprisingly get an "operation not permitted" error.

Trivial mitigation?

Posted Mar 16, 2026 0:03 UTC (Mon) by cypherpunks2 (guest, #152408) [Link] (3 responses)

I'm aware of how sysfs works. I'm surprised it's not letting you chmod it on WSL, since it works totally fine on stock Debian. In general, most sysfs files can have their permissions arbitrarily modified.
root@cpunks:~# strace -u cpunks -P /sys/kernel/security/apparmor -qqe trace=%file ls /sys/kernel/security/apparmor
statx(AT_FDCWD, "/sys/kernel/security/apparmor", AT_STATX_SYNC_AS_STAT|AT_NO_AUTOMOUNT, STATX_MODE, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFDIR|0755, stx_size=0, ...}) = 0
openat(AT_FDCWD, "/sys/kernel/security/apparmor", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
features  policy  profiles  raw_data_compression_level_max  raw_data_compression_level_min  revision

root@cpunks:~# strace -P /sys/kernel/security -qqe trace=%file chmod 700 /sys/kernel/security newfstatat(AT_FDCWD, "/sys/kernel/security", {st_mode=S_IFDIR|0755, st_size=0, ...}, 0) = 0 fchmodat(AT_FDCWD, "/sys/kernel/security", 0700) = 0
root@cpunks:~# strace -u cpunks -P /sys/kernel/security/apparmor -qqe trace=%file ls /sys/kernel/security/apparmor statx(AT_FDCWD, "/sys/kernel/security/apparmor", AT_STATX_SYNC_AS_STAT|AT_NO_AUTOMOUNT, STATX_MODE, 0x7ffe64661e30) = -1 EACCES (Permission denied) ls: cannot access '/sys/kernel/security/apparmor': Permission denied

Trivial mitigation?

Posted Mar 16, 2026 22:35 UTC (Mon) by geofft (subscriber, #59789) [Link]

WSL (assuming we mean WSL2, as WSL1 isn't even actual Linux) runs each of your distros in its own container-ish setup on the same kernel. It wouldn't totally surprise me if it didn't grant enough ownership into the container to sysadmin sysfs.

NYKevin, are you able to do the chmod inside `wsl --debug-shell` (which requires running as administrator)? That gets you access to the real root namespace of the WSL kernel.

For what it's worth, you can also chmod the individual files at issue. Those are even in their own filesystem (apparmorfs) but the default kernel behavior for imagined files totally includes imagining their ownership and permissions. (As another example, consider files in /dev, which absolutely need correct permission and ownership tracking!)

Trivial mitigation?

Posted Mar 17, 2026 0:00 UTC (Tue) by geuder (subscriber, #62854) [Link] (1 responses)

FWIW, /sys/kernel/security is not sysfs but securityfs.

Whether those work differently when it comes to using chmod, I cannot comment on.

If it were different on WSL, that wouldn't surprise me too much. Isn't the configuration rather different than on most distro kernels?

Trivial mitigation?

Posted Mar 29, 2026 8:05 UTC (Sun) by cypherpunks2 (guest, #152408) [Link]

The contents within the mount point are securityfs, but the directory /sys/kernel/security itself is still in sysfs. Both support changing permissions, though, so chmod 700 /sys/kernel/security/apparmor works just as well.

Trivial mitigation?

Posted Mar 16, 2026 10:27 UTC (Mon) by smcv (subscriber, #53363) [Link]

That's certainly a "big hammer" approach, but as noted in another comment thread above, it does break some intended functionality.

Denying things that were meant to be allowed can sometimes turn otherwise-harmless bugs into vulnerabilities themselves (as seen in Qualys' example attack for this very vulnerability via sudo and postfix, where sudo probably should have immediately crashed out with an error after setuid() failed, but didn't), so it's difficult to recommend this mitigation as anything beyond a stopgap to reach a point where the machine can safely be rebooted into the fixed kernel.


Copyright © 2026, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds