FUSE-based exploitation¶
Bir memory mapping'i unprivileged userspace bir FUSE filesystem ile arkala; böylece kernel o page'lerde fault ettiğinde attacker'ın FUSE handler'ı çalışır ve bir kernel thread'i operasyonun ortasında bloklayabilir.
Mechanism¶
FUSE, unprivileged bir kullanıcının filesystem I/O callback'lerini userspace'te implemente etmesine izin verir; in-kernel FUSE driver'ı her operasyon için userspace daemon'a çıkış yapmak ve cevabını beklemek zorundadır. FUSE-backed bir dosya mmap()'lendiğinde (demand paging), kernel'in bir page'e ilk dokunuşu fault eder ve kernel, userspace FUSE daemon'a yönlendirilen bir read çıkarır.
Note
Invariant şu: FUSE-backed bir page'den kopyalama yapan bir kernel thread'i, userspace read'i döndürene kadar faulting access'in ötesine ilerleyemez. Attacker, libfuse'un struct fuse_operations'ındaki .read callback'ini kontrol eder, dolayısıyla kernel thread'inin ne zaman (ya da hiç) devam edeceğine o karar verir. Bu, unprivileged userfaultfd'nin gate'lenmeden önce sağladığı aynı yetenektir — FUSE, genel amaçlı yerini tutar.
Warning
userfaultfd'nin aksine, FUSE kernel'in başka bir context adına aldığı fault'lara hizmet eder. Project Zero şunu not ediyor: "userfaultfd won't work here; it doesn't catch remote memory accesses" — FUSE bunları yine de handle eder; daha genel stall primitive olmasının sebebi de budur.
Walkthrough¶
Handler'ları register et ve daemon'ı başlat, stall'ı read callback'inin içine yerleştir:
static int hello_read(const char *path, char *buf, size_t size,
off_t off, struct fuse_file_info *fi)
{
sleep(2); /* block the kernel thread here */
/* ... return controlled bytes ... */
}
static struct fuse_operations ops = { .read = hello_read, /* ... */ };
int main(int argc, char **argv) { return fuse_main(argc, argv, &ops, NULL); }
Exploit process'i ardından FUSE dosyasını map'ler ve ondan bir kernel kopyası tetikler:
int fd = open("fuse_dir/lol", O_RDWR);
char *p = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
/* pass p to a syscall whose handler does copy_from_user(p) → kernel stalls */
Gerçek kullanım — CVE-2021-32606 (CAN ISOTP): FUSE read handler'ı uyur; böylece kernel, isotp_setsockopt() içindeki copy_from_sockptr()'de durur ve window, eşzamanlı bir isotp_bind()'in tamamlanıp bir UAF üretmesine yetecek kadar genişler. Bkz. CAN ISOTP UAF LPE.
Detection¶
Bir FUSE filesystem mount edip ardından hassas bir syscall'dan hemen önce onu mmap()'leyen bir process gözlemlenebilir bir pattern'dir; FUSE mount'larını ve kernel kopya path'lerindeki olağandışı bloklanmaları denetlemek bunu ortaya çıkarabilir.
Mitigation¶
Teknik tam olarak vm.unprivileged_userfaultfd kısıtlandığı için önem kazandı. Benzer hardening: unprivileged FUSE mount'larını kısıtla (user_allow_other, user-namespace/container FUSE policy'si) ve kernel tarafında window'unu FUSE'un esnetebileceği TOCTOU pattern'lerinden kaçın.