Summary
On platforms that have back_from_native_retstub as a zero-sized label, DynamoRIO still relies on its hard-coded 40 bytes check and crashes when BOLT places an unrelated function in the 40 Bytes that follow back_from_native_retstub. Crash dump from aarch64 confirms native_retstack_cur == 1 with -native_exec disabled.
Bug
Dispatcher loop checks if a return addr falls within the retstub but hard codes it as a static 40B check that matches x86's layout:
|
static inline bool |
|
native_exec_is_back_from_native(app_pc pc) |
|
{ |
|
ptr_uint_t diff = (ptr_uint_t)pc - (ptr_uint_t)back_from_native_retstubs; |
|
return (diff < MAX_NATIVE_RETSTACK * BACK_FROM_NATIVE_RETSTUB_SIZE); |
|
} |
MAX_NATIVE_RETSTACK * BACK_FROM_NATIVE_RETSTUB_SIZE is 40B
On aarch64, this check can erroneously return true when BOLT places an unrelated function within the 40B of the zero-sized back_from_native_retstubs label. Now, if the return address is to this unrelated function, the dispatcher incorrectly classifies this as a return from native_exec.
This is apparent from the differences between the x86 and ARM "implementation" of native_exec. On ARM, On AArch64 the two labels are adjacent (back_from_native_retstubs and back_from_native_retstubs_end), so the region is always zero-sized.
X86:
|
# define Lback_from_native GLOBAL_REF(back_from_native) |
|
#endif |
|
RAW(6a) RAW(0) /* push 0 */ |
|
jmp short Lback_from_native |
|
RAW(6a) RAW(1) /* push 1 */ |
|
jmp short Lback_from_native |
|
RAW(6a) RAW(2) /* push 2 */ |
|
jmp short Lback_from_native |
|
RAW(6a) RAW(3) /* push 3 */ |
|
jmp short Lback_from_native |
|
RAW(6a) RAW(4) /* push 4 */ |
|
jmp short Lback_from_native |
|
RAW(6a) RAW(5) /* push 5 */ |
|
jmp short Lback_from_native |
|
RAW(6a) RAW(6) /* push 6 */ |
|
jmp short Lback_from_native |
|
RAW(6a) RAW(7) /* push 7 */ |
|
jmp short Lback_from_native |
|
RAW(6a) RAW(8) /* push 8 */ |
|
jmp short Lback_from_native |
|
RAW(6a) RAW(9) /* push 9 */ |
|
jmp short Lback_from_native |
|
DECLARE_GLOBAL(back_from_native_retstubs_end) |
AARCH64:
|
GLOBAL_LABEL(back_from_native_retstubs:) |
|
DECLARE_GLOBAL(back_from_native_retstubs_end) |
|
ADDRTAKEN_LABEL(back_from_native_retstubs_end:) |
|
bl GLOBAL_REF(unexpected_return) /* TODO i#1569: NYI */ |
|
END_FUNC(back_from_native_retstubs) |
A simple fix would be to change the check to:
static inline bool
native_exec_is_back_from_native(app_pc pc)
{
// Non-x86 platforms can have zero sized retstubs if native_exec is not implemented
ptr_uint_t retstub_region_size =
(ptr_uint_t)back_from_native_retstubs_end - (ptr_uint_t)back_from_native_retstubs;
ptr_uint_t diff = (ptr_uint_t)pc - (ptr_uint_t)back_from_native_retstubs;
return (diff < retstub_region_size);
}
Summary
On platforms that have
back_from_native_retstubas a zero-sized label, DynamoRIO still relies on its hard-coded 40 bytes check and crashes when BOLT places an unrelated function in the 40 Bytes that followback_from_native_retstub. Crash dump from aarch64 confirms native_retstack_cur == 1 with-native_execdisabled.Bug
Dispatcher loop checks if a return addr falls within the
retstubbut hard codes it as a static 40B check that matches x86's layout:dynamorio/core/native_exec.h
Lines 120 to 125 in c1ae794
MAX_NATIVE_RETSTACK * BACK_FROM_NATIVE_RETSTUB_SIZEis 40BOn aarch64, this check can erroneously return true when BOLT places an unrelated function within the 40B of the zero-sized
back_from_native_retstubslabel. Now, if the return address is to this unrelated function, the dispatcher incorrectly classifies this as a return from native_exec.This is apparent from the differences between the x86 and ARM "implementation" of native_exec. On ARM, On AArch64 the two labels are adjacent (
back_from_native_retstubsandback_from_native_retstubs_end), so the region is always zero-sized.X86:
dynamorio/core/arch/x86/x86.asm
Lines 1630 to 1652 in c1ae794
AARCH64:
dynamorio/core/arch/aarch64/aarch64.asm
Lines 610 to 614 in c1ae794
A simple fix would be to change the check to: