An toàn bộ nhớ cho phông chữ web

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

Ngày phát hành: 19 tháng 3 năm 2025

Skrifa được viết bằng Rust và được tạo để thay thế FreeType nhằm đảm bảo quá trình xử lý phông chữ trong Chrome an toàn cho tất cả người dùng. Skifra tận dụng tính năng an toàn bộ nhớ của Rust và cho phép chúng tôi lặp lại nhanh hơn các điểm cải tiến về công nghệ phông chữ trong Chrome. Việc chuyển từ FreeType sang Skrifa cho phép chúng ta linh hoạt và không sợ hãi khi thực hiện các thay đổi đối với mã phông chữ. Giờ đây, chúng tôi dành ít thời gian hơn để khắc phục lỗi bảo mật, nhờ đó có thể cập nhật nhanh hơn và nâng cao chất lượng mã.

Bài đăng này chia sẻ lý do Chrome đã chuyển sang không sử dụng FreeType, cũng như một số thông tin kỹ thuật thú vị về những điểm cải tiến mà quyết định này mang lại.

Tại sao phải thay thế FreeType?

Web là một nền tảng đặc biệt vì cho phép người dùng tìm nạp các tài nguyên không đáng tin cậy từ nhiều nguồn không đáng tin cậy với kỳ vọng mọi thứ sẽ hoạt động và họ sẽ an toàn khi làm như vậy. Giả định này thường đúng, nhưng việc giữ lời hứa đó với người dùng sẽ phải trả giá. Ví dụ: để sử dụng phông chữ web một cách an toàn (phông chữ được phân phối qua mạng), Chrome sử dụng một số biện pháp giảm thiểu bảo mật:

Chrome đi kèm với FreeType và sử dụng FreeType làm thư viện xử lý phông chữ chính trên Android, ChromeOS và Linux. Điều đó có nghĩa là nhiều người dùng sẽ bị lộ thông tin nếu có lỗ hổng trong FreeType.

Chrome sử dụng thư viện FreeType để tính toán các chỉ số và tải đường viền gợi ý từ phông chữ. Nhìn chung, việc sử dụng FreeType là một chiến thắng lớn đối với Google. Thư viện này thực hiện một công việc phức tạp và thực hiện tốt công việc đó, chúng tôi dựa vào thư viện này rất nhiều và đóng góp lại cho thư viện này. Tuy nhiên, hàm này được viết bằng mã không an toàn và bắt nguồn từ thời điểm mà dữ liệu đầu vào độc hại ít có khả năng xảy ra. Chỉ cần theo dõi luồng vấn đề phát hiện được bằng cách tìm lỗi mã nguồn, Google đã phải tốn ít nhất 0,25 kỹ sư phần mềm toàn thời gian. Tệ hơn nữa, chúng ta có thể quan sát thấy rằng không tìm thấy mọi thứ hoặc chỉ tìm thấy sau khi mã được gửi đến người dùng.

Mẫu vấn đề này không chỉ xảy ra với FreeType, chúng tôi nhận thấy rằng các thư viện không an toàn khác cũng gặp phải vấn đề ngay cả khi chúng tôi sử dụng các kỹ sư phần mềm giỏi nhất có thể tìm thấy, xem xét mã cho mọi thay đổi và yêu cầu kiểm thử.

Lý do khiến vấn đề liên tục xuất hiện

Khi đánh giá tính bảo mật của FreeType, chúng tôi nhận thấy có 3 loại vấn đề chính xảy ra (không đầy đủ):

Sử dụng ngôn từ không an toàn

Mẫu/Vấn đề Ví dụ:
Quản lý bộ nhớ theo cách thủ công
Truy cập mảng chưa được kiểm tra CVE-2022-27404
Số nguyên tràn Trong quá trình thực thi máy ảo nhúng để gợi ý TrueType về bản vẽ và gợi ý CFF
https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow
Sử dụng không chính xác phương thức phân bổ bằng 0 so với phương thức phân bổ không bằng 0 Thảo luận trong https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94, 8 vấn đề về trình tạo dữ liệu ngẫu nhiên sau đó
Truyền không hợp lệ Xem hàng sau đây về mức sử dụng macro

Vấn đề cụ thể về dự án

Mẫu/Vấn đề Ví dụ:
Macro làm mờ việc thiếu kiểu kích thước rõ ràng
  • Các macro như FT_READ_*FT_PEEK_* làm lu mờ loại số nguyên đang được sử dụng, ẩn các loại C99 có kích thước rõ ràng (int16_t, v.v.) không được sử dụng
Mã mới liên tục thêm lỗi, ngay cả khi được viết theo cách phòng thủ.
  • COLRv1 và OT-SVG đều hỗ trợ cả hai vấn đề đã tạo ra
  • Tính năng tìm lỗi mã nguồn tìm thấy một số lỗi, nhưng không nhất thiết là tất cả, #32421, #52404
Thiếu kiểm thử
  • Việc tạo phông chữ kiểm thử rất tốn thời gian và khó khăn

Vấn đề về phần phụ thuộc

Tính năng tìm lỗi mã nguồn đã nhiều lần xác định được các vấn đề trong các thư viện mà FreeType phụ thuộc vào, chẳng hạn như bzip2, libpng và zlib. Ví dụ: so sánh freetype_bdf_fuzzer: Use-of-uninitialized-value in inflate.

Không đủ để tìm lỗi

Tìm lỗi mã nguồn (fuzzing) – kiểm thử tự động với nhiều loại dữ liệu đầu vào, bao gồm cả dữ liệu đầu vào không hợp lệ được tạo ngẫu nhiên – nhằm tìm ra nhiều loại vấn đề có thể xảy ra trong bản phát hành ổn định của Chrome. Chúng tôi tạo lỗi cho FreeType trong dự án oss-fuzz của Google. Công cụ này tìm thấy vấn đề, nhưng các phông chữ đã được chứng minh là có khả năng chống lại việc làm rối mã nguồn vì những lý do sau.

Tệp phông chữ rất phức tạp, tương đương với tệp video vì chứa nhiều loại thông tin. Tệp phông chữ là định dạng vùng chứa cho nhiều bảng, trong đó mỗi bảng phục vụ một mục đích khác nhau trong việc xử lý văn bản và phông chữ cùng nhau để tạo ra một ký tự được định vị chính xác trên màn hình. Trong tệp phông chữ, bạn sẽ thấy:

  • Siêu dữ liệu tĩnh như tên phông chữ và thông số cho phông chữ biến.
  • Các mối liên kết từ ký tự Unicode đến ký tự.
  • Quy tắc và ngữ pháp phức tạp cho bố cục màn hình của các ký tự.
  • Thông tin hình ảnh: Hình dạng ký tự và thông tin hình ảnh mô tả hình dạng của các ký tự được đặt trên màn hình.
    • Các bảng hình ảnh có thể bao gồm các chương trình gợi ý TrueType, là các chương trình nhỏ được thực thi để thay đổi hình dạng ký tự.
    • Chuỗi Char trong bảng CFF hoặc CFF2 là các lệnh vẽ và gợi ý đường cong bắt buộc được thực thi trong công cụ kết xuất CFF.

Các tệp phông chữ có độ phức tạp tương đương với việc có ngôn ngữ lập trình và quy trình xử lý máy trạng thái riêng, đòi hỏi các máy ảo cụ thể để thực thi các tệp đó.

Do sự phức tạp của định dạng, việc tìm lỗi mã nguồn có nhiều hạn chế trong việc tìm lỗi trong tệp phông chữ.

Khó đạt được mức độ sử dụng mã hoặc tiến trình của trình kiểm thử mờ tốt vì những lý do sau:

  • Việc làm rối các chương trình gợi ý TrueType, chuỗi ký tự CFF và bố cục OpenType bằng cách sử dụng các phương thức sửa đổi kiểu lật bit/chuyển/chèn/xoá đơn giản sẽ gặp khó khăn khi tiếp cận tất cả các tổ hợp trạng thái.
  • Tính năng tìm lỗi mã nguồn ngẫu nhiên ít nhất phải tạo ra các cấu trúc hợp lệ một phần. Phương thức đột biến ngẫu nhiên hiếm khi làm được điều này, khiến việc đạt được mức độ phù hợp cao trở nên khó khăn, đặc biệt là đối với các cấp độ mã sâu hơn.
  • Các nỗ lực hiện tại về việc tìm lỗi mã nguồn ngẫu nhiên trong ClusterFuzz và oss-fuzz chưa sử dụng tính năng đột biến nhận biết cấu trúc. Việc sử dụng các công cụ sửa đổi nhận biết ngữ pháp hoặc cấu trúc có thể giúp tránh tạo ra các biến thể bị từ chối sớm, với chi phí là mất nhiều thời gian hơn để phát triển và tạo ra cơ hội bỏ lỡ một số phần của không gian tìm kiếm.

Dữ liệu trong nhiều bảng cần được đồng bộ hoá để quá trình tìm lỗi mã nguồn có thể tiến triển:

  • Các mẫu đột biến thông thường của trình tìm lỗi mã nguồn không tạo ra dữ liệu hợp lệ một phần, vì vậy, nhiều lần lặp lại bị từ chối và tiến trình trở nên chậm.
  • Việc liên kết phông chữ, bảng bố cục OpenType và bản vẽ phông chữ được kết nối và phụ thuộc lẫn nhau, tạo thành một không gian kết hợp mà các góc khó tiếp cận bằng tính năng làm rối mã nguồn.
  • Ví dụ: phải mất hơn 10 tháng mới tìm thấy lỗ hổng tt_face_get_paint COLUMV1 có mức độ nghiêm trọng cao.

Mặc dù chúng tôi đã nỗ lực hết sức, nhưng các vấn đề về bảo mật phông chữ vẫn liên tục ảnh hưởng đến người dùng cuối. Việc thay thế FreeType bằng một giải pháp thay thế Rust sẽ ngăn chặn nhiều lớp lỗ hổng.

Skrifa trong Chrome

Skia là thư viện đồ hoạ mà Chrome sử dụng. Skia dựa vào FreeType để tải siêu dữ liệu và chữ cái từ phông chữ. Skrifa là một thư viện Rust, thuộc gia đình thư viện Fontations, cung cấp một giải pháp thay thế an toàn cho các phần của FreeType mà Skia sử dụng.

Để chuyển đổi FreeType sang Skia, nhóm Chrome đã phát triển một phần phụ trợ phông chữ Skia mới dựa trên Skrifa và từng bước triển khai thay đổi cho người dùng:

  • Trong Chrome 128 (Tháng 8 năm 2024), chúng tôi đã bật Fontations để sử dụng trong các định dạng phông chữ ít phổ biến hơn, chẳng hạn như cho phông chữ màu và CFF2, dưới dạng một bản chạy thử an toàn.
  • Trong Chrome 133 (tháng 2 năm 2025), chúng tôi đã bật Fontations cho tất cả các hoạt động sử dụng phông chữ web trên Linux, Android và ChromeOS, cũng như cho hoạt động sử dụng phông chữ web làm phương án dự phòng trên Windows và Mac – trong trường hợp hệ thống không hỗ trợ định dạng phông chữ nhưng Chrome cần hiển thị phông chữ đó.

Để tích hợp vào Chrome, chúng tôi dựa vào việc tích hợp liền mạch Rust vào cơ sở mã do nhóm bảo mật Chrome giới thiệu.

Trong tương lai, chúng tôi cũng sẽ chuyển sang Fontations cho phông chữ hệ điều hành, bắt đầu với Linux và ChromeOS, sau đó là trên Android.

An toàn là trên hết

Mục tiêu chính của chúng tôi là giảm thiểu (hoặc tốt nhất là loại bỏ!) các lỗ hổng bảo mật do quyền truy cập ngoài giới hạn vào bộ nhớ gây ra. Rust cung cấp tính năng này ngay từ đầu miễn là bạn tránh mọi khối mã không an toàn.

Mục tiêu hiệu suất của chúng ta yêu cầu chúng ta thực hiện một thao tác hiện không an toàn: diễn giải lại các byte tuỳ ý dưới dạng cấu trúc dữ liệu được xác định kiểu mạnh. Điều này cho phép chúng ta đọc dữ liệu từ tệp phông chữ mà không cần thực hiện các bản sao không cần thiết và là yếu tố cần thiết để tạo trình phân tích cú pháp phông chữ nhanh.

Để tránh mã không an toàn của riêng mình, chúng tôi đã chọn thuê ngoài trách nhiệm này cho bytemuck. Đây là một thư viện Rust được thiết kế riêng cho mục đích này, đồng thời được kiểm thử và sử dụng rộng rãi trên hệ sinh thái. Việc tập trung vào việc diễn giải lại dữ liệu thô trong bytemuck đảm bảo chúng ta có chức năng này ở một nơi và được kiểm tra, đồng thời tránh lặp lại mã không an toàn cho mục đích này. Dự án chuyển đổi an toàn nhằm mục đích tích hợp trực tiếp chức năng này vào trình biên dịch Rust và chúng tôi sẽ chuyển đổi ngay khi có thể.

Tính chính xác là quan trọng

Skrifa được xây dựng từ các thành phần độc lập, trong đó hầu hết các cấu trúc dữ liệu đều được thiết kế để không thể thay đổi. Điều này giúp cải thiện khả năng đọc, khả năng bảo trì và tính năng đa luồng. Điều này cũng giúp mã dễ kiểm thử đơn vị hơn. Chúng tôi đã tận dụng cơ hội này và tạo ra một bộ gồm khoảng 700 chương trình kiểm thử đơn vị bao gồm toàn bộ ngăn xếp từ các quy trình phân tích cú pháp cấp thấp đến các máy ảo gợi ý cấp cao.

Độ chính xác cũng ngụ ý độ chân thực và FreeType được đánh giá cao về khả năng tạo đường viền chất lượng cao. Chúng ta phải khớp chất lượng này để có thể thay thế một cách phù hợp. Để đạt được mục tiêu đó, chúng tôi đã xây dựng một công cụ tuỳ chỉnh có tên là fauntlet. Công cụ này so sánh kết quả của Skrifa và FreeType cho các lô tệp phông chữ trên nhiều cấu hình. Điều này giúp chúng tôi có thể đảm bảo rằng chúng tôi có thể tránh được sự suy giảm chất lượng.

Ngoài ra, trước khi tích hợp vào Chromium, chúng tôi đã chạy một nhóm các phép so sánh pixel trong Skia, so sánh kết xuất FreeType với kết xuất Skrifa và Skia để đảm bảo sự khác biệt về pixel là tối thiểu, trong tất cả các chế độ kết xuất bắt buộc (trên nhiều chế độ khử răng cưa và gợi ý).

Kiểm thử tìm lỗi mã nguồn là một công cụ quan trọng để xác định cách một phần mềm phản ứng với dữ liệu đầu vào độc hại và không đúng định dạng. Chúng tôi liên tục tạo mã nguồn ngẫu nhiên cho mã mới kể từ tháng 6 năm 2024. Phần này bao gồm chính thư viện Rust và mã tích hợp. Mặc dù trình tìm lỗi mã nguồn đã tìm thấy 39 lỗi (tính đến thời điểm viết bài này), nhưng đáng chú ý là không có lỗi nào trong số này là lỗi bảo mật nghiêm trọng. Các lỗi này có thể gây ra kết quả hình ảnh không mong muốn hoặc thậm chí là các sự cố có thể kiểm soát, nhưng sẽ không dẫn đến các lỗ hổng có thể khai thác.

Tiếp tục!

Chúng tôi rất hài lòng với kết quả của những nỗ lực sử dụng Rust cho văn bản. Việc cung cấp mã an toàn hơn cho người dùng tăng năng suất cho nhà phát triển là một thành công lớn đối với chúng tôi. Chúng tôi dự định tiếp tục tìm kiếm cơ hội sử dụng Rust trong ngăn xếp văn bản. Nếu bạn muốn biết thêm, Oxidize trình bày một số kế hoạch trong tương lai của Google Fonts.