1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "gfxGraphiteShaper.h"
8 #include "gfxContext.h"
9 #include "gfxFontConstants.h"
10 #include "gfxTextRun.h"
12 #include "graphite2/Font.h"
13 #include "graphite2/GraphiteExtra.h"
14 #include "graphite2/Segment.h"
16 #include "harfbuzz/hb.h"
18 #include "mozilla/ScopeExit.h"
20 #include "ThebesRLBox.h"
22 #define FloatToFixed(f) (65536 * (f))
23 #define FixedToFloat(f) ((f) * (1.0 / 65536.0))
24 // Right shifts of negative (signed) integers are undefined, as are overflows
25 // when converting unsigned to negative signed integers.
26 // (If speed were an issue we could make some 2's complement assumptions.)
27 #define FixedToIntRound(f) \
28 ((f) > 0 ? ((32768 + (f)) >> 16) : -((32767 - (f)) >> 16))
30 #define CopyAndVerifyOrFail(t, cond, failed) \
31 (t).copy_and_verify([&](auto val) { \
38 using namespace mozilla
; // for AutoSwap_* types
41 * Creation and destruction; on deletion, release any font tables we're holding
44 gfxGraphiteShaper::gfxGraphiteShaper(gfxFont
* aFont
)
45 : gfxFontShaper(aFont
),
46 mGrFace(mFont
->GetFontEntry()->GetGrFace()),
47 mSandbox(mFont
->GetFontEntry()->GetGrSandbox()),
48 mCallback(mFont
->GetFontEntry()->GetGrSandboxAdvanceCallbackHandle()),
49 mFallbackToSmallCaps(false) {
50 mCallbackData
.mFont
= aFont
;
53 gfxGraphiteShaper::~gfxGraphiteShaper() {
54 auto t_mGrFont
= rlbox::from_opaque(mGrFont
);
56 sandbox_invoke(*mSandbox
, gr_font_destroy
, t_mGrFont
);
58 mFont
->GetFontEntry()->ReleaseGrFace(mGrFace
);
62 thread_local
gfxGraphiteShaper::CallbackData
*
63 gfxGraphiteShaper::tl_GrGetAdvanceData
= nullptr;
66 tainted_opaque_gr
<float> gfxGraphiteShaper::GrGetAdvance(
67 rlbox_sandbox_gr
& sandbox
,
68 tainted_opaque_gr
<const void*> /* appFontHandle */,
69 tainted_opaque_gr
<uint16_t> t_glyphid
) {
70 CallbackData
* cb
= tl_GrGetAdvanceData
;
72 // GrGetAdvance callback called unexpectedly. Just return safe value.
73 tainted_gr
<float> ret
= 0;
74 return ret
.to_opaque();
76 auto glyphid
= rlbox::from_opaque(t_glyphid
).unverified_safe_because(
77 "Here the only use of a glyphid is for lookup to get a width. "
78 "Implementations of GetGlyphWidth in this code base use a hashtable "
79 "which is robust to unknown keys. So no validation is required.");
80 tainted_gr
<float> ret
= FixedToFloat(cb
->mFont
->GetGlyphWidth(glyphid
));
81 return ret
.to_opaque();
84 static inline uint32_t MakeGraphiteLangTag(uint32_t aTag
) {
85 uint32_t grLangTag
= aTag
;
86 // replace trailing space-padding with NULs for graphite
87 uint32_t mask
= 0x000000FF;
88 while ((grLangTag
& mask
) == ' ') {
95 struct GrFontFeatures
{
96 tainted_gr
<gr_face
*> mFace
;
97 tainted_gr
<gr_feature_val
*> mFeatures
;
98 rlbox_sandbox_gr
* mSandbox
;
101 static void AddFeature(const uint32_t& aTag
, uint32_t& aValue
, void* aUserArg
) {
102 GrFontFeatures
* f
= static_cast<GrFontFeatures
*>(aUserArg
);
104 tainted_gr
<const gr_feature_ref
*> fref
=
105 sandbox_invoke(*(f
->mSandbox
), gr_face_find_fref
, f
->mFace
, aTag
);
107 sandbox_invoke(*(f
->mSandbox
), gr_fref_set_feature_value
, fref
, aValue
,
112 // Count the number of Unicode characters in a UTF-16 string (i.e. surrogate
113 // pairs are counted as 1, although they are 2 code units).
114 // (Any isolated surrogates will count 1 each, because in decoding they would
115 // be replaced by individual U+FFFD REPLACEMENT CHARACTERs.)
116 static inline size_t CountUnicodes(const char16_t
* aText
, uint32_t aLength
) {
118 const char16_t
* end
= aText
+ aLength
;
119 while (aText
< end
) {
120 if (NS_IS_HIGH_SURROGATE(*aText
) && aText
+ 1 < end
&&
121 NS_IS_LOW_SURROGATE(*(aText
+ 1))) {
131 bool gfxGraphiteShaper::ShapeText(DrawTarget
* aDrawTarget
,
132 const char16_t
* aText
, uint32_t aOffset
,
133 uint32_t aLength
, Script aScript
,
134 nsAtom
* aLanguage
, bool aVertical
,
135 RoundingFlags aRounding
,
136 gfxShapedText
* aShapedText
) {
137 const gfxFontStyle
* style
= mFont
->GetStyle();
138 auto t_mGrFace
= rlbox::from_opaque(mGrFace
);
139 auto t_mGrFont
= rlbox::from_opaque(mGrFont
);
146 if (mFont
->ProvidesGlyphWidths()) {
147 auto p_ops
= mSandbox
->malloc_in_sandbox
<gr_font_ops
>();
151 auto clean_ops
= MakeScopeExit([&] { mSandbox
->free_in_sandbox(p_ops
); });
152 p_ops
->size
= sizeof(*p_ops
);
153 p_ops
->glyph_advance_x
= *mCallback
;
154 p_ops
->glyph_advance_y
= nullptr; // vertical text not yet implemented
155 t_mGrFont
= sandbox_invoke(
156 *mSandbox
, gr_make_font_with_ops
, mFont
->GetAdjustedSize(),
157 // For security, we do not pass the callback data to this arg, and use
158 // a TLS var instead. However, gr_make_font_with_ops expects this to
159 // be a non null ptr, and changes its behavior if it isn't. Therefore,
160 // we should pass some dummy non null pointer which will be passed to
161 // the GrGetAdvance callback, but never used. Let's just pass p_ops
162 // again, as this is a non-null tainted pointer.
163 p_ops
/* mCallbackData */, p_ops
, t_mGrFace
);
165 t_mGrFont
= sandbox_invoke(*mSandbox
, gr_make_font
,
166 mFont
->GetAdjustedSize(), t_mGrFace
);
168 mGrFont
= t_mGrFont
.to_opaque();
174 // determine whether petite-caps falls back to small-caps
175 if (style
->variantCaps
!= NS_FONT_VARIANT_CAPS_NORMAL
) {
176 switch (style
->variantCaps
) {
177 case NS_FONT_VARIANT_CAPS_ALLPETITE
:
178 case NS_FONT_VARIANT_CAPS_PETITECAPS
:
179 bool synLower
, synUpper
;
180 mFont
->SupportsVariantCaps(aScript
, style
->variantCaps
,
181 mFallbackToSmallCaps
, synLower
, synUpper
);
189 gfxFontEntry
* entry
= mFont
->GetFontEntry();
191 if (style
->languageOverride
) {
192 grLang
= MakeGraphiteLangTag(style
->languageOverride
);
193 } else if (entry
->mLanguageOverride
) {
194 grLang
= MakeGraphiteLangTag(entry
->mLanguageOverride
);
195 } else if (aLanguage
) {
196 nsAutoCString langString
;
197 aLanguage
->ToUTF8String(langString
);
198 grLang
= GetGraphiteTagForLang(langString
);
200 tainted_gr
<gr_feature_val
*> grFeatures
=
201 sandbox_invoke(*mSandbox
, gr_face_featureval_for_lang
, t_mGrFace
, grLang
);
203 // insert any merged features into Graphite feature list
204 GrFontFeatures f
= {t_mGrFace
, grFeatures
, mSandbox
};
205 MergeFontFeatures(style
, mFont
->GetFontEntry()->mFeatureSettings
,
206 aShapedText
->DisableLigatures(),
207 mFont
->GetFontEntry()->FamilyName(), mFallbackToSmallCaps
,
210 // Graphite shaping doesn't map U+00a0 (nbsp) to space if it is missing
211 // from the font, so check for that possibility. (Most fonts double-map
212 // the space glyph to both 0x20 and 0xA0, so this won't often be needed;
213 // so we don't copy the text until we know it's required.)
214 nsAutoString transformed
;
215 const char16_t NO_BREAK_SPACE
= 0x00a0;
216 if (!entry
->HasCharacter(NO_BREAK_SPACE
)) {
217 nsDependentSubstring
src(aText
, aLength
);
218 if (src
.FindChar(NO_BREAK_SPACE
) != kNotFound
) {
220 transformed
.ReplaceChar(NO_BREAK_SPACE
, ' ');
221 aText
= transformed
.BeginReading();
225 size_t numChars
= CountUnicodes(aText
, aLength
);
226 gr_bidirtl grBidi
= gr_bidirtl(
227 aShapedText
->IsRightToLeft() ? (gr_rtl
| gr_nobidi
) : gr_nobidi
);
229 tainted_gr
<char16_t
*> t_aText
=
230 mSandbox
->malloc_in_sandbox
<char16_t
>(aLength
);
234 auto clean_txt
= MakeScopeExit([&] { mSandbox
->free_in_sandbox(t_aText
); });
236 rlbox::memcpy(*mSandbox
, t_aText
, aText
, aLength
* sizeof(char16_t
));
238 tl_GrGetAdvanceData
= &mCallbackData
;
239 auto clean_adv_data
= MakeScopeExit([&] { tl_GrGetAdvanceData
= nullptr; });
241 tainted_gr
<gr_segment
*> seg
=
242 sandbox_invoke(*mSandbox
, gr_make_seg
, mGrFont
, t_mGrFace
, 0, grFeatures
,
243 gr_utf16
, t_aText
, numChars
, grBidi
);
245 sandbox_invoke(*mSandbox
, gr_featureval_destroy
, grFeatures
);
252 SetGlyphsFromSegment(aShapedText
, aOffset
, aLength
, aText
,
253 t_aText
.to_opaque(), seg
.to_opaque(), aRounding
);
255 sandbox_invoke(*mSandbox
, gr_seg_destroy
, seg
);
257 return NS_SUCCEEDED(rv
);
260 nsresult
gfxGraphiteShaper::SetGlyphsFromSegment(
261 gfxShapedText
* aShapedText
, uint32_t aOffset
, uint32_t aLength
,
262 const char16_t
* aText
, tainted_opaque_gr
<char16_t
*> t_aText
,
263 tainted_opaque_gr
<gr_segment
*> aSegment
, RoundingFlags aRounding
) {
264 typedef gfxShapedText::CompressedGlyph CompressedGlyph
;
266 int32_t dev2appUnits
= aShapedText
->GetAppUnitsPerDevUnit();
267 bool rtl
= aShapedText
->IsRightToLeft();
269 // identify clusters; graphite may have reordered/expanded/ligated glyphs.
270 tainted_gr
<gr_glyph_to_char_association
*> data
=
271 sandbox_invoke(*mSandbox
, gr_get_glyph_to_char_association
, aSegment
,
272 aLength
, rlbox::from_opaque(t_aText
));
275 return NS_ERROR_FAILURE
;
278 tainted_gr
<gr_glyph_to_char_cluster
*> clusters
= data
->clusters
;
279 tainted_gr
<uint16_t*> gids
= data
->gids
;
280 tainted_gr
<float*> xLocs
= data
->xLocs
;
281 tainted_gr
<float*> yLocs
= data
->yLocs
;
283 CompressedGlyph
* charGlyphs
= aShapedText
->GetCharacterGlyphs() + aOffset
;
285 bool roundX
= bool(aRounding
& RoundingFlags::kRoundX
);
286 bool roundY
= bool(aRounding
& RoundingFlags::kRoundY
);
288 bool failedVerify
= false;
290 // cIndex is primarily used to index into the clusters array which has size
291 // aLength below. As cIndex is not changing anymore, let's just verify it
292 // and remove the tainted wrapper.
294 CopyAndVerifyOrFail(data
->cIndex
, val
< aLength
, &failedVerify
);
296 return NS_ERROR_ILLEGAL_VALUE
;
298 // now put glyphs into the textrun, one cluster at a time
299 for (uint32_t i
= 0; i
<= cIndex
; ++i
) {
300 // We makes a local copy of "clusters[i]" which is of type
301 // tainted_gr<gr_glyph_to_char_cluster> below. We do this intentionally
302 // rather than taking a reference. Taking a reference with the code
304 // tainted_volatile_gr<gr_glyph_to_char_cluster>& c = clusters[i];
306 // produces a tainted_volatile which means the value can change at any
307 // moment allowing for possible time-of-check-time-of-use vuln. We thus
308 // make a local copy to simplify the verification.
309 tainted_gr
<gr_glyph_to_char_cluster
> c
= clusters
[i
];
311 tainted_gr
<float> t_adv
; // total advance of the cluster
314 t_adv
= sandbox_invoke(*mSandbox
, gr_seg_advance_X
, aSegment
) -
317 t_adv
= xLocs
[clusters
[i
- 1].baseGlyph
] - xLocs
[c
.baseGlyph
];
321 t_adv
= sandbox_invoke(*mSandbox
, gr_seg_advance_X
, aSegment
) -
324 t_adv
= xLocs
[clusters
[i
+ 1].baseGlyph
] - xLocs
[c
.baseGlyph
];
328 float adv
= t_adv
.unverified_safe_because(
329 "Per Bug 1569464 - this is the advance width of a glyph or cluster of "
330 "glyphs. There are no a-priori limits on what that might be. Incorrect "
331 "values will tend to result in bad layout or missing text, or bad "
332 "nscoord values. But, these will not result in safety issues.");
334 // check unexpected offset - offs used to index into aText
336 CopyAndVerifyOrFail(c
.baseChar
, val
< aLength
, &failedVerify
);
338 return NS_ERROR_ILLEGAL_VALUE
;
341 // Check for default-ignorable char that didn't get filtered, combined,
342 // etc by the shaping process, and skip it.
343 auto one_glyph
= c
.nGlyphs
== static_cast<uint32_t>(1);
344 auto one_char
= c
.nChars
== static_cast<uint32_t>(1);
346 if ((one_glyph
&& one_char
)
347 .unverified_safe_because(
348 "using this boolean check to decide whether to ignore a "
349 "character or not. The worst that can happen is a bad "
351 if (aShapedText
->FilterIfIgnorable(aOffset
+ offs
, aText
[offs
])) {
356 uint32_t appAdvance
= roundX
? NSToIntRound(adv
) * dev2appUnits
357 : NSToIntRound(adv
* dev2appUnits
);
359 const char gid_simple_value
[] =
360 "Per Bug 1569464 - these are glyph IDs that can range from 0 to the "
361 "maximum glyph ID supported by the font. However, out-of-range values "
362 "here should not lead to safety issues; they would simply result in "
363 "blank rendering, although this depends on the platform back-end.";
365 // gids[c.baseGlyph] is checked and used below. Since this is a
366 // tainted_volatile, which can change at any moment, we make a local copy
367 // first to prevent a time-of-check-time-of-use vuln.
368 uint16_t gid_of_base_glyph
=
369 gids
[c
.baseGlyph
].unverified_safe_because(gid_simple_value
);
371 const char fast_path
[] =
372 "Even if the number of glyphs set is an incorrect value, the else "
373 "branch is a more general purpose algorithm which can handle other "
376 if (one_glyph
.unverified_safe_because(fast_path
) &&
377 CompressedGlyph::IsSimpleGlyphID(gid_of_base_glyph
) &&
378 CompressedGlyph::IsSimpleAdvance(appAdvance
) &&
379 charGlyphs
[offs
].IsClusterStart() &&
380 (yLocs
[c
.baseGlyph
] == 0).unverified_safe_because(fast_path
)) {
381 charGlyphs
[offs
].SetSimpleGlyph(appAdvance
, gid_of_base_glyph
);
384 // not a one-to-one mapping with simple metrics: use DetailedGlyph
385 AutoTArray
<gfxShapedText::DetailedGlyph
, 8> details
;
389 (c
.baseGlyph
+ c
.nGlyphs
)
390 .unverified_safe_because(
391 "This only controls the total number of glyphs set for this "
392 "particular text. Worst that can happen is a bad rendering");
394 // check overflow - ensure loop start is before the end
395 uint32_t glyph_start
=
396 CopyAndVerifyOrFail(c
.baseGlyph
, val
<= glyph_end
, &failedVerify
);
398 return NS_ERROR_ILLEGAL_VALUE
;
401 for (uint32_t j
= glyph_start
; j
< glyph_end
; ++j
) {
402 gfxShapedText::DetailedGlyph
* d
= details
.AppendElement();
403 d
->mGlyphID
= gids
[j
].unverified_safe_because(gid_simple_value
);
405 const char safe_coordinates
[] =
406 "There are no limits on coordinates. Worst case, bad values would "
407 "force rendering off-screen, but there are no memory safety "
410 float yLocs_j
= yLocs
[j
].unverified_safe_because(safe_coordinates
);
411 float xLocs_j
= xLocs
[j
].unverified_safe_because(safe_coordinates
);
413 d
->mOffset
.y
= roundY
? NSToIntRound(-yLocs_j
) * dev2appUnits
414 : -yLocs_j
* dev2appUnits
;
415 if (j
== glyph_start
) {
416 d
->mAdvance
= appAdvance
;
417 clusterLoc
= xLocs_j
;
420 rtl
? (xLocs_j
- clusterLoc
) : (xLocs_j
- clusterLoc
- adv
);
422 roundX
? NSToIntRound(dx
) * dev2appUnits
: dx
* dev2appUnits
;
426 aShapedText
->SetDetailedGlyphs(aOffset
+ offs
, details
.Length(),
430 // check unexpected offset
431 uint32_t char_end
= CopyAndVerifyOrFail(c
.baseChar
+ c
.nChars
,
432 val
<= aLength
, &failedVerify
);
433 // check overflow - ensure loop start is before the end
434 uint32_t char_start
=
435 CopyAndVerifyOrFail(c
.baseChar
+ 1, val
<= char_end
, &failedVerify
);
437 return NS_ERROR_ILLEGAL_VALUE
;
440 for (uint32_t j
= char_start
; j
< char_end
; ++j
) {
441 CompressedGlyph
& g
= charGlyphs
[j
];
442 NS_ASSERTION(!g
.IsSimpleGlyph(), "overwriting a simple glyph");
443 g
.SetComplex(g
.IsClusterStart(), false);
447 sandbox_invoke(*mSandbox
, gr_free_char_association
, data
);
451 // for language tag validation - include list of tags from the IANA registry
452 #include "gfxLanguageTagList.cpp"
454 nsTHashSet
<uint32_t>* gfxGraphiteShaper::sLanguageTags
;
457 uint32_t gfxGraphiteShaper::GetGraphiteTagForLang(const nsCString
& aLang
) {
458 int len
= aLang
.Length();
463 // convert primary language subtag to a left-packed, NUL-padded integer
464 // for the Graphite API
466 for (int i
= 0; i
< 4; ++i
) {
469 uint8_t ch
= aLang
[i
];
471 // found end of primary language subtag, truncate here
475 if (ch
< 'a' || ch
> 'z') {
476 // invalid character in tag, so ignore it completely
483 // valid tags must have length = 2 or 3
484 if (len
< 2 || len
> 3) {
488 if (!sLanguageTags
) {
489 // store the registered IANA tags in a hash for convenient validation
490 sLanguageTags
= new nsTHashSet
<uint32_t>(ArrayLength(sLanguageTagList
));
491 for (const uint32_t* tag
= sLanguageTagList
; *tag
!= 0; ++tag
) {
492 sLanguageTags
->Insert(*tag
);
496 // only accept tags known in the IANA registry
497 if (sLanguageTags
->Contains(grLang
)) {
505 void gfxGraphiteShaper::Shutdown() {
506 #ifdef NS_FREE_PERMANENT_DATA
508 sLanguageTags
->Clear();
509 delete sLanguageTags
;
510 sLanguageTags
= nullptr;