Skip to content

/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()'a unsigned long *args tipinde bir hedef verilir. 32-bit bir kernel'de sizeof(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:

# 32-bit ARM/i386 target
cat /proc/self/syscall

Çı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ın syscall node'ları herkesçe okunamaz olur.
  • Bug yalnızca 32-bit'tir; sizeof(unsigned long) == sizeof(__u64) olan 64-bit (LP64) kernel'ler etkilenmez.

References