为什么特殊阿拉伯字符串能造成iOS系统和OS X下应用崩溃?解决方案是什么?15年重现bug。?

更新:15年5月14日,微信朋友出现字符串: ॣ ॣ ॣ ॣ。一旦查看该字符串,微信,手q,陌陌,来往等多个IOS应用崩溃。 ——分割线—— Fenn…
关注者
716
被浏览
92,250

16 个回答

先说结论吧:

@Bill Cheng

@麦子龙

@李铁柱

的答案都是片面的。

导致这个 bug 的元凶,在 iOS 6 上是 WebCore,在 OS X 10.8 上是 CoreText。

========

以下是反驳

@Bill Cheng

的观点:

这是那个越狱补丁的源码:

github.com/FilippoBiga/

很明显是给 WebCore 打的补丁。

代码的注释里提到了 WebCore 的 characterRangeCodePath() 函数会在处理 U+0600 ~ U+109F 的字符(其中 U+0600 ~ U+06FF、U+0750 ~ U+077F 是阿拉伯字母)时当成复杂类型,而它调用的 ComplexTextController::adjustGlyphsAndAdvances() 方法在处理这种类型的文字时却出错了。

以下代码来自 WebKit 源码(

github.com/WebKit/webki

):

float Font::getGlyphsAndAdvancesForComplexText(const TextRun& run, int from, int to, GlyphBuffer& glyphBuffer, ForTextEmphasisOrNot forTextEmphasis) const
{
    float initialAdvance;

    ComplexTextController controller(this, run, false, 0, forTextEmphasis);
    controller.advance(from);
    float beforeWidth = controller.runWidthSoFar();
    controller.advance(to, &glyphBuffer);

    if (glyphBuffer.isEmpty())
        return 0;

    float afterWidth = controller.runWidthSoFar();

    if (run.rtl()) {
        initialAdvance = controller.totalWidth() + controller.finalRoundingWidth() - afterWidth;
        glyphBuffer.reverse(0, glyphBuffer.size());
    } else
        initialAdvance = beforeWidth;

    return initialAdvance;
}

它构造了一个 ComplexTextController 类型的对象,这个对象在初始化时崩溃了。

这个类是 WebCore 框架的,根据

@麦子龙

和匿名用户的回答(

zhihu.com/question/2156

),这里并没有更深的调用栈(也不排除调用了内联函数),基本可以认定是 WebCore 的 bug。我并不同意

@麦子龙

说异常栈可能不完整的问题,因为汇编代码都显示出来了,在哪崩溃是很显而易见的。

补丁给出的解决办法是将类型设为自动,也就不会调用上述方法了。但这种解决办法也许会引来其他的 bug(主要是显示方面,对不用阿拉伯语的人应该没影响);只是我对阿拉伯语不了解,也就不妄做判断了。

更直接的依据是

@祝博韬

的回答:iOS 6 的 CoreText 是能正常显示这段文字的。

========

但上述结论也并不代表 CoreText 就没问题,我今天在测试时发现某些字符组合甚至可以让 OS X 10.8 的 Terminal 崩溃,而它是不太可能使用 WebCore 的。

此外,这个 bug 并不是在 iOS 7 和 OS X 10.9 上就不存在了,在某些情况下仍能造成 crash。例如用那串文字做文件名,那么 Finder 在显示这个文件时就会崩溃。这也让我觉得 CoreText 很可疑。

以 Mac Safari 的崩溃异常栈为例,它是在 CoreText 里崩溃的;虽然也用到了 WebCore,但崩溃的位置和 iOS 下不一样:

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libvDSP.dylib                 	0x00007fff8efdbadb 0x7fff8efbf000 + 117467
1   com.apple.CoreText            	0x00007fff8e6c1d5c TRun::TRun(TRun const&, CFRange, TRun::SubrangingStyle) + 850
2   com.apple.CoreText            	0x00007fff8e6c19ee CTGlyphRun::CloneRange(CTRun const*, CFRange, TRun::SubrangingStyle) + 142
3   com.apple.CoreText            	0x00007fff8e6d0764 TLine::SetLevelRange(CFRange, unsigned char, bool) + 162
4   com.apple.CoreText            	0x00007fff8e6d1e2c TLine::SetTrailingWhitespaceLevel(unsigned char) + 70
5   com.apple.CoreText            	0x00007fff8e6d1d58 TRunReorder::ReorderRuns(TBidiLevelsProvider const&, TLine&) + 122
6   com.apple.CoreText            	0x00007fff8e6d1bfe TTypesetter::FinishLineFill(TLine&, double, double) const + 142
7   com.apple.CoreText            	0x00007fff8e6c1775 CTTypesetterCreateLine + 131
8   com.apple.WebCore             	0x00007fff8f7f1f1a WebCore::ComplexTextController::collectComplexTextRunsForCharactersCoreText(unsigned short const*, unsigned int, unsigned int, WebCore::SimpleFontData const*) + 1562
9   com.apple.WebCore             	0x00007fff8f7f176a WebCore::ComplexTextController::collectComplexTextRuns() + 522

这是 Mac QQ 崩溃的异常栈,受伤的也是 CoreText:

Exception Type:  EXC_CRASH (SIGSEGV)
Exception Codes: 0x0000000000000000, 0x0000000000000000

Application Specific Information:
Performing @selector(paste:) from sender NSMenuItem 0x6b85170

Thread 0:: Dispatch queue: com.apple.main-thread
0   com.apple.CoreText            	0x9568e48f TStorageRange::SetStorageSubRange(CFRange) + 307
1   com.apple.CoreText            	0x9568cdf7 TRun::TRun(TRun const&, CFRange, TRun::SubrangingStyle) + 853
2   com.apple.CoreText            	0x9568ca8e CTGlyphRun::CloneRange(CTRun const*, CFRange, TRun::SubrangingStyle) + 166
3   com.apple.CoreText            	0x95699947 TLine::SetLevelRange(CFRange, unsigned char, bool) + 157
4   com.apple.CoreText            	0x9569b000 TLine::SetTrailingWhitespaceLevel(unsigned char) + 86
5   com.apple.CoreText            	0x9569aee7 TRunReorder::ReorderRuns(TBidiLevelsProvider const&, TLine&) + 107
6   com.apple.CoreText            	0x9569ad8c TTypesetter::FinishLineFill(TLine&, double, double) const + 122
7   com.apple.CoreText            	0x9568c870 TTypesetter::FillLine(TLine&, CFRange, double, double) const + 90
8   com.apple.CoreText            	0x9568c7ff CTTypesetterCreateLine + 185
9   com.apple.AppKit              	0x98085a9a -[NSATSLineFragment layoutForStartingGlyphAtIndex:characterIndex:minPosition:maxPosition:lineFragmentRect:] + 802
10  com.apple.AppKit              	0x9808452c -[NSATSTypesetter _layoutLineFragmentStartingWithGlyphAtIndex:characterIndex:atPoint:renderingContext:] + 2254
11  com.apple.AppKit              	0x980a91cb -[NSATSTypesetter layoutParagraphAtPoint:] + 147
12  com.apple.AppKit              	0x98645f21 -[NSTypesetter _layoutGlyphsInLayoutManager:startingAtGlyphIndex:maxNumberOfLineFragments:maxCharacterIndex:nextGlyphIndex:nextCharacterIndex:] + 3403
13  com.apple.AppKit              	0x980a813a -[NSTypesetter layoutCharactersInRange:forLayoutManager:maximumNumberOfLineFragments:] + 215
14  com.apple.AppKit              	0x980a8011 -[NSATSTypesetter layoutCharactersInRange:forLayoutManager:maximumNumberOfLineFragments:] + 1185
15  com.apple.AppKit              	0x980a69c5 -[NSLayoutManager(NSPrivate) _fillLayoutHoleForCharacterRange:desiredNumberOfLines:isSoft:] + 738
16  com.apple.AppKit              	0x980d9f1e _NSFastFillAllLayoutHolesForGlyphRange + 227
17  com.apple.AppKit              	0x980a4f95 -[NSLayoutManager textContainerForGlyphAtIndex:effectiveRange:] + 198
18  com.apple.AppKit              	0x97ee4a80 -[NSTextView(NSPrivate) _ensureLayoutCompleteToEndOfCharacterRange:] + 125
19  com.apple.AppKit              	0x97ee442b -[NSTextView(NSSharing) didChangeText] + 159
20  com.apple.AppKit              	0x97ee1f71 -[NSTextView insertText:replacementRange:] + 2261
21  com.tencent.qq                	0x003ed850 -[TXTypingTextView insertText:replacementRange:] + 163
22  com.apple.AppKit              	0x985b57f0 -[NSTextView insertText:] + 319
23  com.tencent.qq                	0x003cfd6c -[TXBaseTextView AddAttributedString:] + 69
24  com.tencent.qq                	0x003d2a0c -[TXBaseTextView ReadFromPasteborad:] + 392
25  com.tencent.qq                	0x003ee97f -[TXTypingTextView paste:] + 98
26  libobjc.A.dylib               	0x96dbc5d3 -[NSObject performSelector:withObject:] + 70
27  com.apple.AppKit              	0x98110ad2 -[NSApplication sendAction:to:from:] + 436
28  com.apple.AppKit              	0x9824d2fc -[NSMenuItem _corePerformAction] + 529
29  com.apple.AppKit              	0x9824cf8b -[NSCarbonMenuImpl performActionWithHighlightingForItemAtIndex:] + 163
30  com.apple.AppKit              	0x9824c614 -[NSMenu _performActionWithHighlightingForItemAtIndex:sendAccessibilityNotification:] + 79
31  com.apple.AppKit              	0x9824c5c0 -[NSMenu _performActionWithHighlightingForItemAtIndex:] + 48
32  com.apple.AppKit              	0x9824bac5 -[NSMenu performKeyEquivalent:] + 306
33  com.tencent.qq                	0x0053233e -[TXMenu performKeyEquivalent:] + 503
34  com.apple.AppKit              	0x9824ae7c -[NSApplication _handleKeyEquivalent:] + 915
35  com.apple.AppKit              	0x98100de1 -[NSApplication sendEvent:] + 5512
36  com.apple.AppKit              	0x9801a62c -[NSApplication run] + 951
37  com.apple.AppKit              	0x97fbd5f6 NSApplicationMain + 1053
38  com.tencent.qq                	0x000028b5 start + 53

这就说明在 OS X 10.8 上,CoreText 才是真凶。

因此 iOS 和 OS X 上各有不同的 bug,而不能想当然地认为凶手只有一个。

不完全同意

@Bill Cheng

的答案,iOS 6 上即使是 UILabel 也是用 WebCore 渲染的。

我倾向于 CoreText 和 WebCore 各自都有问题。这个 bug 应该归结为苹果从 WebCore 到 CoreText 都存在设计缺陷,对从右往左的编辑方向支持的不好,设计上没有考虑这种字符序列,而不能单纯说 WebCore 或者 CoreText 的某个地方有个小 bug。

我们看一下它们 crash 在什么地方。从 iOS 和 OSX 的 crash 上看它们 crash 在不同的地方。

iOS crash 在 WebCore 的 ComplexTextController::adjustGlyphsAndAdvances(),OSX 则 crash 在 CoreText 的 TRun::TRun(TRun const&, CFRange, TRun::SubrangingStyle) 或者 TRun::TruncateUnorderedEnd(CFRange, bool, TCharStream const&) 等地方。

因为不知道 binary 对应的源码版本,不确定 iOS adjustGlyphsAndAdvances crash 在源码的哪一行上。OSX 虽然 crash 在 CoreText 上,也不能排除是 WebCore 调用 CoreText 的 CTTypesetterCreateLine 函数时传的 typesetter 参数有问题。

10.9 上部分控件同样有这个问题,当把一个文件的文件名设为这段字符的时候 Finder 崩溃(如果你把这个文件放在桌面上……)。

那么,为什么说这个问题不是单独由 CoreText 引起的呢?

首先,OSX 10.8 使用 Firefox 是不会 crash 的。

其次,OSX 10.8 上只修改 WebCore,可以绕过这个 bug(注释掉这个 if 的前一半):

//    if (!m_mayUseNaturalWritingDirection || m_run.directionalOverride()) {
//        static const void* optionKeys[] = { kCTTypesetterOptionForcedEmbeddingLevel };
//        const short ltrForcedEmbeddingLevelValue = 0;
//        const short rtlForcedEmbeddingLevelValue = 1;
//        static const void* ltrOptionValues[] = { CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &ltrForcedEmbeddingLevelValue) };
//        static const void* rtlOptionValues[] = { CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &rtlForcedEmbeddingLevelValue) };
//        static CFDictionaryRef ltrTypesetterOptions = CFDictionaryCreate(kCFAllocatorDefault, optionKeys, ltrOptionValues, WTF_ARRAY_LENGTH(optionKeys), &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
//        static CFDictionaryRef rtlTypesetterOptions = CFDictionaryCreate(kCFAllocatorDefault, optionKeys, rtlOptionValues, WTF_ARRAY_LENGTH(optionKeys), &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
//
//#if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
//        ProviderInfo info = { cp, length, stringAttributes.get() };
//        RetainPtr<CTTypesetterRef> typesetter = adoptCF(wkCreateCTTypesetterWithUniCharProviderAndOptions(&provideStringAndAttributes, 0, &info, m_run.ltr() ? ltrTypesetterOptions : rtlTypesetterOptions));
//#else
//        RetainPtr<CFStringRef> string = adoptCF(CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, cp, length, kCFAllocatorNull));
//        RetainPtr<CFAttributedStringRef> attributedString = adoptCF(CFAttributedStringCreate(kCFAllocatorDefault, string.get(), stringAttributes.get()));
//        RetainPtr<CTTypesetterRef> typesetter = adoptCF(CTTypesetterCreateWithAttributedStringAndOptions(attributedString.get(), m_run.ltr() ? ltrTypesetterOptions : rtlTypesetterOptions));
//#endif
//
//        line = adoptCF(CTTypesetterCreateLine(typesetter.get(), CFRangeMake(0, 0)));
//    } else {
        ProviderInfo info = { cp, length, stringAttributes.get() };

        line = adoptCF(wkCreateCTLineWithUniCharProvider(&provideStringAndAttributes, 0, &info));
//    }

修改后这段字符串可以正常显示:

另外,在 iOS 6 上直接用 CoreText 渲染这段文字,是没有问题的(有图有真相)

10.8 上 Safari crash 在 WebCore 调用 CoreText 里:


10.9 上 Finder 崩溃的部分异常栈:

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libvDSP.dylib                 	0x00007fff8df210b3 0x7fff8deb4000 + 446643
1   com.apple.CoreText            	0x00007fff853b7ce7 TRun::TruncateUnorderedEnd(CFRange, bool, TCharStream const&) + 1207
2   com.apple.CoreText            	0x00007fff853b77ed TTruncator::AppendTruncatedRun(TLine&, CTRun const*, CFRange, bool) + 109
3   com.apple.CoreText            	0x00007fff853b7261 TTruncator::TruncateEndChars(long, double, TLine&, bool*) + 425
4   com.apple.CoreText            	0x00007fff853c7b92 TTruncator::MiddleTruncate(double, __CTRun const* (__CTLine const*, CFRange*, __CFDictionary const*) block_pointer) + 342
5   com.apple.CoreText            	0x00007fff853b6939 CTLineCreateTruncatedLineWithTokenHandler + 236
6   com.apple.CoreText            	0x00007fff853b6847 CTLineCreateTruncatedLineWithTokenCallback + 81
7   com.apple.AppKit              	0x00007fff83be9ff4 -[NSATSLineFragment layoutForStartingGlyphAtIndex:characterIndex:minPosition:maxPosition:lineFragmentRect:] + 2372
8   com.apple.AppKit              	0x00007fff83be825a -[NSATSTypesetter _layoutLineFragmentStartingWithGlyphAtIndex:characterIndex:atPoint:renderingContext:] + 2777
9   com.apple.AppKit              	0x00007fff83ebdddf -[NSSingleLineTypesetter 

iOS 6 的 crash 只 crash 在 WebCore 里:

	4   libsystem_platform.dylib            0x992ebdeb _sigtramp + 43
	5   ???                                 0xffffffff 0x0 + 4294967295
	6   WebCore                             0x04936db7 _ZN7WebCore21ComplexTextControllerC2EPKNS_4FontERKNS_7TextRunEbPN3WTF7HashSetIPKNS_14SimpleFontDataENS7_7PtrHashISB_EENS7_10HashTraitsISB_EEEEb + 855
	7   WebCore                             0x04936a5a _ZN7WebCore21ComplexTextControllerC1EPKNS_4FontERKNS_7TextRunEbPN3WTF7HashSetIPKNS_14SimpleFontDataENS7_7PtrHashISB_EENS7_10HashTraitsISB_EEEEb + 58
	8   WebCore                             0x04b71216 _ZNK7WebCore4Font34getGlyphsAndAdvancesForComplexTextERKNS_7TextRunEiiRNS_11GlyphBufferENS0_20ForTextEmphasisOrNotE + 70
	9   WebCore                             0x04b713fd _ZNK7WebCore4Font15drawComplexTextEPNS_15GraphicsContextERKNS_7TextRunERKNS_10FloatPointEii + 173
	10  WebCore                             0x04b69058 _ZNK7WebCore4Font8drawTextEPNS_15GraphicsContextERKNS_7TextRunERKNS_10FloatPointEii + 296
	11  WebCore                             0x04bd9c68 _ZN7WebCore15GraphicsContext12drawBidiTextERKNS_4FontERKNS_7TextRunERKNS_10FloatPointEPNS_10BidiStatusEi + 968
	12  WebKit                              0x0470d786 _ZL11drawAtPointPKtiRKN7WebCore10FloatPointERKNS1_4FontEPNS1_15GraphicsContextEbPNS1_10BidiStatusEi + 326


总结起来,CoreText 和 WebCore 都有问题。