2 puan yazan GN⁺ 11 일 전 | 1 yorum | WhatsApp'ta paylaş
  • C/C++ pointer'larını AllocationRecord metaverisi ile birlikte izleyen ve dereference sırasında bellek sınırı denetimi yapan bir yapı
  • Pointer ataması, aritmetiği, fonksiyon argümanı aktarımı, dönüş ve malloc·free çağrılarına kadar, özgün pointer değeri ile karşılık gelen metaveriyi birlikte taşıyan ya da bunları Fil-C'ye özgü çağrılara dönüştüren yaklaşım
  • Yığın belleği içindeki pointer metaverisi invisible_bytes içinde ayrı tutulur; pointer load/store işlemlerinde değer ve metaveri birlikte okunup yazılır, ayrıca hizalama denetimi de uygulanır
  • filc_free, yalnızca visible_bytes ve invisible_bytes alanlarını serbest bırakır; AllocationRecord'un kendisi korunur, sonraki temizlik işini çöp toplayıcı üstlenir ve adresi dışarı kaçabilecek yerel değişkenler için heap'e yükseltme yapılır
  • Thread'ler, fonksiyon pointer'ları, bellek ve performans optimizasyonları gibi gerçek uygulama karmaşıklıkları sürse de, büyük C/C++ kod tabanlarında bellek güvenliği doğrulaması veya pointer provenance için somut bir sistem örneği olarak kullanılma potansiyeli vardır

Basitleştirilmiş Fil-C modeli

  • Fil-C, C/C++ kodunu bellek güvenli biçimde ele almak için pointer ile birlikte AllocationRecord* metaverisini izleyen bir yapı kullanır
    • Gerçek uygulama LLVM IR yeniden yazımı yaklaşımını kullanır, ancak basitleştirilmiş model C/C++ kaynak kodunu otomatik dönüştüren bir biçimdedir
    • Her fonksiyonun pointer türündeki yerel değişkeni için karşılık gelen bir AllocationRecord* yerel değişkeni eklenir
    • Örneğin T1* p1 için AllocationRecord* p1ar = NULL eklenir
  • Pointer yerel değişkenlerine yönelik basit atama ve hesaplamalar, özgün pointer değeriyle birlikte AllocationRecord* bilgisini de birlikte taşır
    • p1 = p2, p1 = p2, p1ar = p2ar biçimine dönüştürülür
    • p1 = p2 + 10 için de p1ar = p2ar eşlik eder
    • Tamsayıdan pointer'a cast işlemi metaveriyi NULL olarak ayarlar
    • Pointer'ı tamsayıya çeviren cast ise olduğu gibi bırakılır
  • Fonksiyon argümanı aktarımı ve dönüşlerde de pointer ile birlikte ek AllocationRecord* aktarılır; bazı standart kütüphane çağrıları ise Fil-C'ye özgü fonksiyonlarla değiştirilir
    • malloc ve free çağrıları sırasıyla filc_malloc, filc_free biçimine dönüştürülür
    • Örneğin p1 = malloc(x); free(p1);, {p1, p1ar} = filc_malloc(x); filc_free(p1, p1ar); biçimine dönüşür
  • filc_malloc, istenen bellek için tek bir allocation yapmak yerine üç allocation gerçekleştirir
    • AllocationRecord nesnesinin allocation'ı
    • Gerçek veri için visible_bytes allocation'ı
    • Görünmez metaveri depolaması için invisible_bytes alanının calloc ile allocation'ı
    • AllocationRecord, visible_bytes, invisible_bytes ve length alanlarını içerir

Dereference ve sınır denetimi

  • Pointer dereference edilirken eşlik eden AllocationRecord* kullanılarak sınır denetimi yapılır
    • Pointer metaverisinin NULL olup olmadığı kontrol edilir
    • Geçerli pointer konumu ile visible_bytes başlangıç adresi arasındaki fark hesaplanır
    • Ofsetin toplam uzunluktan küçük olup olmadığı kontrol edilir
    • Kalan uzunluğun dereference edilecek hedef boyutu için yeterli olup olmadığı doğrulanır
  • Aynı denetim prosedürü hem okuma hem yazma için uygulanır
    • x = *p1 öncesinde de denetim yapılır
    • *p2 = x öncesinde de aynı türde denetim uygulanır
  • Bu yapı, pointer'ın allocation sınırlarının dışına taşan erişimleri engeller

Heap içindeki pointer'lar ve invisible_bytes

  • Heap belleğine kaydedilen pointer'lar, yerel değişkenler gibi derleyici tarafından doğrudan ayrı değişkenlerle yönetilemediği için invisible_bytes kullanılır
    • visible_bytes + i konumunda bir pointer varsa, ona karşılık gelen AllocationRecord*, invisible_bytes + i konumunda tutulur
    • Yani invisible_bytes, eleman türü AllocationRecord* olan bir dizi gibi davranır
  • Pointer değerini bellekten okuma ya da belleğe yazma sırasında, normal sınır denetimine ek olarak hizalama denetimi de yapılır
    • Ofset i değerinin sizeof(AllocationRecord*) katı olup olmadığı kontrol edilir
    • Ancak bu koşul sağlanırsa invisible_bytes alanına AllocationRecord** dizisi gibi güvenli şekilde erişilebilir
  • Pointer load edilirken veri pointer'ı ile birlikte metaveri de yüklenir
    • p2 = *p1 işleminin ardından p2ar = *(AllocationRecord**)(p1ar->invisible_bytes + i) eklenir
  • Pointer store edilirken yalnızca pointer değeri değil, ilgili metaveri de birlikte yazılır
    • *p1 = p2 için gerçek veri yazıldıktan sonra *(AllocationRecord**)(p1ar->invisible_bytes + i) = p2ar gerçekleştirilir

filc_free ve çöp toplayıcı

  • filc_free, pointer NULL değilse AllocationRecord ile tutarlılık denetimi yaptıktan sonra yalnızca iki bellek alanını serbest bırakır
    • par != NULL kontrolü
    • p == par->visible_bytes kontrolü
    • visible_bytes ve invisible_bytes alanlarının serbest bırakılması
    • Sonrasında visible_bytes, invisible_bytes alanlarının NULL, length değerinin ise 0 yapılması
  • filc_malloc üç allocation yapmasına rağmen, filc_free AllocationRecord nesnesinin kendisini serbest bırakmaz
    • Bu fark çöp toplayıcı tarafından ele alınır
  • Basitleştirilmiş model için stop-the-world GC yeterlidir; gerçek Fil-C ise paralel, eşzamanlı ve artımlı bir toplayıcı kullanır
    • GC, AllocationRecord nesnelerini izleyerek tarama yapar
    • Ulaşılamayan AllocationRecord nesnelerini serbest bırakılacak hedefler olarak işler
  • GC ayrıca iki ek görev daha yapar
    • Ulaşılamayan AllocationRecord nesnelerini serbest bırakırken filc_free çağırır
    • length değeri 0 olan AllocationRecord'ları gösteren tüm pointer'ları, uzunluğu 0 olan tek bir kanonik AllocationRecord'a yönlendirir
  • Bu davranış sayesinde free çağrılmasa bile bellek sızıntısı oluşmaz
    • GC serbest bırakmayı otomatik gerçekleştirir
    • Ancak free çağrısı, GC'den daha erken bir anda belleğin serbest bırakılmasını sağlayabilir
  • free sonrasında ilgili AllocationRecord zamanla ulaşılamaz hale gelir ve daha sonra temizlenebilir

Yerel değişken adresinin kaçması ve heap'e yükseltme

  • GC'nin varlığı, yerel değişken adreslerinin güvenli biçimde ele alınabildiği kapsamı genişletir
    • Bir yerel değişkenin adresi alınmışsa ve derleyici bu adresin değişkenin ömrü dışına kaçmadığını kanıtlayamıyorsa, değişken heap allocation'ına yükseltilir
  • Bu tür yerel değişkenler stack yerine malloc ile allocation edilir
    • Bunlar için ayrıca karşılık gelen bir free eklemek gerekmez
    • Toplamayı GC üstlenir

Fil-C sürümü memmove

  • C standart kütüphanesindeki memmove, keyfi belleği işlediği için derleyici onun içindeki pointer'ların varlığını bilmeyebilir
  • Bunun için bir heuristic uygulanır
    • Keyfi bellek içindeki pointer'lar, o bellek aralığının içine tam olarak sığmış olmalıdır
    • Pointer'lar doğru hizalanmış olmalıdır
  • Bu kural nedeniyle, aynı 8 baytın taşınmasında bile davranış farkı oluşur
    • Hizalı 8 bayt tek seferde memmove edilirse, ilgili invisible_bytes bölgesi de birlikte taşınır
    • Aynı işlem 1 baytlık 8 ayrı memmove ile yapılırsa invisible_bytes taşınmaz

Gerçek uygulamada ek karmaşıklıklar

  • Thread'ler

    • Eşzamanlılık, GC karmaşıklığını artıran bir etkendir
    • filc_free belleği hemen serbest bırakamaz
      • Çünkü serbest bırakan thread ile başka bir thread'in aynı belleğe erişimi arasında yarış durumu olabilir
    • Pointer'lara yönelik atomik işlemler de ek işlem gerektirir
      • Temel yeniden yazım, pointer load/store işlemlerini iki ayrı load/store'a dönüştürdüğü için atomikliği bozar
  • Fonksiyon pointer'ları

    • AllocationRecord içindeki ek metaveriyle visible_bytes alanının sıradan veri değil, bir çalıştırılabilir kod pointer'ı olup olmadığı işaretlenir
    • Fonksiyon pointer'ı p1 üzerinden çağrı yapılırken, p1 == p1ar->visible_bytes denetiminin yanı sıra p1ar'ın bir fonksiyon pointer'ını temsil edip etmediği de kontrol edilir
    • Fonksiyon pointer'larında type confusion saldırılarını engellemek için çağrı ABI'sinde de tip imzası doğrulaması gerekir
    • Bunun bir yolu, tüm fonksiyonların aynı tip imzasına sahip olmasını sağlamaktır
      • Örneğin tüm argümanlar bir struct içine konup bellek üzerinden aktarılıyormuş gibi ele alınabilir
      • ABI sınırında her fonksiyon, o struct'a karşılık gelen tek bir AllocationRecord alır
  • Bellek kullanımı optimizasyonu

    • filc_malloc için invisible_bytes alanını hemen allocation etmek yerine, gerektiğinde gecikmeli allocation düşünülerek uygulanabilir
    • AllocationRecord ile visible_bytes alanını tek bir allocation içinde bitişik yerleştirme yaklaşımı da düşünülebilir
    • Alttaki malloc, her allocation'ın başına kendi metaverisini ekliyorsa, bu metaverinin AllocationRecord içine alınması da seçeneklerden biridir
  • Performans optimizasyonu

    • Fil-C'nin bellek güvenliği performans maliyeti getirir
    • Kaybedilen performansın bir bölümünü geri kazanmak için çeşitli teknikler uygulanabilir

Fil-C'nin kullanım zamanı

  • Büyük C/C++ kod tabanları çalışıyor gibi görünse bile bellek güvenliği doğrulaması yoksa ve bellek güvenliği uğruna GC eklemeyi ve ciddi performans kaybını kabul etmek mümkünse kullanılabilir
    • Java, Go veya Rust'a yeniden yazılmadan önce geçici bir önlem olabileceği belirtilir
  • ASan benzeri şekilde bellek hatalarını tespit etmek amacıyla da Fil-C çalıştırılabilir
    • C/C++ kodu Fil-C altında çalıştırılarak bellek hataları doğrulanabilir
  • Derleme zamanı dili ile çalışma zamanı dili aynı olan ve derleme zamanı güvenliği güçlü olan dillerde, güvenli derleme zamanı değerlendirmesi amacıyla kullanılabilir
    • Örnek olarak Zig anılır
    • Çalışma zamanı değerlendirmesi güvenli olmasa bile, derleme zamanı değerlendirmesi Fil-C yapısını kullanabilir
  • Ayrıca pointer provenance ele alan somut bir sistem örneği olarak da anlam taşır
    • p1 ve p2 türleri aynıysa if (p1 == p2) { f(p1); } ifadesinin if (p1 == p2) { f(p2); } olarak optimize edilip edilemeyeceği sorusu ortaya konur
    • Fil-C'de f'ye aktarılan AllocationRecord* farklı olabileceği için, cevabın açıkça hayır olduğu belirtilir
    • Bu yönüyle Fil-C, pointer provenance taşıyan somut bir sistem örneği işlevi görür

1 yorum

 
GN⁺ 11 일 전
Hacker News yorumları
  • invisicaps'i chibicc ya da slimcc gibi bir şeye eklemek oldukça ilginç bir deney olabilir gibi geliyor
    referans sayımı ya da invisible capability system türevlerini de denemek için alan var; bir miktar dolaylı başvuru maliyeti karşılığında bellekten tasarruf etmek de mümkün olabilir diye düşünüyorum
  • Ben filc-bazel-template'i hazırlayıp bir Bazel target olarak paketledim
    ikisini birlikte kullanıp hermetic builds yapmak isteyenlere yardımcı olmasını umuyorum
  • Bu cümlenin ne demek istediğini pek anlayamıyorum
    Upon freeing an unreachable AllocationRecord, call filc_free on it.
    Bana kalırsa burada kastedilen, erişilemeyen AR'yi serbest bırakmadan önce visible_bytes ve invisible_bytes alanlarının işaret ettiği belleği önce serbest bırakmak
  • Bence Fil-C, şu ana kadar gördüğüm projeler arasında en az değer verilenlerden biri
    güvenlik için “rewrite it in Rust” denmesindense, mevcut C programlarını tamamen bellek güvenli şekilde derleyebilmesi daha ilginç geliyor
    • Bence burada birkaç noktaya birlikte bakmak gerekiyor
      Birincisi, Fil-C daha yavaş ve daha büyük. Eğer bu kabul edilebilirse, son 10 yılda neden Rust yerine önce Java ya da C#'a geçilmediği de sorulabilir
      İkincisi, hâlâ C kullanıyorsunuz. Mevcut kodu korumak için sorun olmayabilir ama çok sayıda yeni kod yazılacaksa bana göre Rust çok daha rahat
      Üçüncüsü, Fil-C çalışma zamanı güvenliği sunuyor; Rust ise bunun bir kısmını derleme zamanında ifade edebiliyor. Hatta WUFFS gibi diller, çalışma zamanı kontrolü olmadan da güvenliği derleme aşamasında kanıtlamaya çalışıyor; yani kod mantıksal olarak hatalı olabilir ama çökme ya da keyfi kod çalıştırma engellenmiş oluyor
    • Ben bunun burada az değer gördüğünü düşünmüyorum. Zaten bununla ilgili epey tartışma oldu
      Fil-Qt: A Qt Base build with Fil-C experience, Linux Sandboxes and Fil-C, Ported freetype, fontconfig, harfbuzz, and graphite to Fil-C, A Note on Fil-C, Notes by djb on using Fil-C, Fil-C: A memory-safe C implementation, Fil's Unbelievable Garbage Collector gibi başlıklar vardı
    • Bana göre Fil-C'nin temel sınırı, runtime memory safety sunması
      Hâlâ bellek açısından güvensiz kod yazabiliyorsunuz; sadece sonuç artık bir açık yerine kesin bir çökme oluyor
      Güvenilmeyen girdi alan bir web API gibi bir şey yapıyorsanız, bu tür sorunlar sonuçta denial-of-service'e yol açabilir; yani daha iyi olsa da yeterince iyi demek zor
      Fil-C üzerinde yapılan işi küçümsemek istemiyorum ama çalışma zamanı yaklaşımının net sınırları olduğunu düşünüyorum
    • İlginiz için teşekkürler
      Ama adil olmak gerekirse Fil-C, Rust'tan belirgin şekilde daha yavaş ve daha fazla bellek kullanıyor
      Buna karşılık Fil-C, safe dynamic linking destekliyor ve bazı açılardan Rust'tan daha katı biçimde güvenli olduğu bile söylenebilir
      Sonuçta bunlar birer ödünleşim; herkes kendi koşuluna göre seçmeli diye düşünüyorum
    • Bana göre C/C++ programcılarına programlarına bir garbage collector ekleyebileceklerini söylediğinizde gözlerinin parlaması pek sık görülen bir şey değil
      Bu yüzden fikir teknik olarak ilginç olsa bile duygusal olarak kolay kabul görmüyor gibi
  • Bence Fil-C, data race durumlarında bellek güvenli değil
    çünkü capability ve pointer değerleri atama sırasında parçalanabiliyor; iş parçacıkları kötü bir şekilde iç içe girerse yanlış pointer ile nesneye erişilip keyfi hatalı davranış ortaya çıkabiliyor
    Bu sınırın kendisi kabul edilebilir ama bunu işaret edenlere, destekçilerin bile sert biçimde yüklenmesi üzücü geliyor
    • Bildiğim kadarıyla bu kısım atomic ops ile ele alınıyor
      Ne yazık ki bu da ek yükün büyük nedenlerinden biri
  • Bana göre bu sonuçta fat pointers ailesinden tekniklerin bir başka çeşidi
    Bu tür yaklaşımlar, yeterli güvenlik garantisi vermemesi, non-fat ABI boundaries üzerinden geçmenin zor olması ya da ek yükün fazla olması gibi nedenlerle defalarca uygulanıp sonra elendi
    • Ama bugünlerde fat pointers için doğrudan donanım desteği yönünde bir eğilim yeniden ortaya çıkıyor; bu yüzden onu fazla erken gözden çıkarmamak gerekebilir
      Ayrıca bence filc, yalnızca basit bir fat pointer ile açıklanabilecek bir şey de değil
    • Birçok platformda zaten hardware memory tagging sunulduğunu da hesaba katmak gerekir bence