เผยแพร่: 19 มีนาคม 2025
Skrifa เขียนด้วยภาษา Rust และสร้างขึ้นเพื่อแทนที่ FreeType เพื่อให้การประมวลผลแบบอักษรใน Chrome ปลอดภัยสำหรับผู้ใช้ทุกคน Skifra ใช้ประโยชน์จากความปลอดภัยของหน่วยความจำของ Rust และช่วยให้เราปรับปรุงเทคโนโลยีแบบฟอนต์ใน Chrome ได้เร็วขึ้น การเปลี่ยนจาก FreeType ไปใช้ Skrifa ช่วยให้เรามีความยืดหยุ่นและกล้าทำการเปลี่ยนแปลงโค้ดแบบอักษร ตอนนี้เราใช้เวลาในการแก้ไขข้อบกพร่องด้านความปลอดภัยน้อยลงมาก ส่งผลให้อัปเดตได้เร็วขึ้นและโค้ดมีคุณภาพดีขึ้น
โพสต์นี้จะอธิบายสาเหตุที่ Chrome เลิกใช้ FreeType และรายละเอียดทางเทคนิคที่น่าสนใจเกี่ยวกับการปรับปรุงที่เกิดจากการเปลี่ยนนี้
เหตุผลที่แทนที่ FreeType
เว็บมีความพิเศษตรงที่อนุญาตให้ผู้ใช้ดึงข้อมูลทรัพยากรที่ไม่เชื่อถือจากแหล่งที่มาที่ไม่เชื่อถือที่หลากหลาย โดยคาดหวังว่าทุกอย่างจะใช้งานได้และผู้ใช้จะปลอดภัยเมื่อดึงข้อมูลดังกล่าว โดยทั่วไปแล้วสมมติฐานนี้ถูกต้อง แต่การทำให้สัญญากับผู้ใช้เป็นจริงนั้นย่อมมีค่าใช้จ่าย ตัวอย่างเช่น หากต้องการใช้แบบอักษรบนเว็บอย่างปลอดภัย (แบบอักษรที่ส่งผ่านเครือข่าย) Chrome จะใช้มาตรการลดความเสี่ยงด้านความปลอดภัยหลายประการ ดังนี้
- การประมวลผลแบบอักษรจะแยกพื้นที่ทำงานตามกฎ 2 ข้อ ดังนี้ แบบอักษรไม่น่าเชื่อถือและโค้ดที่ใช้ไม่ปลอดภัย
- ระบบจะส่งแบบอักษรผ่าน OpenType Sanitizer ก่อนประมวลผล
- ไลบรารีทั้งหมดที่เกี่ยวข้องกับการแตกไฟล์และประมวลผลแบบอักษรได้รับการทดสอบแบบไม่เจาะจง
Chrome มาพร้อมกับ FreeType และใช้ FreeType เป็นไลบรารีการประมวลผลแบบอักษรหลักใน Android, ChromeOS และ Linux ซึ่งหมายความว่าผู้ใช้จำนวนมากจะได้รับผลกระทบหาก FreeType มีช่องโหว่
Chrome ใช้ไลบรารี FreeType เพื่อคํานวณเมตริกและโหลดขอบที่บอกใบ้จากแบบอักษร โดยรวมแล้ว การใช้ FreeType เป็นตัวเลือกที่ยอดเยี่ยมสำหรับ Google เครื่องมือนี้ทำงานที่ซับซ้อนและทำงานได้ดี เราพึ่งพาเครื่องมือนี้เป็นอย่างมากและมีส่วนร่วมในการพัฒนาเครื่องมือนี้ แต่เขียนด้วยโค้ดที่ไม่ปลอดภัยและเกิดขึ้นในช่วงเวลาที่การป้อนข้อมูลที่เป็นอันตรายมีแนวโน้มน้อยลง เพียงการติดตามปัญหาที่พบจากการทดสอบแบบ 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 พบว่ามีปัญหาเกี่ยวกับ Fuzzer 8 รายการหลังจากนั้น |
การแคสต์ที่ไม่ถูกต้อง | ดูแถวต่อไปนี้เกี่ยวกับการใช้งานมาโคร |
ปัญหาเฉพาะโปรเจ็กต์
รูปแบบ/ปัญหา | ตัวอย่าง |
---|---|
มาโครบดบังการไม่มีการกำหนดขนาดที่ชัดเจน |
|
โค้ดใหม่จะเพิ่มข้อบกพร่องอยู่เสมอ แม้ว่าจะเขียนขึ้นเพื่อการป้องกันแล้วก็ตาม |
|
ไม่มีการทดสอบ |
|
ปัญหาการขึ้นต่อกัน
การตรวจหาข้อบกพร่องพบปัญหาในไลบรารีที่ FreeType ต้องใช้อยู่ซ้ำๆ เช่น bzip2, libpng และ zlib ตัวอย่างเช่น ให้เปรียบเทียบ freetype_bdf_fuzzer: Use-of-uninitialized-value in inflate
การทดสอบแบบไม่เจาะจงไม่เพียงพอ
การทดสอบแบบ Fuzzing เป็นการทดสอบอัตโนมัติโดยใช้อินพุตที่หลากหลาย ซึ่งรวมถึงอินพุตที่ไม่ถูกต้องแบบสุ่ม โดยมีจุดประสงค์เพื่อค้นหาปัญหาหลายประเภทที่พบใน Chrome เวอร์ชันเสถียร เราทำการทดสอบข้อบกพร่องของ FreeType เป็นส่วนหนึ่งของโปรเจ็กต์ oss-fuzz ของ Google เครื่องมือนี้สามารถพบปัญหาได้ แต่แบบอักษรได้รับการพิสูจน์แล้วว่าทนต่อการทดสอบแบบไม่เจาะจงได้ในระดับหนึ่งด้วยเหตุผลต่อไปนี้
ไฟล์แบบอักษรมีความซับซ้อน เทียบได้กับไฟล์วิดีโอเนื่องจากมีข้อมูลหลายประเภท ไฟล์แบบอักษรเป็นรูปแบบคอนเทนเนอร์สำหรับตารางหลายตาราง โดยแต่ละตารางมีวัตถุประสงค์ต่างกันในการประมวลผลข้อความและแบบอักษรร่วมกันเพื่อสร้างสัญลักษณ์ที่มีตำแหน่งถูกต้องบนหน้าจอ ในไฟล์แบบอักษร คุณจะเห็นข้อมูลต่อไปนี้
- ข้อมูลเมตาแบบคงที่ เช่น ชื่อแบบอักษรและพารามิเตอร์สำหรับแบบอักษรแบบผันแปร
- การแมปจากอักขระ Unicode กับสัญลักษณ์
- กฎและไวยากรณ์ที่ซับซ้อนสำหรับเลย์เอาต์หน้าจอของสัญลักษณ์
- ข้อมูลภาพ: รูปร่างและข้อมูลรูปภาพที่อธิบายลักษณะของสัญลักษณ์ที่วางบนหน้าจอ
- ตารางภาพอาจรวมโปรแกรมการบอกใบ้ TrueType ซึ่งเป็นโปรแกรมขนาดเล็กที่ทำงานเพื่อเปลี่ยนรูปร่างของสัญลักษณ์
- สตริงอักขระในตาราง CFF หรือ CFF2 ซึ่งเป็นคำสั่งการวาดเส้นโค้งและคำแนะนำในการบอกใบ้ที่บังคับซึ่งดำเนินการในเครื่องมือแสดงผล CFF
ไฟล์แบบอักษรมีความซับซ้อนเทียบเท่ากับการมีภาษาการเขียนโปรแกรมและการประมวลผลสถานะแมชชีนของตัวเอง ซึ่งต้องใช้เครื่องเสมือนที่เฉพาะเจาะจงเพื่อเรียกใช้
เนื่องจากการที่ซอฟต์แวร์นี้จะสร้างชุดค่าผสมแบบสุ่มจากรูปแบบต่างๆ จึงทำให้มีข้อบกพร่องในการค้นหาปัญหาในไฟล์แบบอักษร
การครอบคลุมโค้ดที่ดีหรือความคืบหน้าของโปรแกรมจำลองข้อบกพร่องนั้นทำได้ยากเนื่องจากเหตุผลต่อไปนี้
- การทดสอบโปรแกรมการบอกใบ้ TrueType, สตริงอักขระ CFF และเลย์เอาต์ OpenType แบบไม่เจาะจงโดยใช้ตัวเปลี่ยนรูปแบบแบบการพลิก/เลื่อน/แทรก/ลบบิตแบบง่าย ๆ นั้นทำได้ยากที่จะเข้าถึงชุดค่าผสมของสถานะทั้งหมด
- การทดสอบแบบ Fuzzing อย่างน้อยต้องสร้างโครงสร้างที่ถูกต้องบางส่วน แต่การกลายพันธุ์แบบสุ่มไม่ค่อยทำเช่นนั้น ทำให้การครอบคลุมที่ดีทำได้ยาก โดยเฉพาะสำหรับโค้ดในระดับที่ลึกยิ่งขึ้น
- ขณะนี้ โปรแกรม Fuzzing ใน ClusterFuzz และ oss-fuzz ยังไม่ใช้การกลายพันธุ์ที่คำนึงถึงโครงสร้าง การใช้ตัวเปลี่ยนรูปแบบที่คำนึงถึงไวยากรณ์หรือโครงสร้างอาจช่วยหลีกเลี่ยงการสร้างตัวแปรที่ถูกปฏิเสธตั้งแต่เนิ่นๆ แต่ก็ต้องแลกมาด้วยเวลาที่ใช้ในการพัฒนานานขึ้นและโอกาสที่จะพลาดพื้นที่การค้นหาบางส่วน
ข้อมูลในหลายตารางต้องซิงค์กันเพื่อให้การทดสอบการใช้คำที่คลุมเครือทำงานต่อไปได้
- รูปแบบการกลายพันธุ์ตามปกติของโปรแกรมสร้างข้อมูลเท็จจะไม่สร้างข้อมูลที่ถูกต้องบางส่วน ดังนั้นการวนซ้ำหลายครั้งจึงถูกปฏิเสธและเกิดความล่าช้า
- การแมปแบบอักษร ตารางเลย์เอาต์ OpenType และการวาดแบบอักษรจะเชื่อมโยงและพึ่งพาซึ่งกันและกัน ทำให้เกิดพื้นที่การผสมผสานที่ยากต่อการเข้าถึงด้วยการใช้การทดสอบแบบไม่เจาะจง
- ตัวอย่างเช่น ช่องโหว่tt_face_get_paint COLRv1 ความรุนแรงสูงใช้เวลาค้นหานานกว่า 10 เดือน
แม้ว่าเราจะพยายามอย่างเต็มที่แล้ว แต่ปัญหาด้านความปลอดภัยของแบบอักษรก็ยังคงเกิดขึ้นกับผู้ใช้ปลายทางซ้ำๆ การเปลี่ยน FreeType ไปใช้ทางเลือกของ Rust จะป้องกันช่องโหว่ได้หลายคลาส
Skrifa ใน Chrome
Skia คือไลบรารีกราฟิกที่ Chrome ใช้ Skia อาศัย FreeType เพื่อโหลดข้อมูลเมตาและรูปแบบตัวอักษรจากแบบอักษร Skrifa เป็นไลบรารี Rust ซึ่งเป็นส่วนหนึ่งของตระกูลไลบรารี Fontations ที่ทำหน้าที่แทนชิ้นส่วนของ FreeType ที่ Skia ใช้ได้อย่างปลอดภัย
ทีม Chrome ได้พัฒนาแบ็กเอนด์แบบอักษร Skia ใหม่เพื่อเปลี่ยนจาก FreeType เป็น Skia โดยอิงตาม Skrifa และทยอยเปิดตัวการเปลี่ยนแปลงนี้แก่ผู้ใช้
- ใน Chrome 128 (สิงหาคม 2024) เราได้เปิดใช้ Fontation เพื่อใช้ในแบบอักษรรูปแบบที่ใช้กันไม่บ่อยนัก เช่น แบบอักษรสีและ CFF2 เป็นการทดสอบอย่างปลอดภัย
- ใน Chrome 133 (กุมภาพันธ์ 2025) เราได้เปิดใช้ Fontation สำหรับการใช้แบบอักษรเว็บทั้งหมดใน Linux, Android และ ChromeOS รวมถึงการใช้แบบอักษรเว็บเป็นค่าเริ่มต้นใน Windows และ Mac ในกรณีที่ระบบไม่รองรับรูปแบบแบบอักษร แต่ Chrome จำเป็นต้องแสดงแบบอักษรนั้น
สำหรับการผสานรวมกับ Chrome เราอาศัยการผสานรวม Rust เข้ากับโค้ดเบสที่ทีมรักษาความปลอดภัยของ Chrome แนะนำ
ในอนาคต เราจะเปลี่ยนไปใช้ Fontations สำหรับแบบอักษรของระบบปฏิบัติการด้วย โดยเริ่มจาก Linux และ ChromeOS จากนั้นจึงเปลี่ยนไปใช้กับ Android
ความปลอดภัยสำคัญที่สุด
เป้าหมายหลักของเราคือการลด (หรือหากเป็นไปได้ก็กำจัด) ช่องโหว่ด้านความปลอดภัยที่เกิดจากการเข้าถึงหน่วยความจำนอกขอบเขต Rust มีฟังก์ชันนี้ให้ใช้งานโดยค่าเริ่มต้น ตราบใดที่คุณหลีกเลี่ยงโค้ดบล็อกที่ไม่ปลอดภัย
เป้าหมายด้านประสิทธิภาพของเรากำหนดให้เราดำเนินการหนึ่งอย่างที่ปัจจุบันไม่ปลอดภัย ซึ่งก็คือการตีความไบต์แบบกำหนดเองเป็นโครงสร้างข้อมูลที่เป็นแบบคงที่ ซึ่งช่วยให้เราอ่านข้อมูลจากไฟล์แบบอักษรได้โดยไม่ต้องทำสำเนาที่ไม่จำเป็น และจำเป็นต่อการสร้างโปรแกรมแยกวิเคราะห์แบบอักษรที่รวดเร็ว
เราได้เลือกที่จะ outsource หน้าที่นี้ให้กับ bytemuck ซึ่งเป็นไลบรารี Rust ที่ออกแบบมาเพื่อวัตถุประสงค์นี้โดยเฉพาะ รวมถึงได้รับการทดสอบและใช้งานอย่างกว้างขวางทั่วทั้งระบบนิเวศ เพื่อหลีกเลี่ยงการใช้โค้ดที่ไม่ปลอดภัย การรวมการตีความข้อมูลดิบใน Bytemuck ช่วยให้เรามีฟังก์ชันการทำงานนี้ในที่เดียวและได้รับการตรวจสอบ รวมถึงหลีกเลี่ยงการใช้โค้ดที่ไม่ปลอดภัยซ้ำ โปรเจ็กต์การแปลงแบบปลอดภัยมีจุดมุ่งหมายเพื่อรวมฟังก์ชันการทำงานนี้ไว้ในคอมไพเลอร์ Rust โดยตรง และเราจะเปลี่ยนไปใช้ฟังก์ชันนี้ทันทีที่พร้อมใช้งาน
ความถูกต้องมีความสำคัญ
Skrifa สร้างขึ้นจากคอมโพเนนต์อิสระที่โครงสร้างข้อมูลส่วนใหญ่ได้รับการออกแบบให้เป็นแบบคงที่ ซึ่งจะช่วยปรับปรุงความสามารถในการอ่าน ความสามารถในการบำรุงรักษา และระบบหลายเธรด และยังทําให้โค้ดเหมาะสําหรับการทดสอบหน่วยมากขึ้นด้วย เราจึงใช้ประโยชน์จากโอกาสนี้และสร้างชุดการทดสอบยูนิตประมาณ 700 ชุดที่ครอบคลุมสแต็กทั้งหมดของเรา ตั้งแต่กิจวัตรการแยกวิเคราะห์ระดับล่างไปจนถึงเครื่องเสมือนการบอกใบ้ระดับสูง
ความถูกต้องยังหมายถึงความเที่ยงตรงด้วย และ FreeType ได้รับการยกย่องอย่างมากในด้านการขึ้นรูปขอบคุณภาพสูง เราต้องจับคู่คุณภาพนี้เพื่อให้เป็นภาพทดแทนที่เหมาะสม ด้วยเหตุนี้ เราจึงสร้างเครื่องมือที่ออกแบบมาโดยเฉพาะที่เรียกว่า fauntlet ซึ่งจะเปรียบเทียบเอาต์พุตของ Skrifa กับ FreeType สำหรับไฟล์แบบอักษรหลายไฟล์ในการกำหนดค่าที่หลากหลาย ซึ่งช่วยให้เรามั่นใจได้ว่าจะหลีกเลี่ยงการถดถอยของคุณภาพได้
นอกจากนี้ ก่อนที่จะผสานรวมกับ Chromium เราได้ทำการเปรียบเทียบพิกเซลในลักษณะต่างๆ ใน Skia โดยเปรียบเทียบการแสดงผลของ FreeType กับ Skrifa และ Skia เพื่อให้แน่ใจว่าความแตกต่างของพิกเซลจะน้อยที่สุดอย่างแน่นอนในโหมดการแสดงผลที่จำเป็นทั้งหมด (ในโหมดการลบรอยหยักและโหมดการบอกใบ้แบบต่างๆ)
การทดสอบแบบ Fuzz เป็นเครื่องมือสําคัญในการกําหนดวิธีที่ซอฟต์แวร์จะตอบสนองต่ออินพุตที่มีรูปแบบไม่ถูกต้องและเป็นอันตราย เราทำการทดสอบโค้ดใหม่แบบ Fuzzing อย่างต่อเนื่องมาตั้งแต่เดือนมิถุนายน 2024 ซึ่งครอบคลุมไลบรารี Rust เองและรหัสการผสานรวม แม้ว่าโปรแกรมสร้างข้อมูลเท็จจะพบข้อบกพร่อง 39 รายการ (ณ เวลาที่เขียนบทความนี้) แต่ข้อบกพร่องเหล่านี้ไม่มีข้อใดที่ส่งผลร้ายแรงต่อความปลอดภัย อาจมีการแสดงผลที่ไม่พึงประสงค์หรือแม้แต่การขัดข้องที่ควบคุมได้ แต่ไม่ก่อให้เกิดช่องโหว่ที่ใช้ประโยชน์ได้
ไปต่อเลย
เรายินดีอย่างยิ่งกับผลลัพธ์ที่ได้จากการพยายามใช้ Rust สำหรับข้อความ การนำส่งโค้ดที่ปลอดภัยยิ่งขึ้นให้แก่ผู้ใช้และการเพิ่มประสิทธิภาพการทำงานของนักพัฒนาซอฟต์แวร์ถือเป็นเรื่องสำคัญอย่างยิ่งสำหรับเรา เราวางแผนที่จะมองหาโอกาสในการใช้ Rust ในแพลตฟอร์มข้อความของเราต่อไป หากต้องการข้อมูลเพิ่มเติม โปรดไปที่ Oxidize ซึ่งจะแสดงแผนในอนาคตของ Google Fonts