/proc/pid/syscall info disclosure (CVE-2020-28588)¶
proc_pid_syscall()içindeki 32-bit bir type-conversion bug'ı,/proc/<pid>/syscallüzerinden ~24 byte initialize edilmemiş kernel stack sızdırır; bir KASLR bypass için kullanılabilir.
Mechanism¶
Leak neden olur: bir long/u64 genişlik uyuşmazlığı
/proc/<pid>/syscall, (durdurulmuş ya da çalışan) bir task için syscall
numarasını ve argümanlarını raporlamak içindir. fs/proc/base.c'deki
handler sabit bir array doldurur ve sonra onu %llx tabanlı bir format
string ile biçimlendirir.
İhlal edilen invariant, argüman array'inin producer'ı ile consumer'ı arasındaki bir element genişliği üzerinde anlaşmadır:
- Architecture helper'ı
syscall_get_arguments()'aunsigned long *argstipinde bir hedef verilir. 32-bit bir kernel'desizeof(unsigned long) == 4, dolayısıyla helper 6 × 4 = 24 byte yazar. - İçine yazdığı buffer
__u64 args[6]olarak bildirilmiştir — yani 48 byte — ve format string onu altı%llx(8-byte) alanı olarak tüketir.
Producer alt yarıyı (24 byte) doldurur; üst 24 byte initialize
edilmemiş kalır. Buffer kernel stack'inde durduğu için, o 24 byte
önceki stack frame'in geride bıraktığı her neyse odur — sıklıkla
saklanmış kernel pointer'ları ve return address'ler. %llx
formatlayıcı 48 byte'ın tümünü titizlikle basar ve bayat yarıyı
unprivileged bir okuyucuya dışa aktarır.
Bu klasik bir CWE-681 (numeric tipler arası yanlış dönüşüm)'dir:
kod unsigned long ve __u64'ün birbirinin yerine geçebileceğini
varsaydı; bu yalnızca 64-bit (LP64) hedeflerde geçerlidir. 32-bit
ARM/i386 (ILP32)'de varsayım çöker ve kısmi doldurma bir stack
infoleak'e dönüşür. KASLR yenilir çünkü sızdırılan bayat pointer'lar
absolute kernel .text/stack adresleridir.
Walkthrough¶
Bug v5.1-rc4'te (commit
631b7abacd02b88f4b0795c08b54ad4fc3e7c7c0) tanıtıldı ve v5.10-rc4'e kadar
uzanıyor. Yalnızca 32-bit build'lerde ortaya çıkar (orijinal rapor 32-bit
bir ARM cihaz, 5.4.66-mt3620 kernel'li Azure Sphere kullandı).
1. Dosyayı doğrudan oku¶
Vulnerable bir 32-bit kernel'de dosyayı okumak, bayat kuyruk dahil argüman array'ini dump eder:
Çıktının beklenen şekli — bir syscall numarasının ardından altı 64-bit arg alanı, bir stack pointer ve bir program counter:
3 0x3 0x... 0x... 0xc0201900 0xc0009d84 ... 0xbef... 0xc0...
^---- args[0..5]; args[3..5] (the upper 24 bytes) are entirely stale ----^
0xc0xxxxxx değerleri 32-bit ARM kernel .text/stack penceresine
düşer — tam olarak KASLR'yi geri almak için gereken absolute adresler.
2. Pointer toplamak için tekrar tekrar örnekle¶
Bayat byte'lar o stack bölgesini en son ne kullandıysa ondan geldiği için, stack'i bozarken okumayı zorlamak farklı sızdırılmış değerleri yüzeye çıkarır:
# Optional: pin userland layout so only kernel noise varies
echo 0 > /proc/sys/kernel/randomize_va_space
# Re-read while churning the kernel stack with cheap syscalls
while true; do cat /proc/self/syscall; done | uniq
while true; do free &>/dev/null; done # in another shell, stir the stack
Iterasyonlar boyunca farklı kernel adresleri belirir; bunları toplayıp kernel base hizalamasına maskelemek KASLR slide'ını verir.
3. Sonuç¶
Kernel stack içeriğini (kernel-text ve stack pointer'ları) açığa çıkaran, tamamen yerel, unprivileged bir read primitive'i. Bu bir write değil, bir information disclosure'dır — tipik olarak bir memory-corruption primitive'i ateşlenmeden önce KASLR'yi yenmek üzere daha büyük bir exploit'in leak aşaması olarak zincirlenir. Daha geniş deseni information disclosure / memory disclosure içinde ve ilgili leak-to-kernel-pointer tekniğini stack infoleak to kernel pointer leak içinde gör.
Detection¶
- Kararlı bir 32-bit dağıtımda process'lerin
/proc/<pid>/syscall'ı tekrar tekrar poll etmesi için meşru bir neden yoktur; o node üzerindeki sıkı read döngüleri anormaldir. - Sızdırılan üst yarılar vulnerable bir kernel'de sıfır değildir, yamalı olanda ise sıfırdır; bu başlı başına ucuz bir on-host vulnerability check'idir.
Mitigation¶
- Argüman buffer/copy genişliklerini uyumlu hâle getiren upstream düzeltmesini uygula, böylece initialize edilmemiş byte yayılmaz.
- procfs'e erişimi sıkılaştır (
hidepid=2), böylece diğer user'larınsyscallnode'ları herkesçe okunamaz olur. - Bug yalnızca 32-bit'tir;
sizeof(unsigned long) == sizeof(__u64)olan 64-bit (LP64) kernel'ler etkilenmez.