Prefetch timing KASLR bypass¶
Unprivileged user code'dan
prefetchinstruction'ını kernel adresleri üzerinde zamanla: prefetch, address translation'ı bir privilege check olmadan yapar, dolayısıyla latency'si bir kernel adresinin map'li olup olmadığını sızdırır — KASLR offset'ini ve direct-physical map'i kurtarır (Gruss vd., CCS 2016).
Mechanism¶
Note
x86 software-prefetch instruction'ları (prefetcht0/t1/t2/nta) birer hint'tir: bir adres
için page table'ları gezip cache'i ısıtırlar, ama normal bir load'un aksine fault vermez ve
privilege zorlamazlar. Bu yüzden unprivileged bir process bir kernel virtual address üzerinde
prefetch çalıştırıp ne kadar sürdüğünü gözlemleyebilir. Latency, translation'ın durumuna
bağlıdır: gerçekten map'li bir kernel adresi (translation'ı cache'lenebilir / TLB'ye düşer),
page-table walk'u erken ya da farklı sonlanan map'siz bir adrese göre ölçülebilir biçimde daha
hızlı prefetch edilir. KASLR yalnızca sabit bir kernel layout'una randomize bir offset eklediği
için, aday base adreslerini taramak ve "mapped" timing imzasına sahip olanı seçmek randomization
sırrını doğrudan açığa çıkarır. Aynı oracle, tüm RAM'i sabit bir virtual region'da map'leyen
kernel'in direct-physical map'i (physmap) üzerine uygulandığında virtual adresleri physical
olanlara çözer; ret2dir tarzı SMAP bypass'larını mümkün kılan da budur.
Walkthrough¶
Temel ölçüm: serialize et, cycle counter'ı oku, hedefi prefetch et, counter'ı tekrar oku ve gürültüyü süzmek için birçok denemenin minimum'unu sakla.
static inline uint64_t time_prefetch(void *addr) {
uint32_t lo0, hi0, lo1, hi1;
/* rdtscp, TSC'yi EDX:EAX'a yazar; iki 32-bit half'i ayrı ayrı yakala,
yoksa üst 32 bit kaybolur (rcx = processor-id, clobber). */
asm volatile("mfence; lfence; rdtscp" : "=a"(lo0), "=d"(hi0) :: "rcx");
asm volatile("lfence");
__builtin_prefetch(addr, 0, 0); /* prefetchnta */
asm volatile("rdtscp" : "=a"(lo1), "=d"(hi1) :: "rcx");
asm volatile("lfence");
uint64_t t0 = ((uint64_t)hi0 << 32) | lo0;
uint64_t t1 = ((uint64_t)hi1 << 32) | lo1;
return t1 - t0;
}
Kernel base'ini bulmak için her KASLR-aligned adayı prob et ve minimum latency'yi al:
for (uint64_t base = KERNEL_LO; base < KERNEL_HI; base += KASLR_ALIGN) {
uint64_t best = ~0ULL;
for (int i = 0; i < ROUNDS; i++) {
uint64_t t = time_prefetch((void *)base);
if (t < best) best = t;
}
record(base, best); /* mapped base shows a distinct low band */
}
Timing dağılımı ne gösterir
Bu oracle üzerine iki saldırı kurulur:
- Translation-level oracle — aday adres başına bir mapping'in var olup olmadığını ayırt eder, paging hiyerarşisini ve dolayısıyla user-space ve kernel-space ASLR'yi kurtarır.
- Address-translation (physmap) recovery — hangi physmap adresinin aynı şekilde prefetch edildiğini bularak bir virtual adresi physical frame'ine çözer, ret2dir için 64-bit Linux'ta SMAP'i atlatır.
Sinyal, denemeler üzerindeki minimum'dur: tek bir örnek TLB/cache durumu ve interrupt'lar tarafından domine edilir, ama dağılımın tabanı "mapped" ile "unmapped" adayları ayırabilecek kadar kararlıdır. Makale ayrıca aynı primitive ile Windows 10 kernel ASLR'sini kırmayı da gösterdi ve saldırılar Amazon EC2 VM'leri içinde koştu.
Warning
Sonuçlar microarchitecture'a ve microcode'a bağlıdır. rdtsc granülaritesi, prefetch
davranışı ve sonraki mitigation'lar (özellikle aynı yazarların önerdiği güçlü kernel
isolation olan KPTI/KAISER) sinyali değiştirir ya da ortadan kaldırır. KPTI, kernel'in
çoğunu user page table'larından unmap ederek saldırının dayandığı timing farkını çökertir —
bu yüzden KPTI sistemlerinde bu spesifik oracle büyük ölçüde etkisizleşir ve onun yerine
başka leak'ler (bkz. entrybleed-kpti-kaslr-bypass) gerekir.
Mitigation¶
- KPTI / page-table isolation (KAISER) — kanonik düzeltme; kernel'i user address space'inden unmap etmek, kernel'in çoğu için prefetch timing farkını ortadan kaldırır.
- Unprivileged code'a sunulan timer çözünürlüğünü düşürmek ölçüm eşiğini yükseltir ama tam bir savunma değildir.