wchan absolute kernel address leak via /proc/PID/stat¶
2015 hardening'inden önce
/proc/PID/stat'takiwchanfield'ı, 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ısalwchanfield'ı) ham pointer'ı yazdırıyordu./proc/PID/wchansymbol lookup başarısız olduğunda (CONFIG_KALLSYMSyokken 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):
(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/*/statfield 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'takiwchankoşulsuz olarak0'dır. - Kardeş pointer-leak kanallarını kapatmak için
kernel.kptr_restrict=2ayarlayın vekernel.perf_event_paranoiddeğerini yüksek tutun. /proc'uhidepid=2ile mount edin; böylece process'ler başkalarınınstatdosyalarını hiç okuyamaz.