CVE-2025-32463 ile ilgili yazımı şu cümleyle tamamladım; geriden bakıldığında bu cümle, başkasına verdiğim bir araştırma planı gibi duruyor:
İzinli kod, saldırganın kontrolündeki bir namespace’e girdiğinde; sadece yamada adı geçen dosya değil, her türlü açıklayıcı yorumlayıcıyı (interpreter), yükleyiciyi (loader), çözücüye (resolver) ve yapılandırma arama mekanizmasını da envanterize edin.
Sonra bunu bir sonuç olarak yayınladım ve konuyu bıraktım. Bu cümle, yerel katalogları, dinamik yükleyici yapılandırmasını, PAM yığınlarını, eklenti keşfini ve sertifika depolarını adlandırıyor. Hiçbirini envanterize etmemiştim. Başkasının bulgusuna ilişkin iyi bir açıklama yazmıştım; ancak uyarıyı dikkatlice okumamıştım.
Bu yüzden aleti inşa ettim. Henüz envanteri değil — envanterin anlamını veren aleti. Bu, iki tasarımın çekirdek (kernel) ile temasından hayatta kalamaması dahil olmak üzere maliyetimdi.
Sinyal mekanik olmalı
İlk soru “hangi araçlar zafiyet içeriyor” değil, “tam olarak neyi ölçüyorum”dur. Cevap bir yargı kararı ise, tarama görüşler üretir.
CVE-2025-32463 temiz bir tanım sunar. sudo -R, izinsiz bir çağrıdan kök dizin seçmesini sağlar; sudo, politika değerlendirmesi tamamlanmadan önce bu dizine girer; izni olan bir NSS sorgulaması çağırıcının /etc/nsswitch.conf dosyasını okur; libc, çağırıcı tarafından sağlanan paylaşılan nesneyi yükler. Genelikleme sudo veya NSS hakkında değildir:
İzinli bir süreç, bir işlemin uygulamasını seçer — sadece veri değil kod yükler — ve bunu çağırıcının seçtiği bir namespace’in içinde yapar.
Bu, makinenin karar verebileceği bir şeye indirgenir: Süreç, çağırıcı tarafından sağlanan bir dosyayı destekleyen bir PROT_EXEC bölgesini haritalıyor mu? Yapılandırma okumaları bağlamdır. Çalıştırılabilir haritalama bulgudur.
Bilinen pozitif ilk gelir
Hiçbir zaman yakalaması gereken bir şeyi yakalamamış bir alet, sessiz kaldığında size hiçbir şey söylemez. Bu nedenle, herhangi bir tarama hedefinden önce, halihazırda bildiğim hatayı tespit eden ve üç kontrol mekanizması üzerinde sessiz kalan bir kasa (harness) olmalıdır; bu kontroller hiçbir şey üretmelidir.
| Kontrol | Derleme | Kök sağlandı mı? | nsswitch.conf | Beklenen |
|---|---|---|---|---|
| primary | 1.9.17 | evet | evet | TASDİK EDİLDİ |
| no-config | 1.9.17 | evet | hayır | TEMİZ |
| no-root | 1.9.17 | hayır | evet | TEMİZ |
| fixed-build | 1.9.17p1 | evet | evet | TEMİZ |
Her iki sudo derlemesi de yukarı akış (upstream) tarball’larından gelir ve atılabilir bir VM’de yan yana derlenmiştir:
build 1.9.17 /opt/sudo-vuln
build 1.9.17p1 /opt/sudo-fixed
Çağrı tarafından sağlanan taraf, bir isim hizmeti seçici ve hareketsiz bir modül içeren sentetik bir köktür. Modül hiçbir NSS giriş noktasını uygulamaz. Uygulamaya gerek yoktur; yüklenmesi sinyaldir ve hiçbir şeyi çözmeyen bir modül konak davranışını değiştiremez.
__attribute__((constructor))
static void prns_marker_loaded(void)
{
FILE *log = fopen("/tmp/prns-marker.log", "a");
if (log == NULL) {
return;
}
fprintf(log, "LOADED pid=%d uid=%u euid=%u\n",
(int)getpid(), (unsigned)getuid(), (unsigned)geteuid());
fclose(log);
}
Bu yapılandırıcı tüm yükü (payload) oluşturur. Şu yazdı:
LOADED pid=47155 uid=1000 euid=0
uid 1000 tarafından çağrılır. euid 0 ile çalıştırılır. İzinsiz bir kullanıcı tarafından yerleştirilen bir dosya root olarak çalışır.
Trace bunu iki satırda anlatıyor
strace katmanı -y ile çalışır; bu, her dosya tanımlayıcısını /proc/PID/fd üzerinden çözdüğü yola not düşer. Çağrı tarafından sağlanan bir köke girmiş bir süreç için, bu not ve ham syscall argümanı artık uyuşmaz — ve bu uyuşmazlık zafiyettir; yazdırılır:
openat(AT_FDCWD</tmp/prns>, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3</etc/nsswitch.conf>
openat(AT_FDCWD</…/synthroot>, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 8</…/synthroot/etc/nsswitch.conf>
openat(AT_FDCWD</…/synthroot>, "/lib/aarch64-linux-gnu/libnss_lab.so.2", …) = 8</…/synthroot/lib/aarch64-linux-gnu/libnss_lab.so.2>
mmap(…, PROT_READ|PROT_EXEC, …, 8</…/synthroot/lib/aarch64-linux-gnu/libnss_lab.so.2>, 0)
Süreç /etc/nsswitch.conf dosyasını iki kez ister. İlk kez gerçek dosyayı alır. İkinci kez çağırıcınınkini alır. İsteğin kendisi değişmez; altındaki zemin değişir. Ardından çağırıcının paylaşılan nesnesi çalıştırılabilir olarak haritalanır.
Bunu çizdiğim herhangi bir diyagramdan daha ikna edici buluyorum ki bu da tam olarak nokta budur.
İkinci katman ilk temasla ayakta kalamadı
Bir katman yeterli değildir. strace, ptrace kullanır; bu zamanlamayı bozar ve — ileride göreceğim gibi — setuid ikili karşıtı tüm testi sessizce yenilgiye uğratabilir. Bağımsız olarak onaylayan veya reddeden bir çekirdek tarafı gözlemci istiyordum.
Açık tasarım, security_mmap_file üzerindeki bir kprobe idi; destekleyen dosyanın yolunu yazdıran:
kprobe:security_mmap_file { … path((struct file *)arg0) … }
ERROR: BPF_FUNC_d_path not available for your kernel version
ERROR: The path function can only be used with 'kfunc', 'kretfunc', 'iter' probes
Tamam — yanlış probe türü. Bunu kfunc olarak yeniden yazdım; bu da tiplemeli argümanlar alır:
kfunc:security_mmap_file { … path(args.file) … }
ERROR: BPF_FUNC_d_path not available for your kernel version
Probe-tipi şikayeti ortadan kalktı ve yardımcı hala reddedildi. Bu noktada hata mesajı aktif olarak yanıltıcıdır; çünkü çekirdek 6.8 sürümündedir ve bpf_d_path 5.9’dan beri mevcuttur. Gerçek kısıtlama daha dar: bpf_d_path() bir BTF allowlist’in arkasında kapalıdır ve security_mmap_file bu listede değildir. Bu listede olan güvenlik kançaları security_file_permission, security_inode_getattr ve security_file_open’dur.
Bu nedenle, çalışmasını bekleyip bana korelasyon sağlayacak izin verilen birini test ettim:
kfunc:security_file_open { … path(args.file) … }
ERROR: BPF_FUNC_d_path not available for your kernel version
Burada da reddedildi. Bu bpftrace derlemesinin özellik algılama ne yaptığını fark etmiş olsa bile, d_path bana bu çiftte hiç yok — ve var olsaydı bile, gerçek probe noktam çekirdek tarafından hariç tutulur. İki bağımsız neden, bir sonuç: bu katman yolları çözmeyecek.
Path bilgisini kaybetmek detector’ı daha iyi yaptı
Çekirdek bana bir isim vermezse yine de bir kimlik verecektir:
kfunc:security_mmap_file
{
$f = args.file;
if ($f == 0) { return; }
if ((args.prot & 4) == 0) { return; }
printf("EXECMAP pid=%d comm=%s dev=%u ino=%lu\n",
pid, comm, $f->f_inode->i_sb->s_dev, $f->f_inode->i_ino);
}
EXECMAP pid=47155 comm=sudo dev=265289729 ino=530735
Kasa, sentetik köke yerleştirdiği her marker kopyasını stat() ile kontrol eder; bu sayede hangi inode’ların saldırgan tarafından sağlandığını tam olarak bilir. Bir eşleşme bir kanıttır — ve yol stringinden daha güçlü bir tür kanıttır. Bir yolu symlink veya bind mount ile başka bir yere yönlendirmek mümkündür. Inode nesnedir.
HARNESS_ERROR rather than a finding.İki katman artık birbirinin yedeği olmaktan çıkmıştır. strace, okunabilir yolları ve chroot uyuşmazlığını sağlar; bpftrace kimlik sağlar. Uyuşmadıklarında, hüküm HARNESS_ERROR olur — asla bir bulgu değil. Kanalları birbirine çelişen bir alet hiçbir şey keşfetmemiştir.
Ölçümün yalan söylediği üç durum
Her biri anlamsız olan temiz bir sonuç üretir; bu bir çöküşten daha kötüdür.
Setuid ikilisi yanlış kullanıcı olarak izlemek. strace’i izinsiz çağrıyı çalıştırın ve çekirdek setuid bitini düşürür. sudo asla root olmaz, zafiyetli yol asla ulaşılmaz ve kasa tam güvenle TEMİZ raporlar. strace, izlenen komut için kendisi yetkileri bırakmak zorundadır ve root olarak kalmalıdır:
strace -f -y -e trace=openat,mmap -u labuser -- /opt/sudo-vuln/bin/sudo -n -R "$ROOT" /bin/true
Çekirdek sınırını aşan cihaz numaralarını karşılaştırma. Probe dev=265289729 raporlar. Python’un aynı dosya için os.stat().st_dev tamamen farklı bir şey rapor eder; çünkü çekirdek dev_t’yi (major << 20) | minor olarak paketler ve glibc yapmaz. Onları ham olarak karşılaştırın ve hiçbir zaman eşleşmez — temiz bir tarama gibi görünen kalıcı olarak sessiz bir algılayıcı. Her iki taraf da birleştirme öncesi (major, minor)’a normalize edilir.
Asla gelmeyecek bir olay için bekleyen bir kapı. Probe-eğilim kontrolüm şuydu:
bpftrace -e 'kprobe:security_mmap_file { printf("ok\n"); exit(); }'
Her zaman manuel çalıştırdığımda çalıştı ve temiz oda yeniden derlemesi sırasında hiçbir şey üretmedi. Asla bozulmadı. security_mmap_file sadece bir süreç dosya haritaladığında ateşlenir ve boş bir VM’de bu oldukça uzun sürebilir — kendi kabuk aktivitem zaten bunu tetikliyordu. Bir zaman aşımı altında zor bir başarısızlık gibi görünür. Düzeltme, ortam aktiviteye bağımlılığı bırakmaktır:
bpftrace -e 'kprobe:security_mmap_file { printf("ok\n"); exit(); }' -c /bin/true
Genel dersim, varlığınızın geçmesi nedeniyle geçen bir kontrolün aslında bir kontrol olmadığı yönündedir.
Araç şimdi ne üretiyor
primary CONFIRMED
no-config CLEAN
no-root CLEAN
fixed-build CLEAN
CALIBRATION: PASS
VM’i yok edip dokümantasyondan yeniden inşa etmek bunu yeniden üretir. Sadece onu oluşturan makinede çalışan bir laboratuvar bir laboratuvar değildir.
Kasa, VM olmadan ev sahibinde çalışabilen 73 birim testine sahip stdlib-sade Python’dur; çünkü izleme ayrıştırma ve kapsama mantığı saf fonksiyonlardır ve bu şekilde test edilmeyi hak ederler. Laboratuvar labs/privileged-root-namespace/ altında repodaki getirme komutları ve beklenen kanıtlarla birlikte yaşamaktadır.
Bu ne değildir
Bu bir zafiyet değildir. Burada CVE-2025-32463 ile ilgili hiçbir şey yeni değildir; bu 1.9.17p1’de düzeltildi ve sadece bilinen pozitif olarak kullanılmaktadır — aletin çalışıp çalışmadığını bulmak için alete gösterebileceğim bir hata.
Tarama sonraki parçadır ve hiçbir şey üretebilecek olan kısmıdır. Çağrı tarafından sağlanan kök alanını alan izni olan ikiliyi saymak — --root, --installroot, --sysroot, RootDirectory=, ChrootDirectory — gerçek okumadır ve hepsi yükleyicilerini girmeden önce çözebilir veya sadece zaten root olan biri tarafından erişilebilir olabilir. Bu sonuç da yayınlamaya değerdir, eğer bunu üreten aletin görebildiğini kanıtladıysa.
Bugün yaptığım tek iddia budur: tarama temiz dediğinde bu bir şey ifade edecektir. Geçen hafta öyle değildi.
Bu analiz için kullanılan kanıtlar
Sürüm ve sömürü durumu değişebilir; üretici kayıtlarını takip edin.
Birincil kamu kayıtları kontrol edildi. Ortama özgü davranış, ayrıca yeniden üretilmedikçe iddianın dışındadır.
