Skip to content

Universal heap spray

userfaultfd + setxattr (ya da msg_msg), bir saldırganın herhangi bir kmalloc boyutunda tamamen içeriği kontrol edilen bir nesne allocate etmesine ve onu copy_from_user ortası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

  1. İ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, &reg);
  1. 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
  1. Bir child thread'den, o payload'u size = TARGET_SIZE'ın value'su olarak vererek setxattr'ı ateşle:
setxattr("/tmp/x", "user.spray", payload, TARGET_SIZE, 0);
// kernel: kmalloc(TARGET_SIZE); copy_from_user(...) -> FAULTS on page 2 -> hangs
  1. uffd handler thread'i fault'u alır ve copy/wake etmez — uyur. kvalue nesnesi artık hedef slab slot'unu saldırgan içeriğiyle işgal eder.

  2. Spray donmuşken, UAF/overlap'i diğer thread'de tetikle ki dangling pointer artık kvalue ile örtüşsün. Onu kullan (örneğin offset 0'daki sahte function pointer üzerinden çağrı yap).

  3. Fault'u çöz (UFFDIO_COPY/UFFDIO_WAKE) ki setxattr tamamlansın ve kfree ile temizlik yapsın.

Reported successes (Nikolenko)
16-byte DCCP UAF   -> fake func ptr at offset 0, frozen via setxattr+uffd
56-byte IrDA bug   -> target pointers in first 16 bytes
works across all kmalloc-N caches; size + content fully user-controlled

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 userfaultfd register edip sonra uffd-register edilmiş bir page'i aşan bir value buffer ile setxattr çağırması; setxattr/msgsnd altında copy_from_user içinde uzun süre block'lanmış task'lar.
  • KASAN sonraki UAF write/read'i yakalar; slab redzone/CONFIG_SLAB_FREELIST_HARDENED zorluğ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ıca CONFIG_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 dedicated kmalloc cache'leri ve init_on_alloc=1.

References