In the Linux kernel, the following vulnerability has been resolved:
pid: take a reference when initializing cad_pid
During boot, kernelinitfreeable() initializes cad_pid
to the init
task's struct pid. Later on, we may change cad_pid
via a sysctl, and
when this happens procdocadpid() will increment the refcount on the
new pid via getpid(), and will decrement the refcount on the old pid
via putpid(). As we never called getpid() when we initialized
cad_pid
, we decrement a reference we never incremented, can therefore
free the init task's struct pid early. As there can be dangling
references to the struct pid, we can later encounter a use-after-free
(e.g. when delivering signals).
This was spotted when fuzzing v5.13-rc3 with Syzkaller, but seems to
have been around since the conversion of cad_pid
to struct pid in
commit 9ec52099e4b8 ("[PATCH] replace cad_pid by a struct pid") from the
pre-KASAN stone age of v2.6.19.
Fix this by getting a reference to the init task's struct pid when we
assign it to cad_pid
.
Full KASAN splat below.
================================================================== BUG: KASAN: use-after-free in nsofpid include/linux/pid.h:153 [inline] BUG: KASAN: use-after-free in taskactivepid_ns+0xc0/0xc8 kernel/pid.c:509 Read of size 4 at addr ffff23794dda0004 by task syz-executor.0/273
CPU: 1 PID: 273 Comm: syz-executor.0 Not tainted 5.12.0-00001-g9aef892b2d15 #1 Hardware name: linux,dummy-virt (DT) Call trace: nsofpid include/linux/pid.h:153 [inline] taskactivepidns+0xc0/0xc8 kernel/pid.c:509 donotifyparent+0x308/0xe60 kernel/signal.c:1950 exitnotify kernel/exit.c:682 [inline] doexit+0x2334/0x2bd0 kernel/exit.c:845 dogroupexit+0x108/0x2c8 kernel/exit.c:922 getsignal+0x4e4/0x2a88 kernel/signal.c:2781 dosignal arch/arm64/kernel/signal.c:882 [inline] donotifyresume+0x300/0x970 arch/arm64/kernel/signal.c:936 workpending+0xc/0x2dc
Allocated by task 0: slabpostallochook+0x50/0x5c0 mm/slab.h:516 slaballocnode mm/slub.c:2907 [inline] slaballoc mm/slub.c:2915 [inline] kmemcachealloc+0x1f4/0x4c0 mm/slub.c:2920 allocpid+0xdc/0xc00 kernel/pid.c:180 copyprocess+0x2794/0x5e18 kernel/fork.c:2129 kernelclone+0x194/0x13c8 kernel/fork.c:2500 kernelthread+0xd4/0x110 kernel/fork.c:2552 restinit+0x44/0x4a0 init/main.c:687 archcallrestinit+0x1c/0x28 start_kernel+0x520/0x554 init/main.c:1064 0x0
Freed by task 270: slabfreehook mm/slub.c:1562 [inline] slabfreefreelisthook+0x98/0x260 mm/slub.c:1600 slabfree mm/slub.c:3161 [inline] kmemcachefree+0x224/0x8e0 mm/slub.c:3177 putpid.part.4+0xe0/0x1a8 kernel/pid.c:114 putpid+0x30/0x48 kernel/pid.c:109 procdocadpid+0x190/0x1b0 kernel/sysctl.c:1401 procsyscallhandler+0x338/0x4b0 fs/proc/procsysctl.c:591 procsyswrite+0x34/0x48 fs/proc/procsysctl.c:617 callwriteiter include/linux/fs.h:1977 [inline] newsyncwrite+0x3ac/0x510 fs/readwrite.c:518 vfswrite fs/readwrite.c:605 [inline] vfswrite+0x9c4/0x1018 fs/readwrite.c:585 ksyswrite+0x124/0x240 fs/readwrite.c:658 _dosyswrite fs/readwrite.c:670 [inline] _sesyswrite fs/readwrite.c:667 [inline] _arm64syswrite+0x78/0xb0 fs/readwrite.c:667 _invokesyscall arch/arm64/kernel/syscall.c:37 [inline] invokesyscall arch/arm64/kernel/syscall.c:49 [inline] el0svccommon.constprop.1+0x16c/0x388 arch/arm64/kernel/syscall.c:129 doel0svc+0xf8/0x150 arch/arm64/kernel/syscall.c:168 el0svc+0x28/0x38 arch/arm64/kernel/entry-common.c:416 el0synchandler+0x134/0x180 arch/arm64/kernel/entry-common.c:432 el0sync+0x154/0x180 arch/arm64/kernel/entry.S:701
The buggy address belongs to the object at ffff23794dda0000 which belongs to the cache pid of size 224 The buggy address is located 4 bytes inside of 224-byte region [ff ---truncated---