Skip to content

Unbalanced set_fs oops addr_limit leak (CVE-2010-4258)

addr_limit hâlâ KERNEL_DS iken oluşan bir kernel oops'u, do_exit'in clear_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.

  1. /proc/kallsyms'ten ya da sembol DB'sinden commit_creds / prepare_kernel_cred'i (ve bir hedef function-pointer slot'unu, örneğin bir ops table) çöz.

  2. 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
}
  1. 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);
  1. trigger() içinde, o yolda set_fs(KERNEL_DS) aktifken sendmsg/splice aracılığıyla Econet NULL-deref'ini provoke et. Bug oops'lar; thread KERNEL_DS hâlâ set'liyken do_exit()'e girer.

  2. mm_release(), put_user(0, clear_child_tid)'i çalıştırır → target_kernel_addr'a 0 yazar. NULL'lanan ops-table entry'si artık 0 adresine işaret eder; oraya getroot()'a giden trampoline'i mmap ederiz.

  3. Clobber edilmiş pointer üzerinden çağrı yapan kernel yoluna tekrar gir → getroot()'u ring 0'da çalıştırır → root shell.

Expected effect
$ id
uid=1000(user) gid=1000(user) ...
$ ./full-nelson
[*] Resolving kernel symbols...
[*] Triggering oops under KERNEL_DS...
[*] Got root!
# id
uid=0(root) gid=0(root)

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_limit restore 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_DS yüzeyini küçültür.

References