網路字型記憶體安全性

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

發布日期:2025 年 3 月 19 日

Skrifa 是以 Rust 編寫,用來取代 FreeType,確保所有使用者在 Chrome 中處理字型時的安全。Skrifa 採用 Rust 的記憶體安全機制,可讓我們在 Chrome 中更快疊代字型技術改良項目。從 FreeType 遷移至 Skrifa 後,我們在變更字型程式碼時,就能兼顧靈活度和無畏精神。我們現在修正安全性錯誤的時間大幅減少,因此更新速度更快,程式碼品質也更好。

這篇文章將說明 Chrome 不再使用 FreeType 的原因,以及這項異動帶來的改良功能和相關技術細節。

為何要取代 FreeType?

網路的獨特之處在於,使用者可以從各種不受信任的來源擷取不受信任的資源,並期望一切正常運作,且這麼做是安全的。這項假設大致上正確,但要向使用者保證這一點,需要付出代價。舉例來說,為了安全地使用網路字型 (透過網路傳送的字型),Chrome 採用了多種安全防護措施:

  • 字型處理程序會根據「二的法則沙箱化:字型處理程序不可信,且耗用程式碼不安全。
  • 字型會先通過 OpenType Sanitizer,再進行處理。
  • 所有參與字型解壓縮和處理作業的程式庫都經過模糊測試

Chrome 隨附 FreeType,並在 Android、ChromeOS 和 Linux 上將其做為主要字型處理程式庫。這表示如果 FreeType 存在安全漏洞,大量使用者就會受到影響。

Chrome 會使用 FreeType 程式庫計算指標,並從字型載入提示的大綱。總體而言,使用 FreeType 對 Google 來說是一大勝利。這項工具能勝任複雜的工作,而且表現優異,因此我們大量使用並回饋給社群。不過,這項函式是以不安全的程式碼編寫,而且源自於惡意輸入較少的時期。光是跟上模糊測試發現的問題串,就讓 Google 至少要投入 0.25 位全職軟體工程師。更糟的是,我們顯然無法找到所有內容,或是在程式碼發布給使用者後才找到。

這類問題並非 FreeType 獨有,我們發現即使使用最優秀的軟體工程師、進行程式碼審查並要求進行測試,其他不安全的程式庫仍會出現問題。

為什麼問題會不斷出現

評估 FreeType 的安全性時,我們觀察到三種主要問題 (不完全列舉):

使用不安全的語言

模式/問題 範例
手動記憶體管理
未經檢查的陣列存取 CVE-2022-27404
整數溢位 執行 CFF 繪圖和提示的 TrueType 提示嵌入式虛擬機器時
https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow
未正確使用歸零與非歸零分配 https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94 的討論內容,隨後發現 8 個模糊測試器問題
無效的轉換 請參閱以下巨集使用情形列

專案專屬問題

模式/問題 範例
巨集會遮蓋缺少明確大小型別的情況
  • FT_READ_*FT_PEEK_* 等巨集會遮蓋所用的整數型別,隱藏未使用的 C99 型別 (具有明確大小,例如 int16_t 等)
即使是防禦性程式碼,新程式碼仍會持續新增錯誤。
  • COLRv1 和 OT-SVG 支援都產生問題
  • 模糊測試會找出部分 (但不一定全部) #32421#52404
缺少測試
  • 製作測試字型既耗時又困難

依附元件問題

模糊測試已在 FreeType 依附的程式庫中重複發現問題,例如 bzip2、libpng 和 zlib。舉例來說,比較 freetype_bdf_fuzzer:inflate 中使用未初始化的值

模糊測試不夠

模糊測試是使用各種輸入內容 (包括隨機無效的輸入內容) 進行自動測試,目的是找出許多會進入 Chrome 穩定版的問題類型。我們在 Google 的 oss-fuzz 專案中模糊處理 FreeType。雖然會發現問題,但由於下列原因,字型在某種程度上可抵禦模糊測試。

字型檔案相當複雜,可比擬影片檔案,因為當中包含多種不同類型的資訊。字型檔案是多個表格的容器格式,每個表格在處理文字和字型時都有不同用途,可共同在畫面上產生正確位置的字元。字型檔案包含:

  • 靜態中繼資料,例如字型名稱和可變字型的參數。
  • 從 Unicode 字元到字形的對應。
  • 複雜的規則集和文法,用於設定字形的螢幕版面配置。
  • 視覺資訊:字元形狀和圖片資訊,說明螢幕上顯示的字元外觀。
    • 視覺化表格則可包含 TrueType 字型微調技術程式,這些是執行來變更字符形狀的小型程式。
    • CFF 或 CFF2 資料表中的字元字串,是 CFF 算繪引擎執行的必要曲線繪製和字型微調技術指令。

字型檔案的複雜程度相當於擁有自己的程式設計語言和狀態機器處理程序,因此需要特定的虛擬機器才能執行。

由於格式複雜,模糊測試在尋找字型檔案問題時有缺點。

由於下列原因,很難達到良好的程式碼涵蓋率或模糊測試器進度:

  • 使用簡單的位元翻轉/位移/插入/刪除樣式突變子,對 TrueType 提示程式、CFF 字元字串和 OpenType 版面配置進行模糊測試時,很難觸及所有狀態組合。
  • 模糊測試至少要產生部分有效的結構。隨機突變很少會這樣做,因此很難達成良好的涵蓋範圍,尤其是較深層級的程式碼。
  • ClusterFuzz 和 oss-fuzz 目前的模糊測試工作尚未採用結構感知突變。使用文法或結構感知突變子,或許有助於避免產生早期遭拒的變體,但會增加開發時間,並可能錯過部分搜尋空間。

多個資料表中的資料必須同步處理,模糊測試才能繼續進行:

  • 模糊測試器的常見突變模式不會產生部分有效的資料,因此許多疊代都會遭到拒絕,進度也會變慢。
  • 字元形狀對應、OpenType 版面配置表和字元形狀繪製功能彼此相連並相互依附,形成組合空間,而模糊測試難以觸及這個空間的角落。
  • 舉例來說,高嚴重程度的 tt_face_get_paint COLRv1 安全漏洞花了超過 10 個月才找到。

儘管我們已盡力防範,字型安全性問題仍不斷影響終端使用者。以 Rust 替代方案取代 FreeType,可避免多個整類型的安全漏洞。

在 Chrome 中使用 Skrifa

Skia 是 Chrome 使用的圖形程式庫。Skia 依賴 FreeType 從字型載入中繼資料和字形。Skrifa 是 Rust 程式庫,屬於 Fontations 系列程式庫,可安全地取代 Skia 使用的部分 FreeType。

為將 FreeType 轉換為 Skia,Chrome 團隊開發了以 Skrifa 為基礎的新 Skia 字型後端,並逐步向使用者推出這項變更:

為了整合至 Chrome,我們仰賴 Chrome 安全性團隊導入的程式碼集,將 Rust 順利整合至其中。

未來我們也會將作業系統字型切換為 Fontations,首先是 Linux 和 ChromeOS,然後是 Android。

安全至上

我們的首要目標是減少 (或最好是消除!) 因記憶體存取超出範圍而導致的安全漏洞。只要避免使用任何不安全的程式碼區塊,Rust 就會提供這項功能。

為達成效能目標,我們必須執行目前不安全的作業:將任意位元組重新解讀為強型別資料結構。這樣一來,我們就能從字型檔案讀取資料,而不必執行不必要的複製作業,這對於產生快速字型剖析器至關重要。

為避免使用不安全的程式碼,我們選擇將這項責任外包給 bytemuck,這是專為此目的設計的 Rust 程式庫,且已在整個生態系統中經過廣泛測試和使用。在 bytemuck 中集中重新解讀原始資料,可確保我們在一個位置擁有這項功能並經過稽核,且避免重複使用不安全的程式碼。安全轉移專案的目標是將這項功能直接併入 Rust 編譯器,我們會在功能推出後立即切換。

正確性至關重要

Skrifa 是由獨立元件建構而成,大多數資料結構都設計為不可變更。這有助於提升可讀性、可維護性和多執行緒功能。此外,程式碼也更適合進行單元測試。我們把握這個機會,製作了約 700 個單元測試,涵蓋從低階剖析常式到高階字型微調技術虛擬機器的完整堆疊。

正確性也代表忠實度,而 FreeType 生成高品質輪廓的能力備受肯定。我們必須達到這個品質,才能成為合適的替代方案。為此,我們打造了名為「fauntlet」的專屬工具,可比較 Skrifa 和 FreeType 在各種設定下,對批次字型檔案的輸出結果。這可確保我們能避免品質倒退。

此外,在整合至 Chromium 之前,我們在 Skia 中進行了廣泛的像素比較,比較 FreeType 算繪與 Skrifa,以及 Skia 算繪,確保所有必要算繪模式 (包括不同反鋸齒和提示模式) 的像素差異絕對極小。

模糊測試是重要的工具,可判斷軟體對格式錯誤和惡意輸入內容的反應。自 2024 年 6 月起,我們就不斷對新程式碼進行模糊測試。這涵蓋 Rust 程式庫本身和整合代碼。截至本文撰寫時,模糊測試器已找出 39 個錯誤,但值得注意的是,這些錯誤都不屬於安全關鍵。這類錯誤可能會導致不想要的視覺效果,甚至造成受控當機,但不會導致可供利用的安全性弱點。

Onward!

我們很高興能使用 Rust 處理文字,並獲得豐碩成果。為使用者提供更安全的程式碼提升開發人員生產力,對我們來說是一大成就。我們計畫繼續尋找在文字堆疊中使用 Rust 的機會。如要瞭解詳情,請參閱 Oxidize 說明文件,瞭解 Google Fonts 未來的計畫。