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));


Reply via email to