Skip to content

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'ler PERF_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 (0xffffffff800000000xffffffffc0000000) 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:

  1. 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_4M gibi 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.
  2. IP sampling. perf_event_paranoid varsayılanı sıkılaştırılmadan önceki kernel'lerde, PERF_SAMPLE_IP ile perf_event_open bir 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çin kernel.perf_event_paranoid'i 2'ye (modern Debian/Ubuntu'da varsayılan) ya da 3'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ıyla rdpmc'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.

References