15 puan yazan xguru 2023-11-23 | 3 yorum | WhatsApp'ta paylaş
  • C dilindeki setenv() ve unsetenv() fonksiyonları, thread kullanan programlarda güvenle kullanılamıyor
  • Bu fonksiyonlar global durumu değiştiriyor ve başka bir thread getenv() çağırdığında çakışmaya yol açabiliyor
  • Go’daki os.Setenv ve Rust’taki std::env::set_var() gibi C standart kütüphanesi fonksiyonlarını kullanan diğer dillerde de çakışmalar ortaya çıkıyor
  • Go programında ilgili sorunun izini sürmek ve bug raporu hazırlamak 2 gün sürdü
    • Çünkü Go’nun DNS resolver’ı dahili olarak getaddrinfo() kullanıyor ve bu da getenv() çağırıyor
  • Ama bu sorun çok eski
    • 2017’de de bununla ilgili bir yazı var ve yazının sonunda “5 yıl sonra 2022’de görüşürüz!” deniyor; konu 2023’te yeniden karşımıza çıkmış
  • Bu, POSIX standardındaki bir kusur; C standardı genişletilerek environment variable’ların değiştirilebilmesi sağlanmış
    • En sinir bozucu kısım, standartları etkileyebilen ya da C kütüphanelerini sürdüren birçok kişinin bunu sorun olarak görmemesi
    • Bunun nedeni, belirtimde setenv()’in thread’lerle birlikte kullanılamayacağının açıkça yazılı olması
    • Yani biri bunu yaparsa çakışma onun suçu sayılıyor
  • O halde “tüm fonksiyonların belirtimlerini dikkatle okuyalım, başkalarının yazdığı yazılımları kullanmayalım ve thread kullanmayalım” deniyor
    • Ama modern yazılımda bu gerçekçi bir varsayım değil
    • Bunun yerine, bozması daha zor olan ve ekosistemdeki değişimlere göre evrilen API’ler geliştirmeye çalışmak gerekiyor
  • C dili ve standart kütüphanesi, yazılımların büyük bölümünün temelinde hâlâ önemli bir rol oynuyor; bu yüzden ya iyileştirmenin bir yolunu bulmalı ya da terk etmenin bir yolunu bulmalıyız

setenv() neden thread-safe değil?

  • getenv() bir char* döndürüyor ve uygulamanın bunu daha sonra serbest bırakması gerekmiyor
  • Bir thread bu pointer’ı kullanırken başka bir thread setenv() veya unsetenv() ile aynı environment variable’ı değiştirebiliyor
  • C standardında yalnızca getenv() bulunuyor; ancak çoğu implementasyon POSIX standardını izlediği için environment’ı değiştiren fonksiyonları da içeriyor
  • putenv(), environment variable kümesine bir char* ekliyor; uygulama putenv() döndükten sonra bu belleği değiştirirse environment variable da değişiyor
  • environ, uygulamanın okuyabildiği ve atama yapabildiği, NULL ile sonlanan bir pointer dizisi (char**); bu diziye erişim thread-safe değil

Environment variable’lar nasıl implemente ediliyor?

  • Uygulama mevcut bir değişkenin üzerine yazdığında implementasyonun bunu nasıl ele alacağına karar vermesi gerekiyor
  • glibc ile Solaris/Illumos, environment variable’ları asla serbest bırakmıyor; bu yüzden getenv() tarafından döndürülen değer sabit kalıyor ve thread’ler arasında güvenle kullanılabiliyor
  • musl ile FreeBSD/Apple ise environment variable’ları serbest bırakıyor; başka bir thread setenv() çağırdıktan sonra getenv()’in döndürdüğü pointer’ı kullanmak çakışmaya yol açabiliyor
  • Environment variable kümesinin thread-safe biçimde güncellendiğinin garanti edilmesi ikinci sorun ve glibc’deki çakışmanın nedeni de bu

Programlar environment variable’ları neden kullanır?

  • Environment variable’lar, başka programlara gömülü paylaşımlı kütüphaneleri veya dil runtime’larını yapılandırmak için kullanışlıdır
  • Kullanıcılar, program yazarının yapılandırmayı açıkça iletmesine gerek kalmadan ayarları değiştirebilir
  • Pek çok kütüphane getenv() çağırır ve programlar kullandıkları kütüphaneleri yapılandırmak için bu değişkenleri değiştirmek zorunda kalır

Bu sorun çözülmeli ve şu yollar izlenebilir

  • Bana göre bunun uzun zamandır bilinen bir sorun olması akıl almaz
  • Sorunu debug etmek veya geçici çözümleri tartışmak için binlerce saat boşa harcandı
  • Sorunu çözme yolları
    • Illumos/Solaris’teki gibi thread-safe bir implementasyon yapmak
      • Bunun bazı sınırları var. setenv() içinde memory leak oluşuyor ve program putenv() ya da environment variable’ları kullanıyorsa yine de güvenli olmuyor
      • Yine de bu, mevcut Linux ve Apple implementasyonlarına göre bir iyileştirme
    • İkinci yol ise Microsoft’un getenv_s() fonksiyonuna benzer şekilde, tasarım gereği thread-safe olan ve tüm environment variable’ları okumayı sağlayan yeni bir API eklemek
  • Benim tercih ettiğim çözüm ikisini birden yapmak
    • Böylece mevcut programlar ve kütüphaneler için sorun çıkma olasılığı azaltılırken, Go ve Rust gibi yeni kodlar veya diller için sorunun tamamen önüne geçilecek bir yol da sunulmuş olur
    • getenv_s() benzeri şekilde, tek bir environment variable’ı kullanıcının sağladığı buffer’a kopyalayan bir fonksiyon eklemek
    • Tüm environment variable’lar üzerinde dolaşmayı veya hepsini kopyalamayı sağlayan thread-safe API’ler eklemek
    • getenv()’i artık önerilmeyen olarak işaretleyip yerine thread-safe yeni bir getenv() fonksiyonunu tavsiye etmek
    • putenv()’i artık önerilmeyen olarak işaretleyip yerine setenv()’i tavsiye etmek
    • environ kullanımını artık önerilmeyen olarak işaretleyip yerine environment variable fonksiyonlarını tavsiye etmek
    • Environment variable implementasyonunu thread-safe olacak şekilde güncellemek

3 yorum

 
ahwjdekf 2023-11-24

"Spesifikasyonda, setenv() iş parçacıklarıyla birlikte kullanılamayacağının açıkça belirtilmiş olması" ==> API ya da SDK kullanırken specification'ı dikkatle kontrol etmenin temel kuralı tam da budur. Bana göre bu, zorla kullanmaya çalışmaktan başka bir şey değil.

 
carnoxen 2025-01-24

Sorun, en baştan kötü tasarlanmış bir özelliği kullanmakta.

 
cosine20 2023-11-27

....