Fil-C'nin Basitleştirilmiş Modeli
(corsix.org)- 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ızcavisible_bytesveinvisible_bytesalanları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* p1içinAllocationRecord* p1ar = NULLeklenir
- 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 = p2arbiçimine dönüştürülürp1 = p2 + 10için dep1ar = p2areşlik eder- Tamsayıdan pointer'a cast işlemi metaveriyi
NULLolarak 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
mallocvefreeçağrıları sırasıylafilc_malloc,filc_freebiç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ştirirAllocationRecordnesnesinin allocation'ı- Gerçek veri için
visible_bytesallocation'ı - Görünmez metaveri depolaması için
invisible_bytesalanınıncallocile allocation'ı AllocationRecord,visible_bytes,invisible_bytesvelengthalanları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
NULLolup olmadığı kontrol edilir - Geçerli pointer konumu ile
visible_bytesbaş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
- Pointer metaverisinin
- 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 + ikonumunda bir pointer varsa, ona karşılık gelenAllocationRecord*,invisible_bytes + ikonumunda 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
ideğerininsizeof(AllocationRecord*)katı olup olmadığı kontrol edilir - Ancak bu koşul sağlanırsa
invisible_bytesalanınaAllocationRecord**dizisi gibi güvenli şekilde erişilebilir
- Ofset
- Pointer load edilirken veri pointer'ı ile birlikte metaveri de yüklenir
p2 = *p1işleminin ardındanp2ar = *(AllocationRecord**)(p1ar->invisible_bytes + i)eklenir
- Pointer store edilirken yalnızca pointer değeri değil, ilgili metaveri de birlikte yazılır
*p1 = p2için gerçek veri yazıldıktan sonra*(AllocationRecord**)(p1ar->invisible_bytes + i) = p2argerçekleştirilir
filc_free ve çöp toplayıcı
filc_free, pointerNULLdeğilse AllocationRecord ile tutarlılık denetimi yaptıktan sonra yalnızca iki bellek alanını serbest bırakırpar != NULLkontrolüp == par->visible_byteskontrolüvisible_bytesveinvisible_bytesalanlarının serbest bırakılması- Sonrasında
visible_bytes,invisible_bytesalanlarınınNULL,lengthdeğerinin ise 0 yapılması
filc_mallocüç allocation yapmasına rağmen,filc_freeAllocationRecord 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,
AllocationRecordnesnelerini izleyerek tarama yapar - Ulaşılamayan
AllocationRecordnesnelerini serbest bırakılacak hedefler olarak işler
- GC,
- GC ayrıca iki ek görev daha yapar
- Ulaşılamayan
AllocationRecordnesnelerini serbest bırakırkenfilc_freeçağırır lengthdeğeri 0 olanAllocationRecord'ları gösteren tüm pointer'ları, uzunluğu 0 olan tek bir kanonikAllocationRecord'a yönlendirir
- Ulaşılamayan
- 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
freesonrasında ilgiliAllocationRecordzamanla 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
mallocile allocation edilir- Bunlar için ayrıca karşılık gelen bir
freeeklemek gerekmez - Toplamayı GC üstlenir
- Bunlar için ayrıca karşılık gelen bir
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
memmoveedilirse, ilgiliinvisible_bytesbölgesi de birlikte taşınır - Aynı işlem 1 baytlık 8 ayrı
memmoveile yapılırsainvisible_bytestaşınmaz
- Hizalı 8 bayt tek seferde
Gerçek uygulamada ek karmaşıklıklar
-
Thread'ler
- Eşzamanlılık, GC karmaşıklığını artıran bir etkendir
filc_freebelleğ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ı
AllocationRecordiçindeki ek metaveriylevisible_bytesalanı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_bytesdenetiminin yanı sırap1ar'ı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
AllocationRecordalır
-
Bellek kullanımı optimizasyonu
filc_mallociçininvisible_bytesalanını hemen allocation etmek yerine, gerektiğinde gecikmeli allocation düşünülerek uygulanabilirAllocationRecordilevisible_bytesalanı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 metaverininAllocationRecordiç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
p1vep2türleri aynıysaif (p1 == p2) { f(p1); }ifadesininif (p1 == p2) { f(p2); }olarak optimize edilip edilemeyeceği sorusu ortaya konur- Fil-C'de
f'ye aktarılanAllocationRecord*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
Hacker News yorumları
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
ikisini birlikte kullanıp hermetic builds yapmak isteyenlere yardımcı olmasını umuyorum
Upon freeing an unreachable AllocationRecord, call filc_free on it.Bana kalırsa burada kastedilen, erişilemeyen AR'yi serbest bırakmadan önce
visible_bytesveinvisible_bytesalanlarının işaret ettiği belleği önce serbest bırakmakgüvenlik için “rewrite it in Rust” denmesindense, mevcut C programlarını tamamen bellek güvenli şekilde derleyebilmesi daha ilginç geliyor
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
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ı
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
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
Bu yüzden fikir teknik olarak ilginç olsa bile duygusal olarak kolay kabul görmüyor gibi
çü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
Ne yazık ki bu da ek yükün büyük nedenlerinden biri
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
Ayrıca bence filc, yalnızca basit bir fat pointer ile açıklanabilecek bir şey de değil