waitid LPE / container escape (CVE-2017-5123)¶
4.13'teki bir
waitid()refactor'u sonuçsiginfo'sunu userland'eunsafe_put_userile yazıyordu ama ondan önce gelenaccess_ok()kontrolünü düşürmüştü; bu da unprivileged bir caller'a arbitrary bir kernel adresine constrained bir write veriyordu — SMEP/SMAP altında ve Chrome sandbox'ı içinde bile KASLR defeat ve privilege escalation için kullanılabilir.
Mechanism¶
Note
Bir usercopy için user/kernel sınırı access_ok() tarafından zorlanır; bu da
hedef pointer'ın address-space limitinin altında olduğunu doğrular.
unsafe_put_user() / user_access_begin() ailesi bir fast path'tir: bir
access_ok-doğrulanmış pencere açar (ve x86'da STAC ile SMAP'i kapatır), ama
bunu caller'ın pointer'ı zaten valide ettiği açık sözleşmesi üzerine yapar.
4.13 waitid() yeniden yazımında bu sözleşme bozuldu — syscall, user infop
yapısını unsafe helper'lar üzerinden, hiç access_ok() çağırmadan dolduruyordu.
Pointer'ı yeniden kontrol eden hiçbir şey olmadığı için caller'ın sağladığı bir
kernel adresi kabul ediliyor ve oraya yazılıyor. Aşılan invariant şu:
"unsafe usercopy ⇒ pointer önceden valide edilmişti" artık geçerli değildi.
Bu write constrained ama exploit edilebilir: kernel, sabit şekilli bir siginfo'yu
(signal number, errno, code, pid, uid, status) hedefe kopyalar; yani attacker tam
olarak arbitrary içerik elde etmez ama kısmen attacker etkisindeki 32-bit word'lerin
controlled-address bir write'ını elde eder.
Walkthrough¶
Public Chris Salls write-up'ından high-level reproduction (yalnızca kavramsal):
- Write'ı bir leak'e çevir.
unsafe_put_user, unmapped/geçersiz bir adres için-EFAULTdöner ama oops etmez.waitid()'i probe adresleriyle çağırıp başarı vs.EFAULTgözlemlemek, hangi kernel page'lerinin map'li olduğunu açığa çıkarır — info-leak bug'ı gerektirmeyen bir KASLR oracle. - İçeriği şekillendir. Yazılan değerler, attacker'ın kontrol ettiği bir child'ın
(yani
fork()ve child'ın exit/stop state'i üzerinden) ürettiği birsiginfo'dan gelir; dolayısıyla status/code word'leri gibi alanlar, birkaç byte'lık parçalar halinde işe yarar sıfırdan-farklı write'lar üretecek şekilde yönlendirilebilir. - Bir kernel object'i hedefle. Base bilindikten sonra, constrained write'ı mevcut task'tan erişilebilen seçilmiş bir kernel yapısına nişanla (public write-up, seccomp'u etkisizleştirmek ve sonra daha tam bir R/W primitive kurmak için task state üzerinden pivot eder).
- Escalate et. Daha güçlü bir arbitrary read/write bootstrap edildiğinde standart final uygulanır: task'ın root olarak çalışması için process credential'larını bul ve overwrite et (bkz. cred-struct-overwrite / commit-creds), ardından bir chroot/container sandbox'ından çıkmak için filesystem/namespace state'ini onar.
Warning
Bu bug orantısız biçimde önemli çünkü waitid, yaygın seccomp/sandbox
policy'lerinin allowlist'inde (Chrome'unki dahil) yer alır. Bir sandbox'ın
içinden erişilebilen constrained bir kernel write, renderer seviyesindeki bir
compromise'i tam bir host/container escape'e çevirir; bu CVE'nin kanonik bir
"allowlist'teki syscall" vaka çalışması olmasının nedeni de budur.
Why the unsafe helper is the dangerous part
put_user() kendi access_ok()'unu yapar; unsafe_put_user() ise hız için, zaten
açılmış bir user_access_begin()/end() bölgesi içinde bunu bilerek atlar. Bu bug
sınıfı için audit yapmak, çevreleyen bölgesi yazılan her pointer üzerinde
dominate eden bir access_ok()'a sahip olmayan her unsafe_*_user kullanımını
işaretlemek demektir.
Detection¶
- Static / source audit: kernel ve out-of-tree driver'larda, guard'lanan
pointer'ları bir
access_ok()ile kanıtlanabilir şekilde kapsanmayanunsafe_put_user/unsafe_get_user/user_access_beginkullanımlarını grep'le (fix'in geri eklediği tam pattern budur). Yeniuser_access_begincaller'ları yüksek değerli bir review hedefidir. - Runtime: leak primitive'i, dağınık adreslere karşı
-EFAULTdönen alışılmadık derecede yüksek orandawaitid()çağrısı üretir — normalde child reaping için kullanılan bir syscall için anormal. Process başına syscall-return telemetry'si (patch'li bir kernel üzerinde eBPF, ya da auditd) probing loop'unu işaretler. - Post-exploitation: privilege adımı her cred-overwrite LPE'ye benzer —
uid/euid/ capability'leri meşru birsetuidpath olmadan 0'a dönen bir process. Credential geçişini tespit etmek (örn.task->cred'in integrity monitoring'i ile) bug'ın kendisini tespit etmekten daha güvenilirdir.
Mitigation¶
- Patch: upstream'de, eksik
access_ok()kontrollerininwaitid()userland write'larına geri eklenmesiyle düzeltildi (Kees Cook, 4.13-stable'a merge edildi). Patch'li stable release'lerde veya sonrasında olan herhangi bir kernel etkilenmez. - SMAP seni burada kurtarmaz: vulnerable path, SMAP'i kasıtlı olarak temizleyen
user_access_begin()içinde çalışır, dolayısıyla mimari savunma yapısı gereği bypass edilir — bu da SMAP'in kazara user access'e karşı koruduğunu, eksik bir software check'e karşı değil, vurgular. - Sandbox policy: olay, seccomp allowlist'lerini minimal tutmayı ve allowlist'teki herhangi bir syscall'ı kernel attack surface olarak görmeyi motive eder; defence-in-depth (Landlock/seccomp-notify gibi ayrı kernel attack-surface azaltımı, unprivileged userns kısıtlamaları) bir sonraki böyle bir bug'ın blast radius'unu sınırlar.