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

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

منتشر شده: ۱۹ مارس ۲۰۲۵

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

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

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

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

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

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

کتابخانه FreeType توسط کروم برای محاسبه معیارها و بارگذاری خطوط راهنما از فونت‌ها استفاده می‌شود. در مجموع، استفاده از FreeType یک برد بزرگ برای گوگل بوده است. این کتابخانه کار پیچیده‌ای را انجام می‌دهد و آن را به خوبی انجام می‌دهد، ما به طور گسترده به آن متکی هستیم و در آن مشارکت می‌کنیم. با این حال، با کد ناامن نوشته شده است و ریشه در زمانی دارد که احتمال ورودی‌های مخرب کمتر بود. صرفاً پیگیری جریان مشکلات یافت شده توسط فازینگ، حداقل 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 مشکل مربوط به فازر پس از آن یافت شد
کست‌های نامعتبر ردیف زیر را در مورد کاربرد ماکرو ببینید

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

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

مسائل مربوط به وابستگی

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

فازینگ کافی نیست

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

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

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

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

به دلیل پیچیدگی فرمت، فازینگ در یافتن مشکلات فایل‌های فونت کاستی‌هایی دارد.

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

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

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

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

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

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

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

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

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

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

ایمنی، اولین و مهمترین چیز

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

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

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

درست بودن مهم است

اسکریفا از اجزای مستقلی ساخته شده است که در آن‌ها اکثر ساختارهای داده به گونه‌ای طراحی شده‌اند که تغییرناپذیر باشند. این امر خوانایی، قابلیت نگهداری و چندنخی بودن را بهبود می‌بخشد. همچنین کد را برای تست واحد (unit testing) مناسب‌تر می‌کند. ما از این فرصت استفاده کرده‌ایم و مجموعه‌ای از تقریباً ۷۰۰ تست واحد تولید کرده‌ایم که تمام پشته ما را از روال‌های تجزیه سطح پایین گرفته تا ماشین‌های مجازی اشاره‌گر سطح بالا پوشش می‌دهد.

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

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

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

به پیش!

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