Skip to content

Instantly share code, notes, and snippets.

@niw
Last active May 25, 2020 21:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save niw/887568cbcecab084d7bbaf4b44f20014 to your computer and use it in GitHub Desktop.
Save niw/887568cbcecab084d7bbaf4b44f20014 to your computer and use it in GitHub Desktop.
Describes why `RTIDocumentState` crashes your app.

RTIDocumentState and related method calls can crash app. Why?

Short version

A pointer to RTIDocumentState is read and written on multi threads.

Long version

When the application launches, it establishes XPC connection with so called RTIInputSystemService and creates a RTIInputSystemServiceSession.

After that point, every keyboard typing, XPC calls invokes -[RTIInputSystemServiceSession remoteTextInputSessionWithID:documentDidChange:].

-[RTIInputSystemServiceSession remoteTextInputSessionWithID:documentDidChange:]:
/* snip */
0000000000003aca         mov        rax, qword [_OBJC_IVAR_$_RTIInputSystemServiceSession._internalQueue] ; _OBJC_IVAR_$_RTIInputSystemServiceSession._internalQueue
0000000000003ad1         mov        rax, qword [r15+rax]
0000000000003ad5         mov        qword [rbp+var_30], rax
0000000000003ad9         mov        rax, qword [__NSConcreteStackBlock_12018]   ; __NSConcreteStackBlock_12018
0000000000003ae0         lea        rbx, qword [rbp+var_68]
0000000000003ae4         mov        qword [rbx], rax
0000000000003ae7         mov        eax, 0xc2000000
0000000000003aec         mov        qword [rbx+8], rax
0000000000003af0         lea        rax, qword [___79-[RTIInputSystemServiceSession remoteTextInputSessionWithID:documentDidChange:]_block_invoke] ; ___79-[RTIInputSystemServiceSession remoteTextInputSessionWithID:documentDidChange:]_block_invoke
0000000000003af7         mov        qword [rbx+0x10], rax
0000000000003afb         lea        rax, qword [___block_descriptor_56_e8_32s40s48s_e5_v8�?0l] ; ___block_descriptor_56_e8_32s40s48s_e5_v8�?0l
0000000000003b02         mov        qword [rbx+0x18], rax
0000000000003b06         mov        qword [rbx+0x20], r15
0000000000003b0a         mov        qword [rbx+0x28], r13
/* snip */
0000000000003b24         mov        rdi, qword [rbp+var_30]                     ; argument "queue" for method imp___stubs__dispatch_async
0000000000003b28         mov        rsi, rbx                                    ; argument "block" for method imp___stubs__dispatch_async
0000000000003b2b         call       imp___stubs__dispatch_async                 ; dispatch_async
/* snip */

In this handler, it is basically calling a block ___79-[RTIInputSystemServiceSession remoteTextInputSessionWithID:documentDidChange:]_block_invoke on their internal queue RTIInputSystemServiceSession._internalQueue with dispatch_async.

___79-[RTIInputSystemServiceSession remoteTextInputSessionWithID:documentDidChange:]_block_invoke:
/* snip */
0000000000003b6f         mov        r14, rdi
/* snip */
0000000000003bb4         mov        rdi, qword [r14+0x20]                       ; argument "instance" for method _objc_msgSend
0000000000003bb8         mov        rdx, qword [r14+0x30]
0000000000003bbc         mov        rsi, qword [0x17910]                        ; argument "selector" for method _objc_msgSend, @selector(setDocumentState:)
0000000000003bc3         call       r12                                         ; Jumps to 0x2b230 (_objc_msgSend), _objc_msgSend
/* snip */

In this block it calls setDocumentState:. This setDocumentState: is a simple property setter, objc_storeStrong call with self + 0x18.

-[RTIInputSystemSession setDocumentState:]:
0000000000006b0f         push       rbp                                         ; Objective C Implementation defined at 0x14f40 (instance method), DATA XREF=0x14f40
0000000000006b10         mov        rbp, rsp
0000000000006b13         add        rdi, 0x18                                   ; argument "addr" for method imp___stubs__objc_storeStrong
0000000000006b17         mov        rsi, rdx                                    ; argument "value" for method imp___stubs__objc_storeStrong
0000000000006b1a         pop        rbp
0000000000006b1b         jmp        imp___stubs__objc_storeStrong               ; objc_storeStrong

Therefore, this self + 0x18 is accessed on internal background queue and changed. And if we see documentState, it's also a simple property getter, just return self + 0x18.

-[RTIInputSystemSession documentState]:
0000000000006b05         push       rbp                                         ; Objective C Implementation defined at 0x14f28 (instance method), DATA XREF=0x14f28
0000000000006b06         mov        rbp, rsp
0000000000006b09         mov        rax, qword [rdi+0x18]
0000000000006b0d         pop        rbp
0000000000006b0e         ret

Therefore, documentState and setDocumentState are thread unsafe property accessors and its setter is called on internal background queue.

However, in the AppKit API, for example, -[NSTextInputContext attributedString_RTI]:, which is always called on main queue.

-[NSTextInputContext attributedString_RTI]:
/* snip */
00000000009f2ecd         mov        qword [rbp+var_30], rbx
00000000009f2ed1         mov        r13, qword [0x10f2ed0]                      ; @selector(documentState)
00000000009f2ed8         mov        r14, qword [_objc_msgSend_dc21f8]           ; _objc_msgSend_dc21f8
00000000009f2edf         mov        rdi, r15                                    ; argument "instance" for method _objc_msgSend
00000000009f2ee2         mov        rsi, r13                                    ; argument "selector" for method _objc_msgSend
00000000009f2ee5         call       r14                                         ; Jumps to 0x1896920 (_objc_msgSend), _objc_msgSend
/* snip */

Here is a problem.

It's accessing RTIInputSystemSession's documentState getter on the main queue, which setter is called on the background queue and therefore, the pointer to the value can be anytime written by setDocumentState:, white reading the value on the main queue.

So, the pointer to documentState, which is RTIDocumentState is really unreliable and randomly crashing app when code is accessing anything on it.

You can verify this fact by setting symbolic breakpoints on -[RTIInputSystemSession setDocumentState:] and -[RTIInputSystemSession documentState].

Environmnet

macOS 10.15.4 (19E287)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment