Commits:
-
d2654054
by Jonathan Kew at 2026-03-21T19:14:56+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
-
4ce26c3e
by Paul Adenot at 2026-03-21T23:04:48+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:
Changes:
dom/media/TimeUnits.cpp
| ... |
... |
@@ -80,7 +80,7 @@ TimeUnit TimeUnit::FromSeconds(double aValue, int64_t aBase) { |
|
80
|
80
|
// base -- we can keep this for some time until we're confident this is
|
|
81
|
81
|
// stable.
|
|
82
|
82
|
double inBase = aValue * static_cast<double>(aBase);
|
|
83
|
|
- if (std::abs(inBase) >
|
|
|
83
|
+ if (std::abs(inBase) >=
|
|
84
|
84
|
static_cast<double>(std::numeric_limits<int64_t>::max())) {
|
|
85
|
85
|
NS_WARNING(
|
|
86
|
86
|
nsPrintfCString("Warning: base %" PRId64
|
dom/media/test/crashtests/2014865.html
|
|
1
|
+<!DOCTYPE html>
|
|
|
2
|
+<html>
|
|
|
3
|
+<head><meta charset="utf-8"></head>
|
|
|
4
|
+<body>
|
|
|
5
|
+<script>
|
|
|
6
|
+/*
|
|
|
7
|
+ * Trigger TimeUnit::FromSeconds boundary overflow via MSE SourceBuffer.remove()
|
|
|
8
|
+ *
|
|
|
9
|
+ * Bug in dom/media/TimeUnits.cpp, FromSeconds():
|
|
|
10
|
+ * double inBase = aValue * static_cast<double>(aBase);
|
|
|
11
|
+ * if (std::abs(inBase) > static_cast<double>(INT64_MAX)) return Infinity;
|
|
|
12
|
+ * return TimeUnit(static_cast<int64_t>(std::round(inBase)), aBase);
|
|
|
13
|
+ *
|
|
|
14
|
+ * static_cast<double>(INT64_MAX) rounds UP to 2^63. The check uses strict >,
|
|
|
15
|
+ * so inBase == 2^63 passes. static_cast<int64_t>(round(2^63)) is UNDEFINED
|
|
|
16
|
+ * BEHAVIOR (2^63 > INT64_MAX). On x86-64 it produces INT64_MIN (negative infinity),
|
|
|
17
|
+ * corrupting the TimeUnit. The Interval(start, end) constructor asserts start <= end;
|
|
|
18
|
+ * with end = -Inf and start = 0, the assertion fires.
|
|
|
19
|
+ */
|
|
|
20
|
+
|
|
|
21
|
+(async function() {
|
|
|
22
|
+ if (!window.MediaSource) return;
|
|
|
23
|
+
|
|
|
24
|
+ // Find a supported MSE type
|
|
|
25
|
+ const types = [
|
|
|
26
|
+ 'audio/webm; codecs="opus"',
|
|
|
27
|
+ 'video/webm; codecs="vp8"',
|
|
|
28
|
+ 'video/webm; codecs="vp9"',
|
|
|
29
|
+ 'audio/mp4; codecs="mp4a.40.2"',
|
|
|
30
|
+ 'video/mp4; codecs="avc1.42E01E"',
|
|
|
31
|
+ 'audio/mp4; codecs="flac"',
|
|
|
32
|
+ ];
|
|
|
33
|
+ let mimeType = null;
|
|
|
34
|
+ for (const t of types) {
|
|
|
35
|
+ if (MediaSource.isTypeSupported(t)) { mimeType = t; break; }
|
|
|
36
|
+ }
|
|
|
37
|
+ if (!mimeType) return;
|
|
|
38
|
+
|
|
|
39
|
+ // Create MediaSource and SourceBuffer
|
|
|
40
|
+ const ms = new MediaSource();
|
|
|
41
|
+ const video = document.createElement('video');
|
|
|
42
|
+ video.src = URL.createObjectURL(ms);
|
|
|
43
|
+ document.body.appendChild(video);
|
|
|
44
|
+ await new Promise(r => ms.addEventListener('sourceopen', r));
|
|
|
45
|
+ const sb = ms.addSourceBuffer(mimeType);
|
|
|
46
|
+
|
|
|
47
|
+ // Critical boundary value: 2^63 / 10^6 ≈ 9223372036854.776
|
|
|
48
|
+ // This is the value where inBase = value * 10^6 ≈ 2^63 exactly,
|
|
|
49
|
+ // which passes the > check but causes UB in static_cast<int64_t>
|
|
|
50
|
+ const criticalValue = 9223372036854.776;
|
|
|
51
|
+
|
|
|
52
|
+ // Set duration large enough to allow the remove
|
|
|
53
|
+ try { ms.duration = criticalValue + 1; } catch(e) {}
|
|
|
54
|
+
|
|
|
55
|
+ // Trigger the bug: remove(0, criticalValue) calls
|
|
|
56
|
+ // TimeUnit::FromSeconds(criticalValue) which overflows
|
|
|
57
|
+ try {
|
|
|
58
|
+ sb.remove(0, criticalValue);
|
|
|
59
|
+ await new Promise(r => {
|
|
|
60
|
+ sb.addEventListener('updateend', r, { once: true });
|
|
|
61
|
+ sb.addEventListener('error', r, { once: true });
|
|
|
62
|
+ setTimeout(r, 500);
|
|
|
63
|
+ });
|
|
|
64
|
+ } catch(e) {}
|
|
|
65
|
+
|
|
|
66
|
+ // Try a few more boundary values
|
|
|
67
|
+ const vals = [
|
|
|
68
|
+ Math.pow(2, 63) / 1e6,
|
|
|
69
|
+ 9223372036854.775,
|
|
|
70
|
+ 1e15,
|
|
|
71
|
+ 1e16,
|
|
|
72
|
+ Number.MAX_SAFE_INTEGER,
|
|
|
73
|
+ ];
|
|
|
74
|
+ for (const val of vals) {
|
|
|
75
|
+ try {
|
|
|
76
|
+ if (ms.readyState !== 'open' || sb.updating) break;
|
|
|
77
|
+ ms.duration = Math.abs(val) + 1;
|
|
|
78
|
+ sb.remove(0, val);
|
|
|
79
|
+ await new Promise(r => {
|
|
|
80
|
+ sb.addEventListener('updateend', r, { once: true });
|
|
|
81
|
+ sb.addEventListener('error', r, { once: true });
|
|
|
82
|
+ setTimeout(r, 300);
|
|
|
83
|
+ });
|
|
|
84
|
+ } catch(e) {}
|
|
|
85
|
+ }
|
|
|
86
|
+
|
|
|
87
|
+ video.remove();
|
|
|
88
|
+})();
|
|
|
89
|
+</script>
|
|
|
90
|
+</body>
|
|
|
91
|
+</html> |
dom/media/test/crashtests/crashtests.list
| ... |
... |
@@ -188,3 +188,4 @@ load 1905231.webm |
|
188
|
188
|
load 1917627.mp4
|
|
189
|
189
|
skip-if(Android) load audioworkletprocessor-recursion.html
|
|
190
|
190
|
load 2014824.html
|
|
|
191
|
+load 2014865.html |
gfx/thebes/gfxHarfBuzzShaper.cpp
| ... |
... |
@@ -1209,9 +1209,6 @@ static void AddOpenTypeFeature(uint32_t aTag, uint32_t aValue, void* aUserArg) { |
|
1209
|
1209
|
* gfxFontShaper override to initialize the text run using HarfBuzz
|
|
1210
|
1210
|
*/
|
|
1211
|
1211
|
|
|
1212
|
|
-static hb_font_funcs_t* sHBFontFuncs = nullptr;
|
|
1213
|
|
-static hb_font_funcs_t* sNominalGlyphFunc = nullptr;
|
|
1214
|
|
-static hb_unicode_funcs_t* sHBUnicodeFuncs = nullptr;
|
|
1215
|
1212
|
MOZ_RUNINIT static const hb_script_t sMathScript =
|
|
1216
|
1213
|
hb_ot_tag_to_script(HB_TAG('m', 'a', 't', 'h'));
|
|
1217
|
1214
|
|
| ... |
... |
@@ -1222,52 +1219,58 @@ bool gfxHarfBuzzShaper::Initialize() { |
|
1222
|
1219
|
mInitialized = true;
|
|
1223
|
1220
|
mCallbackData.mShaper = this;
|
|
1224
|
1221
|
|
|
1225
|
|
- if (!sHBFontFuncs) {
|
|
1226
|
|
- // static function callback pointers, initialized by the first
|
|
1227
|
|
- // harfbuzz shaper used
|
|
1228
|
|
- sHBFontFuncs = hb_font_funcs_create();
|
|
1229
|
|
- hb_font_funcs_set_nominal_glyph_func(sHBFontFuncs, HBGetNominalGlyph,
|
|
1230
|
|
- nullptr, nullptr);
|
|
1231
|
|
- hb_font_funcs_set_nominal_glyphs_func(sHBFontFuncs, HBGetNominalGlyphs,
|
|
1232
|
|
- nullptr, nullptr);
|
|
1233
|
|
- hb_font_funcs_set_variation_glyph_func(sHBFontFuncs, HBGetVariationGlyph,
|
|
1234
|
|
- nullptr, nullptr);
|
|
1235
|
|
- hb_font_funcs_set_glyph_h_advance_func(sHBFontFuncs, HBGetGlyphHAdvance,
|
|
1236
|
|
- nullptr, nullptr);
|
|
1237
|
|
- hb_font_funcs_set_glyph_h_advances_func(sHBFontFuncs, HBGetGlyphHAdvances,
|
|
1238
|
|
- nullptr, nullptr);
|
|
1239
|
|
- hb_font_funcs_set_glyph_v_advance_func(sHBFontFuncs, HBGetGlyphVAdvance,
|
|
1240
|
|
- nullptr, nullptr);
|
|
1241
|
|
- hb_font_funcs_set_glyph_v_origin_func(sHBFontFuncs, HBGetGlyphVOrigin,
|
|
1242
|
|
- nullptr, nullptr);
|
|
1243
|
|
- hb_font_funcs_set_glyph_extents_func(sHBFontFuncs, HBGetGlyphExtents,
|
|
1244
|
|
- nullptr, nullptr);
|
|
1245
|
|
- hb_font_funcs_set_glyph_contour_point_func(sHBFontFuncs, HBGetContourPoint,
|
|
|
1222
|
+ // Function callback pointers; these are local statics to ensure thread-safe
|
|
|
1223
|
+ // initialization on first use.
|
|
|
1224
|
+ static hb_font_funcs_t* sHBFontFuncs = [] {
|
|
|
1225
|
+ auto* funcs = hb_font_funcs_create();
|
|
|
1226
|
+ hb_font_funcs_set_nominal_glyph_func(funcs, HBGetNominalGlyph, nullptr,
|
|
|
1227
|
+ nullptr);
|
|
|
1228
|
+ hb_font_funcs_set_nominal_glyphs_func(funcs, HBGetNominalGlyphs, nullptr,
|
|
|
1229
|
+ nullptr);
|
|
|
1230
|
+ hb_font_funcs_set_variation_glyph_func(funcs, HBGetVariationGlyph, nullptr,
|
|
|
1231
|
+ nullptr);
|
|
|
1232
|
+ hb_font_funcs_set_glyph_h_advance_func(funcs, HBGetGlyphHAdvance, nullptr,
|
|
|
1233
|
+ nullptr);
|
|
|
1234
|
+ hb_font_funcs_set_glyph_h_advances_func(funcs, HBGetGlyphHAdvances, nullptr,
|
|
|
1235
|
+ nullptr);
|
|
|
1236
|
+ hb_font_funcs_set_glyph_v_advance_func(funcs, HBGetGlyphVAdvance, nullptr,
|
|
|
1237
|
+ nullptr);
|
|
|
1238
|
+ hb_font_funcs_set_glyph_v_origin_func(funcs, HBGetGlyphVOrigin, nullptr,
|
|
|
1239
|
+ nullptr);
|
|
|
1240
|
+ hb_font_funcs_set_glyph_extents_func(funcs, HBGetGlyphExtents, nullptr,
|
|
|
1241
|
+ nullptr);
|
|
|
1242
|
+ hb_font_funcs_set_glyph_contour_point_func(funcs, HBGetContourPoint,
|
|
1246
|
1243
|
nullptr, nullptr);
|
|
1247
|
|
- hb_font_funcs_set_glyph_h_kerning_func(sHBFontFuncs, HBGetHKerning, nullptr,
|
|
|
1244
|
+ hb_font_funcs_set_glyph_h_kerning_func(funcs, HBGetHKerning, nullptr,
|
|
1248
|
1245
|
nullptr);
|
|
1249
|
|
- hb_font_funcs_make_immutable(sHBFontFuncs);
|
|
1250
|
|
-
|
|
1251
|
|
- sNominalGlyphFunc = hb_font_funcs_create();
|
|
1252
|
|
- hb_font_funcs_set_nominal_glyph_func(sNominalGlyphFunc, HBGetNominalGlyph,
|
|
1253
|
|
- nullptr, nullptr);
|
|
1254
|
|
- hb_font_funcs_make_immutable(sNominalGlyphFunc);
|
|
1255
|
|
-
|
|
1256
|
|
- sHBUnicodeFuncs = hb_unicode_funcs_create(hb_unicode_funcs_get_empty());
|
|
1257
|
|
- hb_unicode_funcs_set_mirroring_func(sHBUnicodeFuncs, HBGetMirroring,
|
|
1258
|
|
- nullptr, nullptr);
|
|
1259
|
|
- hb_unicode_funcs_set_script_func(sHBUnicodeFuncs, HBGetScript, nullptr,
|
|
1260
|
|
- nullptr);
|
|
1261
|
|
- hb_unicode_funcs_set_general_category_func(
|
|
1262
|
|
- sHBUnicodeFuncs, HBGetGeneralCategory, nullptr, nullptr);
|
|
1263
|
|
- hb_unicode_funcs_set_combining_class_func(
|
|
1264
|
|
- sHBUnicodeFuncs, HBGetCombiningClass, nullptr, nullptr);
|
|
1265
|
|
- hb_unicode_funcs_set_compose_func(sHBUnicodeFuncs, HBUnicodeCompose,
|
|
1266
|
|
- nullptr, nullptr);
|
|
1267
|
|
- hb_unicode_funcs_set_decompose_func(sHBUnicodeFuncs, HBUnicodeDecompose,
|
|
1268
|
|
- nullptr, nullptr);
|
|
1269
|
|
- hb_unicode_funcs_make_immutable(sHBUnicodeFuncs);
|
|
1270
|
|
- }
|
|
|
1246
|
+ hb_font_funcs_make_immutable(funcs);
|
|
|
1247
|
+ return funcs;
|
|
|
1248
|
+ }();
|
|
|
1249
|
+
|
|
|
1250
|
+ static hb_font_funcs_t* sNominalGlyphFunc = [] {
|
|
|
1251
|
+ auto* funcs = hb_font_funcs_create();
|
|
|
1252
|
+ hb_font_funcs_set_nominal_glyph_func(funcs, HBGetNominalGlyph, nullptr,
|
|
|
1253
|
+ nullptr);
|
|
|
1254
|
+ hb_font_funcs_make_immutable(funcs);
|
|
|
1255
|
+ return funcs;
|
|
|
1256
|
+ }();
|
|
|
1257
|
+
|
|
|
1258
|
+ static hb_unicode_funcs_t* sHBUnicodeFuncs = [] {
|
|
|
1259
|
+ auto* funcs = hb_unicode_funcs_create(hb_unicode_funcs_get_empty());
|
|
|
1260
|
+ hb_unicode_funcs_set_mirroring_func(funcs, HBGetMirroring, nullptr,
|
|
|
1261
|
+ nullptr);
|
|
|
1262
|
+ hb_unicode_funcs_set_script_func(funcs, HBGetScript, nullptr, nullptr);
|
|
|
1263
|
+ hb_unicode_funcs_set_general_category_func(funcs, HBGetGeneralCategory,
|
|
|
1264
|
+ nullptr, nullptr);
|
|
|
1265
|
+ hb_unicode_funcs_set_combining_class_func(funcs, HBGetCombiningClass,
|
|
|
1266
|
+ nullptr, nullptr);
|
|
|
1267
|
+ hb_unicode_funcs_set_compose_func(funcs, HBUnicodeCompose, nullptr,
|
|
|
1268
|
+ nullptr);
|
|
|
1269
|
+ hb_unicode_funcs_set_decompose_func(funcs, HBUnicodeDecompose, nullptr,
|
|
|
1270
|
+ nullptr);
|
|
|
1271
|
+ hb_unicode_funcs_make_immutable(funcs);
|
|
|
1272
|
+ return funcs;
|
|
|
1273
|
+ }();
|
|
1271
|
1274
|
|
|
1272
|
1275
|
gfxFontEntry* entry = mFont->GetFontEntry();
|
|
1273
|
1276
|
if (!mUseFontGetGlyph) {
|
| ... |
... |
@@ -1314,23 +1317,23 @@ bool gfxHarfBuzzShaper::Initialize() { |
|
1314
|
1317
|
hb_buffer_set_cluster_level(mBuffer,
|
|
1315
|
1318
|
HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
|
|
1316
|
1319
|
|
|
1317
|
|
- auto* funcs =
|
|
1318
|
|
- mFont->GetFontEntry()->HasFontTable(TRUETYPE_TAG('C', 'F', 'F', ' '))
|
|
1319
|
|
- ? sNominalGlyphFunc
|
|
1320
|
|
- : sHBFontFuncs;
|
|
1321
|
|
- mHBFont = CreateHBFont(mFont, funcs, &mCallbackData);
|
|
|
1320
|
+ bool isCFF =
|
|
|
1321
|
+ mFont->GetFontEntry()->HasFontTable(TRUETYPE_TAG('C', 'F', 'F', ' '));
|
|
|
1322
|
+ auto* funcs = isCFF ? sNominalGlyphFunc : sHBFontFuncs;
|
|
|
1323
|
+ mHBFont = CreateHBFont(mFont, funcs, &mCallbackData, isCFF);
|
|
1322
|
1324
|
|
|
1323
|
1325
|
return true;
|
|
1324
|
1326
|
}
|
|
1325
|
1327
|
|
|
1326
|
1328
|
hb_font_t* gfxHarfBuzzShaper::CreateHBFont(gfxFont* aFont,
|
|
1327
|
1329
|
hb_font_funcs_t* aFontFuncs,
|
|
1328
|
|
- FontCallbackData* aCallbackData) {
|
|
|
1330
|
+ FontCallbackData* aCallbackData,
|
|
|
1331
|
+ bool aCreateSubfont) {
|
|
1329
|
1332
|
auto face(aFont->GetFontEntry()->GetHBFace());
|
|
1330
|
1333
|
hb_font_t* result = hb_font_create(face);
|
|
1331
|
1334
|
|
|
1332
|
1335
|
if (aFontFuncs && aCallbackData) {
|
|
1333
|
|
- if (aFontFuncs == sNominalGlyphFunc) {
|
|
|
1336
|
+ if (aCreateSubfont) {
|
|
1334
|
1337
|
hb_font_t* subfont = hb_font_create_sub_font(result);
|
|
1335
|
1338
|
hb_font_destroy(result);
|
|
1336
|
1339
|
result = subfont;
|
gfx/thebes/gfxHarfBuzzShaper.h
| ... |
... |
@@ -95,7 +95,8 @@ class gfxHarfBuzzShaper : public gfxFontShaper { |
|
95
|
95
|
// bounds, etc; if not, the built-in hb_ot font functions will be used.
|
|
96
|
96
|
static hb_font_t* CreateHBFont(gfxFont* aFont,
|
|
97
|
97
|
hb_font_funcs_t* aFontFuncs = nullptr,
|
|
98
|
|
- FontCallbackData* aCallbackData = nullptr);
|
|
|
98
|
+ FontCallbackData* aCallbackData = nullptr,
|
|
|
99
|
+ bool aCreateSubfont = false);
|
|
99
|
100
|
|
|
100
|
101
|
hb_font_t* GetHBFont() const { return mHBFont; }
|
|
101
|
102
|
hb_face_t* GetHBFace() const { return hb_font_get_face(mHBFont); }
|
|