Skip to content

wchan absolute kernel address leak via /proc/PID/stat

2015 hardening'inden önce /proc/PID/stat'taki wchan field'ı, bir task'ın uyuduğu ham kernel adresini olduğu gibi yazdırıyordu; bu da KASLR slide'ını herhangi bir unprivileged user'a apaçık sızdırıyordu.

Mechanism

Bir uyku adresi KASLR'ı neden boşa düşürür

Bir task block olduğunda scheduler, içinde uyuduğu kernel text adresini kaydeder. /proc/PID/stat pseudo-file'ı tarihsel olarak bunu wchan field'ı olarak seq_put_decimal_ull(m, ' ', wchan) çağrısıyla expose ediyordu — yani absolute adresi hiçbir access check olmadan yazıyordu. Kernel text tek bir KASLR slide ile randomize edildiği için, bilinen tek bir uyku adresi (örneğin iyi bilinen bir syscall wait'inde park etmiş bir task) slide = leaked_addr - known_static_addr değerini açığa çıkarır. O tek çıkarma işlemi tüm kernel image'ını de-randomize eder ve sonraki her exploit-time symbol'ünü bilinen bir adrese çevirir.

İki adet exposure vector vardı:

  • /proc/PID/stat (sayısal wchan field'ı) ham pointer'ı yazdırıyordu.
  • /proc/PID/wchan symbol lookup başarısız olduğunda (CONFIG_KALLSYMS yokken veya bir lookup error'unda) absolute adresi yazdırmaya geri düşüyordu — yine hiçbir check olmadan.

2015 fix'i ("fs/proc, core/debug: Don't expose absolute kernel addresses via wchan") /proc/PID/stat'ın wchan için her zaman 0 yazmasını sağlar ve /proc/PID/wchan'ın yalnızca symbolic function adını (ya da 0) döndürmesini, asla ham bir adres döndürmemesini sağlar. Modern proc(5), wchan'ı field 35 olarak belgeler; bir ptrace access-mode check'ine tabidir. İzin reddedildiğinde ya da değer bir kernel pointer olacağında 0 olarak okunur. Bu, şu politikayla aynı aileden gelir kptr-restrict-pk-pointer-leak-bypass: %pK / kptr_restrict, unprivileged reader'lar için pointer'ları sıfırlar.

Walkthrough

Modern, patch'lenmiş bir kernel'de leak kapatılmıştır — wchan 0 okunur:

$ cat /proc/self/stat
1234 (cat) R 1100 1234 1100 ... 0 0 17 0 ...
                                ^ field 35 (wchan) = 0 on patched kernels

Özellikle field 35'i çıkarın (dikkat: comm field'ı boşluk/parantez içerebilir, bu yüzden kapanış ) karakterinden itibaren parse edin):

$ awk '{ s=$0; sub(/^[^)]*\) /,"",s); n=split(s,a," "); print a[33] }' /proc/self/stat
0

(wchan toplamda field 35'tir; pid (comm) kısmı sıyrıldıktan sonra geriye kalan 33. token olur.)

Eski / patch'siz bir kernel'de (sorunu barındıran pre-4.x), aynı okuma 0 yerine ham bir kernel pointer döndürüyordu:

# vulnerable kernel, illustrative output
$ cat /proc/$$/stat | awk '{...}'
18446744071579792256          # 0xffffffff8123b380 -> a kernel text address

Sleep site'ının randomize edilmemiş adresini (eşleşen System.map / vendor kernel'inden) bildikten sonra bunu KASLR slide'ına çevirmek:

unsigned long leaked = read_wchan_field(getpid());   /* e.g. 0xffffffff8123b380 (runtime leak) */
unsigned long known  = 0xffffffff8103b380UL;          /* same symbol from System.map, no KASLR   */
unsigned long slide  = leaked - known;                /* KASLR base offset, e.g. 0x00200000      */

Kurtardığınız slide'ı, absolute kernel adreslerine ihtiyaç duyan herhangi bir tekniğe zincirleyin, örneğin kernel-function-pointer-overwrite veya ROP. proc-kallsyms-symbol-address-leak gibi diğer text-leak oracle'larıyla karşılaştırın.

Symbolic wchan neden güvenli ama numeric neden değildi

procps araçlarının (ps -o wchan) ihtiyacı yalnızca block eden function'ın adıydı ve bunu /proc/PID/wchan'dan okuyorlardı. stat field'ındaki sayısal adres herhangi bir userland amacına hizmet etmiyordu, dolayısıyla onu sıfırlamak leak'i kaldırırken hiçbir şeyi bozmadı.

Detection

  • Eski kernel'ler için audit yapın: /proc/self/stat'ın field 35'ini okuyun; KASLR kernel'inde sıfır olmayan bir değer eski (legacy) davranışa işaret eder.
  • /proc/*/stat field 35'i parse edip hemen pointer arithmetic yapan unprivileged process'ler, KASLR-leak tooling'i için makul bir hunt hedefidir.

Mitigation

  • 2015 fix'ini içeren bir kernel çalıştırın (bu fix çoktandır tüm bakımı yapılan ağaçlarda mevcut): /proc/PID/stat'taki wchan koşulsuz olarak 0'dır.
  • Kardeş pointer-leak kanallarını kapatmak için kernel.kptr_restrict=2 ayarlayın ve kernel.perf_event_paranoid değerini yüksek tutun.
  • /proc'u hidepid=2 ile mount edin; böylece process'ler başkalarının stat dosyalarını hiç okuyamaz.

References