Talos Vulnerability Report

TALOS-2021-1246

Apple macOS SMB server TREE_CONNECT stack buffer overflow vulnerability

June 2, 2021
CVE Number

Summary

A remote code execution vulnerability exists in the SMB Server Apple macOS 10.15.7. A specially crafted SMB packet can trigger a stack-based buffer overflow, which can lead to arbitrary code execution and denial of service. This vulnerability can be triggered by sending a malicious packet to the vulnerable server.

Tested Versions

Apple macOS Catalina 10.15.7

Product URLs

https://apple.com

CVSSv3 Score

8.5 - CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H

CWE

CWE-121 - Stack-based Buffer Overflow

Details

macOS is a series of proprietary operating systems developed by Apple. Among other services, macOS contains a proprietary implementation of an SMB server to support network file sharing.

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 TREE_CONNECT request processing. TREE_CONNECT request is used to connect to a specified SMB share and has a relatively simple structure consisting of structure size, flags, path offset, path length and a utf16 string specifying a path. When processing the TREE_CONNECT request, function smb2_dispatch_tree_connect is invoked. First, parts of the TREE_CONNECT structure are extracted by calling the appropriate smb2::extract method:

__text:0000000100002C5E lea     rdi, [rbp+pTreeConnectBuffer] ; this
__text:0000000100002C65 mov     [rdi], rsi
__text:0000000100002C68 lea     rsi, [rbp+var_868]
__text:0000000100002C6F mov     [rsi], rdx
__text:0000000100002C72 lea     rcx, [r15+90h]
__text:0000000100002C79 lea     rdx, [rbp+tree_connect_request_extracted]
__text:0000000100002C7D call    smb2::extract(uchar *&,uchar * const&,smb2::tree_connect_request &,uchar * const&)

Calling smb::extract will extract the utf16 path string as well as its length. Shortly following that is a memcpy call:

__text:0000000100002CCF cmovs   rsi, rax        ; void *
__text:0000000100002CD3 lea     edx, [rbx+rbx]  ; size_t
__text:0000000100002CD6 lea     r12, [rbp+wcSharePath]
__text:0000000100002CDD mov     rdi, r12        ; void *
__text:0000000100002CE0 call    _memcpy

From the above, we can observe that destination for the memcpy call is a stack buffer rbp+wcSharePath, source is previously extracted pointer to path string and length is the specified length from the TREE_CONNECT structure. Since the local stack buffer wcSharePath is located in the stack and of static size this can constitute a straightforward stack buffer overflow. Indeed, looking up stack layout of this function reveals that wcSharePath is 2048 bytes. Therefore, a specially crafted TREE_CONNECT request packet with a very long share path utf16 string can result in a buffer overflow. This can be observed in a debugger:

Process 32047 launched: '/usr/sbin/smbd' (x86_64)
Thu Jan 28 17:51:28 2021 is_darwin_xpc_error [darwin_xpc.cpp:43] is_darwin_xpc_error: XPC: listener, ipc error: Connection invalid
Thu Jan 28 17:51:31 2021 smb2_dispatch_negotiate [negotiate.cpp:595] smb2_dispatch_negotiate: Client requires signing.
2021-01-28 17:51:31.766732-0600 smbd[32047:32558705] [User Defaults] All kCFPreferencesCurrentUser domains in this process will be volatile, because homeDirPath starts with /var/empty
Thu Jan 28 17:51:31 2021 reply_smb2_negotiate [negotiate.cpp:292] reply_smb2_negotiate: SIGN: security_mode: Enabled: True, Required: True
Thu Jan 28 17:51:31 2021 smb2_dispatch_session_setup [session_setup.cpp:538] smb2_dispatch_session_setup: Client requires signing, session_id: 0x3c18c5f800000001
Thu Jan 28 17:51:32 2021 smb2_dispatch_session_setup [session_setup.cpp:538] smb2_dispatch_session_setup: Client requires signing, session_id: 0x3c18c5f800000001
Thu Jan 28 17:51:32 2021 check_account [pam_mechanism.cpp:62] pam_end returned : 0

Thu Jan 28 17:51:32 2021 smb2_dispatch_session_setup [session_setup.cpp:607] smb2_dispatch_session_setup: New session established, session_id: 0x3c18c5f800000001
Process 32047 stopped
* thread #3, queue = 'com.apple.root.default-qos', stop reason = breakpoint 1.1
    frame #0: 0x0000000100002c3b smbd`smb2_dispatch_tree_connect(smb_request&, unsigned char*, unsigned char*)
smbd`smb2_dispatch_tree_connect:
->  0x100002c3b <+0>: push   rbp
    0x100002c3c <+1>: mov    rbp, rsp
    0x100002c3f <+4>: push   r15
    0x100002c41 <+6>: push   r14
(lldb) mem read $rsi
0x100813840: 09 00 00 00 48 00 ff 0a 41 41 41 41 41 41 41 41  ....H.�.AAAAAAAA
0x100813850: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
(lldb)

First, a breakpoint is set at smb2_dispatch_tree_connect to observe TREE_CONNECT buffer from our crafted packet. Continuing execution till memcpy call lets us observe the parameters:

Process 32047 stopped
* thread #3, queue = 'com.apple.root.default-qos', stop reason = instruction step over
    frame #0: 0x0000000100002ce0 smbd`smb2_dispatch_tree_connect(smb_request&, unsigned char*, unsigned char*) + 165
smbd`smb2_dispatch_tree_connect:
->  0x100002ce0 <+165>: call   0x10006bf96               ; symbol stub for: memcpy
    0x100002ce5 <+170>: mov    word ptr [rbp + 2*rbx - 0x840], 0x0
    0x100002cef <+180>: lea    eax, [rbx - 0x80000000]
    0x100002cf5 <+186>: mov    qword ptr [rbp - 0x860], r12
Target 0: (smbd) stopped.
(lldb) mem read $rdi
0x7000066b24d0: 00 25 6b 06 00 70 00 00 c7 70 fe 71 ff 7f 00 00  .%k..p..�p�q�...
0x7000066b24e0: 00 00 00 00 00 00 00 00 c0 de 83 98 ff 7f 00 00  ........��..�...
(lldb) mem read $rsi
0x100813848: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0x100813858: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
(lldb) mem read $rdx
error: memory read failed for 0xa00
(lldb) register read rdx
     rdx = 0x0000000000000a28

Notice that rdi is an address on the stack, rsi points to a path buffer and rdx (length) matches buffer size from TREE_CONNECT request packet. Continuing execution forward results in:

 * thread #3, queue = 'com.apple.root.default-qos', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
    frame #0: 0x0000000100025bcd smbd`smb2_schedule_error(smb_request&, unsigned int) + 78
smbd`smb2_schedule_error:
->  0x100025bcd <+78>: mov    dword ptr [rax], esi
    0x100025bcf <+80>: lea    r14, [rdi + 0xb8]
    0x100025bd6 <+87>: mov    rcx, qword ptr [rdi + 0xb8]
    0x100025bdd <+94>: mov    rax, qword ptr [rdi + 0xc0]
Target 0: (smbd) stopped.
(lldb) register read
General Purpose Registers:
       rax = 0x4141414141414149
       rbx = 0x0000000000000514
       rcx = 0x00007000066b2e40
       rdx = 0x00000000007ed200
       rdi = 0x00007000066b2d90
       rsi = 0x00000000c00000be
       rbp = 0x00007000066b2470
       rsp = 0x00007000066b2420
        r8 = 0x0000000000008009
        r9 = 0x0000000000000000
       r10 = 0x0000000000000071
       r11 = 0x0000000101000000
       r12 = 0x00007000066b24d0
       r13 = 0x00007000066b2dd0
       r14 = 0x0000000000000001
       r15 = 0x00007000066b2d90
       rip = 0x0000000100025bcd  smbd`smb2_schedule_error(smb_request&, unsigned int) + 78
    rflags = 0x0000000000010206
        cs = 0x000000000000002b
        fs = 0x0000000000000000
        gs = 0x0000000000000000

(lldb) bt
* thread #3, queue = 'com.apple.root.default-qos', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
  * frame #0: 0x0000000100025bcd smbd`smb2_schedule_error(smb_request&, unsigned int) + 78
    frame #1: 0x0000000100002ee7 smbd`smb2_dispatch_tree_connect(smb_request&, unsigned char*, unsigned char*) + 684
(lldb)

The above shows a write access violation when trying to write to address pointed to by rax which is clearly under control. Depending on the stack structures that were overwritten, the crash could happen at a different place. Additionally, while this particular function has stack cookie check enabled, there are number of other locals, and those of other stack frames, stack variables that could be overwritten which could lead to further memory corruption and ultimately arbitrary code execution.

Timeline

2021-02-01 - Vendor Disclosure
2021-05-25 - Vendor Patched
2021-06-02 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.