kptr_restrict / %pK pointer leak bypass¶
%pKformat specifier'ının credential- ve context-duyarlı logic'ini suistimal et, böylecekptr_restrict=1altında yazdırılan bir kernel pointer sana sıfırlanmak yerine açık olarak gösterilsin.
Mechanism¶
Kernel pointer'ları vsnprintf üzerinden yazdırır. Leak'ler için iki specifier önemli: 4.15'ten beri hashed (her boot'ta randomize edilmiş) bir pointer yazdıran %p ve gerçek adresi yalnızca uygun şekilde yetkili reader'lara göstermesi gereken %pK ("restricted pointer"). %pK için kapı, lib/vsprintf.c:restricted_pointer()'da değerlendirilen kptr_restrict sysctl'idir.
Note
Kernel'in zorlamaya çalıştığı invariant şudur: "gerçek bir kernel pointer userspace'e yalnızca reader CAP_SYSLOG tutuyorsa ve başlatıldığı aynı uid/gid ile çalışıyorsa ulaşır." Klasik bypass, bu check'in open time'da değil read/print time'da ve yalnızca kptr_restrict=1'de yapıldığı gerçeğini suistimal eder. İlgili logic:
switch (kptr_restrict) {
case 0:
/* Handle as %p, hash and do _not_ leak addresses. */
return default_pointer(buf, end, ptr, spec);
case 1: {
const struct cred *cred;
/* kptr_restrict==1 cannot be used in IRQ context ... */
if (in_hardirq() || in_serving_softirq() || in_nmi()) {
...
return error_string(buf, end, "pK-error", spec);
}
cred = current_cred();
if (!has_capability_noaudit(current, CAP_SYSLOG) ||
!uid_eq(cred->euid, cred->uid) ||
!gid_eq(cred->egid, cred->gid))
ptr = NULL;
break;
}
case 2:
default:
/* Always print 0's for %pK */
ptr = NULL;
break;
}
Yani bypass aslında bir code defect'i değil, bir configuration/operasyonel hatadır:
kptr_restrict=0(kernel default'u),%pK'yı hiç restrict etmez —%phashing'e düşer ve pointer hashing'den önce gelen herhangi bir%pKsite'ı ya da herhangi bir düz%lx/%pxprint'i gerçek bir adresi leak eder.kptr_restrict=1, pointer'ı yalnızca readerCAP_SYSLOG'tan yoksunsa ya da uyumsuz real/effective id'lere sahipse sıfırlar. Privileged, non-setuid bir reader yine de gerçek değeri alır. Tree içindeki yorum, check'in neden read time'da olduğunu açıkça belirtir: bir binary bir%pKdosyasınıopen()edip ardındanread()'ten önce bir setuid wrapper'a düşebilir, dolayısıyla kernel credential'ları print time'da yeniden kontrol eder.kptr_restrict=2,%pK'yı koşulsuz sıfırlayan tek değerdir.
Önemli olarak, %pK birçok specifier arasında biridir. Birçok leak sink'i (/proc/kallsyms, dmesg, /sys/kernel/..., slab debug dosyaları, /proc/*/stack) başka toggle'lar (dmesg_restrict, perf_event_paranoid, /proc/sys/kernel/kallsyms perm'leri) tarafından yönetilir — dolayısıyla "%pK altında adres 0000000000000000" demek, adresin başka bir path'ten ulaşılamaz olduğu anlamına gelmez.
Walkthrough¶
Mevcut policy'yi incele ve değerler arasındaki farkı göster.
/proc/kallsyms gibi bir %pK sink'i (symbol adreslerini %pK ile yazdırır), kptr_restrict=1 altında unprivileged bir reader için adresleri sıfırlar:
$ head -n 3 /proc/kallsyms
0000000000000000 T startup_64
0000000000000000 T secondary_startup_64
0000000000000000 T __pfx_secondary_startup_64
Aynı dosya, eşleşen real/effective id'lerle CAP_SYSLOG tutan bir process tarafından okunduğunda gerçek adresleri döndürür, çünkü has_capability_noaudit(current, CAP_SYSLOG) geçer ve uid_eq(euid,uid) sağlanır:
Warning
CAP_SYSLOG tutmak ama ona bir setuid program yoluyla düşmüş olmak leak etmez: !uid_eq(cred->euid, cred->uid) clause'u effective ve real id'ler farklı olduğunda tetiklenir, dolayısıyla pointer NULL'a zorlanır. Bu "open-then-elevate" savunmasıdır ve aynı zamanda dikkatsiz bir kptr_restrict=1 deployment'ının basitçe CAP_SYSLOG'a doğrudan sahip olan (örn. birçok container/service account) yerel bir process'e karşı gerçek bir koruma olmamasının nedenidir.
En yaygın pratik "bypass", makinenin upstream default'u 0'da bırakılmasıdır:
$ cat /proc/sys/kernel/kptr_restrict
0
$ grep ' commit_creds$' /proc/kallsyms
ffffffff810c8e40 T commit_creds
Burada %pK path'i %p hashing'e devreder, ama /proc/kallsyms da kendi permission check'ine tabidir; eğer kptr_restrict=0 ise ve dosya okunabilirse, gerçek adresler doğrudan çıkar. Bu tek leak edilen symbol KASLR'ı yener: kernel base'i kurtarmak için leak edilen değerden commit_creds'in bilinen offset'ini çıkar (proc-kallsyms-symbol-address-leak'a bak).
Yalnızca leak'in (data'nın değil) ayrıcalığını yükseltmek için şunu ayarla:
Warning
%pK yalnızca %pK kullanan site'ları kapsar. Pointer leak'leri sıklıkla hâlâ %lx/%px ile yazdıran code'dan, dmesg'den (dmesg_restrict ile kapılı, dmesg-kernel-log-buffer-pointer-leak'a bak) ya da uninitialized-memory infoleak'lerinden gelir. kptr_restrict=2 ayarlamak %pK kanalını kapatır ama tek başına genel bir anti-KASLR önlemi değildir.
Detection¶
- Hardening baseline'larında
kptr_restrictvedmesg_restrict'in her ikisinin de sıfır olmadığını denetle;kptr_restrict=0upstream default'tur ve işaretlenmelidir. /proc/kallsyms,/proc/<pid>/stackve slab debug dosyalarını okuyan unprivileged process'leri izle; bir non-CAP_SYSLOGtask'a tüm-sıfır olmayan bir değer döndürülmesi bir misconfiguration ya da non-%pKbir leak path'ine işaret eder.restricted_pointer()path'i,%pKhard IRQ / softirq / NMI context'inde (yanlışlıkla) kullanıldığındapK-erroryayar — loglarda görünmesi buggy bir print site'ını işaretler.
Mitigation¶
kernel.kptr_restrict=2vekernel.dmesg_restrict=1ayarla;%pKaçan dosyalardan world-read'i kaldır.CAP_SYSLOG'u hassas bir capability olarak ele al: bunu bir service account'a vermek,kptr_restrict=1altında gerçek-pointer okumalarını fiilen yeniden etkinleştirir.- Yeni code için
%pKyerine%p'nin per-boot pointer hashing'ine (4.15'ten beri) güven ve ulaşılabilir sink'lerde adresleri asla%px/%lxile yazdırma. KASLR ile birleştir, böylece tek bir leak edilen adres tüm image'i tetikçe map'lemesin.