Unbalanced set_fs oops addr_limit leak (CVE-2010-4258)¶
addr_limithâlâKERNEL_DSiken oluşan bir kernel oops'u,do_exit'inclear_child_tid'inin keyfi bir kernel adresine sıfır bir NULL yazmasına izin verir ve bir NULL-deref DoS'unu root'a çevirir.
Mechanism¶
set_fs(), current_thread_info()->addr_limit'i yazarak current task için user/kernel adres ayrımını override eder. set_fs(KERNEL_DS) altında access_ok() her zaman true döner, dolayısıyla alışılmış user-pointer copy helper'ları (put_user, copy_to_user) kernel adreslerine seve seve dokunur. set_fs(KERNEL_DS) çağıran kernel kodunun, userspace'e dönmeden önce bunu set_fs(old_fs) ile dengelemesi beklenir.
Note
Invariant dengedir: her set_fs(KERNEL_DS) geri alınmalıdır. CVE-2010-4258 bunu bozar çünkü do_exit() (bir thread oops'ladığında çalışır) aynı task context'inde — aktif addr_limit override'ı dahil çalışır. KERNEL_DS set'liyken kurtarılabilir bir bug (bir BUG/NULL-deref/page fault) provoke edilirse, task do_exit() üzerinden access_ok fiilen devre dışı bırakılmış halde ölür. Teardown sırasında mm_release(), put_user(0, tsk->clear_child_tid) yaparak CLONE_CHILD_CLEARTID'i onurlandırır. access_ok etkisizleştirilmişken, o write herhangi bir adrese düşer — kernel uzayında keyfi bir *ptr = 0 (NULL-write) primitive'i.
Fix (commit e0e817392b9a, "Fix pktcdvd ioctl dev_minor range check" dönemi temizliği artı do_exit guard'ı if (unlikely(in_atomic()))... ve özellikle syscall dönüşündeki addr_limit kontrolü), addr_limit'i USER_DS'e geri zorlar ve USER_DS'te olmadığında clear-child-tid write'ını reddeder.
Walkthrough¶
Dan Rosenberg'in public "Full-Nelson" exploit'i üç Nelson-Elhage bug'unu zincirler; CVE-2010-4258 arbitrary-write'ı sağlar, CVE-2010-3849/3850 (Econet) KERNEL_DS altındaki oops'u sağlar.
-
/proc/kallsyms'ten ya da sembol DB'sindencommit_creds/prepare_kernel_cred'i (ve bir hedef function-pointer slot'unu, örneğin bir ops table) çöz. -
Privesc primitive'ini çağıran bir userspace payload page'i kur:
void __attribute__((regparm(3))) getroot(void) {
commit_creds(prepare_kernel_cred(0)); // -> uid 0
}
clear_child_tid'i NULL'lamak istediğimiz kernel ops-table entry'sine işaret eden bir child'ıclone()et:
clone(trigger, child_stack,
CLONE_VM | CLONE_CHILD_CLEARTID | SIGCHLD,
NULL, NULL, NULL, /* ctid = */ target_kernel_addr);
-
trigger()içinde, o yoldaset_fs(KERNEL_DS)aktifkensendmsg/splicearacılığıyla Econet NULL-deref'ini provoke et. Bug oops'lar; threadKERNEL_DShâlâ set'liykendo_exit()'e girer. -
mm_release(),put_user(0, clear_child_tid)'i çalıştırır →target_kernel_addr'a0yazar. NULL'lanan ops-table entry'si artık 0 adresine işaret eder; orayagetroot()'a giden trampoline'immapederiz. -
Clobber edilmiş pointer üzerinden çağrı yapan kernel yoluna tekrar gir →
getroot()'u ring 0'da çalıştırır → root shell.
Expected effect
Detection¶
Bir privilege değişiminin hemen öncesinde gelen kernel oops/BUG mesajlarını ve syscall dönüşünde addr_limit/USER_DS uyuşmazlık uyarılarını (modern kernel'ler WARN eder) ara. CLONE_CHILD_CLEARTID ve adres alanı dışındaki bir ctid pointer'ı ile tekrar tekrar clone yapan bir process anormaldir.
Mitigation¶
- 2.6.36.2 / vendor backport'larında düzeltildi:
addr_limitrestore edilir ve clear-child-tid write'ıUSER_DS'e bağlanır. - Yapısal fix
set_fs()'i tamamen kaldırmaktır — mainline bunu 5.x boyunca yaptı (CONFIG_SET_FS=n), addr_limit override'ını ve bu tüm bug sınıfını ortadan kaldırdı. - Defense in depth: KASLR +
kptr_restrict(leak maliyetini artırır) ve egzotik protokolleri (Econet) disable etmek oops-under-KERNEL_DSyüzeyini küçültür.