1 puan yazan GN⁺ 1 시간 전 | Henüz yorum yok. | WhatsApp'ta paylaş
  • 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, String merkezli işleme ya da from_utf8_lossy, unwrap, expect kullanı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::fs API'si, varsayılan olarak &Path tabanlı yeniden yorumlama kullandığı için bu tür hataları yapmayı kolaylaştırıyor
  • CVE-2026-35355te, bir dosyanın silinmesinin ardından aynı yol üzerinde yeni dosya oluşturulması istismar edildi
    • src/uu/install/src/install.rs içinde fs::remove_file(to)? sonrasında File::create(to)? geliyordu
    • Silme ile oluşturma arasında to, /etc/shadow gibi 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
  • 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 chmod uygulamak da kısa bir maruz kalma penceresi yaratır
    • fs::create_dir(&path)? ardından fs::set_permissions(&path, Permissions::from_mode(0o700))? yazılırsa, aradaki sürede path varsayılan izinlerle var olur
    • Diğer kullanıcılar bu aralıkta open() çağırabilir ve sonradan chmod yapılsa bile önceden alınmış dosya tanımlayıcıları geri alınamaz
  • İzinler oluşturma anında birlikte belirtilmelidir
    • OpenOptions::mode() ve DirBuilderExt::mode() kullanılarak hedef izinlerle oluşturulmalıdır
    • Kernel burada ek olarak umask uygular; bunun etkisi önemliyse umask da açıkça ele alınmalıdır

Yol String'lerini Karşılaştırmak Dosya Sistemi Eşdeğerliği Değildir

  • chmod için ilk --preserve-root kontrolü yalnızca string karşılaştırması yapıyordu
    • recursive && 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::canonicalize ile 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-root durumunda / 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-35363te rm, . 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 String ve &str her 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_lossy gibi kayıplı dönüşümler, geçersiz baytları U+FFFD ile değiştirip veriyi sessizce bozar
    • unwrap ya da ? gibi katı dönüşümler, girdiyi reddedebilir veya süreci sonlandırabilir
  • comm için CVE-2026-35346, kayıplı dönüşüm yüzünden çıktının bozulduğu bir durumdu
    • src/uu/comm/src/comm.rs içinde girdi baytları ra, rb, String::from_utf8_lossy ile çevrilip print! ile yazdırılıyordu
    • GNU comm, ikili dosyalarda da baytları olduğu gibi taşırken uutils geçersiz UTF-8'i U+FFFD'ye dönüştürerek çıktıyı bozuyordu
    • Düzeltme, BufWriter ve write_all ile ham baytları doğrudan stdouta yazmaktı
  • print!, Display üzerinden geçerek UTF-8 gidiş-dönüşünü zorunlu kılar; ama Write::write_all bunu yapmaz
  • Unix benzeri sistem kodunda, duruma uygun tipler kullanılmalıdır
    • Dosya yolları için Path, PathBuf
    • Ortam değişkenleri için OsString
    • Akış içeriği için Vec<u8> veya &[u8]
  • 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 ve from_utf8, saldırganın girdiyi kontrol edebildiği durumlarda DoS noktası olabilir
    • panic!, 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-from için CVE-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
  • Güvenilmeyen girdiyi işleyen kodda unwrap, expect, indeksleme ve as cast, potansiyel CVE olarak görülmelidir
    • ?, get, checked_*, try_from kullanılmalı ve gerçek hata çağırana iletilmelidir
  • CI'da yakalamak için önerilen clippy kuralları da veriliyor
    • unwrap_used
    • expect_used
    • panic
    • indexing_slicing
    • arithmetic_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 -R ve chown -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ç 0 olabiliyordu
    • Script'ler de tüm işlemin sorunsuz bittiğini sanabiliyordu
  • dd, /dev/null üzerindeki GNU davranışını taklit etmek için set_len() sonucunda Result::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 _ = ile Result atı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
  • Result mutlaka 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 -1 için CVE-2026-35369 bunun tipik örneği
    • GNU, -1 ifadesini 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
  • 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, chroot iç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ülmesiydi
    • get_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ümlemesini chroot ö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ında libnss_* modüllerini dlopen ile yükleyebilir

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
    • pwd deep path buffer overflow
    • numfmt out-of-bounds read
    • unexpand --tabs heap buffer overflow
    • od --strings -N heap buffer dışına NUL yazımı
    • sort için heap buffer öncesinden 1 bayt okuma
    • split --line-bytes için heap overwrite olan CVE-2024-0684
    • b2sum --check için malformed input durumunda ayrılmamış bellekten okuma
    • tail -f iç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 clippy uyarı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ı
    • String yerine OsStr
    • unwrap yerine ?
    • 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

Henüz yorum yok.

Henüz yorum yok.