Skip to content

On platforms with native_exec not implemented, DR can still end up in the native_exec logic #8034

Description

@suyashmahar

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);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions