Universal heap spray¶
userfaultfd+setxattr(ya damsg_msg), bir saldırganın herhangi birkmallocboyutunda tamamen içeriği kontrol edilen bir nesne allocate etmesine ve onucopy_from_userortasında süresiz dondurmasına izin verir — free edilmiş slot'ları ve elastic object'leri doldurmak için universal bir primitive.
Mechanism¶
Çoğu slab spray kısıtlıdır: msgsnd() 48 byte'lık bir struct msg_msg header'ını önüne ekler, pipe buffer'lar ve sk_buff sabit şekillere sahiptir ve birçoğu allocate edip hemen free eder, dolayısıyla bir UAF tetiklediğin anda nesneyi canlı tutamazsın. "Universal" bir spray üç koşulu sağlamalıdır: (1) en küçük cache'lere kadar saldırganın seçtiği boyut, (2) kontrolsüz bir header olmadan saldırganın seçtiği içerik ve (3) bug tetiklenirken kalıcılık.
Note
setxattr() şunu yapar: kvalue = kmalloc(size, ...); copy_from_user(kvalue, value, size); ... kfree(kvalue);. Bu (1) ve (2) koşullarını verir — size'ı ve her byte'ı sen seçersin. Eksik parça kalıcılıktır, ki bunu userfaultfd sağlar: bir userspace page üzerinde bir userfaultfd handler register et ve setxattr source buffer'ını page sınırını aşacak şekilde yerleştir. Kernel ilk byte'ları kopyalar, sonra register edilmiş page üzerinde copy_from_user ortasında page-fault olur; fault, basitçe uyuyan bir userspace thread'ine teslim edilir. Yarısı kopyalanmış kvalue nesnesi, o thread uyuduğu sürece allocate edilmiş ve içeriği kontrol edilen halde kalır — tam da UAF / overlap'i tetiklediğin an. Fault'u serbest bırakmak setxattr'ın bitmesine ve free etmesine izin verir.
Aynı dondurma hilesi msg_msg'e de genelleşir: çok-segmentli bir mesajın copy_from_user'ını msg->next pointer'ını takip etmeden hemen önce askıya almak, ilk segment üzerindeki bir UAF'nin next'i keyfi bir adrese yeniden yönlendirmesine izin verir ve msg_msg üzerinden arbitrary read/write verir.
Walkthrough¶
- İki bitişik page allocate et;
userfaultfd'yi yalnızca ikinci page üzerinde register et:
char *area = mmap(NULL, 2*PAGE, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
struct uffdio_register reg = {
.range = { .start = (long)(area + PAGE), .len = PAGE },
.mode = UFFDIO_REGISTER_MODE_MISSING,
};
ioctl(uffd, UFFDIO_REGISTER, ®);
- Spray payload'unu öyle yerleştir ki kontrol edilen byte'lar (örneğin offset 0'daki sahte bir function pointer) page 1'de dursun ve buffer fault'lanmamış page 2'ye geçsin:
char *payload = area + PAGE - TARGET_SIZE + N; // straddle boundary
build_fake_object(payload); // first N bytes fully controlled
- Bir child thread'den, o payload'u
size = TARGET_SIZE'ın value'su olarak verereksetxattr'ı ateşle:
setxattr("/tmp/x", "user.spray", payload, TARGET_SIZE, 0);
// kernel: kmalloc(TARGET_SIZE); copy_from_user(...) -> FAULTS on page 2 -> hangs
-
uffd handler thread'i fault'u alır ve copy/wake etmez — uyur.
kvaluenesnesi artık hedef slab slot'unu saldırgan içeriğiyle işgal eder. -
Spray donmuşken, UAF/overlap'i diğer thread'de tetikle ki dangling pointer artık
kvalueile örtüşsün. Onu kullan (örneğin offset 0'daki sahte function pointer üzerinden çağrı yap). -
Fault'u çöz (
UFFDIO_COPY/UFFDIO_WAKE) kisetxattrtamamlansın vekfreeile temizlik yapsın.
Reported successes (Nikolenko)
Spray'i çalıştırmak için bir child fork et ki spray thread'i fault üzerinde block'lanmışken parent çalışmaya devam etsin.
Detection¶
- Anormal: bir process'in
userfaultfdregister edip sonra uffd-register edilmiş bir page'i aşan bir value buffer ilesetxattrçağırması;setxattr/msgsndaltındacopy_from_useriçinde uzun süre block'lanmış task'lar. - KASAN sonraki UAF write/read'i yakalar; slab redzone/
CONFIG_SLAB_FREELIST_HARDENEDzorluğu artırır.
Mitigation¶
vm.unprivileged_userfaultfd = 0(sysctl),userfaultfd'yi privileged kullanıcılara kısıtlar — unprivileged saldırganlar için freeze primitive'ini kaldırır. Modern kernel'ler onu ayrıcaCONFIG_USERFAULTFD+ SECCOMP-dostu flag'lerin arkasına alır.- FUSE, page-fault-hang oracle'ı olarak userfaultfd'nin yerine geçebilir; unprivileged FUSE'u da aynı şekilde kısıtla.
- Genel slab hardening:
CONFIG_SLAB_FREELIST_RANDOM,RANDOM_KMALLOC_CACHES, hassas nesneler için dedicatedkmalloccache'leri veinit_on_alloc=1.