Skip to content

Kernel base leak via ops pointer

Freed bir slab chunk'ını kernel function pointer'larıyla dolu bir nesneyle (örn. seq_operations) reclaim et, bu pointer'lardan birini geri oku ve compile-time offset'ini çıkararak KASLR base'ini kurtar.

Mechanism

KASLR, kernel image'in load adresini her boot'ta tek bir slide ile randomize eder. Kernel text'indeki her fonksiyon göreli konumunu korur; sadece base kayar. Yani bir bilinen symbol'ün runtime adresini bir kez öğrenirsen, slide leaked_addr - known_offset olur ve tüm text/data layout'u buradan çıkar. Dolayısıyla bütün oyun, attacker'ın symbolic kimliğini bildiği tek bir pointer'ı geri okumaktan ibaret.

Note

Küçük bir grup kernel nesnesi "tamamen function pointer ve başka hiçbir şey değil" şeklindedir, bu da onları ideal leak verici yapar. struct seq_operations kanonik örnek: start, stop, next ve show callback'lerini tutar. single_open() ile desteklenen bir dosya (örn. /proc/self/stat) açıldığında kernel bir seq_operations allocate eder ve onu generic helper'lar single_start, single_next, single_stop ile dosyanın kendi show pointer'larıyla doldurur. seq_operations 0x20 byte olduğu için kmalloc-32'ye düşer (accounted path'lerde kmalloc-cg-32). Bir attacker kmalloc-32 chunk'ı üzerinde dangling/overlapping bir read primitive tutuyorsa ve ardından /proc/self/stat açarsa, freed chunk taze bir seq_operations ile reclaim edilir. Chunk'ı okumak artık dört canlı kernel-text pointer'ı verir. Bunlardan herhangi biri KASLR'ı yener.

Bunu çalıştıran invariant: single_next (ve arkadaşları) belirli bir kernel build için _text'ten sabit bir offset'te yaşar. Randomisation, image'in tümüne uygulanan tek bir additive slide'dır, dolayısıyla kernel_base = leaked_single_next - offsetof_single_next_from_base. Brute force yok, olasılık yok — bir read, bir çıkarma.

Walkthrough

Bu pattern (a) kmalloc-32 chunk'ı üzerinde bir memory-disclosure veya UAF read ve (b) kernel'i o chunk'a bir seq_operations allocate etmeye zorlama yeteneği gerektirir.

  1. Hâlâ okuyabildiğin kmalloc-32 boyutunda bir nesneyi free et (ya da ona bir dangling pointer elde et).
  2. Çok sayıda /proc/self/stat descriptor'ı açarak seq_operations spray'le, böylece freed chunk reclaim edilir:
// each open() of a single_open()-backed file allocates one struct seq_operations (kmalloc-32)
int fds[256];
for (int i = 0; i < 256; i++)
    fds[i] = open("/proc/self/stat", O_RDONLY);
  1. Overlap edilmiş chunk'a karşı read primitive'ini tetikle. Reclaim edilen layout, sırasıyla, dört seq_operations field'ıdır:
struct seq_operations {
    void *(*start)(struct seq_file *m, loff_t *pos);  // single_start
    void  (*stop) (struct seq_file *m, void *v);      // single_stop
    void *(*next) (struct seq_file *m, void *v, loff_t *pos); // single_next
    int   (*show) (struct seq_file *m, void *v);      // the file's show handler
};
  1. Offset 0x10'da (next field'ı) leak edilen qword single_next'tir. Base'i kurtar:
unsigned long leaked_next = read_qword(overlap, 0x10);   // = single_next runtime addr
unsigned long kbase = leaked_next - SINGLE_NEXT_OFFSET;  // offset from System.map / vmlinux

SINGLE_NEXT_OFFSET, eşleşen bir vmlinux/System.map'ten bir kez okunan single_next - _text değeridir:

$ grep -E " single_next$" System.map
ffffffff8131c0e0 t single_next        # subtract _text (ffffffff81000000) -> 0x31c0e0

Yani o build'de kbase = leaked_next - 0x31c0e0. Mantıklı bir sanity check kbase & 0xfff == 0 ve kbase'in kanonik kernel aralığında olmasıdır.

Warning

Gerçekten bir symbol'üne sahip olduğun target'ın field'ını seç. start/stop/next generic single_* helper'larına işaret eder (dosyalar arası stabil); show, dosyaya özgü handler'a işaret eder (proc_pid_stat ile ilgili), bu da gayet uygun ama eşleşen offset'i kullanman gerekir. Offset'leri karıştırmak makul görünen ama yanlış bir base verir. Ayrıca CONFIG_MEMCG accounting'li kernel'lerde /proc/self/stat allocation'ı ayrı bir cache olan kmalloc-cg-32'den gelebilir — freed chunk'ının aynı cache'te yaşaması gerekir (cg-accounted bir victim nesne ya da bir cross-cache adımı kullan).

Detection

  • User'a açık read path'lerinden okunan function-pointer değerleri imzadır; KASAN, disclosure'ı mümkün kılan alttaki UAF/OOB read'i yakalar.
  • Anormal open("/proc/self/stat") patlamaları (yüzlerce descriptor) reclaim spray'i için ucuz bir heuristic'tir.

Mitigation

  • kptr_restrict ve dmesg_restrict, doğrudan pointer yazdıran path'leri kapatır ama buradaki gibi UAF güdümlü bir memory read'e karşı hiçbir şey yapmaz — pointer %pK üzerinden değil, ham byte olarak dışarı sızdırılır.
  • CONFIG_RANDOM_KMALLOC_CACHES ve cache separation (kmalloc-cg-*), freed chunk ile seq_operations allocation'ının aynı cache'e düşmesini zorlaştırır ve same-cache reclaim'i körelttir (kmalloc-cache-feng-shui.md ve cross-cache-attack.md'a bak).
  • Gerçek çözüm, read primitive'inin (UAF/OOB) kendisini ortadan kaldırmaktır.

References