KASAN: use-after-free Read in post_one_notification

From: Dipanjan Das
Date: Wed Jul 27 2022 - 17:29:04 EST


Hi,

We would like to report the following bug which has been found by our
modified version of syzkaller.

======================================================
description: KASAN: use-after-free Read in post_one_notification
affected file: kernel/watch_queue.c
kernel version: 5.10.131
kernel commit: 8f95261a006489c828f1d909355669875649668b
git tree: upstream
kernel config: https://syzkaller.appspot.com/x/.config?x=e49433cfed49b7d9
crash reproducer: attached
patch: This bug was previously reported by syzkaller for kernel
version 5.17. The same patch works for kernel version 5.10 as well,
i.e., we tested that the repro can no longer triggers the reported
crash with this patch:
https://syzkaller.appspot.com/text?tag=Patch&x=13b8c83c080000
======================================================
Crash log:
======================================================
BUG: KASAN: use-after-free in __lock_acquire+0x3867/0x5840
kernel/locking/lockdep.c:4824
Read of size 8 at addr ffff8880aa5f8ca8 by task syz-executor.5/1878

CPU: 0 PID: 1878 Comm: syz-executor.5 Tainted: G OE 5.10.131+ #3
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS
1.13.0-1ubuntu1.1 04/01/2014
Call Trace:
__dump_stack lib/dump_stack.c:77 [inline]
dump_stack+0x107/0x163 lib/dump_stack.c:118
print_address_description.constprop.0.cold+0xd3/0x4f7 mm/kasan/report.c:385
__kasan_report mm/kasan/report.c:545 [inline]
kasan_report.cold+0x1f/0x37 mm/kasan/report.c:562
__lock_acquire+0x3867/0x5840 kernel/locking/lockdep.c:4824
lock_acquire kernel/locking/lockdep.c:5564 [inline]
lock_acquire+0x1a8/0x4b0 kernel/locking/lockdep.c:5529
__raw_spin_lock_irq include/linux/spinlock_api_smp.h:128 [inline]
_raw_spin_lock_irq+0x32/0x50 kernel/locking/spinlock.c:167
spin_lock_irq include/linux/spinlock.h:379 [inline]
post_one_notification+0x59/0x860 kernel/watch_queue.c:86
__post_watch_notification kernel/watch_queue.c:206 [inline]
__post_watch_notification+0x562/0x840 kernel/watch_queue.c:176
post_watch_notification include/linux/watch_queue.h:109 [inline]
notify_key security/keys/internal.h:199 [inline]
__key_update security/keys/key.c:774 [inline]
key_create_or_update+0xbff/0xd00 security/keys/key.c:977
__do_sys_add_key security/keys/keyctl.c:134 [inline]
__se_sys_add_key security/keys/keyctl.c:74 [inline]
__x64_sys_add_key+0x2ab/0x4b0 security/keys/keyctl.c:74
do_syscall_64+0x2d/0x70 arch/x86/entry/common.c:46
entry_SYSCALL_64_after_hwframe+0x44/0xa9
RIP: 0033:0x7fc85a2514ed
Code: 02 b8 ff ff ff ff c3 66 0f 1f 44 00 00 f3 0f 1e fa 48 89 f8 48
89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d
01 f0 ff ff 73 01 c3 48 c7 c1 b8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fc858201be8 EFLAGS: 00000246 ORIG_RAX: 00000000000000f8
RAX: ffffffffffffffda RBX: 00007fc85a36ff60 RCX: 00007fc85a2514ed
RDX: 0000000020000080 RSI: 0000000020000040 RDI: 0000000020000000
RBP: 00007fc85a2bd2e1 R08: fffffffffffffffc R09: 0000000000000000
R10: 0000000000000001 R11: 0000000000000246 R12: 0000000000000000
R13: 00007ffde3e9524f R14: 00007fc85a36ff60 R15: 00007fc858201d80

Allocated by task 1368:
kasan_save_stack+0x1b/0x40 mm/kasan/common.c:49
kasan_set_track mm/kasan/common.c:57 [inline]
__kasan_kmalloc.constprop.0+0xcf/0xe0 mm/kasan/common.c:471
kmalloc include/linux/slab.h:552 [inline]
kzalloc include/linux/slab.h:664 [inline]
alloc_pipe_info+0x10c/0x500 fs/pipe.c:789
get_pipe_inode fs/pipe.c:880 [inline]
create_pipe_files+0x8f/0x7d0 fs/pipe.c:912
__do_pipe_flags+0x41/0x240 fs/pipe.c:961
do_pipe2+0x77/0x160 fs/pipe.c:1009
__do_sys_pipe2 fs/pipe.c:1027 [inline]
__se_sys_pipe2 fs/pipe.c:1025 [inline]
__x64_sys_pipe2+0x50/0x70 fs/pipe.c:1025
do_syscall_64+0x2d/0x70 arch/x86/entry/common.c:46
entry_SYSCALL_64_after_hwframe+0x44/0xa9

Freed by task 1402:
kasan_save_stack+0x1b/0x40 mm/kasan/common.c:49
kasan_set_track+0x1c/0x30 mm/kasan/common.c:57
kasan_set_free_info+0x1b/0x30 mm/kasan/generic.c:363
__kasan_slab_free+0x111/0x150 mm/kasan/common.c:427
slab_free_hook mm/slub.c:1542 [inline]
slab_free_freelist_hook mm/slub.c:1576 [inline]
slab_free mm/slub.c:3149 [inline]
kfree+0xfa/0x460 mm/slub.c:4125
put_pipe_info+0xb9/0xe0 fs/pipe.c:710
pipe_release+0x1d2/0x260 fs/pipe.c:733
__fput+0x285/0x920 fs/file_table.c:281
task_work_run+0xe0/0x1a0 kernel/task_work.c:151
tracehook_notify_resume include/linux/tracehook.h:188 [inline]
exit_to_user_mode_loop kernel/entry/common.c:164 [inline]
exit_to_user_mode_prepare+0x195/0x1b0 kernel/entry/common.c:191
syscall_exit_to_user_mode+0x38/0x260 kernel/entry/common.c:266
entry_SYSCALL_64_after_hwframe+0x44/0xa9

The buggy address belongs to the object at ffff8880aa5f8c00
which belongs to the cache kmalloc-512 of size 512
The buggy address is located 168 bytes inside of
512-byte region [ffff8880aa5f8c00, ffff8880aa5f8e00)
The buggy address belongs to the page:
page:000000000cd222be refcount:1 mapcount:0 mapping:0000000000000000
index:0x0 pfn:0xaa5f8
head:000000000cd222be order:2 compound_mapcount:0 compound_pincount:0
flags: 0xfff00000010200(slab|head)
raw: 00fff00000010200 dead000000000100 dead000000000122 ffff888100041280
raw: 0000000000000000 0000000000100010 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected

Memory state around the buggy address:
ffff8880aa5f8b80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffff8880aa5f8c00: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff8880aa5f8c80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff8880aa5f8d00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff8880aa5f8d80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================


--
Thanks and Regards,

Dipanjan
// autogenerated by syzkaller (https://github.com/google/syzkaller)

#define _GNU_SOURCE

#include <dirent.h>
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#include <linux/futex.h>

#ifndef __NR_close_range
#define __NR_close_range 436
#endif

static void sleep_ms(uint64_t ms)
{
usleep(ms * 1000);
}

static uint64_t current_time_ms(void)
{
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts))
exit(1);
return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
}

static void thread_start(void* (*fn)(void*), void* arg)
{
pthread_t th;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 128 << 10);
int i = 0;
for (; i < 100; i++) {
if (pthread_create(&th, &attr, fn, arg) == 0) {
pthread_attr_destroy(&attr);
return;
}
if (errno == EAGAIN) {
usleep(50);
continue;
}
break;
}
exit(1);
}

typedef struct {
int state;
} event_t;

static void event_init(event_t* ev)
{
ev->state = 0;
}

static void event_reset(event_t* ev)
{
ev->state = 0;
}

static void event_set(event_t* ev)
{
if (ev->state)
exit(1);
__atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE);
syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000);
}

static void event_wait(event_t* ev)
{
while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0);
}

static int event_isset(event_t* ev)
{
return __atomic_load_n(&ev->state, __ATOMIC_ACQUIRE);
}

static int event_timedwait(event_t* ev, uint64_t timeout)
{
uint64_t start = current_time_ms();
uint64_t now = start;
for (;;) {
uint64_t remain = timeout - (now - start);
struct timespec ts;
ts.tv_sec = remain / 1000;
ts.tv_nsec = (remain % 1000) * 1000 * 1000;
syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, &ts);
if (__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
return 1;
now = current_time_ms();
if (now - start > timeout)
return 0;
}
}

static bool write_file(const char* file, const char* what, ...)
{
char buf[1024];
va_list args;
va_start(args, what);
vsnprintf(buf, sizeof(buf), what, args);
va_end(args);
buf[sizeof(buf) - 1] = 0;
int len = strlen(buf);
int fd = open(file, O_WRONLY | O_CLOEXEC);
if (fd == -1)
return false;
if (write(fd, buf, len) != len) {
int err = errno;
close(fd);
errno = err;
return false;
}
close(fd);
return true;
}

static void kill_and_wait(int pid, int* status)
{
kill(-pid, SIGKILL);
kill(pid, SIGKILL);
for (int i = 0; i < 100; i++) {
if (waitpid(-1, status, WNOHANG | __WALL) == pid)
return;
usleep(1000);
}
DIR* dir = opendir("/sys/fs/fuse/connections");
if (dir) {
for (;;) {
struct dirent* ent = readdir(dir);
if (!ent)
break;
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
continue;
char abort[300];
snprintf(abort, sizeof(abort), "/sys/fs/fuse/connections/%s/abort", ent->d_name);
int fd = open(abort, O_WRONLY);
if (fd == -1) {
continue;
}
if (write(fd, abort, 1) < 0) {
}
close(fd);
}
closedir(dir);
} else {
}
while (waitpid(-1, status, __WALL) != pid) {
}
}

static void setup_test()
{
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
setpgrp();
write_file("/proc/self/oom_score_adj", "1000");
}

struct thread_t {
int created, call;
event_t ready, done;
};

static struct thread_t threads[16];
static void execute_call(int call);
static int running;

static void* thr(void* arg)
{
struct thread_t* th = (struct thread_t*)arg;
for (;;) {
event_wait(&th->ready);
event_reset(&th->ready);
execute_call(th->call);
__atomic_fetch_sub(&running, 1, __ATOMIC_RELAXED);
event_set(&th->done);
}
return 0;
}

static void execute_one(void)
{
int i, call, thread;
for (call = 0; call < 5; call++) {
for (thread = 0; thread < (int)(sizeof(threads) / sizeof(threads[0])); thread++) {
struct thread_t* th = &threads[thread];
if (!th->created) {
th->created = 1;
event_init(&th->ready);
event_init(&th->done);
event_set(&th->done);
thread_start(thr, th);
}
if (!event_isset(&th->done))
continue;
event_reset(&th->done);
th->call = call;
__atomic_fetch_add(&running, 1, __ATOMIC_RELAXED);
event_set(&th->ready);
if (call == 3)
break;
event_timedwait(&th->done, 50);
break;
}
}
for (i = 0; i < 100 && __atomic_load_n(&running, __ATOMIC_RELAXED); i++)
sleep_ms(1);
}

static void execute_one(void);

#define WAIT_FLAGS __WALL

static void loop(void)
{
int iter = 0;
for (;; iter++) {
int pid = fork();
if (pid < 0)
exit(1);
if (pid == 0) {
setup_test();
execute_one();
exit(0);
}
int status = 0;
uint64_t start = current_time_ms();
for (;;) {
if (waitpid(-1, &status, WNOHANG | WAIT_FLAGS) == pid)
break;
sleep_ms(1);
if (current_time_ms() - start < 5000)
continue;
kill_and_wait(pid, &status);
break;
}
}
}

uint64_t r[2] = {0xffffffffffffffff, 0x0};

void execute_call(int call)
{
intptr_t res = 0;
switch (call) {
case 0:
res = syscall(__NR_pipe2, 0x20000140ul, 0x80ul);
if (res != -1)
r[0] = *(uint32_t*)0x20000140;
break;
case 1:
memcpy((void*)0x20000000, "big_key\000", 8);
memcpy((void*)0x20000040, "syz", 3);
*(uint8_t*)0x20000043 = 0x21;
*(uint8_t*)0x20000044 = 0;
memset((void*)0x20000080, 64, 1);
res = syscall(__NR_add_key, 0x20000000ul, 0x20000040ul, 0x20000080ul, 1ul, 0xfffffffc);
if (res != -1)
r[1] = res;
break;
case 2:
syscall(__NR_pipe2, 0ul, 0x80ul);
break;
case 3:
syscall(__NR_keyctl, 0x20ul, r[1], r[0], 0ul, 0);
break;
case 4:
syscall(__NR_close_range, r[0], -1, 0ul);
break;
}

}
int main(void)
{
syscall(__NR_mmap, 0x1ffff000ul, 0x1000ul, 0ul, 0x32ul, -1, 0ul);
syscall(__NR_mmap, 0x20000000ul, 0x1000000ul, 7ul, 0x32ul, -1, 0ul);
syscall(__NR_mmap, 0x21000000ul, 0x1000ul, 0ul, 0x32ul, -1, 0ul);
loop();
return 0;
}

Attachment: repro.syz
Description: Binary data