CVE-2021-30717
A memory corruption vulnerability exists in the SMB Server on Apple macOS 11.2. A specially crafted SMB packet can trigger an integer overflow when handling directory query requests which can result in memory corruption, potentially leading to remote code execution and denial of service. This vulnerability can be triggered by sending a malicious packet to the vulnerable server.
Apple macOS 11.2
7.5 - CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE-190 - Integer Overflow or Wraparound
macOS is a series of proprietary operating systems developed by Apple with macOS 11.2, with Big Sur being the latest.
Server Message Block (SMB) is a network file sharing protocol widely used in Windows network environments and macOS contains a proprietary implementation of both server and client components. SMB is often used in office and enterprise environments for file and printer sharing.
Three distinct versions and multiple dialects of SMB protocol are supported by macOS’ SMB server. This vulnerability is present in SMB2 and newer versions of the protocol, more specifically in the QUERY_DIRECTORY
request processing.
Protocol specification shows that QUERY_DIRECTORY
request is to be used to get information about directory’s contents and search for files. As such, important parts of the QUERY_DIRECTORY
structure are FileId
that refers to the previously opened directory , and an arbitrary length search pattern which is sent in a form of a unicode string at the end. When processing QUERY_DIRECTORY
request in smb2_dispatch_query_directory
function, following code is executed:
1000291d0 int32_t rax_14 = platform::utf::ConvertUTF16toUTF8(&var_4e0, rax_13 + (rdx_2 << 1), &var_490, rcx_2, 4) [1]
1000291e2 int32_t rcx_4 = var_490.d - &var_448
1000291e6 int32_t rdx_5 = neg.d(rax_14) [2]
1000291e8 if (rax_14 == 0) [3]
1000291ea rdx_5 = rcx_4 [4]
1000291ed int64_t rax_15 = sx.q(rdx_5)
1000291f0 *(&var_448 + rax_15) = 0 [5]
1000291f8 r15 = 0
1000291fb int32_t rax_18
1000291fb int32_t rax_28
1000291fb if (rax_15.d s< 0)
100029257 rax_18 = smb2_schedule_error(arg1, 0xc00000e5)
10002920d else
10002920d std::__1::basic_string<c..., std::__1::allocator<char> >::assign(&var_488)
In the above code, a call to platform::utf::ConvertUTF16toUTF8
is made to covert a UTF16 search pattern string from the packet into a UTF8 one used by the rest of the code. Function ConvertUTF16toUTF8
has four distinct return values specifying different success conditions, zero being success and 1
, 2
and 3
being different error codes. At [2] in the above code, return value from ConvertUTF16toUTF8
call is negated and then the original value is compared to zero at [3]. If return code was zero (success), calculated length value is copied into rdx_5
, otherwise negated and sign extended value is copied into rax_15
which is then used to dereference a var_448
pointer and write a single NULL byte at the specified address at [5]. In regular execution, this code would write a single zero byte at the end of the converted string. However, if check at [3] fails, a negated error code is used instead. Possible error codes are 1
, 2
or 3
, which when negated and sign extended result in a very large value due to integer wraparound. At [5], this bogus value is used as an index to a pointer which results in an out of bounds memory write. Since the wraparound value is very large, this wraparound at [5] actually ends up accessing a stack pointer. With error value 3
, the wraparound actually ends up overwriting a most significant non-zero byte of a pointer on the stack which can lead to further memory corruption. We can observe this behavior in the debugger:
* thread #5, queue = 'com.apple.root.default-qos', stop reason = breakpoint 2.1
frame #0: 0x00000001070688af smbd`smb2_dispatch_query_directory(smb_request&, unsigned char*, unsigned char*) + 564
smbd`smb2_dispatch_query_directory:
-> 0x1070688af <+564>: call 0x107073bd2 ; platform::utf::ConvertUTF16toUTF8(unsigned short const**, unsigned short const*, unsigned char**, unsigned char*, unsigned int)
0x1070688b4 <+569>: mov ecx, dword ptr [rbp - 0x480]
0x1070688ba <+575>: lea rdx, [rbp - 0x438]
0x1070688c1 <+582>: sub ecx, edx
Target 0: (smbd) stopped.
(lldb) memory read $rdi -s 8 -fx
0x7000039b2840: 0x00007fc9adc13270 0x00007000039b28b8
0x7000039b2850: 0x00007000039b2870 0x00007fff6a2a9903
0x7000039b2860: 0x0000000001010021 0x0000000000000003
0x7000039b2870: 0x0000000000000001 0x0000000000060060
(lldb) memory read 0x00007fc9adc13270
0x7fc9adc13270: 06 d8 d8 06 00 00 00 20 35 2b dc 9a fc 07 07 00 .��.... 5+�.�...
0x7fc9adc13280: 00 0e 43 74 ff 7f 00 00 ab b1 58 ef 00 00 00 00 ..Ct�...��X�....
Above output shows a breakpoint just before a call to ConvertUTF16toUTF8
showing the unicode data to be converted. Stepping over the function call :
(lldb) ni
Process 85684 stopped
* thread #5, queue = 'com.apple.root.default-qos', stop reason = instruction step over
frame #0: 0x00000001070688b4 smbd`smb2_dispatch_query_directory(smb_request&, unsigned char*, unsigned char*) + 569
smbd`smb2_dispatch_query_directory:
-> 0x1070688b4 <+569>: mov ecx, dword ptr [rbp - 0x480]
0x1070688ba <+575>: lea rdx, [rbp - 0x438]
0x1070688c1 <+582>: sub ecx, edx
0x1070688c3 <+584>: mov edx, eax
Target 0: (smbd) stopped.
(lldb) register read rax
rax = 0x0000000000000003
Above shows the return value from ConvertUTF16toUTF8
being 3
which signifies illegal unicode sequence was found and conversion was stopped. Continuing forward to the point where out of bounds write occurs:
* thread #5, queue = 'com.apple.root.default-qos', stop reason = instruction step over
frame #0: 0x00000001070688cf smbd`smb2_dispatch_query_directory(smb_request&, unsigned char*, unsigned char*) + 596
smbd`smb2_dispatch_query_directory:
-> 0x1070688cf <+596>: mov byte ptr [rbp + rax - 0x438], 0x0
0x1070688d7 <+604>: xor r15d, r15d
0x1070688da <+607>: test eax, eax
0x1070688dc <+609>: js 0x107068977 ; <+764>
Target 0: (smbd) stopped.
(lldb) reg read rax
rax = 0xfffffffffffffffd
(lldb) memory read $rbp+$rax-0x438
0x7000039b28d5: 7f 00 00 a0 d7 c2 ad c9 7f 00 00 a0 a0 c1 ad c9 ...���...����
0x7000039b28e5: 7f 00 00 10 32 c1 ad c9 7f 00 00 66 00 00 00 00 ....2��...f....
(lldb) memory read $rbp+$rax-0x438 -s 8 -fx
0x7000039b28d5: 0xc9adc2d7a000007f 0xc9adc1a0a000007f
0x7000039b28e5: 0xc9adc1321000007f 0x000000006600007f
0x7000039b28f5: 0x00039b2908000000 0x0000000000000070
0x7000039b2905: 0xff00000066660000 0xc9adc1a0a000007f
Above shows the code right before the out of bounds write happens. Value in rax
is used to index into a stack buffer and we can see it’s value is abnormally large. The pointer dereference points to $rbp+$rax-0x438
which is this same function’s stack. As it is currently lined up, out of bounds write will overwrite the 0x7f MSB of a pointer.
(lldb) ni
Process 85684 stopped
* thread #5, queue = 'com.apple.root.default-qos', stop reason = instruction step over
frame #0: 0x00000001070688d7 smbd`smb2_dispatch_query_directory(smb_request&, unsigned char*, unsigned char*) + 604
smbd`smb2_dispatch_query_directory:
-> 0x1070688d7 <+604>: xor r15d, r15d
0x1070688da <+607>: test eax, eax
0x1070688dc <+609>: js 0x107068977 ; <+764>
0x1070688e2 <+615>: lea rdi, [rbp - 0x460]
Target 0: (smbd) stopped.
(lldb) memory read $rbp+$rax-0x438 -s 8 -fx
0x7000039b28d5: 0xc9adc2d7a0000000 0xc9adc1a0a000007f
0x7000039b28e5: 0xc9adc1321000007f 0x000000006600007f
0x7000039b28f5: 0x00039b2908000000 0x0000000000000070
0x7000039b2905: 0xff00000066660000 0xc9adc1a0a000007f
(lldb)
And indeed, after the out of bounds write, the pointer on the stack is corrupted.
Continuing execution will lead to a crash when this pointer is next used:
* thread #5, queue = 'com.apple.root.default-qos', stop reason = EXC_BAD_ACCESS (code=1, address=0xc9adc2d7b0)
frame #0: 0x00000001070687e2 smbd`smb2_dispatch_query_directory(smb_request&, unsigned char*, unsigned char*) + 359
smbd`smb2_dispatch_query_directory:
-> 0x1070687e2 <+359>: lock
0x1070687e3 <+360>: dec dword ptr [rdi + 0x10]
0x1070687e6 <+363>: jne 0x1070687f2 ; <+375>
0x1070687e8 <+365>: add rdi, 0x8
Target 0: (smbd) stopped.
(lldb) reg read rdi
rdi = 0x000000c9adc2d7a0
(lldb) bt
* thread #5, queue = 'com.apple.root.default-qos', stop reason = EXC_BAD_ACCESS (code=1, address=0xc9adc2d7b0)
* frame #0: 0x00000001070687e2 smbd`smb2_dispatch_query_directory(smb_request&, unsigned char*, unsigned char*) + 359
frame #1: 0x000000010706aabe smbd`smb2_dispatch_compound(smb_transport*, unsigned char*, unsigned char*) + 1358
frame #2: 0x00000001070561a1 smbd`invocation function for block in smb_transport::dispatch() + 54
frame #3: 0x00007fff6a04e6c4 libdispatch.dylib`_dispatch_call_block_and_release + 12
frame #4: 0x00007fff6a04f658 libdispatch.dylib`_dispatch_client_callout + 8
frame #5: 0x00007fff6a05daa8 libdispatch.dylib`_dispatch_root_queue_drain + 663
frame #6: 0x00007fff6a05e097 libdispatch.dylib`_dispatch_worker_thread2 + 92
frame #7: 0x00007fff6a2a99f7 libsystem_pthread.dylib`_pthread_wqthread + 220
frame #8: 0x00007fff6a2a8b77 libsystem_pthread.dylib`start_wqthread + 15
(lldb)
Above shows a write access violation when a corrupt pointer is being dereferenced. The corrupted pointer is being dereferenced during function teardown to free allocated resources, and what’s more important, the access violation happens right before the following code:
0x107068c4b <+1488>: lock
0x107068c4c <+1489>: dec dword ptr [rdi + 0x10]
0x107068c4f <+1492>: jne 0x107068c5b ; <+1504>
0x107068c51 <+1494>: add rdi, 0x8
0x107068c55 <+1498>: mov rax, qword ptr [rdi]
0x107068c58 <+1501>: call qword ptr [rax + 0x8]
Above code shows a vtable dereference from rdi
(the corrupted pointer) and a call instruction using that dereference. If sufficient control over memory layout is achieved by the attacker, and corrupted pointer ends up pointing to memory under attacker’s control, this could lead to arbitrary code execution.
2021-03-09 - Vendor Disclosure
2021-05-25 - Vendor Patched
2021-06-02 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.