A pointer to RTIDocumentState
is read and written on multi threads.
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]
.
macOS 10.15.4 (19E287)