Keamanan memori untuk font web

Dominik Röttsches
Dominik Röttsches
Rod Sheeter
Rod Sheeter
Chad Brokaw
Chad Brokaw

Dipublikasikan: 19 Maret 2025

Skrifa ditulis dalam Rust, dan dibuat sebagai pengganti FreeType untuk membuat pemrosesan font di Chrome aman bagi semua pengguna kami. Skifra memanfaatkan keamanan memori Rust, dan memungkinkan kita melakukan iterasi lebih cepat pada peningkatan teknologi font di Chrome. Beralih dari FreeType ke Skrifa memungkinkan kita menjadi lincah dan berani saat membuat perubahan pada kode font. Kini kami menghabiskan jauh lebih sedikit waktu untuk memperbaiki bug keamanan, sehingga menghasilkan update yang lebih cepat, dan kualitas kode yang lebih baik.

Postingan ini menjelaskan alasan Chrome beralih dari FreeType, dan beberapa detail teknis menarik tentang peningkatan yang diaktifkan oleh langkah ini.

Mengapa FreeType diganti?

Web bersifat unik karena memungkinkan pengguna mengambil resource tidak tepercaya dari berbagai sumber tidak tepercaya dengan harapan bahwa semuanya akan berfungsi, dan mereka aman melakukannya. Asumsi ini umumnya benar, tetapi ada konsekuensinya jika Anda memenuhi janji tersebut kepada pengguna. Misalnya, untuk menggunakan font web dengan aman (font yang dikirimkan melalui jaringan), Chrome menggunakan beberapa mitigasi keamanan:

  • Pemrosesan font di-sandbox sesuai dengan aturan dua: font tidak dapat dipercaya dan kode yang menggunakannya tidak aman.
  • Font diteruskan melalui OpenType Sanitizer sebelum diproses.
  • Semua library yang terlibat dalam mendekompresi dan memproses font diuji fuzz.

Chrome dilengkapi dengan FreeType dan menggunakannya sebagai library pemrosesan font utama di Android, ChromeOS, dan Linux. Artinya, banyak pengguna akan terekspos jika ada kerentanan di FreeType.

Library FreeType digunakan oleh Chrome untuk menghitung metrik dan memuat garis batas yang diisyaratkan dari font. Secara keseluruhan, penggunaan FreeType telah menjadi keuntungan besar bagi Google. CPU melakukan tugas yang kompleks, dan melakukannya dengan baik, kami mengandalkannya secara ekstensif dan berkontribusi kembali ke CPU. Namun, kode ini ditulis dalam kode yang tidak aman dan berasal dari saat input berbahaya kurang mungkin terjadi. Hanya mengikuti aliran masalah yang ditemukan oleh fuzzing, Google memerlukan setidaknya 0,25 engineer software purnawaktu. Lebih buruk lagi, kita tidak dapat menemukan semuanya atau hanya menemukan hal-hal setelah kode dikirim ke pengguna.

Pola masalah ini tidak unik untuk FreeType, kami mengamati bahwa library tidak aman lainnya mengalami masalah meskipun kami menggunakan engineer software terbaik yang dapat kami temukan, peninjauan kode setiap perubahan, dan memerlukan pengujian.

Alasan masalah terus muncul

Saat mengevaluasi keamanan FreeType, kami mengamati tiga jenis masalah utama yang terjadi (tidak lengkap):

Penggunaan bahasa yang tidak aman

Pola/Masalah Contoh
Pengelolaan memori manual
Akses array yang tidak dicentang CVE-2022-27404
Kelebihan bilangan bulat Selama eksekusi virtual machine tersemat untuk petunjuk TrueType dari gambar dan petunjuk CFF
https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow
Penggunaan alokasi nol versus non-nol yang salah Diskusi di https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94, 8 masalah fuzzer ditemukan setelahnya
Transmisi tidak valid Lihat baris berikut tentang penggunaan makro

Masalah khusus project

Pola/Masalah Contoh
Makro mengaburkan kurangnya pengetikan ukuran yang eksplisit
  • Makro seperti FT_READ_* dan FT_PEEK_* mengaburkan jenis bilangan bulat yang digunakan, sehingga jenis C99 dengan ukuran eksplisit (int16_t, dll.) tidak digunakan
Kode baru secara konsisten menambahkan bug, meskipun ditulis secara defensif.
  • COLRv1 dan OT-SVG mendukung masalah yang dihasilkan
  • Fuzzing menemukan beberapa, tetapi tidak semua, #32421, #52404
Kurangnya pengujian
  • Membuat font pengujian memakan waktu dan sulit

Masalah dependensi

Fuzzing telah berulang kali mengidentifikasi masalah dalam library yang menjadi dependensi FreeType, seperti bzip2, libpng, dan zlib. Sebagai contoh, bandingkan freetype_bdf_fuzzer: Use-of-uninitialized-value in inflate.

Fuzzing saja tidak cukup

Fuzzing–pengujian otomatis dengan berbagai input, termasuk input yang tidak valid secara acak – dimaksudkan untuk menemukan banyak jenis masalah yang masuk ke dalam rilis stabil Chrome. Kami melakukan fuzzing FreeType sebagai bagian dari project oss-fuzz Google. Alat ini memang menemukan masalah, tetapi font telah terbukti agak tahan terhadap fuzzing, karena alasan berikut.

File font bersifat kompleks, sebanding dengan file video karena berisi beberapa jenis informasi yang berbeda. File font adalah format penampung untuk beberapa tabel, dengan setiap tabel memiliki tujuan yang berbeda dalam memproses teks dan font bersama-sama untuk menghasilkan glyph yang diposisikan dengan benar di layar. Dalam file font, Anda akan menemukan:

  • Metadata statis seperti nama font dan parameter untuk font variabel.
  • Pemetaan dari karakter Unicode ke glyph.
  • Aturan dan tata bahasa kompleks untuk tata letak layar glyph.
  • Informasi visual: Bentuk glyph dan informasi gambar yang menjelaskan tampilan glyph yang ditempatkan di layar.
    • Tabel visual pada gilirannya dapat menyertakan program petunjuk TrueType, yang adalah program mini yang dijalankan untuk mengubah bentuk glyph.
    • String karakter dalam tabel CFF atau CFF2 yang merupakan petunjuk dan gambar kurva imperatif yang dieksekusi di mesin rendering CFF.

Ada kompleksitas dalam file font yang setara dengan memiliki bahasa pemrograman dan pemrosesan mesin statusnya sendiri, yang memerlukan virtual machine tertentu untuk menjalankannya.

Karena kompleksitas formatnya, fuzzing memiliki kekurangan dalam menemukan masalah dalam file font.

Cakupan kode atau progres fuzzer yang baik sulit dicapai karena alasan berikut:

  • Fuzzing program petunjuk TrueType, string karakter CFF, dan tata letak OpenType menggunakan mutator gaya bit-flipping/shift/insertion/deletion sederhana kesulitan menjangkau semua kombinasi status.
  • Fuzzing setidaknya harus menghasilkan struktur yang valid sebagian. Mutasi acak jarang melakukannya, sehingga cakupan yang baik sulit dicapai, terutama untuk tingkat kode yang lebih dalam.
  • Upaya fuzzing saat ini di ClusterFuzz dan oss-fuzz belum menggunakan mutasi berbasis struktur. Penggunaan mutator yang memahami tata bahasa atau struktur dapat membantu menghindari produksi varian yang ditolak lebih awal, dengan mengorbankan waktu yang lebih lama untuk dikembangkan, dan memperkenalkan peluang yang melewatkan bagian dari ruang penelusuran.

Data di beberapa tabel harus disinkronkan agar fuzzing dapat membuat progres:

  • Pola mutasi fuzzer yang biasa tidak menghasilkan data yang valid sebagian sehingga banyak iterasi yang ditolak dan progres menjadi lambat.
  • Pemetaan glyph, tabel tata letak OpenType, dan gambar glyph terhubung dan saling bergantung, membentuk ruang kombinatoris yang sudut-sudutnya sulit dijangkau dengan fuzzing.
  • Misalnya, kerentanan tt_face_get_paint COLRv1 dengan tingkat keparahan tinggi memerlukan waktu lebih dari 10 bulan untuk ditemukan.

Meskipun kami telah berupaya sebaik mungkin, masalah keamanan font berulang kali menjangkau pengguna akhir. Mengganti FreeType dengan alternatif Rust akan mencegah beberapa kelas kerentanan.

Skrifa di Chrome

Skia adalah library grafis yang digunakan oleh Chrome. Skia mengandalkan FreeType untuk memuat metadata dan bentuk huruf dari font. Skrifa adalah library Rust, bagian dari library keluarga Fontations, yang menyediakan pengganti yang aman untuk bagian FreeType yang digunakan oleh Skia.

Untuk melakukan transisi FreeType ke Skia, tim Chrome mengembangkan backend font Skia baru berdasarkan Skrifa dan meluncurkan perubahan secara bertahap kepada pengguna:

Untuk integrasi ke Chrome, kami mengandalkan integrasi Rust yang lancar ke dalam codebase yang diperkenalkan oleh tim keamanan Chrome.

Di masa mendatang, kami juga akan beralih ke Fontations untuk font sistem operasi, mulai dari Linux dan ChromeOS, lalu di Android.

Keselamatan, yang paling penting

Sasaran utama kami adalah mengurangi (atau idealnya, menghilangkan!) kerentanan keamanan yang disebabkan oleh akses di luar batas ke memori. Rust menyediakannya secara otomatis selama Anda menghindari blok kode yang tidak aman.

Sasaran performa kami mengharuskan kita melakukan satu operasi yang saat ini tidak aman: penafsiran ulang byte arbitrer sebagai struktur data dengan jenis yang kuat. Hal ini memungkinkan kita membaca data dari file font tanpa melakukan penulisan ulang yang tidak perlu dan sangat penting untuk menghasilkan parser font yang cepat.

Untuk menghindari kode yang tidak aman, kami memilih untuk melakukan outsourcing tanggung jawab ini ke bytemuck yang merupakan library Rust yang dirancang khusus untuk tujuan ini dan diuji serta digunakan secara luas di seluruh ekosistem. Memfokuskan reinterpretasi data mentah di bytemuck memastikan kita memiliki fungsi ini di satu tempat dan diaudit, serta menghindari pengulangan kode yang tidak aman untuk tujuan tersebut. Project transmutasi aman bertujuan untuk menggabungkan fungsi ini langsung ke compiler Rust dan kami akan melakukan pengalihan segera setelah tersedia.

Ketepatan itu penting

Skrifa dibuat dari komponen independen dengan sebagian besar struktur data dirancang agar tidak dapat diubah. Hal ini meningkatkan keterbacaan, kemudahan pemeliharaan, dan multithreading. Hal ini juga membuat kode lebih mudah untuk pengujian unit. Kami telah memanfaatkan peluang ini dan telah menghasilkan serangkaian sekitar 700 pengujian unit yang mencakup stack lengkap kami dari rutinitas penguraian tingkat rendah hingga virtual machine petunjuk tingkat tinggi.

Kebenaran juga menyiratkan fidelitas dan FreeType sangat dihargai karena pembuatan garis batas berkualitas tinggi. Kami harus mencocokkan kualitas ini agar menjadi pengganti yang sesuai. Untuk itu, kami telah membuat alat khusus yang disebut fauntlet yang membandingkan output Skrifa dan FreeType untuk batch file font di berbagai konfigurasi. Hal ini memberi kita beberapa jaminan bahwa kita dapat menghindari regresi kualitas.

Selain itu, sebelum integrasi ke Chromium, kami menjalankan kumpulan perbandingan piksel yang luas di Skia, yang membandingkan rendering FreeType dengan rendering Skrifa dan Skia untuk memastikan perbedaan piksel benar-benar minimal, di semua mode rendering yang diperlukan (di berbagai mode anti-aliasing dan petunjuk).

Pengujian fuzz adalah alat penting untuk menentukan bagaimana sebuah software akan bereaksi terhadap input yang salah format dan berbahaya. Kami terus melakukan fuzzing pada kode baru sejak Juni 2024. Hal ini mencakup library Rust itu sendiri dan kode integrasi. Meskipun fuzzer telah menemukan (hingga penulisan ini) 39 bug, perlu diperhatikan bahwa tidak ada satu pun dari bug tersebut yang bersifat kritis terhadap keamanan. Hal ini dapat menyebabkan hasil visual yang tidak diinginkan atau bahkan error terkontrol, tetapi tidak akan menyebabkan kerentanan yang dapat dieksploitasi.

Mari kita lanjutkan.

Kami sangat puas dengan hasil upaya kami untuk menggunakan Rust untuk teks. Memberikan kode yang lebih aman kepada pengguna dan meningkatkan produktivitas developer adalah pencapaian besar bagi kami. Kami berencana untuk terus mencari peluang untuk menggunakan Rust dalam stack teks kami. Jika Anda ingin mengetahui lebih lanjut, Oxidize menguraikan beberapa rencana mendatang Google Fonts.