perf performance-counter KASLR break¶
CPU performance counter'larına (
perf_event_open/rdpmcüzerinden) unprivileged erişim KASLR kernel base'ini sızdırır: page-table walk'larından etkilenen bir counter, map'lenmiş kernel image'ını non-present address'lerden ayırt eder ve eski kernel'lerPERF_SAMPLE_IPüzerinden ham kernel IP'lerini bile sızdırırdı.
Mechanism¶
Note
x86-64 kernel image'ı 1 GB'lık bir pencere içinde (0xffffffff80000000–
0xffffffffc0000000) 2 MB sınırına map'lenir, bu da 9 bit entropy → 512 aday
slot verir. KASLR'ın sırrı sadece bu 512 slot'tan hangisinin kernel'i
tuttuğudur. İki ayrı performance-monitoring oracle bunu açığa çıkarır:
- Page-walk sayımı (PMC oracle). Non-present page'ler TLB'de asla cache'lenmez,
dolayısıyla unmapped bir address'e bir load her zaman bir page-table walk
tetikler; oysa present, yakın zamanda erişilmiş bir kernel page'ine bir load
TLB'ye hit eder ve hiçbir walk yapmaz. Dolayısıyla
DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4Mgibi bir counter (ya da page-walk sayısı/cycle'larıyla korele herhangi bir counter) gerçek kernel base'inde 511 unmapped slot'tan farklı okur. Kernel address'e dokunmaktan doğan fault, speculative/transient execution, bir TSX transaction ya da bir fault handler ile bastırılır. - IP sampling.
perf_event_paranoidvarsayılanı sıkılaştırılmadan önceki kernel'lerde,PERF_SAMPLE_IPileperf_event_openbir sampled event'in tetiklendiği instruction pointer'ı kaydeder. Syscall-yoğun bir child'ı sample'layıp kernel IP'lerinin minimum high-bits prefix'ini almak, randomize edilmiş kernel base'ini doğrudan açığa çıkarır — timing'e gerek yok.
Warning
Counter isimleri ve offset'leri microarchitecture'a ve kernel'e özgüdür. Page-walk
counter'ı sabit bir değer değil, bir event'tir — onu hedef başına türet.
perf_event_paranoid gating'i (aşağıda) her iki oracle'ın da unprivileged
ulaşılabilir olup olmadığını belirler.
Walkthrough¶
PMC page-walk timing (CounterLeak, Weber et al. "Reviving Meltdown 3a"):
for slot in 0 .. 511: # 512 possible 2 MB-aligned kernel slots
addr = 0xffffffff80000000 + slot * 0x200000
arm/program DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4M # page-walk counter
transient/TSX/fault-suppressed load from addr # touch candidate
delta = leak_counter_via_rdpmc() # CounterLeak read
# The first slot whose page-walk count/cycles change abnormally is the kernel
# (its .rodata first page, __start_rodata, is frequently accessed -> TLB-resident).
Raporlanan sonuç: Intel Celeron J4005, Ubuntu 20.04, Linux 5.4.0 — %98 başarı (n=100), medyan 4.7 s. (Celeron N3350 / Ubuntu 22.04 / 5.15.0 üzerinde de gösterildi.)
Stall-counter varyantı (EchoLoad, "KASLR: Break It, Fix It, Repeat", Canella/Gruss
et al.): page walk yerine CPU stall'larını say. Intel'de,
CYCLE_ACTIVITY.STALLS_MEM_ANY / L1D_PEND_MISS.PENDING_CYCLES non-present page'lerin
stall ettiğini (memory-hierarchy erişimi yok) ama present kernel page'lerinin etmediğini
gösterir; bunu 512 offset'in tamamı üzerinde çalıştırmak, Meltdown'a bağışık Cascade Lake'te
bile kernel'i de-randomize eder.
Legacy IP sampling (Lizzie Dixon, "Breaking KASLR with perf", 2017):
struct perf_event_attr pe = {
.type = PERF_TYPE_SOFTWARE,
.config = PERF_COUNT_SW_TASK_CLOCK,
.sample_type = PERF_SAMPLE_IP, /* record instruction pointer of samples */
/* ... */
};
/* Fork a child looping on uname(); sample its IP; collect ~100 kernel-range
* samples from the syscall handler; the minimum high-bits prefix = kernel base. */
uint64_t prefix = sample->ip & ~0xffffffULL;
if (prefix < min) min = prefix; /* min prefix reveals KASLR slide */
Bu, /proc/sys/kernel/perf_event_paranoid varsayılan olarak 1 iken (pre-4.6) çalışıyordu;
o değer unprivileged kernel-IP sampling'e izin veriyordu.
Detection¶
perf_event_open açıp ardından 0xffffffff8... range'ini tarayan bir process ya da kernel
address'lerinde faulting load'larla korele sıkı rdpmc loop'ları olağandışı bir pattern'dir —
ama kullanılan tüm instruction'lar unprivileged'dır ve kernel tarafında hiçbir artifact bırakmaz.
Mitigation¶
- Unprivileged
perf_event_open'ı reddetmek içinkernel.perf_event_paranoid'i2'ye (modern Debian/Ubuntu'da varsayılan) ya da3'e yükselt; bu, IP-sampling oracle'ını ve unprivileged counter programlamayı kapatır. - Bir counter zaten programlanmış ve
rdpmc(CR4.PCE) üzerinden okunabilirse page-walk/stall oracle'ları hayatta kalır, dolayısıylardpmc'yi ring 0'a kısıtla. FLARE(Canella et al.) dummy page'ler map'ler ki her kernel-range address present olsun, böylece oracle'ların dayandığı present/non-present ayrımını kaldırır.