ma1 pushed to branch mullvad-browser-140.9.0esr-15.0-1 at The Tor Project / Applications / Mullvad Browser Commits: 171b00e6 by Jonathan Kew at 2026-03-23T09:15:13+01:00 Bug 2009213 - Use local statics for harfbuzz callback pointers, to ensure thread-safe initialization. r=gfx-reviewers,lsalzman Differential Revision: https://phabricator.services.mozilla.com/D278945 - - - - - bd670ab3 by Paul Adenot at 2026-03-23T09:15:19+01:00 Bug 2014865 - Fix int64_t overflow in TimeUnit::FromSeconds boundary condition. r=media-playback-reviewers,alwu TimeUnit::FromSeconds() had undefined behavior when converting values at the boundary of int64_t representability. Specifically, when the input value times the base equals exactly 2^63: static_cast<double>(INT64_MAX) rounds UP to 2^63 (INT64_MAX = 2^63-1 cannot be exactly represented as double) The check used strict >, so inBase == 2^63 passed the overflow check static_cast<int64_t>(std::round(2^63)) is undefined behavior since 2^63 exceeds INT64_MAX, producing INT64_MIN on x86-64 This created invalid TimeUnits, causing assertion failures in TimeInterval construction (mStart <= mEnd) The fix changes > to >= to properly catch this boundary case. Differential Revision: https://phabricator.services.mozilla.com/D282394 - - - - - 5 changed files: - dom/media/TimeUnits.cpp - + dom/media/test/crashtests/2014865.html - dom/media/test/crashtests/crashtests.list - gfx/thebes/gfxHarfBuzzShaper.cpp - gfx/thebes/gfxHarfBuzzShaper.h Changes: ===================================== dom/media/TimeUnits.cpp ===================================== @@ -80,7 +80,7 @@ TimeUnit TimeUnit::FromSeconds(double aValue, int64_t aBase) { // base -- we can keep this for some time until we're confident this is // stable. double inBase = aValue * static_cast<double>(aBase); - if (std::abs(inBase) > + if (std::abs(inBase) >= static_cast<double>(std::numeric_limits<int64_t>::max())) { NS_WARNING( nsPrintfCString("Warning: base %" PRId64 ===================================== dom/media/test/crashtests/2014865.html ===================================== @@ -0,0 +1,91 @@ +<!DOCTYPE html> +<html> +<head><meta charset="utf-8"></head> +<body> +<script> +/* + * Trigger TimeUnit::FromSeconds boundary overflow via MSE SourceBuffer.remove() + * + * Bug in dom/media/TimeUnits.cpp, FromSeconds(): + * double inBase = aValue * static_cast<double>(aBase); + * if (std::abs(inBase) > static_cast<double>(INT64_MAX)) return Infinity; + * return TimeUnit(static_cast<int64_t>(std::round(inBase)), aBase); + * + * static_cast<double>(INT64_MAX) rounds UP to 2^63. The check uses strict >, + * so inBase == 2^63 passes. static_cast<int64_t>(round(2^63)) is UNDEFINED + * BEHAVIOR (2^63 > INT64_MAX). On x86-64 it produces INT64_MIN (negative infinity), + * corrupting the TimeUnit. The Interval(start, end) constructor asserts start <= end; + * with end = -Inf and start = 0, the assertion fires. + */ + +(async function() { + if (!window.MediaSource) return; + + // Find a supported MSE type + const types = [ + 'audio/webm; codecs="opus"', + 'video/webm; codecs="vp8"', + 'video/webm; codecs="vp9"', + 'audio/mp4; codecs="mp4a.40.2"', + 'video/mp4; codecs="avc1.42E01E"', + 'audio/mp4; codecs="flac"', + ]; + let mimeType = null; + for (const t of types) { + if (MediaSource.isTypeSupported(t)) { mimeType = t; break; } + } + if (!mimeType) return; + + // Create MediaSource and SourceBuffer + const ms = new MediaSource(); + const video = document.createElement('video'); + video.src = URL.createObjectURL(ms); + document.body.appendChild(video); + await new Promise(r => ms.addEventListener('sourceopen', r)); + const sb = ms.addSourceBuffer(mimeType); + + // Critical boundary value: 2^63 / 10^6 ≈ 9223372036854.776 + // This is the value where inBase = value * 10^6 ≈ 2^63 exactly, + // which passes the > check but causes UB in static_cast<int64_t> + const criticalValue = 9223372036854.776; + + // Set duration large enough to allow the remove + try { ms.duration = criticalValue + 1; } catch(e) {} + + // Trigger the bug: remove(0, criticalValue) calls + // TimeUnit::FromSeconds(criticalValue) which overflows + try { + sb.remove(0, criticalValue); + await new Promise(r => { + sb.addEventListener('updateend', r, { once: true }); + sb.addEventListener('error', r, { once: true }); + setTimeout(r, 500); + }); + } catch(e) {} + + // Try a few more boundary values + const vals = [ + Math.pow(2, 63) / 1e6, + 9223372036854.775, + 1e15, + 1e16, + Number.MAX_SAFE_INTEGER, + ]; + for (const val of vals) { + try { + if (ms.readyState !== 'open' || sb.updating) break; + ms.duration = Math.abs(val) + 1; + sb.remove(0, val); + await new Promise(r => { + sb.addEventListener('updateend', r, { once: true }); + sb.addEventListener('error', r, { once: true }); + setTimeout(r, 300); + }); + } catch(e) {} + } + + video.remove(); +})(); +</script> +</body> +</html> ===================================== dom/media/test/crashtests/crashtests.list ===================================== @@ -188,3 +188,4 @@ load 1905231.webm load 1917627.mp4 skip-if(Android) load audioworkletprocessor-recursion.html load 2014824.html +load 2014865.html ===================================== gfx/thebes/gfxHarfBuzzShaper.cpp ===================================== @@ -1209,9 +1209,6 @@ static void AddOpenTypeFeature(uint32_t aTag, uint32_t aValue, void* aUserArg) { * gfxFontShaper override to initialize the text run using HarfBuzz */ -static hb_font_funcs_t* sHBFontFuncs = nullptr; -static hb_font_funcs_t* sNominalGlyphFunc = nullptr; -static hb_unicode_funcs_t* sHBUnicodeFuncs = nullptr; MOZ_RUNINIT static const hb_script_t sMathScript = hb_ot_tag_to_script(HB_TAG('m', 'a', 't', 'h')); @@ -1222,52 +1219,58 @@ bool gfxHarfBuzzShaper::Initialize() { mInitialized = true; mCallbackData.mShaper = this; - if (!sHBFontFuncs) { - // static function callback pointers, initialized by the first - // harfbuzz shaper used - sHBFontFuncs = hb_font_funcs_create(); - hb_font_funcs_set_nominal_glyph_func(sHBFontFuncs, HBGetNominalGlyph, - nullptr, nullptr); - hb_font_funcs_set_nominal_glyphs_func(sHBFontFuncs, HBGetNominalGlyphs, - nullptr, nullptr); - hb_font_funcs_set_variation_glyph_func(sHBFontFuncs, HBGetVariationGlyph, - nullptr, nullptr); - hb_font_funcs_set_glyph_h_advance_func(sHBFontFuncs, HBGetGlyphHAdvance, - nullptr, nullptr); - hb_font_funcs_set_glyph_h_advances_func(sHBFontFuncs, HBGetGlyphHAdvances, - nullptr, nullptr); - hb_font_funcs_set_glyph_v_advance_func(sHBFontFuncs, HBGetGlyphVAdvance, - nullptr, nullptr); - hb_font_funcs_set_glyph_v_origin_func(sHBFontFuncs, HBGetGlyphVOrigin, - nullptr, nullptr); - hb_font_funcs_set_glyph_extents_func(sHBFontFuncs, HBGetGlyphExtents, - nullptr, nullptr); - hb_font_funcs_set_glyph_contour_point_func(sHBFontFuncs, HBGetContourPoint, + // Function callback pointers; these are local statics to ensure thread-safe + // initialization on first use. + static hb_font_funcs_t* sHBFontFuncs = [] { + auto* funcs = hb_font_funcs_create(); + hb_font_funcs_set_nominal_glyph_func(funcs, HBGetNominalGlyph, nullptr, + nullptr); + hb_font_funcs_set_nominal_glyphs_func(funcs, HBGetNominalGlyphs, nullptr, + nullptr); + hb_font_funcs_set_variation_glyph_func(funcs, HBGetVariationGlyph, nullptr, + nullptr); + hb_font_funcs_set_glyph_h_advance_func(funcs, HBGetGlyphHAdvance, nullptr, + nullptr); + hb_font_funcs_set_glyph_h_advances_func(funcs, HBGetGlyphHAdvances, nullptr, + nullptr); + hb_font_funcs_set_glyph_v_advance_func(funcs, HBGetGlyphVAdvance, nullptr, + nullptr); + hb_font_funcs_set_glyph_v_origin_func(funcs, HBGetGlyphVOrigin, nullptr, + nullptr); + hb_font_funcs_set_glyph_extents_func(funcs, HBGetGlyphExtents, nullptr, + nullptr); + hb_font_funcs_set_glyph_contour_point_func(funcs, HBGetContourPoint, nullptr, nullptr); - hb_font_funcs_set_glyph_h_kerning_func(sHBFontFuncs, HBGetHKerning, nullptr, + hb_font_funcs_set_glyph_h_kerning_func(funcs, HBGetHKerning, nullptr, nullptr); - hb_font_funcs_make_immutable(sHBFontFuncs); - - sNominalGlyphFunc = hb_font_funcs_create(); - hb_font_funcs_set_nominal_glyph_func(sNominalGlyphFunc, HBGetNominalGlyph, - nullptr, nullptr); - hb_font_funcs_make_immutable(sNominalGlyphFunc); - - sHBUnicodeFuncs = hb_unicode_funcs_create(hb_unicode_funcs_get_empty()); - hb_unicode_funcs_set_mirroring_func(sHBUnicodeFuncs, HBGetMirroring, - nullptr, nullptr); - hb_unicode_funcs_set_script_func(sHBUnicodeFuncs, HBGetScript, nullptr, - nullptr); - hb_unicode_funcs_set_general_category_func( - sHBUnicodeFuncs, HBGetGeneralCategory, nullptr, nullptr); - hb_unicode_funcs_set_combining_class_func( - sHBUnicodeFuncs, HBGetCombiningClass, nullptr, nullptr); - hb_unicode_funcs_set_compose_func(sHBUnicodeFuncs, HBUnicodeCompose, - nullptr, nullptr); - hb_unicode_funcs_set_decompose_func(sHBUnicodeFuncs, HBUnicodeDecompose, - nullptr, nullptr); - hb_unicode_funcs_make_immutable(sHBUnicodeFuncs); - } + hb_font_funcs_make_immutable(funcs); + return funcs; + }(); + + static hb_font_funcs_t* sNominalGlyphFunc = [] { + auto* funcs = hb_font_funcs_create(); + hb_font_funcs_set_nominal_glyph_func(funcs, HBGetNominalGlyph, nullptr, + nullptr); + hb_font_funcs_make_immutable(funcs); + return funcs; + }(); + + static hb_unicode_funcs_t* sHBUnicodeFuncs = [] { + auto* funcs = hb_unicode_funcs_create(hb_unicode_funcs_get_empty()); + hb_unicode_funcs_set_mirroring_func(funcs, HBGetMirroring, nullptr, + nullptr); + hb_unicode_funcs_set_script_func(funcs, HBGetScript, nullptr, nullptr); + hb_unicode_funcs_set_general_category_func(funcs, HBGetGeneralCategory, + nullptr, nullptr); + hb_unicode_funcs_set_combining_class_func(funcs, HBGetCombiningClass, + nullptr, nullptr); + hb_unicode_funcs_set_compose_func(funcs, HBUnicodeCompose, nullptr, + nullptr); + hb_unicode_funcs_set_decompose_func(funcs, HBUnicodeDecompose, nullptr, + nullptr); + hb_unicode_funcs_make_immutable(funcs); + return funcs; + }(); gfxFontEntry* entry = mFont->GetFontEntry(); if (!mUseFontGetGlyph) { @@ -1314,23 +1317,23 @@ bool gfxHarfBuzzShaper::Initialize() { hb_buffer_set_cluster_level(mBuffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); - auto* funcs = - mFont->GetFontEntry()->HasFontTable(TRUETYPE_TAG('C', 'F', 'F', ' ')) - ? sNominalGlyphFunc - : sHBFontFuncs; - mHBFont = CreateHBFont(mFont, funcs, &mCallbackData); + bool isCFF = + mFont->GetFontEntry()->HasFontTable(TRUETYPE_TAG('C', 'F', 'F', ' ')); + auto* funcs = isCFF ? sNominalGlyphFunc : sHBFontFuncs; + mHBFont = CreateHBFont(mFont, funcs, &mCallbackData, isCFF); return true; } hb_font_t* gfxHarfBuzzShaper::CreateHBFont(gfxFont* aFont, hb_font_funcs_t* aFontFuncs, - FontCallbackData* aCallbackData) { + FontCallbackData* aCallbackData, + bool aCreateSubfont) { auto face(aFont->GetFontEntry()->GetHBFace()); hb_font_t* result = hb_font_create(face); if (aFontFuncs && aCallbackData) { - if (aFontFuncs == sNominalGlyphFunc) { + if (aCreateSubfont) { hb_font_t* subfont = hb_font_create_sub_font(result); hb_font_destroy(result); result = subfont; ===================================== gfx/thebes/gfxHarfBuzzShaper.h ===================================== @@ -95,7 +95,8 @@ class gfxHarfBuzzShaper : public gfxFontShaper { // bounds, etc; if not, the built-in hb_ot font functions will be used. static hb_font_t* CreateHBFont(gfxFont* aFont, hb_font_funcs_t* aFontFuncs = nullptr, - FontCallbackData* aCallbackData = nullptr); + FontCallbackData* aCallbackData = nullptr, + bool aCreateSubfont = false); hb_font_t* GetHBFont() const { return mHBFont; } hb_face_t* GetHBFace() const { return hb_font_get_face(mHBFont); } View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/308... -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/308... You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
participants (1)
-
ma1 (@ma1)