On 10/16/26 10:13, Hang Nan wrote:
apparmor: do not pass a NULL old label to aa_label_is_subset()
This report was produced with the help of AI tooling. Per
Documentation/process/security-bugs.rst ("If you resorted to AI assistance to
identify a bug, you must treat it as public") I am reporting it publicly, and I
am not attaching a reproducer - a tested one is available on request.
1. Affected version
v7.3-rc7 and earlier. File: security/apparmor/af_unix.c:685
(update_sk_ctx()). The file is byte-identical in mainline
0fcb05b72344f40806cbe810de8872f1698bf053 (2026-10-14), so this is not fixed
upstream.
2. The problem
unix_connect_peers() records the peer label in the socket context, and its
only callers are apparmor_socket_socketpair() (lsm.c:1350) and
apparmor_unix_stream_connect() (lsm.c:1213). A connected AF_UNIX SOCK_DGRAM
socket never goes through either of them, so ctx->peer stays NULL while
unix_peer(sk) is set. aa_unix_file_perm() then takes the "there is a peer"
path (af_unix.c:756 peer_sk != NULL, :759 !is_sk_fs for an abstract address,
:763 !is_peer_fs, :781 plabel = peer ctx->label), calls
update_peer_ctx(peer) and then update_sk_ctx(sock->sk, label, plabel) with
ctx->peer == NULL:
af_unix.c:679 if (plabel != ctx->peer_lastupdate) {
af_unix.c:680 old = ctx->peer; <- NULL
af_unix.c:685 if (!aa_label_is_subset(plabel, old))
label.c:522 sub->size <- NULL deref
Any confined task triggers it with a plain read()/write() on such a socket.
3. Triggering conditions
- any AppArmor profile in enforce mode;
- socket(AF_UNIX, SOCK_DGRAM, 0) x2, bind() one side to an abstract address,
connect() the other to it, then write()/read() on the connected side;
- unprivileged user, no capabilities, no user namespace required.
Result: oops in process context. With the default configuration
(panic_on_oops=0) the task exits "with irqs disabled" and
"with preempt_count 1" - the fault happens while the unix socket lock is
held - and the kernel then reports recurring
"rcu: INFO: rcu_preempt self-detected stall on CPU" for that task (observed
at t=21s, 84s and 147s, every ~63s), i.e. the machine does not recover.
With panic_on_oops=1 it is a kernel panic. That crosses the availability
boundary the threat model spells out for unprivileged users
(Documentation/process/threat-model.rst:65-70: users not having elevated
capabilities "may not ... affect the system's availability (shutdown,
reboot, panic, hang, ...)").
Observed on an unmodified v7.3-rc7 kernel (x86_64, qemu/KVM):
BUG: kernel NULL pointer dereference, address: 000000000000004c
#PF: supervisor read access in kernel mode
RIP: 0010:__aa_label_next_not_in_set+0x6/0xd0
Code: ... 41 57 <8b> 42 4c ... (mov 0x4c(%rdx),%eax, rdx = 0)
CR2: 000000000000004c
Call Trace:
<TASK>
aa_label_is_subset+0x37/0x60
aa_unix_file_perm+0x2d1/0x930
aa_file_perm+0x479/0x580
common_file_perm+0x59/0x150
security_file_permission+0x3b/0x50
vfs_write+0x73/0x440
ksys_write+0xbb/0xe0
do_syscall_64+0xdd/0x4a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Two negative controls in the same run (a socketpair, and a stream connect to
an abstract listener) both survive, which is what pins the missing
unix_connect_peers() call on the dgram path.
4. Reproducer
Available on request: statically linked initramfs probe + a minimal
AppArmor policy + the console log above.
5. Proposed fix
Check 'old' before dereferencing it in update_sk_ctx(). Tested on
v7.3-rc7: with the patch applied the same probe runs to completion with no
oops (write -> 1 errno=0, read -> -1 errno=11), the unpatched kernel oopses
as quoted above.
--- git format-patch output ---
(format-patch 信封的 From/Date 行已删去;下方即为补丁正文:提交信息 + diff)
Subject: [PATCH] apparmor: fix NULL pointer dereference in update_sk_ctx()
update_sk_ctx() passes ctx->peer straight to aa_label_is_subset() without
checking it for NULL. aa_label_is_subset() documents @sub as non-NULL
through AA_BUG() only, which compiles to nothing unless
CONFIG_SECURITY_APPARMOR_DEBUG_ASSERTS is enabled, and
__aa_label_next_not_in_set() dereferences sub->size, so a NULL @sub is an
oops.
ctx->peer is only ever populated by unix_connect_peers(), which runs for
socketpair() and for stream connect(). For an AF_UNIX SOCK_DGRAM socket
that has been connect()ed, ctx->peer and ctx->peer_lastupdate are both
still NULL, so the "plabel && peer_lastupdate != plabel" test does not
short-circuit and the NULL dereference is taken from the file permission
hook:
BUG: kernel NULL pointer dereference, address: 000000000000004c
RIP: 0010:__aa_label_next_not_in_set+0x6/0x...
CR2: 000000000000004c (NULL + offsetof(struct aa_label, size))
RDX: 0000000000000000 (@sub)
Call Trace:
aa_label_is_subset
update_sk_ctx
aa_unix_file_perm
aa_file_perm
security_file_permission
vfs_write
A NULL peer label means no peer label has been cached yet, so there is no
subset relationship to evaluate; the comment already on the else branch
("race or a subset - don't update") describes the resulting behaviour.
Reproducer: a connect()ed AF_UNIX SOCK_DGRAM socket under a profile that
mediates the access, followed by write(). Verified on v7.3-rc7 with the
trace above.
Fixes: 88fec3526e84 ("apparmor: make sure unix socket labeling is correctly
updated.")
Signed-off-by: Hang Nan <[email protected]>
Hi Hang,
thank you for the contribution. This issue has already been fixed by another
patch "[PATCH] apparmor: fix NULL ctx->peer derefs in unix socket ctx updates"
---
security/apparmor/af_unix.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c
index b908e7448..52f3cc95b 100644
--- a/security/apparmor/af_unix.c
+++ b/security/apparmor/af_unix.c
@@ -682,7 +682,7 @@ static void update_sk_ctx(struct sock *sk, struct aa_label
*label,
if (old == plabel) {
rcu_assign_pointer(ctx->peer_lastupdate,
aa_get_label(plabel));
- } else if (aa_label_is_subset(plabel, old)) {
+ } else if (old && aa_label_is_subset(plabel, old)) {
rcu_assign_pointer(ctx->peer_lastupdate,
aa_get_label(plabel));
rcu_assign_pointer(ctx->peer, aa_get_label(plabel));