Skip to content

waitid LPE / container escape (CVE-2017-5123)

4.13'teki bir waitid() refactor'u sonuç siginfo'sunu userland'e unsafe_put_user ile yazıyordu ama ondan önce gelen access_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):

  1. Write'ı bir leak'e çevir. unsafe_put_user, unmapped/geçersiz bir adres için -EFAULT döner ama oops etmez. waitid()'i probe adresleriyle çağırıp başarı vs. EFAULT gözlemlemek, hangi kernel page'lerinin map'li olduğunu açığa çıkarır — info-leak bug'ı gerektirmeyen bir KASLR oracle.
  2. İç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 bir siginfo'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.
  3. 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).
  4. 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 kapsanmayan unsafe_put_user / unsafe_get_user / user_access_begin kullanımlarını grep'le (fix'in geri eklediği tam pattern budur). Yeni user_access_begin caller'ları yüksek değerli bir review hedefidir.
  • Runtime: leak primitive'i, dağınık adreslere karşı -EFAULT dönen alışılmadık derecede yüksek oranda waitid() ç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 bir setuid path 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() kontrollerinin waitid() 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.

References