Double-fetch / user-kernel TOCTOU¶
The kernel reads the same userspace address twice — once to validate a size, once to use it — and a racing user thread changes the value in between, turning a passed check into an overflow.
Mechanism¶
Note
Bir double-fetch, kernel tarafında bir time-of-check-to-time-of-use race'tir. Kernel
kodu bir userspace adresinden bir değeri (bir length/size/count) get_user /
copy_from_user ile okur, doğrular, sonra aynı adresi ikinci kez okur ve o değeri
kullanır — değişmediğini varsayarak. İki fetch arasında değeri mutate eden, yarışan bir
user thread, doğrulanmış invariant'ı ihlal eder. Bochspwn'un tanımı: kernel kodunun
user-mode belleği birden çok kez okuduğu ve erişimler arasında değerlerin sabit
kaldığını varsaydığı race condition'lar.
Klasik escalation: fetch #1 count'u okur, bir bounds kontrolünü geçer ve bir kmalloc'u
boyutlandırır; fetch #2 artık daha büyük olan count'u tekrar okur ve o kadar byte kopyalar
-> kontrollü data ile heap overflow. Attacker struct'ı paylaşılan/mmap'lenmiş belleğe
yerleştirir ve değeri "küçük/geçerli" ile "büyük/kötü" arasında flip'leyen ikinci bir thread
çalıştırır; page üzerinde register edilen userfaultfd, ikinci fetch'i fault'latır ve
kernel'i deterministik olarak stall'a sokar; böylece o devam etmeden önce değer flip'lenebilir.
Walkthrough¶
Zafiyetli handler kalıbı:
long vuln_ioctl(struct foo __user *arg) {
u16 len;
get_user(len, &arg->len); /* FETCH #1: get size */
if (len > MAX) return -EINVAL; /* validate fetch #1 */
buf = kmalloc(len, GFP_KERNEL); /* size from fetch #1 */
get_user(len, &arg->len); /* FETCH #2: re-read! */
copy_from_user(buf, arg->data, len); /* uses fetch #2 size */
/* attacker enlarged len between fetches -> heap overflow */
}
Somut gerçek bir bug, ioctl_file_dedupe_range() (fs/ioctl.c) içindeki CVE-2016-6516'dır;
FIDEDUPERANGE ioctl'i üzerinden erişilir: dest_count alanı iki kez fetch edilir, ilk
fetch allocation'ı boyutlandırır ve ikincisi kopyayı sürer; bu da küçük boyutlu bir
allocation ve heap overflow verir. Yayımlanan exploit'in flipping thread'i:
void *flipping_thread(void *addr) {
struct file_dedupe_range *range = addr;
while (!finish) {
flipping_ready = 1;
while (!trigger_ready) {}
usleep(interval);
range->dest_count = evil_value; /* flip count between fetches */
interval++;
}
}
/* #define FIDEDUPERANGE _IOWR(0x94, 54, struct file_dedupe_range)
ret = ioctl(fd1, FIDEDUPERANGE, range); build: gcc exploit.c -lpthread */
userfaultfd, timing-tabanlı usleep/flip döngüsünün yerini alarak kernel'i ikinci
fetch'te deterministik biçimde stall'a sokabilir.
Detection¶
Static pattern matcher'lar bunları bulur: Wang ve ark. Linux kernel'indeki double-fetch durumlarını işaretlemek için Coccinelle kullandı; Bochspwn (Jurczyk/Coldwind) ve Bochspwn Reloaded tekrarlanan user-memory okumalarını yakalamak için tüm sistemi taklit eden bir emülatörü enstrümante eder; DECAF (Schwarz ve ark.) double fetch'leri runtime'da tespit etmek için Intel TSX kullanır.
Mitigation¶
User değerini bir kez bir kernel local'e oku, o local'i doğrula ve yalnızca local'i
kullan — ya da tüm struct'ı bir kez copy_from_user() edip kernel kopyası üzerinde çalış.
Bu single-read prensibi, CVE-2016-6516 upstream fix'inin de biçimidir. Aynı Schwarz ve ark.
makalesi (aşağıdaki DECAF / DropIt referansı; "Automated Detection, Exploitation, and
Elimination of Double-Fetch Bugs using Modern CPU Features") ayrıca DropIt adlı bir
runtime savunma önerir: hardware transactional memory (Intel TSX, XBEGIN/XEND) kullanarak
iki fetch arasındaki concurrent modification'ı algılar ve son tutarlı state'e geri döner;
böylece double-fetch'i exploit edilemez sıradan bir double-fetch'e indirir.