เผยแพร่เมื่อวันที่ 19 มีนาคม 2025
Skrifa เขียนด้วยภาษา Rust และสร้างขึ้นเพื่อใช้แทน FreeType เพื่อให้การประมวลผลแบบอักษรใน Chrome ปลอดภัยสำหรับผู้ใช้ทุกคน Skrifa ใช้ประโยชน์จากความปลอดภัยของหน่วยความจำของ Rust และช่วยให้เราปรับปรุงเทคโนโลยีแบบอักษรใน Chrome ได้เร็วขึ้น การเปลี่ยนจาก FreeType เป็น Skrifa ช่วยให้เรามีความคล่องตัวและมั่นใจมากขึ้นเมื่อทำการเปลี่ยนแปลงโค้ดแบบอักษร ตอนนี้เราใช้เวลาน้อยลงมากในการแก้ไขข้อบกพร่องด้านความปลอดภัย ซึ่งส่งผลให้อัปเดตได้เร็วขึ้นและโค้ดมีคุณภาพดีขึ้น
โพสต์นี้จะแชร์เหตุผลที่ Chrome เปลี่ยนไปใช้ Skrifa แทน FreeType รวมถึงรายละเอียดทางเทคนิคที่น่าสนใจบางอย่างของการปรับปรุงที่การเปลี่ยนแปลงนี้ทำให้เกิดขึ้น
เหตุใดจึงต้องเปลี่ยนไปใช้ Skrifa แทน FreeType
เว็บมีความพิเศษตรงที่อนุญาตให้ผู้ใช้ดึงข้อมูลทรัพยากรที่ไม่น่าเชื่อถือจากแหล่งที่มาที่ไม่น่าเชื่อถือที่หลากหลาย โดยคาดหวังว่าทุกอย่างจะทำงานได้ตามปกติและผู้ใช้จะปลอดภัยเมื่อดำเนินการดังกล่าว โดยทั่วไปแล้วสมมติฐานนี้ถูกต้อง แต่การรักษาสัญญาดังกล่าวกับผู้ใช้ก็มีค่าใช้จ่าย ตัวอย่างเช่น Chrome ใช้การลดความเสี่ยงด้านความปลอดภัยหลายอย่างเพื่อใช้แบบอักษรเว็บ (แบบอักษรที่ส่งผ่านเครือข่าย) ได้อย่างปลอดภัย ดังนี้
- การประมวลผลแบบอักษรจะอยู่ใน แซนด์บ็อกซ์ ตามกฎ 2ข้อ: แบบอักษรไม่น่าเชื่อถือและโค้ดที่ใช้แบบอักษรไม่ปลอดภัย
- ระบบจะส่งแบบอักษรผ่าน OpenType Sanitizer ก่อนที่จะประมวลผล
- ไลบรารีทั้งหมดที่เกี่ยวข้องกับการคลายการบีบอัดและการประมวลผลแบบอักษรจะได้รับการทดสอบ Fuzzing
Chrome มาพร้อมกับ FreeType และใช้ FreeType เป็นไลบรารีการประมวลผลแบบอักษรหลักใน Android, ChromeOS และ Linux ซึ่งหมายความว่าผู้ใช้จำนวนมาก จะมีความเสี่ยงหากมีช่องโหว่ใน FreeType
Chrome ใช้ไลบรารี FreeType เพื่อคำนวณเมตริกและโหลดโครงร่างที่มีการปรับคำแนะนำจากแบบอักษร โดยรวมแล้ว การใช้ FreeType เป็นประโยชน์อย่างมากสำหรับ Google FreeType ทำงานที่ซับซ้อนได้ดี เราจึงใช้งานอย่างกว้างขวางและมีส่วนร่วมในการพัฒนา FreeType อย่างไรก็ตาม FreeType เขียนด้วยโค้ดที่ไม่ปลอดภัยและมีต้นกำเนิดมาจากยุคที่อินพุตที่เป็นอันตรายมีโอกาสเกิดขึ้นน้อยกว่า เพียงแค่ตามทันปัญหาที่พบจากการทดสอบ Fuzzing ก็ทำให้ Google ต้องใช้วิศวกรซอฟต์แวร์แบบเต็มเวลาอย่างน้อย 0.25 คน ที่แย่กว่านั้นคือ เราสังเกตได้ว่าเราไม่พบปัญหาทั้งหมดหรือพบปัญหาหลังจากที่เผยแพร่โค้ดให้กับผู้ใช้แล้ว
รูปแบบปัญหาดังกล่าวไม่ได้เกิดขึ้นกับ FreeType เท่านั้น เราสังเกตว่าไลบรารีอื่นๆ ที่ไม่ปลอดภัยก็มีปัญหาเช่นกัน แม้ว่าเราจะใช้วิศวกรซอฟต์แวร์ที่ดีที่สุดเท่าที่จะหาได้ ทำการตรวจสอบโค้ดทุกการเปลี่ยนแปลง และกำหนดให้มีการทดสอบ
เหตุใดปัญหาจึงเกิดขึ้นอย่างต่อเนื่อง
เมื่อเราประเมินความปลอดภัยของ FreeType เราพบปัญหาหลัก 3 ประเภทที่เกิดขึ้น (ไม่ครอบคลุมทั้งหมด)
การใช้ภาษาที่ไม่ปลอดภัย
| รูปแบบ/ปัญหา | ตัวอย่าง |
|---|---|
| การจัดการหน่วยความจำด้วยตนเอง |
|
| การเข้าถึงอาร์เรย์ที่ไม่ได้ตรวจสอบ | CVE-2022-27404 |
| จำนวนเต็มล้น | ระหว่างการดำเนินการเครื่องเสมือนที่ฝังไว้สำหรับการปรับคำแนะนำ TrueType ของการวาดและการปรับคำแนะนำ CFF https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow |
| การใช้การจัดสรรหน่วยความจำแบบเติมค่าเป็น 0 กับแบบไม่เติมค่าเป็น 0 ไม่ถูกต้อง | การสนทนาใน https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94, พบปัญหา 8 รายการจาก Fuzzer หลังจากนั้น |
| การแคสต์ไม่ถูกต้อง | ดูแถวถัดไปเกี่ยวกับการใช้มาโคร |
ปัญหาเฉพาะโปรเจ็กต์
| รูปแบบ/ปัญหา | ตัวอย่าง |
|---|---|
| มาโครซ่อนการพิมพ์ขนาดที่ชัดเจน |
|
| โค้ดใหม่เพิ่มข้อบกพร่องอย่างต่อเนื่อง แม้ว่าจะเขียนโค้ดเพื่อป้องกันแล้วก็ตาม |
|
| ไม่มีการทดสอบ |
|
ปัญหาทรัพยากร Dependency
การทดสอบ Fuzzing พบปัญหาซ้ำๆ ในไลบรารีที่ FreeType ขึ้นต่อกัน เช่น bzip2, libpng และ zlib ตัวอย่างเช่น ให้เปรียบเทียบ freetype_bdf_fuzzer: Use-of-uninitialized-value ใน inflate
การทดสอบ Fuzzing ไม่เพียงพอ
การทดสอบ Fuzzing ซึ่งเป็นการทดสอบอัตโนมัติด้วยอินพุตที่หลากหลาย รวมถึงอินพุตที่ไม่ถูกต้องแบบสุ่ม มีจุดประสงค์เพื่อค้นหาปัญหาหลายประเภทที่อาจเกิดขึ้นใน Chrome เวอร์ชันเสถียร เราทดสอบ Fuzzing FreeType ซึ่งเป็นส่วนหนึ่งของโปรเจ็กต์ oss-fuzz ของ Google การทดสอบ Fuzzing พบปัญหา แต่แบบอักษรค่อนข้างทนต่อการทดสอบ Fuzzing ด้วยเหตุผลต่อไปนี้
ไฟล์แบบอักษรมีความซับซ้อน เทียบได้กับไฟล์วิดีโอ เนื่องจากมีข้อมูลหลายประเภท ไฟล์แบบอักษรเป็นรูปแบบคอนเทนเนอร์สำหรับตารางหลายรายการ โดยแต่ละตารางมีวัตถุประสงค์ที่แตกต่างกันในการประมวลผลข้อความและแบบอักษรร่วมกันเพื่อสร้าง Glyph ที่วางตำแหน่งอย่างถูกต้องบนหน้าจอ สิ่งที่คุณจะพบในไฟล์แบบอักษรมีดังนี้
- ข้อมูลเมตาแบบคงที่ เช่น ชื่อแบบอักษรและพารามิเตอร์สำหรับแบบอักษรตัวแปร
- การแมปจากอักขระ Unicode ไปยัง Glyph
- ชุดกฎและไวยากรณ์ที่ซับซ้อนสำหรับการจัดวาง Glyph บนหน้าจอ
- ข้อมูลภาพ: รูปร่างรูปอักขระ และข้อมูลรูปภาพที่อธิบายลักษณะของรูปอักขระที่วางบนหน้าจอ
- ตารางภาพอาจรวมถึงโปรแกรมการปรับคำแนะนำ TrueType ซึ่งเป็นโปรแกรมขนาดเล็กที่ดำเนินการเพื่อเปลี่ยนรูปร่างรูปอักขระ
- สตริงอักขระในตาราง CFF หรือ CFF2 ซึ่งเป็นคำสั่งการวาดเส้นโค้งและการปรับคำแนะนำที่จำเป็นต้องดำเนินการในเครื่องมือแสดงผล CFF
ไฟล์แบบอักษรมีความซับซ้อนเทียบเท่ากับการมีภาษาโปรแกรมและการประมวลผลเครื่องสถานะของตัวเอง ซึ่งต้องใช้เครื่องเสมือนที่เฉพาะเจาะจงในการดำเนินการ
เนื่องจากรูปแบบมีความซับซ้อน การทดสอบ Fuzzing จึงมีข้อบกพร่องในการค้นหาปัญหาในไฟล์แบบอักษร
การครอบคลุมของโค้ดที่ดีหรือความคืบหน้าของ Fuzzer ทำได้ยากด้วยเหตุผลต่อไปนี้
- การทดสอบ Fuzzing โปรแกรมการปรับคำแนะนำ TrueType, สตริงอักขระ CFF และเลย์เอาต์ OpenType โดยใช้ตัวกลายพันธุ์แบบพลิกบิต/เลื่อน/แทรก/ลบอย่างง่ายๆ พบปัญหาในการเข้าถึงสถานะทั้งหมด
- การทดสอบ Fuzzing ต้องสร้างโครงสร้างที่ถูกต้องบางส่วนอย่างน้อย การกลายพันธุ์แบบสุ่มไม่ค่อยสร้างโครงสร้างที่ถูกต้องบางส่วน จึงทำให้การครอบคลุมโค้ดที่ดีทำได้ยาก โดยเฉพาะอย่างยิ่งสำหรับโค้ดระดับลึก
- การทดสอบ Fuzzing ปัจจุบันใน ClusterFuzz และ oss-fuzz ยังไม่ได้ใช้การกลายพันธุ์ที่คำนึงถึงโครงสร้าง การใช้ตัวกลายพันธุ์ที่คำนึงถึงไวยากรณ์หรือโครงสร้างอาจช่วยหลีกเลี่ยงการสร้างตัวแปรที่ถูกปฏิเสธตั้งแต่เนิ่นๆ แต่ต้องใช้เวลาในการพัฒนานานขึ้น และอาจพลาดส่วนต่างๆ ของพื้นที่การค้นหา
ข้อมูลในตารางหลายรายการต้องซิงค์กันเพื่อให้การทดสอบ Fuzzing มีความคืบหน้า
- รูปแบบการกลายพันธุ์ปกติของ Fuzzer ไม่สร้างข้อมูลที่ถูกต้องบางส่วน ดังนั้นระบบจึงปฏิเสธการทำซ้ำหลายครั้งและความคืบหน้าจึงช้า
- การแมป Glyph, ตารางเลย์เอาต์ OpenType และการวาด Glyph มีความเชื่อมโยงและขึ้นต่อกัน ซึ่งก่อให้เกิดพื้นที่การรวมกันที่การทดสอบ Fuzzing เข้าถึงได้ยาก
- ตัวอย่างเช่น ช่องโหว่ tt_face_get_paint COLRv1 ที่มีความรุนแรงสูงใช้เวลาค้นหานานกว่า 10 เดือน
แม้ว่าเราจะพยายามอย่างเต็มที่ แต่ปัญหาด้านความปลอดภัยของแบบอักษรก็ยังคงส่งผลกระทบต่อผู้ใช้ปลายทางซ้ำๆ การเปลี่ยนไปใช้ Skrifa ซึ่งเป็นทางเลือกที่เขียนด้วยภาษา Rust แทน FreeType จะป้องกันช่องโหว่หลายประเภทได้
Skrifa ใน Chrome
Skia เป็นไลบรารีกราฟิกที่ Chrome ใช้ Skia ขึ้นต่อกันกับ FreeType เพื่อโหลดข้อมูลเมตาและรูปร่างตัวอักษรจากแบบอักษร Skrifa เป็นไลบรารีที่เขียนด้วยภาษา Rust ซึ่งเป็นส่วนหนึ่งของตระกูลไลบรารี Fontations ที่ใช้แทนส่วนต่างๆ ของ FreeType ที่ Skia ใช้ได้อย่างปลอดภัย
ทีม Chrome ได้พัฒนาแบ็กเอนด์แบบอักษร Skia ใหม่ ที่อิงตาม Skrifa และค่อยๆ เปิดตัวการเปลี่ยนแปลงให้กับผู้ใช้เพื่อเปลี่ยนจาก FreeType เป็น Skia ดังนี้
- ใน Chrome 128 (สิงหาคม 2024) เราได้เปิดใช้ Fontations สำหรับใช้ในรูปแบบแบบอักษรที่ใช้กันโดยทั่วไปน้อยกว่า เช่น แบบอักษรสีและ CFF2 เพื่อเป็นการทดลองใช้ที่ปลอดภัย
- ใน Chrome 133 (กุมภาพันธ์ 2025) เราได้เปิดใช้ Fontations สำหรับการใช้แบบอักษรเว็บทั้งหมดใน Linux, Android และ ChromeOS รวมถึงการใช้แบบอักษรเว็บเป็น แบบอักษรสํารองใน Windows และ Mac ในกรณีที่ระบบไม่รองรับ รูปแบบแบบอักษร แต่ Chrome ต้องแสดงแบบอักษรนั้น
สำหรับการผสานรวมเข้ากับ Chrome เราใช้ประโยชน์จากการผสานรวมภาษา Rust เข้ากับ ฐานของโค้ดอย่างราบรื่น ซึ่งทีมรักษาความปลอดภัยของ Chrome เป็นผู้แนะนำ
ในอนาคต เราจะเปลี่ยนไปใช้ Fontations สำหรับแบบอักษรของระบบปฏิบัติการด้วย โดยจะเริ่มจาก Linux และ ChromeOS แล้วจึงเป็น Android
ความปลอดภัยต้องมาเป็นอันดับแรก
เป้าหมายหลักของเราคือการลด (หรือกำจัดให้หมดไปเลย) ช่องโหว่ด้านความปลอดภัยที่เกิดจากการเข้าถึงหน่วยความจำนอกขอบเขต Rust มีฟังก์ชันนี้ให้พร้อมใช้งานทันทีตราบใดที่คุณหลีกเลี่ยงบล็อกโค้ด unsafe
เป้าหมายด้านประสิทธิภาพกำหนดให้เราต้องดำเนินการอย่างหนึ่งที่ปัจจุบันไม่ปลอดภัย นั่นคือการตีความไบต์ที่กำหนดเองใหม่เป็นโครงสร้างข้อมูลที่มีการพิมพ์อย่างเข้มงวด ซึ่งช่วยให้เราอ่านข้อมูลจากไฟล์แบบอักษรได้โดยไม่ต้องคัดลอกที่ไม่จำเป็น และเป็นสิ่งสำคัญในการสร้าง โปรแกรมแยกวิเคราะห์แบบอักษรที่รวดเร็ว
เราเลือกที่จะมอบความรับผิดชอบนี้ให้กับ bytemuck ซึ่งเป็นไลบรารี Rust ที่ออกแบบมาเพื่อวัตถุประสงค์นี้โดยเฉพาะ และได้รับการทดสอบและใช้งานอย่างกว้างขวางในระบบนิเวศ เพื่อหลีกเลี่ยงการใช้โค้ดที่ไม่ปลอดภัยของเราเอง การรวมการตีความข้อมูลดิบใหม่ไว้ใน bytemuck ช่วยให้มั่นใจได้ว่าเรามีฟังก์ชันนี้อยู่ในที่เดียวและได้รับการตรวจสอบแล้ว รวมถึงหลีกเลี่ยงการใช้โค้ดที่ไม่ปลอดภัยซ้ำๆ เพื่อวัตถุประสงค์นี้ โปรเจ็กต์ safe transmute มีเป้าหมาย ที่จะรวมฟังก์ชันนี้เข้ากับคอมไพเลอร์ Rust โดยตรง และเราจะ เปลี่ยนไปใช้ทันทีที่ฟังก์ชันนี้พร้อมใช้งาน
ความถูกต้องมีความสำคัญ
Skrifa สร้างขึ้นจากคอมโพเนนต์อิสระที่โครงสร้างข้อมูลส่วนใหญ่ได้รับการออกแบบมาให้เปลี่ยนแปลงไม่ได้ ซึ่งช่วยให้อ่านและดูแลรักษาโค้ดได้ง่ายขึ้น รวมถึงรองรับการทำงานแบบมัลติเธรด นอกจากนี้ยังทำให้โค้ดเหมาะกับการทำ Unit Test มากขึ้นด้วย เราได้ใช้ประโยชน์จากโอกาสนี้และสร้างชุดการทดสอบหน่วยประมาณ 700 รายการที่ครอบคลุมสแต็กทั้งหมดของเรา ตั้งแต่กิจวัตรการแยกวิเคราะห์ระดับต่ำไปจนถึงเครื่องเสมือนการปรับคำแนะนำระดับสูง
ความถูกต้องยังหมายถึงความแม่นยำ และ FreeType ได้รับการยกย่องอย่างสูงในด้านการสร้างโครงร่างคุณภาพสูง เราต้องรักษาคุณภาพนี้ไว้เพื่อให้เป็นตัวเลือกที่เหมาะสม ด้วยเหตุนี้ เราจึงสร้างเครื่องมือที่กำหนดเองชื่อ fauntlet ซึ่ง เปรียบเทียบเอาต์พุตของ Skrifa และ FreeType สำหรับชุดไฟล์แบบอักษรในการกำหนดค่าที่ หลากหลาย ซึ่งช่วยให้เรามั่นใจได้ว่าจะหลีกเลี่ยงการถดถอยของคุณภาพได้
การทดสอบ Fuzzing เป็นเครื่องมือสำคัญในการ พิจารณาว่าซอฟต์แวร์จะตอบสนองต่ออินพุตที่ผิดรูปแบบและเป็นอันตราย อย่างไร เราได้ทดสอบ Fuzzing โค้ดใหม่ของเราอย่างต่อเนื่องตั้งแต่เดือนมิถุนายน 2024 ซึ่งครอบคลุมไลบรารี Rust เองและโค้ดการผสานรวม แม้ว่า Fuzzer จะพบข้อบกพร่อง 39 รายการ (ณ เวลาที่เขียนนี้) แต่ควรทราบว่าไม่มีข้อบกพร่องใดที่สำคัญต่อความปลอดภัย ข้อบกพร่องเหล่านี้อาจทำให้เกิดผลลัพธ์ภาพที่ไม่พึงประสงค์หรือแม้แต่การขัดข้องที่ควบคุมได้ แต่จะไม่นำไปสู่ช่องโหว่ที่ใช้ประโยชน์ได้
ก้าวต่อไป
เราพอใจมากกับผลลัพธ์ของความพยายามในการใช้ Rust สำหรับข้อความ การมอบโค้ดที่ปลอดภัยยิ่งขึ้นให้กับผู้ใช้ และ การเพิ่มประสิทธิภาพการทำงานของนักพัฒนาซอฟต์แวร์ถือเป็นประโยชน์อย่างมากสำหรับเรา เราวางแผนที่จะมองหาโอกาสในการใช้ Rust ในสแต็กข้อความของเราต่อไป หากต้องการดูข้อมูลเพิ่มเติม โปรดดูแผนในอนาคตบางส่วนของ Google Fonts ได้ที่ Oxidize