3 puan yazan GN⁺ 2026-04-30 | 1 yorum | 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
    Reklam

İ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
    Reklam

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
    Reklam
  • 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
    Reklam
  • 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
    Reklam
  • 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

1 yorum

 
GN⁺ 2026-04-30
Hacker News görüşleri
  • GNU Coreutils bakımcısı olarak yazıyı ilgiyle okudum, ancak biraz kullandığım Rust'ta std::fs ile TOCTOU race oluşturmak fazlasıyla kolaydı
    Umarım openat benzeri bir API en sonunda standart kütüphaneye girer

    Ayrıca yolları karşılaştırmadan önce resolve et kuralına katılmıyorum
    Genelde fstat çağırıp st_dev ile st_ino değerlerini karşılaştırmak daha iyi olur ve yazıda da buna bir miktar değinilmişti

    Daha az dikkate alınan yan etki ise performans maliyeti
    Gerçek bir örnekte çok derin bir dizin yolunda cp 0.010 saniye sürerken uu_cp 12.857 saniye sürdü

    Gerçek hayatta böyle yolları bilerek oluşturmak nadirdir, ama GNU yazılımı keyfi sınırları önlemek için çok ciddi çaba harcar
    https://www.gnu.org/prep/standards/standards.html#Semantics

    Ayrıca yazıda Rust yeniden yazımında benzer bir zaman aralığında bellek güvenliği hatası sayısının 0 olduğu söyleniyordu, ama bu doğru değil :)
    https://github.com/advisories/GHSA-w9vv-q986-vj7x

    • Evet, std::fs bir lowest common denominator sorunu taşıyor
      Rust 1.0 için bir şeyler koymak gerekiyordu ve ne yazık ki bu durum uzun süre kalıcılaştı

      uutils'in, hata yapması daha zor olan bir std::fs alternatif API tasarlamayı denemek için iyi bir yer olduğunu düşünüyorum

    • Karşı taraftan bu bakış açısını bu kadar özlü anlattığın için teşekkürler

      Buradan ne öğrenmemiz gerektiğini sormak istiyorum
      Bir internet gönderisi için bilerek biraz saldırgan soruyorum, çünkü karşıtlık olduğunda farkları ve hataları daha net görmek mümkün oluyor
      Elbette zamanını ya da zihinsel enerjini harcamak gibi bir yükümlülüğün yok

      Neden sürekli hız, performans, race condition ve st_ino birlikte geliyor, bunu merak ediyorum
      Gecikme, gerçek depolamaya yazma işi, atomiklik, ACID, sonlu bilgi aktarım hızı gibi şeyler sonuçta benzer bir özde birleşiyor gibi görünüyor
      Muhasebe gibi güvenilirliği yüksek sistemler sonunda ACID'e gitmek zorunda gibi, düşük güvenilirlikli sistemler ise fazla hızlı unutulduğu için bilgisayarların farkı büyük değilmiş gibi hissedilebiliyor

      Ayrıca gündelik uygulamalarda throughput'un gerçekten latency'den daha önemli olup olmadığını da merak ediyorum

      Bir de C, Unix türevi işletim sistemleri ve GNU coreutils tarihi nedeniyle inode numaralarına odaklanılmasını anlıyorum,
      ama çok temel bir örnek olarak USB belleği dosya depolamak için sadece düzgün çalışır hâle getirme sorununa bakınca ne olur, onu merak ediyorum
      libc I/O buffering, fflush, kernel buffering, çok çekirdeklilik, time sharing, aynı anda birden fazla uygulamanın çalışması gibi karmaşıklıklardan kaçmadan tabii

    • Tam bir acemiyim ama neden doğrudan $(yes a/ | head -n $((32 * 1024)) | tr -d '\n') ile cd yapılmayıp while döngüsü gerektiğini merak ettim

      Düzenleme: Anladım. Sebep -bash: cd: a/a/a/....../a/a/: File name too long imiş

    • Bilmiyorum gördünüz mü ama wget gibi GNU yardımcı araçlarını bellek güvenli bir C++ subset'ine otomatik dönüştüren bir demo var
      https://duneroadrunner.github.io/scpp_articles/PoC_autotranslation_of_wget

      Tehlikeli C öğelerini davranış olarak karşılık gelen güvenli C++ öğeleriyle neredeyse 1:1 değiştiren bir yöntem olduğu için, yeniden yazımın getirebileceği yeni hatalar ve yeni davranış farklarını sokma ihtimali daha düşük görünüyor

      Kaynak kod biraz temizlenirse dönüşüm tamamen otomatikleştirilebilir; böylece derleme aşamasında özgün C kaynağından, biraz daha yavaş ama bellek güvenli bir çalıştırılabilir dosya üretilebilir

    • Belki biraz aptalca bir soru ama GNU Coreutils tarafında kendi Rust yeniden yazımı üzerinde bir değerlendirme ya da plan var mı diye merak ediyorum

  • Rust kullanmayı biliyor olabilirlerdi ama Unix API'leriyle, onların anlamlarıyla ve tuzaklarıyla yeterince içli dışlı değillerdi
    O hataların çoğu, eski GNU coreutils ya da BSD, Solaris kökenli geliştiricilerin bakış açısından oldukça acemi işi sayılır
    Bu tür sorunların büyük kısmı onlarca yıl önce zaten ortaya çıkmış ve ayıklanmıştı; mevcut kod tabanlarında hâlâ uzun kuyruklu düzeltmeler var ama artık genelde düşük hacimde akıyorlar

    • O Canonical başlığını okuyunca gerçekten dehşete düştüm
      Özeti aşağı yukarı şöyleydi: “Rust daha güvenli, güvenlik en büyük öncelik, dolayısıyla coreutils'in tamamının yeniden yazılmış sürümünü dağıtmak acil. Bir şeyler bozulursa da sorun değil, sonra düzeltilir.”

      Bu şekilde düşünen insanların yazdığı kodu kendi makinemde çalıştırmak istemiyorum
      Ben de Rust yanlısıyım ama Rust'ın daha güvenli olması ancak diğer her şey eşitse geçerlidir
      Burada diğer hiçbir şey eşit değil

      Yeniden yazım, onlarca yıldır bakımı yapılan koda göre kaçınılmaz olarak çok daha fazla hata ve açık barındıracaktır; bu yüzden güvenlik argümanı uzun vadeli geçiş stratejisi için anlamlı olsa da aceleci bir yaygın dağıtımın gerekçesi olamaz

      Dağıtımdan sonra kullanıcı etkisini önemsiz göstermeye çalışmak ya da “hatalar böyle ortaya çıkar”, “mevcut coreutils'in de düzgün testleri yoktu” demek fazlasıyla sorumsuzca
      Kullanıcılar kobay değil
      Bakımcıların, kullanıcı sistemlerinin güvenilirliğini zedelememe yönünde ahlaki bir sorumluluğu olduğunu düşünüyorum

    • Daha da temelde, Rust standart kütüphanesi geliştiricileri yanlış soyutlama düzeyindeki temiz API'lere yönlendiriyor gibi görünüyor
      Örneğin handle tabanlı dosya işlemleri yerine yol tabanlı işlemlere
      Umarım yanılıyorumdur

    • Bence Rust'ın asıl amacı, en büyük ve düşmesi en kolay tuzaklarla özellikle ilgilenmek zorunda kalmamanızı sağlamak

      Bu yazının özü de aslında dosya sistemi API'lerinin bunu yapması gerektiği gibi görünüyor

    • Birileri buna benzer bir ifade olarak disassembler rage diye bir tabir üretmişti
      Yeterince yakından bakarsanız her hata amatörce görünür demek

      Bir de yalnızca disassembler'a bakıp, call stack'te 100 frame aşağıdaki bir fonksiyonda neden switch yerine if kullanıldığını diye yüksek seviye programcıya söven tavrı anlatmak için kullanılıyor

      Şu anda onların yanlış yaptığı birkaç şeye bakıyoruz; etrafındaki binlerce satırlık doğru yazılmış kodu ise neredeyse hiç görmüyoruz

    • Bu tür yardımcı araçlarda panic olması, Rust ölçütlerine göre bile oldukça amatörce bir hata
      Kurtarılamaz alloc hatası gibi bir durum değilse, expect ve unwrap kullanımı, o kod yolunun asla çalışmayacağını garanti eden değişmezler gerçekten çok sıkı değilse zor savunulur

  • Kodu yeniden yazarken zor olan şeylerden biri, özgün kodun gerçek üretim ortamlarında ortaya çıkan sorunlara tepki verirken kademeli olarak şekil değiştirmiş olmasıdır

    O süreçte çıkarılan dersler sessizce kodun içine siner ve dokümante edilmediyse, eşdeğer seviyeye gelmeden önce yapılması gereken gizli iş miktarı çok büyür

    Asıl yazı tam da bu tür bir listeyi iyi gösteriyor

    Yine de hemen amatör damgası vurmadan önce, bunun yazılımın en yazılımsal görünen olgularından biri olduğunu da görmek gerek
    coreutils için gerçekten iyi teknik dokümantasyon ve bu durumları kapsayan testler vardı da bunlar göz ardı edildi değilse, böyle bir şey neredeyse kaçınılmazdı

    • Yazıdaki iyi örneklerden biri chroot + NSS CVE
      NSS'nin dinamik oluşu ve chroot içinde kütüphaneleri dlopen ile yüklemesi kuralı, göze çarpan bir yerde yazmıyor

      Bu daha çok sistem yöneticilerinin 25 yıldan uzun süredir yaşayarak öğrendiği bir gerçek; clean-room yeniden yazımlar da bunu çoğu zaman yeni bir CVE olarak yeniden öğreniyor
      Aynı kodu bir LLM ile taşısanız da durum benzer olur
      Fonksiyon imzalarını okuyabilirsiniz ama gerçekten ihtiyaç duyulan şey, o kodun üzerinde kalmış yaralar ve izlerdir

    • Bunu GPL'den kaçınmak için, özgün kaynağı hiç okumadan yapıyorsanız iş daha da zorlaşıyor

      Bence uutils GPL olsaydı ve coreutils özgün kaynağından doğrudan ilham alabilseydi çok daha iyi olurdu

    • Bu dersleri ya da en azından kaçınılmak istenen hataları ve açıkları dokümante etmemek de kötü bir pratik; bunu söylemek lazım

      Elbette en baştan iyi yazılmış kodun dolaylı olarak kaçındığı tüm hataları belgelemek zor,
      ama gelecekteki okuyucu için “burada bar yerine foo kullanmamızın nedeni, ABC koşulunda bar kullanılırsa XYZ yüzünden tehlikeli bir baz oluşması” gibi açıklamalar bırakmak önemli
      Biraz zaman ve doküman alanı israfı gibi görünse de bunun daha iyi olduğunu düşünüyorum

  • Bu yazının işaret ettiği şeylerin önemli bir kısmı, özellikle de GNU coreutils kaynağıyla karşılaştırıldığında, sıradan bir unit test veya manuel incelemede yakalanmış olmalıydı diye hissediyorum
    coreutils'i yeniden yazmak korkunç bir fikir gibi görünüyor
    https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/
    ve önceki yazılımın biriktirdiği bilgi yeterince alınmadan yanlış biçimde ilerlenmiş gibi duruyor

    Yeniden yazım yapacaksanız önceki sürümü tamamen anlamalı ve ondan öğrenmelisiniz
    Yoksa aynı hataları tekrar edersiniz ve açıkçası bu oldukça utandırıcı olur

    Açık olayım, Rust'ı seviyorum, birçok projede kullanıyorum ve harika buluyorum
    Ama Rust sizi kötü mühendislikten kurtarmıyor

    • İlginç olan şu ki uutils, GNU coreutils test suite'ini kullanıyor

      Ek olarak, GPL kaynak kodunu okuyup yazılmış katkıları kabul etmeyeceğini de açıkça belirtmiş durumda

    • unity, upstart, snap yapan taraftan geliyorsa bu da tam beklenen türden bir şey

    • Yeni sistem programcılarını şöyle karşılamak gerekiyor galiba
      Unix bozuktur ve sonunda çirkin, öğretici bile olmayan dolaşma çözümlerini kendiniz yazmak zorunda kalırsınız; ayrıca ampirik test de yapmanız gerekir
      Güvenilir yazılım ve iyi yazılım mühendisliği zaten böyle işler

  • Neden differential fuzzing bunun gibi hataları yakalayamadı merak ediyorum

    https://github.com/uutils/coreutils/tree/main/fuzz/uufuzz

  • Bir yol üzerinde bir syscall ile kontrol yapıp sonra aynı yola tekrar syscall atarak işlem yapmak, hep aynı soruna yol açar
    Üst dizinde yazma yetkisi olan bir saldırgan bu arada yol bileşenlerini sembolik bağlantı ile değiştirebilir ve kernel ikinci çağrıda yolu baştan yeniden resolve ederek ayrıcalıklı işlemi saldırganın seçtiği hedefe yönlendirir

    • Aslında bundan daha da kötü
      Üst dizinde yazma yetkisi olan bir saldırgan hard link ile de oyun çevirebilir
      Yalnızca normal dosyalar üzerinden oynayabilse bile pratikte düzgün bir hafifletme neredeyse yok
      Örnek için bkz. https://michael.orlitzky.com/articles/posix_hardlink_heartache.xhtml
    • Hm… belki dizin üzerinde write lock almanın bir yolu vardır, ama timeout gibi problemler de girince iş hızlıca daha karmaşık olur gibi
  • Bazı hataların kök nedeni, Unix API'lerinin fazla opak olması gibi görünüyor

    Örneğin get_user_by_name fonksiyonunun yeni kök dosya sistemi içinden shared library yükleyip kullanıcı adını çözmesi ve bu yüzden chroot içine dosya yerleştirebilen bir saldırganın uid 0 ile kod çalıştırabilmesi, neredeyse bir bubi tuzağı gibi hissettiriyor

    Kullanıcı verisi alan bir fonksiyonun birden paylaşımlı kütüphane de yüklemesi, ilgi alanlarının birbirine karıştırıldığı bir tasarım gibi duruyor
    Kullanıcı verisi sorgulama ile kütüphane yükleme işinin fonksiyon düzeyinde ayrılması ya da en azından bunun addan açıkça anlaşılması gerektiğini düşünüyorum

    • Kısmen doğru olabilir ama coreutils'i sıfırdan yeniden yazmayı seçtiyseniz POSIX API'lerini anlamak kelimenin tam anlamıyla işin merkezindedir

      Ayrıca yolun dosya sistemi kökünü gösterip göstermediğini kontrol eden kod file == Path::new("/") idiyse, bu bir API sorunu değil
      Bunu yazan kişinin bu projeye katkı verecek yetkinliği neredeyse yok gibi görünüyor

    • Hatta fonksiyonel güvenli diller kullanmak, ele alınan verilerin de durumsuz olduğu yanılgısını yaratabiliyor olabilir
      Oysa işletim sisteminde gerçekten çok fazla şey sürekli değişir

      Snapshot sağlayan dosya sistemleri ortaya çıkana kadar her şeyi sürekli yeniden kontrol etmek gerekir

      Sonuçta gereken şey, girdi verildiğinde ya başarılı sonuç ya da başarısızlık döndüren API'dir
      Başarı, başarısızlık ve hata diye üçlü döndüren API değil

    • Evet, musl libc tam da böyle bir parçayı kaldırıyor

    • Kök nedenin Unix API'lerinin opaklığı değil, root'un kendi kontrol etmediği bir dizine chroot etmesi durumunun iyi düşünülmemesi olduğunu düşünüyorum

      chroot yapılan her şey, o chroot'un kurulduğu tarafın kontrolü altındadır; bunu anlamayan biri chroot() kullanmamalı

      get_user_by_name tuzak gibi gelebilir ama aslında newroot/etc/passwd kullanmakla newroot/usr/lib/x86_64-linux-gnu/libnss_compat.so, newroot/bin/sh gibi şeyleri kullanmak arasında pratikte çok büyük fark yok

      Bu yüzden /usr/sbin/chroot'un baştan kullanıcı kimliği sorgulaması için bir nedeni olmaması gerektiğini düşünüyorum
      toybox chroot zaten bunu yapmıyor
      Sonuçta hata, bir şeyi yanlış yapma biçiminde değil, o şeyi en başta yapmış olmanın kendisindeydi

    • Unix ve POSIX, neresini keserseniz kesin tuzak çıkan bir fraktal gibi

  • Rust tarafındakiler Linux deneyimi olmadan coreutils'i yeniden yazmış olsalar bile, Ubuntu'nun bunu nasıl mainline'a aldığı bana daha da anlaşılmaz geliyor

    • Ubuntu'nun neredeyse her sürümde sistemin temel parçalarından birini özensiz ve tamamlanmamış bir deneyle değiştirme politikası varmış gibi

      Buradaki esas mesele “vay canına, Rust kodunda hata varmış” değil, tam olarak bu bence

    • Özgün sürüm GPL lisanslı, yeniden yazılan sürüm ise MIT lisanslı

  • “Bu hatalar gerçekten dağıtılan Rust kodunda vardı ve yazarları da ne yaptığını bilen insanlardı” deniyorsa,
    özgün araçlarda test harness yoktu da yeniden yazım buna önce onu kurarak başlamadı mı, diye merak ediyorum

    Uç durum çok olsa bile, OS ve FS'yi bir dereceye kadar soyutlayıp rm .// komutunun gerçekten beklendiği gibi geçerli dizini silmemesini doğrulayamaz mıyız diye düşünüyorum

    Bu bana dağınık kod yazımı ya da dil eleştirisinden çok, yine şu eski sistem programlamada test yapılmaz tavrı gibi geliyor

    Tersine, özgün araçlarda test vardı da yine de bu kadar boşluk kaldıysa, o zaman özgün test suite'in kendisi de ciddi biçimde yetersiz olabilir

    • Öyle düşünüyorum

      Ama OS ve FS'yi doğrulama yapacak kadar soyutlayabileceğimiz konusunda o kadar emin değilim
      İnsanlar ben doğmadan önce de bunu deniyordu ama hâlâ başaramamış gibiler

      Mesela sınamak için kaç tane / ekleneceğine nasıl karar vereceksiniz, orası bile belirsiz

      Daha da ötesi, diyelim ki rm, bir dosyanın ilk 9 baytı important ise silmeyi reddediyor
      O davranışı, o dizgeyi önceden bilmeden yakalayacak testi nasıl düşüneceğinizi hayal etmek zor
      Hele o sihirli kelime sözlükte bile olmayan bir şeyse daha da zor

      “Sistem programlamada test yapılmaz” sözünü ciddi ciddi kullanan neredeyse kimse görmedim
      Ama testlerin insanların beklediği işi her zaman yapmadığını sık sık duydum

    • Benim anladığım kadarıyla uutils geliştirme sürecinde özgün yardımcı araçlarla geniş kapsamlı davranış karşılaştırma testleri vardı ve hatta hataları bile korumaya çalışıyorlardı

    • Windows'un varsayılan olarak symlink'i devre dışı bırakmasının nedenlerinden biri de bu
      Bunu soyutlamayla çözmek yerine, özelliği fiilen ortadan kaldırıyor

      Unix türevleri ise onlarca yıldır symlink'e bağımlı yazılımla dolu olduğu için bunu yapamıyor

      MacOS'ta da benzer türde bir yaklaşım var
      Örneğin chroot() hatası varsayılan ayarlarda pratikte pek sorun olmaz, çünkü MacOS chroot()'u varsayılan olarak engelliyor
      Kullanmak için system integrity protection'ı kapatmanız gerekiyor

      Temel sorun POSIX API'lerinin keskin kenarlarında ve çözüm de bunu soyutlamak değil, neredeyse tamamen ortadan kaldırmak

  • İnsanların denemeler yapması ve acemice girişimlerde bulunması bence sorun değil
    Zaten öğrenme ve gelişme böyle olur

    Asıl merak ettiğim, Ubuntu'nun karar alma zincirinde neyin bozulduğu da bunun üretime kadar girmesine yol açtı

    • Bazen gelişmek sadece boy atmak demektir