網路字型記憶體安全性

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

發布日期:2025 年 3 月 19 日

Skrifa 是使用 Rust 編寫,用來取代 FreeType,讓 Chrome 中的字型處理作業對所有使用者都安全。Skifra 可充分利用 Rust 的記憶體安全性,讓我們更快地在 Chrome 中改善字型技術。從 FreeType 改用 Skrifa 後,我們就能在變更字型程式碼時,靈活且無所畏懼。我們現在花費的時間大幅減少,因此可以更快更新,並提升程式碼品質。

這篇文章將說明 Chrome 為何改用 FreeType,以及這項改變帶來的改善,並提供一些有趣的技術細節。

為什麼要取代 FreeType?

網路的獨特之處在於,它允許使用者從各種不受信任的來源擷取不受信任的資源,並期望一切運作順利,且安全無虞。這項假設通常正確無誤,但要向使用者保證這點,就必須付出代價。舉例來說,為了安全使用網路字型 (透過網路提供的字型),Chrome 採用了多項安全防護措施:

Chrome 隨附 FreeType,並將其用於 Android、ChromeOS 和 Linux 上的主要字型處理程式庫。也就是說,如果 FreeType 存在安全漏洞,就會影響大量使用者。

Chrome 會使用 FreeType 程式庫來計算指標,並從字型載入提示輪廓。整體而言,Google 使用 FreeType 獲得了巨大的勝利。它能執行複雜的工作,而且做得很好,我們也非常依賴它,並為其做出貢獻。不過,這項功能是使用不安全的程式碼編寫,而且起源於惡意輸入內容較少的時期。光是追蹤模糊測試發現的問題,就需要 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 中使用未初始化的值

模糊測試不足以防範所有漏洞

模糊測試 (Fuzzing) 是指使用各種輸入內容 (包括隨機無效的輸入內容) 進行自動化測試,目的是找出許多類型的 Chrome 穩定版問題。我們會在 Google 的 oss-fuzz 專案中模糊處理 FreeType。它確實會找出問題,但字型已證實可在一定程度上抵禦模糊處理,原因如下:

字型檔案與影片檔案相似,都包含多種不同類型的資訊,因此相當複雜。字型檔案是多個表格的容器格式,每個表格都有不同的用途,可一起處理文字和字型,在螢幕上產生正確位置的字形。字型檔案包含以下內容:

  • 靜態中繼資料,例如變數字型名稱和參數。
  • 從 Unicode 字元對應至字形。
  • 用於字符螢幕版面配置的複雜規則集和文法。
  • 視覺資訊:符號形狀和圖像資訊,說明放置在螢幕上的符號外觀。
    • 視覺表格可以反過來包含 TrueType 提示程式,這是執行變更字元形狀的迷你程式。
    • CFF 或 CFF2 表格中的字元字串,這些字串是 CFF 算繪引擎中執行的必要曲線繪製和提示指示。

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

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

以下是難以達成良好程式碼涵蓋率或模糊測試進度的幾個原因:

  • 使用簡單的位元翻轉/移位/插入/刪除式變異子,對 TrueType 提示程式、CFF 字元字串和 OpenType 版面配置進行模糊測試,很難達到所有狀態組合。
  • 模糊測試至少需要產生部分有效的結構體。隨機突變很少會這樣做,因此很難達到良好的涵蓋率,尤其是在較深層的程式碼中。
  • ClusterFuzz 和 oss-fuzz 目前的模糊測試作業尚未使用結構感知突變。使用文法或結構感知變異器,或許有助於避免產生早期遭到拒絕的變化版本,但代價是需要花費更多時間進行開發,並引入遺漏搜尋空間的部分。

多個資料表中的資料必須保持同步,才能讓模糊測試有所進展:

  • 模糊測試器的常見變異模式不會產生部分有效的資料,因此許多迭代作業會遭到拒絕,進度也會變慢。
  • 符號對應、OpenType 版面配置表和符號繪製功能彼此相連且相互依賴,形成一個組合空間,而這個空間的邊角很難透過模糊處理來處理。
  • 舉例來說,高嚴重性的 tt_face_get_paint COLRv1 安全漏洞就花了超過 10 個月才找到。

儘管我們盡力避免,但字型安全性問題仍一再影響使用者。將 FreeType 替換為 Rust 替代方案,可避免多個完整類別的漏洞。

在 Chrome 中使用 Skrifa

Skia 是 Chrome 使用的圖形程式庫。Skia 會使用 FreeType 從字型載入中繼資料和字母外型。Skrifa 是 Rust 程式庫,屬於 Fontations 系列程式庫,可為 Skia 使用的 FreeType 部分提供安全的替代方案。

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

為了將 Rust 整合至 Chrome,我們依賴 Chrome 安全團隊推出的程式碼集,讓 Rust 順利整合至其中。

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

安全第一

我們的首要目標是減少 (理想情況下是完全消除) 因記憶體超出邊界存取權限而造成的安全漏洞。只要避免使用任何「不安全」程式碼區塊,Rust 就會提供這項功能。

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

為避免使用不安全的程式碼,我們選擇將這項工作外包給 bytemuck,這是專為此目的 Rust 程式庫,在整個生態系統中經過廣泛測試和使用。將原始資料重新解讀作業集中在 bytemuck 中,可確保我們將這項功能集中在一個地方並進行稽核,並避免重複使用不安全的程式碼。安全轉換專案旨在將這項功能直接納入 Rust 編譯器,我們會在可用時立即切換。

正確性至關重要

Skrifa 是由獨立元件建構而成,其中大多數資料結構的設計都是不可變動的。這可改善可讀性、可維護性和多執行緒。這也讓程式碼更容易進行單元測試。我們把握這個機會,製作了約 700 個單元測試,涵蓋從低階剖析例程到高階提示虛擬機器的完整堆疊。

正確性也意味著忠實度,而 FreeType 之所以備受推崇,就是因為它能產生高品質的輪廓。我們必須提供相同品質的內容,才能提供適當的替代品。為此,我們特別打造了名為 fauntlet 的專屬工具,可比較 Skrifa 和 FreeType 在各種設定下對批次字型檔案的輸出結果。這可讓我們確保避免品質倒退。

此外,在整合至 Chromium 之前,我們在 Skia 中執行一系列像素比較,將 FreeType 算繪與 Skrifa 和 Skia 算繪進行比較,確保在所有必要的算繪模式 (跨越各種反鋸齒和提示模式) 中,像素差異降到最低。

模糊測試是決定軟體如何回應不正確和惡意輸入內容的重要工具。自 2024 年 6 月起,我們持續對新程式碼進行模糊測試。這包括 Rust 程式庫本身和整合代碼。雖然模糊測試器 (截至本文撰寫時) 已發現 39 個錯誤,但值得注意的是,這些錯誤都不是安全性重大問題。這些問題可能會導致不必要的視覺效果,甚至是受控的當機情形,但不會導致可供利用的漏洞。

繼續前進!

我們很高興使用 Rust 處理文字的成果。為使用者提供更安全的程式碼,並提升開發人員的工作效率,對我們來說都是一大勝利。我們預計會持續尋找在文字堆疊中使用 Rust 的機會。如要進一步瞭解,請參閱 Oxidize 中列出的 Google Fonts 未來計畫。