Model Fil-C yang Disederhanakan
(corsix.org)- Struktur yang melacak pointer C/C++ bersama metadata AllocationRecord yang dilampirkan, lalu melakukan pemeriksaan batas memori saat dereferensi
- Cara kerja yang memindahkan nilai pointer asli beserta metadata yang sesuai, atau mengubahnya menjadi pemanggilan khusus Fil-C, mencakup penugasan pointer, aritmetika, pengiriman argumen fungsi, nilai balik, hingga pemanggilan
malloc·free - Metadata pointer di dalam memori heap disimpan terpisah di invisible_bytes; saat load·store pointer, nilai dan metadata dibaca serta ditulis bersama, dan pemeriksaan alignment juga diterapkan
filc_freehanya membebaskanvisible_bytesdaninvisible_bytessambil mempertahankan AllocationRecord itu sendiri; pembersihan setelahnya ditangani garbage collector, dan variabel lokal yang alamatnya mungkin escape akan mengalami promosi ke heap- Meski kompleksitas implementasi nyata seperti thread, function pointer, dan optimasi memori·kinerja masih tersisa, pendekatan ini tetap dapat dimanfaatkan untuk verifikasi keamanan memori pada kode C/C++ skala besar atau sebagai contoh sistem konkret untuk pointer provenance
Model Fil-C yang Disederhanakan
- Fil-C menggunakan struktur yang melacak metadata AllocationRecord* bersama pointer untuk menangani kode C/C++ secara aman terhadap memori
- Implementasi nyatanya menggunakan penulisan ulang LLVM IR, tetapi model sederhananya berbentuk transformasi otomatis atas kode sumber C/C++
- Untuk setiap variabel lokal bertipe pointer di tiap fungsi, ditambahkan variabel lokal
AllocationRecord*yang bersesuaian - Sebagai contoh, untuk
T1* p1ditambahkanAllocationRecord* p1ar = NULL
- Penugasan dan perhitungan sederhana pada variabel lokal pointer dilakukan dengan memindahkan AllocationRecord* bersama nilai pointer aslinya
p1 = p2diubah menjadip1 = p2, p1ar = p2arp1 = p2 + 10juga disertaip1ar = p2ar- Cast dari integer ke pointer mengatur metadata menjadi
NULL - Cast dari pointer ke integer dipertahankan apa adanya
- Pada pengiriman argumen fungsi dan nilai balik, AllocationRecord* juga ikut diteruskan bersama pointer, dan pemanggilan pustaka standar tertentu diganti dengan fungsi khusus Fil-C
- Pemanggilan
mallocdanfreemasing-masing diubah menjadi bentukfilc_malloc,filc_free - Sebagai contoh,
p1 = malloc(x); free(p1);menjadi{p1, p1ar} = filc_malloc(x); filc_free(p1, p1ar);
- Pemanggilan
filc_malloctidak hanya mengalokasikan satu blok memori yang diminta, tetapi melakukan tiga alokasi- Alokasi objek
AllocationRecord - Alokasi
visible_bytesuntuk data aktual - Alokasi
invisible_bytesdengancallocuntuk menyimpan metadata tak terlihat AllocationRecordberisi fieldvisible_bytes,invisible_bytes, danlength
- Alokasi objek
Dereferensi dan Pemeriksaan Batas
- Saat pointer didereferensi, dilakukan pemeriksaan batas menggunakan AllocationRecord* yang menyertainya
- Memastikan metadata pointer bukan
NULL - Menghitung selisih antara posisi pointer saat ini dan alamat awal
visible_bytes - Memastikan offset lebih kecil dari total panjang
- Memastikan panjang tersisa cukup untuk ukuran target dereferensi
- Memastikan metadata pointer bukan
- Baik pembacaan maupun penulisan menerapkan prosedur pemeriksaan yang sama
- Pemeriksaan dilakukan sebelum
x = *p1 - Pemeriksaan dengan bentuk yang sama juga dilakukan sebelum
*p2 = x
- Pemeriksaan dilakukan sebelum
- Dengan struktur ini, akses pointer yang melampaui rentang alokasi yang dituju dapat diblokir
Pointer di Dalam Heap dan invisible_bytes
- Pointer yang disimpan di memori heap tidak bisa dikelola compiler sebagai variabel terpisah seperti variabel lokal, sehingga digunakan invisible_bytes
- Jika ada pointer di posisi
visible_bytes + i, makaAllocationRecord*yang sesuai disimpan di posisiinvisible_bytes + i - Dengan kata lain,
invisible_bytesberfungsi seperti array dengan tipe elemenAllocationRecord*
- Jika ada pointer di posisi
- Saat membaca atau menulis nilai pointer dari/ke memori, selain pemeriksaan batas biasa juga ditambahkan pemeriksaan alignment
- Memastikan offset
iadalah kelipatan darisizeof(AllocationRecord*) - Hanya jika syarat ini terpenuhi
invisible_bytesdapat diakses dengan aman seperti arrayAllocationRecord**
- Memastikan offset
- Saat load pointer, metadata juga dimuat bersama pointer data
p2 = *p1diikuti dengan penambahanp2ar = *(AllocationRecord**)(p1ar->invisible_bytes + i)setelahp2 = *p1
- Saat store pointer, bukan hanya nilai pointer yang disimpan tetapi juga metadata yang bersesuaian
*p1 = p2akan menjalankan*(AllocationRecord**)(p1ar->invisible_bytes + i) = p2arsetelah penyimpanan data aktual
filc_free dan Garbage Collector
filc_freemembebaskan dua blok memori hanya ketika pointer bukanNULL, setelah melakukan pemeriksaan kecocokan dengan AllocationRecord- Memastikan
par != NULL - Memastikan
p == par->visible_bytes - Membebaskan
visible_bytesdaninvisible_bytes - Lalu mengubah
visible_bytes,invisible_bytesmenjadiNULLdanlengthmenjadi 0
- Memastikan
- Walau
filc_mallocmelakukan tiga alokasi,filc_freetidak membebaskan objek AllocationRecord itu sendiri- Perbedaan ini ditangani oleh garbage collector
- Untuk model sederhana, GC stop-the-world sudah memadai, sementara Fil-C nyata menggunakan kolektor paralel, konkuren, dan inkremental
- GC melakukan pelacakan dengan mengikuti objek
AllocationRecord AllocationRecordyang tak lagi dapat dijangkau diproses sebagai target pembebasan
- GC melakukan pelacakan dengan mengikuti objek
- GC juga melakukan dua tugas tambahan
- Saat membebaskan
AllocationRecordyang tak lagi dapat dijangkau, GC memanggilfilc_free - Semua pointer yang mengarah ke
AllocationRecorddenganlength0 diubah menjadi satuAllocationRecordnormal tunggal dengan panjang 0
- Saat membebaskan
- Dengan perilaku ini, tidak memanggil
freepun tidak otomatis berujung pada kebocoran memori- GC akan melakukan pembebasan secara otomatis
- Namun pemanggilan
freetetap memungkinkan memori dibebaskan lebih awal daripada GC
- Setelah
free,AllocationRecordterkait pada akhirnya akan menjadi tak terjangkau dan bisa dibersihkan kemudian
Escape Alamat Variabel Lokal dan Promosi ke Heap
- Dengan adanya GC, cakupan penanganan aman terhadap alamat variabel lokal menjadi lebih luas
- Jika alamat variabel lokal diambil, dan compiler tidak bisa membuktikan bahwa alamat itu tidak escape keluar dari masa hidup variabel, maka variabel tersebut dipromosikan menjadi alokasi heap
- Variabel lokal seperti ini dialokasikan melalui
malloc, bukan di stack- Tidak perlu menyisipkan
freeyang terpisah - GC yang akan menangani pengumpulannya
- Tidak perlu menyisipkan
Versi memmove ala Fil-C
memmovedi pustaka standar C menangani memori arbitrer, sehingga compiler menghadapi masalah karena tidak dapat mengetahui apakah di dalamnya ada pointer- Untuk mengatasi ini digunakan heuristik
- Pointer di dalam memori arbitrer harus sepenuhnya berada di dalam rentang memori tersebut
- Pointer harus ter-align dengan benar
- Karena aturan ini, bahkan pemindahan 8 byte yang sama pun dapat menghasilkan perilaku berbeda
- Jika 8 byte yang ter-align dipindahkan sekaligus dengan
memmove, bagianinvisible_bytesyang sesuai juga ikut dipindahkan - Jika
memmovedilakukan 8 kali masing-masing 1 byte,invisible_bytestidak ikut dipindahkan
- Jika 8 byte yang ter-align dipindahkan sekaligus dengan
Kompleksitas Tambahan dalam Implementasi Nyata
-
Thread
- Konkruensi adalah faktor yang meningkatkan kompleksitas GC
filc_freetidak dapat langsung membebaskan memori- Karena bisa terjadi race condition antara thread yang sedang membebaskan memori dan thread lain yang mengakses memori yang sama
- Operasi atomik pada pointer juga memerlukan penanganan tambahan
- Karena penulisan ulang dasar mengubah load/store pointer menjadi dua kali load/store, sehingga merusak atomisitas
-
Function pointer
- Metadata tambahan pada
AllocationRecordmenandai apakahvisible_bytesbukan data biasa melainkan pointer ke kode yang dapat dieksekusi - Pemanggilan melalui function pointer
p1memeriksap1 == p1ar->visible_bytesdan juga memeriksa apakahp1armemang merepresentasikan function pointer - Untuk mencegah serangan type confusion pada function pointer, verifikasi type signature juga diperlukan pada ABI pemanggilan
- Salah satu caranya adalah membuat semua fungsi memiliki type signature yang sama
- Semua argumen diperlakukan seolah-olah dimasukkan ke dalam struct dan diteruskan lewat memori
- Di batas ABI, setiap fungsi menerima hanya satu
AllocationRecordtunggal yang sesuai dengan struct tersebut
- Metadata tambahan pada
-
Optimasi penggunaan memori
- Dapat dipertimbangkan agar
filc_malloctidak langsung mengalokasikaninvisible_bytes, tetapi menundanya sampai benar-benar diperlukan - Dapat juga dipertimbangkan menempatkan
AllocationRecorddanvisible_bytesbersama dalam satu alokasi - Jika
mallocdasar menempelkan metadata di bagian depan setiap alokasi, metadata itu juga bisa dimasukkan ke dalamAllocationRecord
- Dapat dipertimbangkan agar
-
Optimasi kinerja
- Keamanan memori Fil-C datang dengan biaya performa
- Masih ada ruang untuk menerapkan berbagai teknik guna merebut kembali sebagian performa yang hilang
Kapan Fil-C Digunakan
- Fil-C dapat digunakan ketika kode C/C++ skala besar tampak berjalan, tetapi verifikasi keamanan memorinya tidak ada, dan demi keamanan memori pengguna bersedia menerima penggunaan GC serta penurunan performa yang besar
- Disebut sebagai kemungkinan langkah sementara sebelum menulis ulang ke Java, Go, atau Rust
- Fil-C juga bisa dijalankan untuk tujuan pendeteksian bug memori seperti ASan
- Kode C/C++ dapat dijalankan di bawah Fil-C untuk memeriksa bug memori
- Pada bahasa yang bahasa saat compile time dan runtime-nya sama, serta memiliki keamanan compile time yang kuat, Fil-C bisa dipakai untuk evaluasi compile time yang aman
- Zig disebut sebagai contoh
- Meski evaluasi runtime tidak aman, evaluasi compile time dapat menggunakan konfigurasi ala Fil-C
- Fil-C juga bermakna sebagai contoh sistem konkret yang menangani pointer provenance
- Diajukan pertanyaan tentang kemungkinan optimasi mengubah
if (p1 == p2) { f(p1); }menjadiif (p1 == p2) { f(p2); }saat tipep1danp2sama - Di Fil-C, karena AllocationRecord* yang diteruskan ke
fbisa berbeda, jawabannya dinyatakan jelas: tidak bisa - Dalam hal ini, Fil-C berperan sebagai contoh sistem konkret yang memiliki pointer provenance
- Diajukan pertanyaan tentang kemungkinan optimasi mengubah
1 komentar
Opini Hacker News
Ada juga ruang untuk menguji reference counting atau varian dari invisible capability system, dan mungkin bisa menghemat memori dengan imbalan sedikit biaya indirect reference
Semoga ini membantu orang yang ingin membuat hermetic builds dengan menggunakan keduanya bersama-sama
Upon freeing an unreachable AllocationRecord, call filc_free on it.Menurut saya, maksudnya adalah sebelum membebaskan AR yang tidak dapat dijangkau, bebaskan dulu memori yang ditunjuk oleh field
visible_bytesdaninvisible_bytesDibanding mengatakan “rewrite it in Rust” demi keamanan, gagasan bahwa program C yang sudah ada bisa dikompilasi menjadi sepenuhnya memory-safe terasa jauh lebih menarik
Pertama, Fil-C itu lebih lambat dan lebih besar. Kalau itu tidak masalah, orang mungkin sudah memilih Java atau C# lebih dulu daripada Rust dalam 10 tahun terakhir
Kedua, ini tetap berarti menulis C. Untuk memelihara kode lama itu masuk akal, tetapi kalau banyak menulis kode baru, saya merasa Rust jauh lebih nyaman
Ketiga, Fil-C adalah keamanan runtime, sedangkan Rust bisa mengekspresikan sebagian hal di compile time. Lebih jauh lagi, bahasa seperti WUFFS berusaha membuktikan keamanan di tahap kompilasi tanpa runtime check, jadi meskipun kodenya masih bisa salah secara logis, arahnya adalah mencegah crash atau arbitrary code execution
Ada thread seperti 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, dan Fil's Unbelievable Garbage Collector
Anda masih bisa menulis kode yang tidak memory-safe, hanya saja sekarang hasilnya lebih cenderung menjadi crash yang pasti daripada kerentanan
Jika Anda membuat sesuatu seperti web API yang menerima input tak tepercaya, masalah seperti ini pada akhirnya bisa berujung pada denial-of-service, jadi memang lebih baik, tetapi sulit dibilang sudah cukup baik
Saya tidak bermaksud meremehkan pekerjaan Fil-C, hanya saja pendekatan runtime memang punya batasan yang jelas
Namun, agar adil, Fil-C cukup jauh lebih lambat daripada Rust, dan juga memakai lebih banyak memori
Di sisi lain, Fil-C mendukung safe dynamic linking, dan dalam beberapa hal bisa dibilang lebih ketat soal keamanan daripada Rust
Pada akhirnya ini trade-off, jadi menurut saya tinggal pilih sesuai kebutuhan masing-masing
Jadi meskipun ide ini menarik secara teknis, secara emosional tampaknya tidak mudah diterima
Nilai capability dan pointer bisa terkoyak saat assignment, sehingga bila interleaving thread sedang buruk, objek bisa diakses dengan pointer yang salah dan memicu arbitrary misbehavior
Keterbatasan seperti ini sendiri masih bisa diterima, tetapi saya menyayangkan suasana ketika orang yang menunjukkan masalah ini justru diserang keras bahkan oleh para pendukungnya
Sayangnya, itu juga salah satu penyebab besar overhead-nya
Pendekatan seperti ini sudah berkali-kali diimplementasikan lalu ditinggalkan karena jaminan keamanannya tidak cukup, sulit melewati non-fat ABI boundaries, atau overhead-nya besar
Lagi pula, menurut saya filc juga tidak bisa dijelaskan hanya sebagai fat pointer biasa