RCU callback UAF read KASLR defeat¶
Kontrol ettiğin bir obje üzerinde
call_rcu()'yu tetikle ki kernel bir.textfunction pointer'ını objeninrcu_head'ine yazsın; sonra bu pointer'ı komşu bir use-after-free / out-of-bounds primitive üzerinden geri oku, kernel base'i leak'le ve KASLR'ı kır.
Mechanism¶
Note
Read-Copy-Update (RCU), bir objenin yok edilmesini bir grace period geçene kadar erteler — bu, her CPU'nun herhangi bir RCU read-side critical section'dan çıkmış olduğunun garanti edildiği an, yani hiçbir reader artık stale bir pointer tutamaz. Bu ertelenmiş free'yi schedule etmek için kernel, objenin içine bir struct rcu_head (alias struct callback_head) gömer ve call_rcu(head, func) çağırır. Suistimal edilen invariant şu: call_rcu(), grace period bitmeden önce callback function pointer'ını objenin kendisine yazar:
struct callback_head {
struct callback_head *next; // offset 0
void (*func)(struct callback_head *head); // offset 8
} __attribute__((aligned(sizeof(void *))));
#define rcu_head callback_head
func, bir kernel .text rutininin (destructor / freeing callback) adresini tutar. KASLR tüm kernel image'ını tek bir random base ile kaydırdığı için, leak'lenen herhangi bir .text pointer, bilinen statik offset'i çıkardığında kernel base'i açığa çıkarır. Eğer grace period callback'i çalıştırmadan önce head->func'a ulaşabilen bir read primitive'in (UAF read ya da komşu out-of-bounds read) varsa, bir deferred-free implementasyon detayını KASLR oracle'ına çevirirsin — write primitive gerekmez.
Walkthrough¶
Klasik örnek, sonunu aşarak okuyabildiğin bir objeyi, ilk member'ı rcu_head olan free edilip-tekrar-allocate edilen bir obje ile eşleştirir. Sık kullanılan bir reclaim hedefi struct user_key_payload'dır; layout'u bir rcu_head ile başlar ve key revoke edildiğinde call_rcu() ile free edilir.
- Aynı slab cache içinde komşu iki obje elde et: obje A (sonunu aşarak out-of-bounds okuyabildiğin) ve obje B (RCU yönetimindeki obje). B'nin
rcu_head'i, A'dan ulaşılabilen sabit bir offset'e düşer.
/* user_key_payload: rcu_head is the first member, so func lands at +8 */
struct user_key_payload {
struct rcu_head rcu; /* next @ +0, func @ +8 */
unsigned short datalen;
char data[];
};
- Kernel'i B üzerinde
call_rcu()çağırmaya zorla. Bir key payload için bu, key güncellendiğinde ya da revoke edildiğinde olur — eski payload RCU-deferred free için schedule edilir, böylece kernel destructor pointer'ınırcu->func'a yazar:
- Hemen — uyumadan — A'nın out-of-bounds / UAF read'i üzerinden obje B'nin
rcu_head.func'unu oku. Race window, RCU grace period'dur; o geçtiğinde callback çalışır ve bellek free edilir (ve tekrar kullanılır), leak'i yok eder. Hemen okumak hem o free'den hem de değerin overwrite edilmesinden kaçınmanı sağlar.
/* read 24 bytes past A into B's rcu_head; func is the leaked .text pointer */
unsigned long leak[3];
oob_read(A, leak, sizeof(leak)); /* leak[0]=next, leak[1]=func */
unsigned long kbase = leak[1] - KNOWN_FUNC_OFFSET;
printf("kernel base: %lx\n", kbase);
func neden stabil, bilinen bir offset
Saklanan pointer, belirli bir freeing callback'in (örn. call_rcu'ya geçirilen payload destructor) adresidir. Bu symbol kernel image'ında sabit bir file offset'te durur, dolayısıyla leaked_func - static_offset(symbol) randomize edilmiş base'i verir. Statik offset, hedef kernel için eşleşen vmlinux / System.map'ten bir kez okunur. Burada hiçbir şey heap adreslerine bağlı değildir, bu yüzden leak boot'lar arasında güvenilirdir.
Detection¶
Bu, kendi başına bir bug class değil, ayrı bir memory-safety bug'ı (UAF/OOB read) üzerine kurulu bir exploitation primitive'dir — detection, altta yatan kusura aittir. KASAN, leak'i besleyen out-of-bounds / use-after-free read'i flag'ler. RCU makinesinin kendisi normal davranır; suistimal tamamen, grace period boyunca meşru şekilde yazılmış callback state'inin bir read'idir.
Mitigation¶
Leak, free edilmiş/komşu bir rcu_head'e ulaşan kullanılabilir bir read primitive'e dayanır, dolayısıyla UAF read'leri köreltici aynı hardening burada da geçerlidir: cache separation (CONFIG_RANDOM_KMALLOC_CACHES) A ve B'yi yan yana getirmeyi zorlaştırır, zero-on-free / freelist hardening ise stale callback state'inin okunabilir olduğu window'u kısaltır. Altta yatan OOB/UAF read'i ortadan kaldırmak oracle'ı tümüyle yok eder. Tek başına KASLR yardımcı olmaz — bu teknik tam da onu yener. İlgili leak vektörleri için bkz. kernel-base-leak-via-ops-pointer ve kptr-restrict-pk-pointer-leak-bypass.