Rust'ın Yakalayamadığı Hatalar
(corrode.dev)- Bellek güvenliği büyük ölçüde iyileşse de, Rust production kodunda da sistem sınırı işleme sorunları aynen kalabiliyor ve zafiyetlere yol açabiliyor
- Aynı yolun birden fazla syscall'da yeniden yorumlanması, oluşturduktan sonra izin değiştirme yaklaşımı ve string tabanlı yol karşılaştırması, TOCTOU ve izin sızıntısı gibi sorunlar üretmeye çok elverişli
- Unix'te yol, ortam değişkeni ve akış verisi ham baytlar olarak dolaştığı için,
Stringmerkezli işleme ya dafrom_utf8_lossy,unwrap,expectkullanımı veri bozulmasına veya DoS'a yol açabiliyor - Hatalar yok sayıldığında başarısızlık başarı gibi görünebilir; GNU coreutils ile olan davranış farkları da shell script'lerde ve privileged araçlarda doğrudan güvenlik sorunlarına dönüşebilir
- Bu denetimde buffer overflow, use-after-free, double-free gibi bellek güvenliği sınıfındaki hatalar görülmedi; geriye kalan temel riskler Rust'ın içinden çok dış dünyayla temas eden sınırlarda yoğunlaşıyordu
Denetimin Ortaya Koyduğu Rust Sınırları
- Canonical'ın açıkladığı uutils'e ait 44 CVE, Rust production kodunda da borrow checker, clippy ve cargo audit'in yakalayamadığı zafiyetlerin kalabileceğini gösteriyor
- Sorunların odağı bellek güvenliğinden çok sistem sınırı işlemeydi
- Yol ile syscall arasında zaman farkı vardı
- Unix bayt verisi ile UTF-8 string'ler arasında uyumsuzluk vardı
- Orijinal araçla davranış farkları vardı
- Eksik hata işleme ve
panic!ile sonlanma vardı
- Bu CVE listesi, Rust sistem kodunda güvenliğin bittiği noktayı yoğun biçimde gösteriyor
Bir Yolu İki Kez Yorumlamak TOCTOU Üretir
- Aynı yolu bir syscall'da kontrol edip sonraki syscall'da yeniden kullanmak, kolayca TOCTOU zafiyetine yol açabilir
- İki çağrı arasında üst dizine yazma izni olan bir saldırgan, yol bileşenini bir symbolic link ile değiştirebilir
- İkinci çağrıda kernel yolu baştan yeniden yorumlarken ayrıcalıklı işlem, saldırganın seçtiği hedefe yönlendirilebilir
- Rust'ın
std::fsAPI'si, varsayılan olarak&Pathtabanlı yeniden yorumlama kullandığı için bu tür hataları yapmayı kolaylaştırıyorfs::metadata,File::create,fs::remove_file,fs::set_permissionsher çağrıda yolu yeniden yorumlar- Yerel saldırganlara karşı koruma gerektiren privileged araçlarda bu varsayılan yol tehlikeli hale gelir
CVE-2026-35355te, bir dosyanın silinmesinin ardından aynı yol üzerinde yeni dosya oluşturulması istismar edildisrc/uu/install/src/install.rsiçindefs::remove_file(to)?sonrasındaFile::create(to)?geliyordu- Silme ile oluşturma arasında
to,/etc/shadowgibi bir hedefi gösteren symbolic link'e çevrilirse ayrıcalıklı süreç o dosyanın üstüne yazabilir
- Düzeltmede
OpenOptions::create_new(true)kullanılarak yalnızca yeni dosya oluşturmaya geçildi- Belgede
create_new, hedef konumda yalnızca mevcut dosyayı değil dangling symlink'i de kabul etmiyor
- Belgede
- Aynı yol üzerinde iki kez işlem yapmak gerekiyorsa, dosya tanımlayıcısına sabitlemek daha güvenlidir
- Yeni dosya oluşturma dışındaki durumlarda, önce üst dizini açıp işlemleri o handle'a göre göreli yolla yapmak daha doğrudur
- Aynı yol üzerinde iki kez işlem yapılıyorsa, aksi kanıtlanana kadar bunun TOCTOU olduğu varsayılmalıdır
İzinler Sonradan Değil, Oluşturma Anında Belirlenmeli
- Dizin ya da dosyayı varsayılan izinlerle oluşturup sonra
chmoduygulamak da kısa bir maruz kalma penceresi yaratırfs::create_dir(&path)?ardındanfs::set_permissions(&path, Permissions::from_mode(0o700))?yazılırsa, aradaki süredepathvarsayılan izinlerle var olur- Diğer kullanıcılar bu aralıkta
open()çağırabilir ve sonradanchmodyapılsa bile önceden alınmış dosya tanımlayıcıları geri alınamaz
- İzinler oluşturma anında birlikte belirtilmelidir
OpenOptions::mode()veDirBuilderExt::mode()kullanılarak hedef izinlerle oluşturulmalıdır- Kernel burada ek olarak
umaskuygular; bunun etkisi önemliyseumaskda açıkça ele alınmalıdır
Yol String'lerini Karşılaştırmak Dosya Sistemi Eşdeğerliği Değildir
chmodiçin ilk--preserve-rootkontrolü yalnızca string karşılaştırması yapıyordurecursive && preserve_root && file == Path::new("/")- Gerçekte kökü gösterdiği halde string olarak
/olmayan/../,/./,/usr/..ya da/i gösteren symbolic link gibi girdiler bu kontrolü aşabiliyordu
- Düzeltme, yolu
fs::canonicalizeile gerçek mutlak yola çözüp sonra karşılaştırma yapacak şekilde değiştirildi- Düzeltme PR'ı
canonicalize,..,.ve symbolic link'leri çözüp gerçek yolu döndürür
--preserve-rootdurumunda/için üst dizin olmadığından bu yaklaşım işe yarar- Genel olarak iki rastgele yolun aynı dosya sistemi nesnesi olup olmadığını karşılaştırmak için string değil
(dev, inode)karşılaştırılmalıdır- GNU coreutils de bunu kullanır
CVE-2026-35363term,.ve..'yi reddederken./ve.///girdilerine izin verdiği için mevcut dizini silebiliyordu- Girdi biçimi farklarını yalnızca string düzeyinde ele almak, kontrollerin kolayca aşılmasına yol açar
Unix Sınırlarında String'den Çok Baytları Öncelemek Gerekir
- Rust'taki
Stringve&strher zaman UTF-8'dir; ama Unix'te yol, ortam değişkeni, argüman ve akış verisi ham baytlar dünyasında yaşar - Bu sınırı geçerken yapılan yanlış seçimler iki tür hataya yol açar
from_utf8_lossygibi kayıplı dönüşümler, geçersiz baytlarıU+FFFDile değiştirip veriyi sessizce bozarunwrapya da?gibi katı dönüşümler, girdiyi reddedebilir veya süreci sonlandırabilir
commiçinCVE-2026-35346, kayıplı dönüşüm yüzünden çıktının bozulduğu bir durumdusrc/uu/comm/src/comm.rsiçinde girdi baytlarıra,rb,String::from_utf8_lossyile çevrilipprint!ile yazdırılıyordu- GNU
comm, ikili dosyalarda da baytları olduğu gibi taşırken uutils geçersiz UTF-8'iU+FFFD'ye dönüştürerek çıktıyı bozuyordu - Düzeltme,
BufWritervewrite_allile ham baytları doğrudanstdouta yazmaktı
print!,Displayüzerinden geçerek UTF-8 gidiş-dönüşünü zorunlu kılar; amaWrite::write_allbunu yapmaz- Unix benzeri sistem kodunda, duruma uygun tipler kullanılmalıdır
- Formatlama kolaylığı için
Stringüzerinden gitmek, veri bozulmasını kod tabanına sızdırmayı kolaylaştırır
Her panic Bir Hizmet Reddi Saldırısına Dönüşebilir
- CLI'da
unwrap,expect, dilim indeksleme, kontrolsüz aritmetik vefrom_utf8, saldırganın girdiyi kontrol edebildiği durumlarda DoS noktası olabilirpanic!, stack'i unwind eder ve süreci durdurur- cron job, CI pipeline ya da shell script içinde çalışıyorsa tüm iş akışı durabilir
- Tekrarlı çalışma ortamlarında crash loop ile tüm sistemi felç etmek bile mümkün olabilir
sort --files0-fromiçinCVE-2026-35348, NUL ile ayrılmış dosya adı listesinde UTF-8 olmayan bir dosya adı görünce duruyordu- Ayrıştırıcı, her ad baytı için
std::str::from_utf8(bytes).expect(...)çağırıyordu - GNU
sort, dosya adlarını kernel gibi ham bayt olarak ele alırken uutils UTF-8'i zorlayıp ilk UTF-8 dışı yolda tüm süreci sonlandırıyordu
- Ayrıştırıcı, her ad baytı için
- Güvenilmeyen girdiyi işleyen kodda
unwrap,expect, indeksleme veascast, potansiyel CVE olarak görülmelidir?,get,checked_*,try_fromkullanılmalı ve gerçek hata çağırana iletilmelidir
- CI'da yakalamak için önerilen clippy kuralları da veriliyor
unwrap_usedexpect_usedpanicindexing_slicingarithmetic_side_effects
- Test kodunda bu uyarılar aşırı gelebileceğinden, bunları
cfg(test)kapsamıyla sınırlamak uygun bir yaklaşım olabilir
Hataları Yok Saymak Başarısızlığı Başarı Gibi Gösterebilir
- Bazı CVE'ler, hataların yok sayılması ya da hata bilgisinin kaybolduğu akışlar yüzünden ortaya çıktı
chmod -Rvechown -R, tüm işlem boyunca yalnızca son dosyanın çıkış kodunu döndürüyordu- Önceki çok sayıdaki dosya işlemi başarısız olsa da son dosya başarılıysa sonuç
0olabiliyordu - Script'ler de tüm işlemin sorunsuz bittiğini sanabiliyordu
- Önceki çok sayıdaki dosya işlemi başarısız olsa da son dosya başarılıysa sonuç
dd,/dev/nullüzerindeki GNU davranışını taklit etmek içinset_len()sonucundaResult::ok()çağırıyordu- Niyet, sınırlı bir durumda hatayı yok saymaktı; ama aynı kod normal dosyalara da uygulanıyordu
- Disk dolu olduğunda bile yarım yazılmış hedef dosya sessizce kalabiliyordu
.ok(),.unwrap_or_default(),let _ =ileResultatıldığında önemli başarısızlık nedenleri kaybolur- İlk hatada hemen durulmasa bile, en ciddi hata kodu hatırlanmalı ve onunla çıkılmalıdır
Resultmutlaka atılacaksa, o hatanın neden güvenle yok sayılabildiği kod içinde belirtilmelidir
Orijinal Araçla Tam Uyumluluk da Bir Güvenlik Özelliğidir
- Birçok CVE, kodun tehlikeli işlem yapmasından değil GNU'dan farklı davranmasından kaynaklandı
- Gerçek shell script'ler orijinal GNU davranışına bağımlı olduğundan, anlam farkı güvenlik sorununa dönüşebiliyor
kill -1içinCVE-2026-35369bunun tipik örneği- GNU,
-1ifadesini signal 1 olarak yorumlar ve PID bekler - uutils ise bunu PID -1'e varsayılan sinyal gönderme olarak yorumladı
- Linux'ta PID -1, görülebilen tüm süreçler anlamına geldiğinden basit bir yazım hatası tüm sistemi kill etmeye dönüşebilir
- GNU,
- Yeniden uygulanan araçlarda bug-for-bug uyumluluk, çıkış kodundan hata mesajına, edge case'lerden seçenek anlamlarına kadar bir güvenlik önlemi haline gelir
- GNU ile farklı davranılan her noktada, shell script'lerin yanlış karar verme olasılığı artar
- uutils artık CI'da upstream GNU coreutils test suite'ini de birlikte çalıştırıyor
- Bu tür farkları önlemek için uygun büyüklükte bir savunma gibi görünüyor
Güven Sınırı Aşılmadan Önce Çözümleme Yapılmalı
CVE-2026-35368,chrootiçinde local root code execution açığıydı- Sorun deseni,
chroot(new_root)?sonrasında saldırganın kontrol ettiği yeni kökün içinde kullanıcı adının çözülmesiydiget_user_by_name(name)?, yeni kök dosya sistemindeki paylaşılan kütüphaneleri okuyarak kullanıcı adını çözmeye çalışıyordu- Saldırgan chroot içine dosya yerleştirebilirse bu, uid 0 kod çalıştırmaya dönüşebiliyordu
- GNU
chroot, kullanıcı çözümlemesinichrootöncesinde yapıyor- Düzeltme de aynı sıraya geçirildi
- Bir kez güven sınırı aşıldıktan sonra, yapılan her kütüphane çağrısı saldırgan kodunu çalıştırabilir
- Statik bağlantı da bu sorunu önlemez
- Çünkü
get_user_by_name, NSS üzerinden çalışma anındalibnss_*modüllerinidlopenile yükleyebilir
- Çünkü
Rust'ın Gerçekten Engellediği Hatalar
- Bu denetimde bulunmayan hata türleri de açıkça ortada
- buffer overflow yoktu
- use-after-free yoktu
- double-free yoktu
- Paylaşılan değişebilir durumdan kaynaklı data race yoktu
- null-pointer dereference yoktu
- uninitialized memory read yoktu
- Araçlarda hatalar olsa bile, bunların keyfi bellek okumasına dönüştürülebilecek türleri denetim sonucunda görülmedi
- GNU coreutils son yıllarda bu tür bellek güvenliği sınıfı CVE'ler üretmeye devam etti
pwddeep path buffer overflownumfmtout-of-bounds readunexpand --tabsheap buffer overflowod --strings -Nheap buffer dışına NUL yazımısortiçin heap buffer öncesinden 1 bayt okumasplit --line-bytesiçin heap overwrite olan CVE-2024-0684b2sum --checkiçin malformed input durumunda ayrılmamış bellekten okumatail -fiçin stack buffer overrun
- Aynı dönem karşılaştırmasında Rust ile yeniden yazılan sürüm, bu kategorilerde 0 hata düzeyini korudu
- Yine de denetimin, bellek güvenliği hatalarının yokluğunu kanıtlamadığı; yalnızca bunları bulamadığı notu da düşülüyor
- Geriye kalan sorunlar, Rust'ın içinden çok dış dünyayla temas eden sınırlarda ortaya çıkıyor
- Yollar
- Baytlar ve string'ler
- syscall'lar
- Zaman farkı ve dosya sistemi durumu değişimleri
Doğru Rust, Aynı Zamanda İdiomatic Rust'tır
- İdiomatic Rust, yalnızca borrow checker'dan geçen ve
clippyuyarısı vermeyen kod demek değildir - Doğruluk da idiomatikliğin bir parçası olmalıdır
- Çünkü gerçek dünyada ayakta kalan kod kalıpları, topluluk deneyimiyle yerleşmiştir
- Sağlam sistemler, gerçeğin dağınıklığını gizlemek yerine olduğu gibi yansıtmalıdır
- Yol yerine dosya tanımlayıcısı
StringyerineOsStrunwrapyerine?- Daha temiz görünen anlamlar yerine orijinalle bug-for-bug uyumluluk
- Tip sistemi çok şeyi ifade edebilir; ama iki syscall arasındaki zaman geçişi gibi denetim dışı koşulları içine alamaz
- İdiomatic Rust'ta kodun tipleri, isimleri ve kontrol akışı çalışma ortamının gerçeğini ortaya koymalıdır
- Tahtada güzel görünen koddan daha az estetik olsa bile, daha dürüst bir biçime ihtiyaç vardır
Kaynaklar
- An update on rust-coreutils: denetim sonuçlarının açıklanması
- Patterns for Defensive Programming in Rust: birlikte okunabilecek savunmacı Rust kalıpları
- Pitfalls of Safe Rust: safe Rust'ta da görülebilen yaygın hatalar
- Sharp Edges In The Rust Standard Library:
stdiçindeki şaşırtıcı davranışlar - uutils/coreutils on GitHub: GNU coreutils'in Rust ile yeniden uygulanmış hali
Henüz yorum yok.