בטיחות זיכרון לגופנים לאינטרנט

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

תאריך פרסום: 19 במרץ 2025

Skrifa נכתב ב-Rust, ונוצר כתחליף ל-FreeType כדי להפוך את עיבוד הגופנים ב-Chrome לאבטח לכל המשתמשים שלנו. ‫Skrifa מנצלת את בטיחות הזיכרון של Rust, ומאפשרת לנו לבצע איטרציות מהירות יותר על שיפורים בטכנולוגיית הגופנים ב-Chrome. המעבר מ-FreeType ל-Skrifa מאפשר לנו להיות גמישים וחסרי פחד כשמבצעים שינויים בקוד הגופן שלנו. עכשיו אנחנו מקדישים הרבה פחות זמן לתיקון באגים שקשורים לאבטחה, ולכן העדכונים מהירים יותר ואיכות הקוד גבוהה יותר.

בפוסט הזה נסביר למה Chrome עבר מ-FreeType, ונציג כמה פרטים טכניים מעניינים על השיפורים שהמעבר הזה אפשר.

למה להחליף את FreeType?

האינטרנט הוא ייחודי בכך שהוא מאפשר למשתמשים לאחזר משאבים לא מהימנים ממגוון רחב של מקורות לא מהימנים, מתוך ציפייה שהדברים פשוט יעבדו ושהם בטוחים לעשות זאת. ההנחה הזו נכונה בדרך כלל, אבל כדי לקיים את ההבטחה הזו למשתמשים יש מחיר. לדוגמה, כדי להשתמש בגופן אינטרנט (גופן שמועבר ברשת) בצורה בטוחה, Chrome משתמש בכמה אמצעי הגנה:

‫Chrome מגיע עם FreeType ומשתמש בו כספרייה העיקרית לעיבוד גופנים ב-Android, ב-ChromeOS וב-Linux. כלומר, הרבה משתמשים חשופים אם יש פגיעות ב-FreeType.

ספריית FreeType משמשת את Chrome לחישוב מדדים ולטעינת מתארים עם רמזים מגופנים. בסך הכול, השימוש ב-FreeType היה הצלחה גדולה עבור Google. הוא מבצע עבודה מורכבת, ועושה את זה היטב. אנחנו מסתמכים עליו במידה רבה ותורמים לו בחזרה. עם זאת, הוא נכתב בקוד לא בטוח והמקור שלו הוא מתקופה שבה היה סיכוי נמוך יותר לקבלת קלט זדוני. רק כדי לעמוד בקצב של הבעיות שמתגלות באמצעות פאזינג, Google צריכה לפחות 0.25 מהנדסי תוכנה במשרה מלאה. גרוע מכך, אנחנו לא מוצאים הכול או מוצאים דברים רק אחרי שהקוד נשלח למשתמשים.

הבעיות האלה לא ייחודיות ל-FreeType. אנחנו רואים שגם בספריות אחרות לא בטוחות יש בעיות, גם כשאנחנו משתמשים במהנדסי התוכנה הכי טובים שאנחנו יכולים למצוא, בודקים כל שינוי בקוד ודורשים בדיקות.

למה בעיות ממשיכות לצוץ

כשבדקנו את האבטחה של FreeType, זיהינו שלושה סוגים עיקריים של בעיות (לא רשימה מלאה):

שימוש בשפה לא בטוחה

דפוס/בעיה דוגמה
ניהול זיכרון ידני
גישה למערך ללא בדיקה CVE-2022-27404
גלישות של מספרים שלמים במהלך ההפעלה של מכונות וירטואליות מוטמעות לשיפור איכות הרינדור של ציור ושיפור איכות הרינדור של CFF
https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow
שימוש שגוי בהקצאה עם איפוס לעומת הקצאה ללא איפוס דיון בכתובת https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94, 8 fuzzer issues found afterwards
המרות לא חוקיות אפשר לראות את השורה הבאה לגבי השימוש במאקרו

בעיות ספציפיות לפרויקט

דפוס/בעיה דוגמה
פקודות מאקרו מסתירות את העובדה שאין הקלדה מפורשת של גודל
  • פקודות מאקרו כמו FT_READ_* ו-FT_PEEK_* מסתירות את סוגי המספרים השלמים שנעשה בהם שימוש, ולא מאפשרות לראות שלא נעשה שימוש בסוגי C99 עם גדלים מפורשים (int16_t וכו')
קוד חדש מוסיף באגים באופן עקבי, גם אם הוא נכתב בצורה שמגנה מפני באגים.
  • יש תמיכה ב-COLRv1 וב-OT-SVG בשני סוגי הבעיות שנוצרו
  • Fuzzing finds some, but not necessarily all, #32421, #52404
אין בדיקות
  • יצירת גופנים לבדיקה היא תהליך ארוך ומורכב

בעיות שקשורות לתלות

במהלך בדיקות Fuzzing זוהו שוב ושוב בעיות בספריות ש-FreeType תלויה בהן, כמו bzip2,‏ libpng ו-zlib. לדוגמה, אפשר להשוות בין freetype_bdf_fuzzer: Use-of-uninitialized-value in inflate.

Fuzzing לא מספיק

טשטוש (Fuzzing) – בדיקה אוטומטית עם מגוון רחב של קלטים, כולל קלטים אקראיים לא תקינים – נועד למצוא הרבה מהבעיות שמופיעות בגרסה היציבה של Chrome. אנחנו מבצעים פאזינג ב-FreeType כחלק מפרויקט oss-fuzz של Google. הכלי אכן מוצא ממצאים, אבל גופנים הוכחו כעמידים יחסית לבדיקת fuzz, מהסיבות הבאות.

קבצי גופנים הם קבצים מורכבים, בדומה לקובצי וידאו, כי הם מכילים כמה סוגים שונים של מידע. קבצים של גופנים הם פורמט קונטיינר לכמה טבלאות, כאשר כל טבלה משמשת למטרה אחרת בעיבוד של טקסט וגופנים יחד, כדי ליצור גליף במיקום הנכון על המסך. בקובץ גופן יופיעו:

  • מטא-נתונים סטטיים כמו שמות גופנים ופרמטרים של גופנים משתנים.
  • מיפויים מתווי Unicode לסימנים גרפיים.
  • קבוצת כללים מורכבת ודקדוק לפריסת גליפים במסך.
  • מידע חזותי: צורות של גליפים ומידע על תמונות שמתארים איך נראים הגליפים שמוצבים על המסך.
    • הטבלאות החזותיות יכולות לכלול בתורן תוכניות הינטינג מסוג TrueType, שהן תוכניות מיני שמופעלות כדי לשנות את צורת הגליף.
    • מחרוזות של תווים בטבלאות CFF או CFF2, שהן הוראות חיוניות לציור עקומות ולרמזים שמופעלות במנוע העיבוד של CFF.

קובצי גופנים הם מורכבים, כמו שפה משלהם לתכנות ומכונת מצבים לעיבוד, ולכן נדרשות מכונות וירטואליות ספציפיות כדי להפעיל אותם.

בגלל המורכבות של הפורמט, לשיטת ה-fuzzing יש חסרונות באיתור בעיות בקובצי גופנים.

קשה להשיג רמת הכיסוי של הקוד או התקדמות טובה של הכלי Fuzzer מהסיבות הבאות:

  • שימוש בבדיקת fuzz בתוכניות הינטינג מסוג TrueType, במחרוזות של CFF ובפריסה של OpenType באמצעות מוטציות בסגנון של היפוך ביטים, הזזה, הוספה או מחיקה מתקשה להגיע לכל השילובים של מצבים.
  • ה-Fuzzing צריך ליצור לפחות מבנים תקפים באופן חלקי. מוטציה אקראית עושה זאת לעיתים רחוקות, ולכן קשה להשיג כיסוי טוב, במיוחד ברמות עמוקות יותר של הקוד.
  • בשלב הזה, המאמצים הנוכחיים של fuzzing ב-ClusterFuzz וב-oss-fuzz עדיין לא משתמשים במוטציה שמודעת למבנה. שימוש במוטציות שמודעות לדקדוק או למבנה עשוי לעזור להימנע מיצירת וריאציות שנפסלות בשלב מוקדם, אבל זה עלול להאריך את זמן הפיתוח ולהגדיל את הסיכוי לפספס חלקים ממרחב החיפוש.

כדי שבדיקת fuzz תתקדם, הנתונים בכמה טבלאות צריכים להיות מסונכרנים:

  • דפוסי המוטציה הרגילים של כלי fuzzing לא יוצרים נתונים תקפים באופן חלקי, ולכן הרבה איטרציות נדחות וההתקדמות איטית.
  • מיפוי הגליפים, טבלאות הפריסה של OpenType וציור הגליפים קשורים ותלויים זה בזה, ויוצרים מרחב קומבינטורי שקשה להגיע לפינות שלו באמצעות בדיקת fuzz.
  • לדוגמה, נקודת החולשה tt_face_get_paint COLRv1 ברמת חומרה גבוהה התגלתה אחרי יותר מ-10 חודשים.

למרות המאמצים שלנו, בעיות אבטחה שקשורות לגופנים הגיעו שוב ושוב למשתמשי הקצה. החלפת FreeType בחלופה של Rust תמנע כמה סוגים של פגיעות.

כתיבה ב-Chrome

Skia היא ספריית הגרפיקה שבה נעשה שימוש ב-Chrome. ‫Skia מסתמך על FreeType כדי לטעון מטא-נתונים וצורות אותיות מגופנים. ‫Skrifa היא ספריית Rust, חלק ממשפחת הספריות Fontations, שמספקת תחליף בטוח לחלקים של FreeType שמשמשים את Skia.

כדי להעביר את FreeType ל-Skia, צוות Chrome פיתח קצה עורפי חדש של גופן Skia שמבוסס על Skrifa והשיק את השינוי למשתמשים באופן הדרגתי:

כדי לשלב את Rust ב-Chrome, אנחנו מסתמכים על השילוב החלק של Rust בבסיס הקוד, כפי שהוצג על ידי צוות האבטחה של Chrome.

בעתיד נעבור ל-Fontations גם לגבי גופנים של מערכות הפעלה, החל מ-Linux ו-ChromeOS, ואחר כך ב-Android.

בטיחות, לפני הכול

המטרה העיקרית שלנו היא לצמצם (או באופן אידיאלי, לבטל!) את נקודות החולשה באבטחה שנגרמות כתוצאה מגישה לזיכרון מחוץ לתחום. ‫Rust מספקת את זה כברירת מחדל, כל עוד נמנעים משימוש בבלוקים של קוד לא בטוח.

כדי להשיג את יעדי הביצועים שלנו, אנחנו צריכים לבצע פעולה אחת שכרגע לא בטוחה: פרשנות מחדש של בייטים שרירותיים כמבנה נתונים עם הקלדה חזקה. השיטה הזו מאפשרת לנו לקרוא את הנתונים מקובץ גופן בלי לבצע עותקים מיותרים, והיא חיונית ליצירת מנתח גופנים מהיר.

כדי להימנע מקוד לא בטוח משלנו, בחרנו להעביר את האחריות הזו ל-bytemuck, שהיא ספריית Rust שנועדה במיוחד למטרה הזו ונבדקה באופן נרחב בשימוש בכל האקוסיסטם. הריכוז של פרשנות מחדש של נתונים גולמיים ב-bytemuck מבטיח שהפונקציונליות הזו תהיה במקום אחד, תעבור ביקורת ולא נחזור על קוד לא בטוח למטרה הזו. המטרה של פרויקט safe transmute היא לשלב את הפונקציונליות הזו ישירות בקומפיילר Rust, ואנחנו נעבור להשתמש בה ברגע שהיא תהיה זמינה.

הנכונות חשובה

‫Skrifa מורכבת מרכיבים עצמאיים, שרוב מבני הנתונים שלהם מתוכננים להיות בלתי ניתנים לשינוי. כך משפרים את נוחות הקריאה, את יכולת התחזוקה ואת ריבוי השרשורים. בנוסף, הקוד מתאים יותר לבדיקות יחידה. ניצלנו את ההזדמנות הזו ויצרנו חבילה של כ-700 בדיקות יחידה שמכסות את כל המערכת שלנו, החל משגרות ניתוח ברמה נמוכה ועד למכונות וירטואליות ברמה גבוהה.

נכונות מרמזת גם על נאמנות, וספריית FreeType נחשבת מאוד בזכות יצירת קווי מתאר באיכות גבוהה. כדי שנוכל להחליף את המודל במודל אחר, הוא צריך להיות באיכות דומה. לשם כך, יצרנו כלי ייעודי בשם fauntlet שמשווה את הפלט של Skrifa ו-FreeType עבור קבוצות של קובצי גופנים במגוון רחב של תצורות. כך אנחנו יכולים לוודא שלא תהיה ירידה באיכות.

בנוסף, לפני השילוב ב-Chromium, ערכנו מגוון רחב של השוואות פיקסלים ב-Skia, והשווינו בין עיבוד FreeType לעיבוד Skrifa ובין עיבוד Skia לעיבוד Skrifa כדי לוודא שההבדלים בפיקסלים מינימליים לחלוטין, בכל מצבי העיבוד הנדרשים (במצבי החלקה שונים ובמצבי רמזים שונים).

בדיקת Fuzzing היא כלי חשוב לקביעת האופן שבו תוכנה תגיב לקלט פגום וזדוני. אנחנו מבצעים בדיקת fuzz רציפה של הקוד החדש שלנו מאז יוני 2024. זה כולל את ספריות Rust עצמן ואת קוד השילוב. נכון לכתיבת המאמר הזה, הכלי לאיתור באגים מצא 39 באגים, אבל חשוב לציין שאף אחד מהם לא קריטי מבחינת אבטחה. הם עלולים לגרום לתוצאות ויזואליות לא רצויות או אפילו לקריסות מבוקרות, אבל הם לא יובילו לפגיעויות שניתן לנצל.

קדימה!

אנחנו מאוד מרוצים מהתוצאות של המאמצים שלנו להשתמש ב-Rust לטקסט. אנחנו רואים בכך הישג משמעותי שאנחנו מספקים למשתמשים קוד בטוח יותר וגם משפרים את הפרודוקטיביות של המפתחים. אנחנו מתכננים להמשיך לחפש הזדמנויות להשתמש ב-Rust במערכות שלנו. אם רוצים לקבל מידע נוסף, אפשר לעיין בתוכניות העתידיות של Google Fonts ב-Oxidize.