ایمنی حافظه برای فونت های وب

دومینیک روتشس
Dominik Röttsches
راد شیتر
Rod Sheeter
چاد بروکاو
Chad Brokaw

تاریخ انتشار: 19 مارس 2025

Skrifa به زبان Rust نوشته شده است و به عنوان جایگزینی برای FreeType ایجاد شده است تا پردازش فونت در Chrome را برای همه کاربران ما ایمن کند. Skifra از ایمنی حافظه Rust بهره می‌برد و به ما امکان می‌دهد در مورد بهبود فناوری فونت در Chrome سریع‌تر تکرار کنیم. حرکت از FreeType به Skrifa به ما این امکان را می دهد که در هنگام ایجاد تغییرات در کد فونت خود چابک و نترس باشیم. اکنون زمان بسیار کمتری را برای رفع اشکالات امنیتی صرف می‌کنیم که در نتیجه به‌روزرسانی‌های سریع‌تر و کیفیت کد بهتر انجام می‌شود.

این پست دلیل دور شدن کروم از FreeType و برخی جزئیات فنی جالب از پیشرفت هایی که این حرکت فعال کرده است را به اشتراک می گذارد.

چرا FreeType را جایگزین کنیم؟

وب از این جهت منحصر به فرد است که به کاربران امکان می دهد منابع نامعتبر را از طیف گسترده ای از منابع نامعتبر واکشی کنند با این انتظار که همه چیز فقط کار کند و آنها در انجام این کار ایمن هستند. این فرض به طور کلی درست است، اما حفظ این وعده به کاربران هزینه دارد. به عنوان مثال، برای استفاده ایمن از یک فونت وب (فونتی که از طریق شبکه تحویل داده می شود) Chrome از چندین کاهش امنیتی استفاده می کند:

  • پردازش فونت بر اساس قانون دو بسته بندی می شود: آنها غیرقابل اعتماد هستند و کد مصرف کننده ناامن است.
  • فونت ها قبل از پردازش از طریق OpenType Sanitizer منتقل می شوند.
  • تمام کتابخانه های درگیر در فشرده سازی و پردازش فونت ها تست فازی شده اند.

Chrome با FreeType عرضه می شود و از آن به عنوان کتابخانه پردازش فونت اصلی در Android، ChromeOS و Linux استفاده می کند. این بدان معناست که در صورت وجود آسیب‌پذیری در FreeType، بسیاری از کاربران در معرض خطر قرار می‌گیرند.

کتابخانه FreeType توسط Chrome برای محاسبه معیارها و بارگیری خطوط کلی از فونت ها استفاده می شود. به طور کلی، استفاده از FreeType یک پیروزی بزرگ برای گوگل بوده است. این کار پیچیده ای را انجام می دهد، و آن را به خوبی انجام می دهد، ما به طور گسترده به آن تکیه می کنیم و به آن کمک می کنیم. با این حال، با کد ناامن نوشته شده است و ریشه در زمانی دارد که ورودی های مخرب کمتر محتمل بودند. صرفاً همراهی با جریان مسائلی که با fuzzing یافت می شود حداقل 0.25 مهندس نرم افزار تمام وقت برای گوگل هزینه دارد. بدتر از آن، مشاهده می شود که ما همه چیز را پیدا نمی کنیم یا چیزهایی را فقط پس از ارسال کد به کاربران پیدا نمی کنیم.

این الگوی مشکلات منحصر به FreeType نیست، ما مشاهده می‌کنیم که سایر کتابخانه‌های ناامن حتی زمانی که از بهترین مهندسان نرم‌افزاری که می‌توانیم پیدا می‌کنیم استفاده می‌کنیم، هر تغییری را بررسی می‌کنیم و نیاز به آزمایش داریم، مشکلات را می‌پذیرند.

چرا مسائل به صورت پنهانی وارد می شوند

هنگامی که امنیت FreeType را ارزیابی کردیم، مشاهده کردیم که سه کلاس اصلی مشکل رخ می دهد (غیر جامع):

استفاده از زبان ناامن

الگو/مسئله مثال
مدیریت حافظه دستی
دسترسی به آرایه بدون علامت CVE-2022-27404
سرریز عدد صحیح در حین اجرای ماشین های مجازی جاسازی شده برای TrueType اشاره به ترسیم و اشاره CFF
https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow
استفاده نادرست از صفر کردن در مقابل تخصیص غیر صفر کردن بحث در https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94 ، 8 مشکل fuzzer که پس از آن یافت شد
بازیگران نامعتبر به ردیف زیر در مورد استفاده ماکرو مراجعه کنید

مسائل خاص پروژه

الگو/مسئله مثال
ماکروها عدم تایپ صریح اندازه را مبهم می کند
  • ماکروهایی مانند FT_READ_* و FT_PEEK_* انواع اعداد صحیح مورد استفاده را پنهان می‌کنند، و پنهان می‌کنند که انواع C99 با اندازه‌های صریح (int16_t و غیره) استفاده نمی‌شوند.
کد جدید به طور مداوم اشکالات را اضافه می کند، حتی زمانی که به صورت دفاعی نوشته شود.
  • COLRv1 و OT-SVG از هر دو مشکل تولید شده پشتیبانی می کنند
  • Fuzzing برخی را پیدا می کند، اما نه لزوماً همه، #32421 ، #52404
عدم وجود آزمایشات
  • ساخت فونت های آزمایشی زمان بر و دشوار است

مسائل وابستگی

Fuzzing مکرراً مشکلاتی را در کتابخانه‌ها شناسایی کرده است که FreeType به آن بستگی دارد، مانند bzip2، libpng، و zlib. به عنوان مثال، freetype_bdf_fuzzer: Use-of-unitialized-value را در inflate مقایسه کنید.

گیج کردن کافی نیست

آزمایش خودکار فازی با طیف گسترده‌ای از ورودی‌ها، از جمله ورودی‌های تصادفی نامعتبر، برای یافتن بسیاری از انواع مشکلاتی است که در نسخه پایدار Chrome رخ می‌دهد. ما FreeType را به عنوان بخشی از پروژه oss-fuzz گوگل fuzz می کنیم. مشکلاتی را پیدا می‌کند، اما فونت‌ها به دلایل زیر تا حدودی در برابر تیرگی مقاوم هستند.

فایل‌های فونت پیچیده هستند و با فایل‌های ویدیویی قابل مقایسه هستند، زیرا حاوی انواع مختلفی از اطلاعات هستند. فایل‌های فونت یک فرمت کانتینری برای چندین جدول هستند، که در آن هر جدول هدف متفاوتی را در پردازش متن و فونت‌ها در کنار هم انجام می‌دهد تا یک گلیف با قرارگیری صحیح روی صفحه نمایش ایجاد کند. در یک فایل فونت خواهید دید:

  • فراداده ایستا مانند نام فونت ها و پارامترهای فونت های متغیر.
  • نگاشت از کاراکترهای یونیکد تا گلیف.
  • یک مجموعه قواعد و دستور زبان پیچیده برای چیدمان صفحه نمایش گلیف ها.
  • اطلاعات بصری: اشکال حروف و اطلاعات تصویری که نشان می دهد گلیف های قرار داده شده بر روی صفحه چگونه به نظر می رسند.
    • جداول بصری به نوبه خود می توانند شامل برنامه های اشاره TrueType باشند که برنامه های کوچکی هستند که برای تغییر شکل گلیف اجرا می شوند.
    • رشته های کاراکتر در جداول CFF یا CFF2 که دستورالعمل های ترسیم منحنی و اشاره ای ضروری هستند که در موتور رندر CFF اجرا می شوند.

در فایل های فونت پیچیدگی وجود دارد که معادل داشتن زبان برنامه نویسی و پردازش ماشین حالت خاص خود است و برای اجرای آنها به ماشین های مجازی خاصی نیاز است.

به دلیل پیچیدگی قالب، fuzzing دارای کاستی هایی در یافتن مسائل در فایل های فونت است.

به دلایل زیر دستیابی به پوشش خوب کد یا پیشرفت فازر دشوار است:

  • برنامه های اشاره گر TrueType مبهم، رشته های کاراکتر CFF و طرح بندی OpenType با استفاده از جهش دهنده های ساده بیت-تغییر/تغییر/درج/حذف برای رسیدن به تمام ترکیبات حالت ها تلاش می کند.
  • Fuzzing حداقل نیاز به تولید ساختارهای تا حدی معتبر دارد. جهش تصادفی به ندرت این کار را انجام می دهد، به خصوص برای سطوح عمیق تر کد، پوشش خوب را دشوار می کند.
  • تلاش‌های فازی فعلی در ClusterFuzz و oss-fuzz هنوز از جهش ساختار آگاه استفاده نمی‌کنند. استفاده از جهش‌دهنده‌های گرامر یا ساختار آگاه ممکن است به جلوگیری از تولید انواعی که زودتر رد می‌شوند، به قیمت صرف زمان بیشتر برای توسعه، و معرفی شانس‌هایی که بخش‌هایی از فضای جستجو را از دست می‌دهند، کمک کند.

داده های موجود در چندین جدول برای ایجاد پیشرفت باید با هم هماهنگ باشند:

  • الگوهای جهش معمولی fuzzer ها داده های تا حدی معتبر تولید نمی کنند، بنابراین بسیاری از تکرارها رد می شوند و پیشرفت کند می شود.
  • نگاشت گلیف، جداول طرح بندی OpenType و طراحی گلیف به یکدیگر متصل هستند و به یکدیگر وابسته هستند و فضایی ترکیبی را تشکیل می دهند که دسترسی به گوشه های آن با ابهام سخت است.
  • برای مثال، یافتن آسیب‌پذیری با شدت بالا tt_face_get_paint COLRv1 بیش از 10 ماه طول کشید.

علی‌رغم تلاش‌های ما، مشکلات امنیتی فونت بارها به کاربران نهایی رسیده است. جایگزینی FreeType با یک جایگزین Rust از چندین کلاس آسیب پذیری جلوگیری می کند.

اسکریفا در کروم

Skia کتابخانه گرافیکی مورد استفاده کروم است. Skia برای بارگیری متادیتا و شکل نامه ها از فونت ها به FreeType متکی است. Skrifa یک کتابخانه Rust، بخشی از خانواده کتابخانه‌های Fontations است که جایگزینی مطمئن برای قطعات FreeType مورد استفاده Skia ارائه می‌کند.

برای انتقال FreeType به Skia، تیم Chrome یک باطن فونت جدید Skia را بر اساس Skrifa توسعه داد و به تدریج این تغییر را برای کاربران ارائه کرد:

برای ادغام در Chrome، ما به ادغام نرم Rust در پایگاه کد معرفی شده توسط تیم امنیتی Chrome متکی هستیم.

در آینده برای فونت‌های سیستم‌عامل نیز به فونت‌ها تغییر می‌کنیم، از Linux و ChromeOS و سپس در اندروید.

ایمنی، اول از همه

هدف اصلی ما کاهش (یا در حالت ایده آل، حذف!) آسیب پذیری های امنیتی است که ناشی از دسترسی خارج از محدوده به حافظه است. Rust تا زمانی که از هر گونه بلوک کد ناامن اجتناب کنید، این را از جعبه فراهم می کند.

اهداف عملکرد ما مستلزم انجام یک عملیات است که در حال حاضر ناامن است: تفسیر مجدد بایت های دلخواه به عنوان یک ساختار داده به شدت تایپ شده. این به ما امکان می دهد تا داده ها را از یک فایل فونت بدون انجام کپی های غیر ضروری بخوانیم و برای تولید یک تجزیه کننده سریع فونت ضروری است.

برای جلوگیری از کد ناامن خود، ما این مسئولیت را به bytemuck برون سپاری کرده ایم که یک کتابخانه Rust است که به طور خاص برای این منظور طراحی شده است و به طور گسترده در سراسر اکوسیستم آزمایش و استفاده می شود. تمرکز تفسیر مجدد داده‌های خام در بایت‌ماک تضمین می‌کند که ما این عملکرد را در یک مکان و ممیزی داریم و از تکرار کد ناامن برای آن هدف خودداری می‌کنیم. هدف پروژه انتقال ایمن این است که این قابلیت را مستقیماً در کامپایلر Rust ادغام کند و به محض اینکه در دسترس قرار گرفت سوئیچ را انجام خواهیم داد.

صحت مهم است

Skrifa از اجزای مستقل ساخته شده است که در آن بیشتر ساختارهای داده غیرقابل تغییر طراحی شده اند. این خوانایی، قابلیت نگهداری و چند رشته ای را بهبود می بخشد. همچنین کد را برای تست واحد سازگارتر می کند. ما از این فرصت استفاده کرده‌ایم و مجموعه‌ای از تقریباً 700 تست واحد تولید کرده‌ایم که پشته کامل ما را از روتین‌های تجزیه سطح پایین تا ماشین‌های مجازی اشاره‌ای سطح بالا پوشش می‌دهد.

صحت همچنین دلالت بر وفاداری دارد و FreeType به دلیل تولید خطوط با کیفیت بالا بسیار مورد توجه است. ما باید این کیفیت را مطابقت دهیم تا جایگزین مناسبی باشیم. برای این منظور، ما یک ابزار سفارشی به نام fauntlet ساخته‌ایم که خروجی Skrifa و FreeType را برای دسته‌ای از فایل‌های فونت در طیف وسیعی از پیکربندی‌ها مقایسه می‌کند. این به ما اطمینان می دهد که می توانیم از پسرفت در کیفیت جلوگیری کنیم.

علاوه بر این، قبل از ادغام با Chromium، مجموعه گسترده‌ای از مقایسه‌های پیکسلی را در Skia انجام دادیم، رندر FreeType را با رندر Skrifa و Skia مقایسه کردیم تا اطمینان حاصل کنیم که تفاوت‌های پیکسلی کاملاً حداقل است، در همه حالت‌های رندر مورد نیاز (در حالت‌های مختلف antialiasing و hinting).

تست فاز یک ابزار مهم برای تعیین نحوه واکنش یک نرم افزار به ورودی های بد شکل و مخرب است. ما از ژوئن سال 2024 به طور مداوم کد جدید خود را در هم ریخته ایم. این شامل خود کتابخانه های Rust و کد ادغام می شود. در حالی که fuzzer (تا زمان نوشتن این مقاله) 39 باگ پیدا کرده است، شایان ذکر است که هیچ یک از این اشکالات امنیتی حیاتی نبوده اند . آنها ممکن است باعث نتایج بصری نامطلوب یا حتی خرابی های کنترل شده شوند، اما منجر به آسیب پذیری های قابل بهره برداری نمی شوند.

رو به جلو!

ما از نتایج تلاش هایمان برای استفاده از Rust برای متن بسیار خرسندیم. ارائه کد ایمن تر به کاربران و به دست آوردن بهره وری توسعه دهندگان یک پیروزی بزرگ برای ما است. ما قصد داریم به جستجوی فرصت‌هایی برای استفاده از Rust در پشته‌های متن خود ادامه دهیم. اگر می‌خواهید بیشتر بدانید، Oxidize برخی از برنامه‌های آتی Google Fonts را شرح می‌دهد.