Xuất bản: Ngày 19 tháng 3 năm 2025
Skrifa được viết bằng Rust và được tạo ra để thay thế FreeType nhằm giúp quá trình xử lý phông chữ trong Chrome trở nên an toàn cho tất cả người dùng. Skrifa 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 giúp chúng tôi vừa linh hoạt vừa tự tin 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 nhiều để sửa các lỗi bảo mật, nhờ đó, các bản cập nhật được phát hành nhanh hơn và chất lượng mã cũng cao hơn.
Bài đăng này chia sẻ lý do Chrome không còn sử dụng FreeType và một số thông tin kỹ thuật thú vị về những điểm cải tiến mà việc chuyển đổi này mang lại.
Tại sao cần 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 rằng mọi thứ sẽ hoạt động bình thườ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:
- Quá trình xử lý phông chữ được đặt trong hộp cát theo quy tắc hai: chúng không đáng tin cậy và mã sử dụng không an toàn.
- Phông chữ được truyền qua OpenType Sanitizer trước khi xử lý.
- Tất cả các thư viện liên quan đến việc giải nén và xử lý phông chữ đều được kiểm thử fuzzing.
Chrome đi kèm với FreeType và sử dụng thư viện này làm thư viện xử lý phông chữ chính trên Android, ChromeOS và Linux. Điều đó có nghĩa là rất nhiều người dùng sẽ gặp rủi ro nếu FreeType có lỗ hổng.
Chrome dùng thư viện FreeType để tính toán các chỉ số và tải các đường viền được gợi ý từ phông chữ. Nhìn chung, việc sử dụng FreeType là một thành công lớn đối với Google. Nó thực hiện một công việc phức tạp và làm rất tốt, chúng tôi dựa vào nó rất nhiều và đóng góp lại cho nó. Tuy nhiên, thư viện này được viết bằng mã không an toàn và có nguồn gốc từ thời điểm mà các đầu vào độc hại ít có khả năng xảy ra. Chỉ cần theo dõi luồng vấn đề được phát hiện bằng phương pháp kiểm thử fuzzing, Google đã phải chi ít nhất 0,25 nhân viên kỹ sư phần mềm toàn thời gian. Tệ hơn nữa, chúng tôi không tìm thấy mọi thứ hoặc chỉ tìm thấy mọi thứ sau khi mã được gửi đến người dùng.
Mô hình vấn đề này không chỉ xảy ra với FreeType. Chúng tôi nhận thấy 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 những kỹ sư phần mềm giỏi nhất mà chúng tôi có thể tìm được, xem xét mã cho mọi thay đổi và yêu cầu kiểm thử.
Lý do các vấn đề cứ 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 3 loại vấn đề chính (không đầy đủ):
Sử dụng ngôn từ không an toàn
| Hoa văn/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 |
| Tràn số nguyên | Trong quá trình thực thi các máy ảo được 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 việc phân bổ bằng 0 so với phân bổ không bằng 0 | Thảo luận tại https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94, sau đó phát hiện thấy 8 vấn đề về trình kiểm thử lỗi |
| Truyền không hợp lệ | Xem hàng sau đây về mức sử dụng macro |
Vấn đề cụ thể của dự án
| Hoa văn/Vấn đề | Ví dụ: |
|---|---|
| Macro che khuất việc thiếu tính năng nhập kích thước rõ ràng |
|
| Mã mới liên tục thêm lỗi, ngay cả khi được viết một cách phòng thủ. |
|
| Thiếu các bài kiểm tra |
|
Vấn đề về phần phụ thuộc
Thử nghiệm fuzzing đã nhiều lần xác định các vấn đề trong những 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 (freetype_bdf_fuzzer: Sử dụng giá trị chưa khởi tạo trong inflate).
Fuzzing là chưa đủ
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ệ ngẫu nhiên) nhằm mục đích tìm ra nhiều loại vấn đề xuất hiện trong bản phát hành ổn định của Chrome. Chúng tôi kiểm thử phần mềm FreeType bằng phương pháp kiểm thử mờ trong dự án oss-fuzz của Google. Công cụ này có thể tìm thấy các vấn đề, nhưng phông chữ đã chứng minh khả năng chống lại việc kiểm thử ngẫu nhiê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úng chứa nhiều loại thông tin khác nhau. Tệp phông chữ là một đị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 một tệp phông chữ, bạn sẽ thấy:
- Siêu dữ liệu tĩnh, chẳng hạn như tên phông chữ và các thông số cho phông chữ có thể thay đổi.
- Mối liên kết từ các ký tự Unicode đến các glyph.
- Một bộ quy tắc và ngữ pháp phức tạp cho bố cục màn hình của các glyph.
- Thông tin trực quan: 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.
- Đến lượt, các bảng hình ảnh có thể bao gồm các chương trình chỉnh nét ngoài TrueType. Đây là các chương trình thu nhỏ được thực thi để thay đổi hình dạng của ký tự.
- Chuỗi ký tự 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 phải có các máy ảo cụ thể để thực thi.
Do độ phức tạp của định dạng, kiểm thử ngẫu nhiên có những thiếu sót trong việc tìm ra các vấn đề trong tệp phông chữ.
Khó đạt được mức độ sử dụng mã mong muốn hoặc tiến trình kiểm thử mờ vì những lý do sau:
- 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 đột biến kiểu lật/dịch chuyển/chèn/xoá bit đơn giản gặp khó khăn trong việc đạt được tất cả các tổ hợp trạng thái.
- Thử nghiệm ngẫu nhiên cần tạo ra ít nhất các cấu trúc hợp lệ một phần. Đột biến ngẫu nhiên hiếm khi làm như vậy, khiến việc đạt được mức độ bao phủ tốt 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 kiểm thử mờ hiện tại trong ClusterFuzz và oss-fuzz chưa sử dụng tính năng biến đổi dựa trên cấu trúc. Việc sử dụng các đột biến có 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à giới thiệu các cơ hội bỏ lỡ các 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 kiểm thử mờ có thể tiến hành:
- Các mẫu đột biến thông thường của fuzzers 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 sẽ bị từ chối và tiến trình trở nên chậm.
- Việc ánh xạ glyph, các bảng bố cục OpenType và việc vẽ glyph đượ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ó đạt được bằng phương pháp kiểm thử mờ.
- Ví dụ: lỗ hổng tt_face_get_paint COLRv1 ở mức độ nghiêm trọng cao mất hơn 10 tháng để tìm ra.
Mặc dù chúng tôi đã cố gắng 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 lựa chọn thay thế Rust sẽ ngăn chặn nhiều lớp lỗ hổng hoàn chỉnh.
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à hình dạng chữ cái từ phông chữ. Skrifa là một thư viện Rust, thuộc họ 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 FreeType sang Skia, nhóm Chrome đã phát triển một chương trình phụ trợ phông chữ Skia mới dựa trên Skrifa và từng bước triển khai thay đổi này 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 được dùng phổ biến, chẳng hạn như 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ị định dạng đó.
Để tích hợp vào Chrome, chúng tôi dựa vào việc tích hợp mượt mà 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ữ của hệ điều hành, bắt đầu với Linux và ChromeOS, sau đó là Android.
An toàn là trên hết
Mục tiêu chính của chúng tôi là giảm (hoặc lý tưởng 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 khi bạn tránh mọi khối mã không an toàn.
Các mục tiêu về hiệu suất đòi hỏi chúng ta phải 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 một cấu trúc dữ liệu được nhập mạnh. Điều này cho phép chúng ta đọc dữ liệu từ mộ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à điều này là cần thiết để tạo ra một 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 và được kiểm thử cũng như sử dụng rộng rãi trong hệ sinh thái. Việc tập trung 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 truyền an toàn nhằm mục đích tích hợp chức năng này trực tiếp 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 rất quan trọng
Skrifa được tạo từ các thành phần độc lập, trong đó hầu hết các cấu trúc dữ liệu được thiết kế để không thay đổi. Điều này giúp cải thiện khả năng đọc, khả năng duy trì và khả 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 kiểm thử đơn vị bao gồm toàn bộ ngăn xếp của chúng tôi, 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.
Tính chính xác cũng ngụ ý độ trung thực và FreeType được đánh giá cao vì khả năng tạo ra các đường viền chất lượng cao. Chúng tôi phải đáp ứng chất lượng này để trở thành một sản phẩm thay thế phù hợp. Để làm được điều đó, chúng tôi đã tạo một công cụ tuỳ chỉnh có tên là fauntlet. Công cụ này so sánh đầu ra 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 yên tâm rằng chúng tôi có thể tránh được tình trạng 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 loạt các phép so sánh pixel trong Skia, so sánh quá trình kết xuất FreeType với Skrifa và quá trình kết xuất Skia để đảm bảo sự khác biệt về pixel là tối thiểu tuyệt đối, trong tất cả các chế độ kết xuất bắt buộc (trên các chế độ khử răng cưa và gợi ý khác nhau).
Kiểm thử ngẫu nhiên là một công cụ quan trọng để xác định cách một phần mềm sẽ phản ứng với các đầu vào bị lỗi và độc hại. Chúng tôi đã liên tục kiểm thử phần mềm mới của mình từ tháng 6 năm 2024. Điều này bao gồm chính các thư viện Rust và mã tích hợp. Mặc dù trình kiểm thử lỗi ngẫu nhiên đã tìm thấy 39 lỗi (tính đến thời điểm viết bài này), nhưng điều đá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. Chúng có thể gây ra kết quả hình ảnh không mong muốn hoặc thậm chí là sự cố có kiểm soát, nhưng sẽ không dẫn đến các lỗ hổng có thể khai thác.
Tiến lên!
Chúng tôi rất hài lòng với kết quả của 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 và tăng năng suất của 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 các ngăn xếp văn bản của mình. Nếu bạn muốn biết thêm, Oxidize sẽ trình bày một số kế hoạch trong tương lai của Google Fonts.