Skip to content

Commit

Permalink
stalker: Improve exclusive store handling on arm64
Browse files Browse the repository at this point in the history
Instead of potentially expanding the current block to include
instructions beyond where the block would naturally end, we move to a
safer approach: Once we encounter an exclusive store, we look back at
the previously generated blocks to see if we can find one with an
exclusive load. If we do, we mark this range of blocks as using
exclusive access. We also invalidate the blocks, so that problematic
instrumentation can be omitted upon recompilation.

To also allow custom transformers to adapt their generated code, we
introduce StalkerIterator.get_memory_access().

Co-authored-by: Håvard Sørbø <havard@hsorbo.no>
  • Loading branch information
oleavr and hsorbo committed Jul 13, 2023
1 parent 283c808 commit 0ff142c
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 42 deletions.
5 changes: 5 additions & 0 deletions bindings/gumjs/runtime/cmodule/gum/gumdefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ enum _GumCallingConvention
GUM_CALL_SYSAPI
};

typedef enum {
GUM_MEMORY_ACCESS_OPEN,
GUM_MEMORY_ACCESS_EXCLUSIVE,
} GumMemoryAccess;

enum _GumInstructionEncoding
{
GUM_INSTRUCTION_DEFAULT,
Expand Down
2 changes: 2 additions & 0 deletions bindings/gumjs/runtime/cmodule/gum/gumstalker.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ union _GumEvent
gboolean gum_stalker_iterator_next (GumStalkerIterator * self,
const cs_insn ** insn);
void gum_stalker_iterator_keep (GumStalkerIterator * self);
GumMemoryAccess gum_stalker_iterator_get_memory_access (
GumStalkerIterator * self);
void gum_stalker_iterator_put_callout (GumStalkerIterator * self,
GumStalkerCallout callout, gpointer data, GDestroyNotify data_destroy);
csh gum_stalker_iterator_get_capstone (GumStalkerIterator * self);
Expand Down
132 changes: 92 additions & 40 deletions gum/backend-arm64/gumstalker-arm64.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Copyright (C) 2014-2023 Ole André Vadla Ravnås <oleavr@nowsecure.com>
* Copyright (C) 2017 Antonio Ken Iannillo <ak.iannillo@gmail.com>
* Copyright (C) 2019 John Coates <john@johncoates.dev>
* Copyright (C) 2023 Håvard Sørbø <havard@hsorbo.no>
*
* Licence: wxWindows Library Licence, Version 3.1
*/
Expand Down Expand Up @@ -43,6 +44,7 @@
#define GUM_STACK_ALIGNMENT 16
#define GUM_INVALIDATE_TRAMPOLINE_MAX_SIZE 40
#define GUM_RESTORATION_PROLOG_SIZE 4
#define GUM_EXCLUSIVE_ACCESS_MAX_DEPTH 8

#define GUM_IC_MAGIC_EMPTY 0xbaadd00ddeadface

Expand Down Expand Up @@ -297,7 +299,10 @@ struct _GumExecBlock

enum _GumExecBlockFlags
{
GUM_EXEC_BLOCK_ACTIVATION_TARGET = 1 << 0,
GUM_EXEC_BLOCK_ACTIVATION_TARGET = 1 << 0,
GUM_EXEC_BLOCK_HAS_EXCLUSIVE_LOAD = 1 << 1,
GUM_EXEC_BLOCK_HAS_EXCLUSIVE_STORE = 1 << 2,
GUM_EXEC_BLOCK_USES_EXCLUSIVE_ACCESS = 1 << 3,
};

struct _GumSlab
Expand Down Expand Up @@ -349,7 +354,6 @@ struct _GumGeneratorContext
GumArm64Writer * slow_writer;
gpointer continuation_real_address;
GumPrologType opened_prolog;
gint exclusive_load_offset;
};

struct _GumInstruction
Expand Down Expand Up @@ -402,7 +406,6 @@ enum _GumVirtualizationRequirements
{
GUM_REQUIRE_NOTHING = 0,
GUM_REQUIRE_RELOCATION = 1 << 0,
GUM_REQUIRE_EXCLUSIVE_STORE = 1 << 1,
};

enum _GumBackpatchType
Expand Down Expand Up @@ -690,6 +693,10 @@ static void gum_exec_block_write_slab_transfer_code (GumArm64Writer * from,
GumArm64Writer * to);
static void gum_exec_block_backpatch_slab (GumExecBlock * block,
gpointer target);
static void gum_exec_block_maybe_inherit_exclusive_access_state (
GumExecBlock * block, GumExecBlock * reference);
static void gum_exec_block_propagate_exclusive_access_state (
GumExecBlock * block);
static void gum_exec_ctx_write_adjust_depth (GumExecCtx * ctx,
GumArm64Writer * cw, gssize adj);
static arm64_reg gum_exec_block_write_inline_cache_code (
Expand Down Expand Up @@ -2530,10 +2537,12 @@ gum_exec_ctx_obtain_block_for (GumExecCtx * ctx,
{
block = gum_exec_block_new (ctx);
block->real_start = real_address;
gum_exec_block_maybe_inherit_exclusive_access_state (block, block->next);
gum_exec_ctx_compile_block (ctx, block, real_address, block->code_start,
GUM_ADDRESS (block->code_start), &block->real_size, &block->code_size,
&block->slow_size);
gum_exec_block_commit (block);
gum_exec_block_propagate_exclusive_access_state (block);

gum_metal_hash_table_insert (ctx->mappings, real_address, block);

Expand Down Expand Up @@ -2713,7 +2722,6 @@ gum_exec_ctx_compile_block (GumExecCtx * ctx,
gc.slow_writer = cws;
gc.continuation_real_address = NULL;
gc.opened_prolog = GUM_PROLOG_NONE;
gc.exclusive_load_offset = GUM_INSTRUCTION_OFFSET_NONE;

iterator.exec_context = ctx;
iterator.exec_block = block;
Expand Down Expand Up @@ -2805,34 +2813,10 @@ gum_stalker_iterator_next (GumStalkerIterator * self,
gc->continuation_real_address = instruction->end;
return FALSE;
}
else if ((self->requirements & GUM_REQUIRE_EXCLUSIVE_STORE) == 0 &&
gum_arm64_relocator_eob (rl))
else if (gum_arm64_relocator_eob (rl))
{
return FALSE;
}

switch (instruction->ci->id)
{
case ARM64_INS_STXR:
case ARM64_INS_STXP:
case ARM64_INS_STXRB:
case ARM64_INS_STXRH:
case ARM64_INS_STLXR:
case ARM64_INS_STLXP:
case ARM64_INS_STLXRB:
case ARM64_INS_STLXRH:
gc->exclusive_load_offset = GUM_INSTRUCTION_OFFSET_NONE;
break;
default:
break;
}

if (gc->exclusive_load_offset != GUM_INSTRUCTION_OFFSET_NONE)
{
gc->exclusive_load_offset++;
if (gc->exclusive_load_offset == 4)
gc->exclusive_load_offset = GUM_INSTRUCTION_OFFSET_NONE;
}
}

instruction = &self->instruction;
Expand Down Expand Up @@ -2896,14 +2880,24 @@ gum_stalker_iterator_keep (GumStalkerIterator * self)
case ARM64_INS_LDXP:
case ARM64_INS_LDXRB:
case ARM64_INS_LDXRH:
gc->exclusive_load_offset = 0;
block->flags |= GUM_EXEC_BLOCK_HAS_EXCLUSIVE_LOAD;
break;
case ARM64_INS_STXR:
case ARM64_INS_STXP:
case ARM64_INS_STXRB:
case ARM64_INS_STXRH:
case ARM64_INS_STLXR:
case ARM64_INS_STLXP:
case ARM64_INS_STLXRB:
case ARM64_INS_STLXRH:
block->flags |= GUM_EXEC_BLOCK_HAS_EXCLUSIVE_STORE;
break;
default:
break;
}

if ((self->exec_context->sink_mask & GUM_EXEC) != 0 &&
gc->exclusive_load_offset == GUM_INSTRUCTION_OFFSET_NONE)
(block->flags & GUM_EXEC_BLOCK_USES_EXCLUSIVE_ACCESS) == 0)
{
gum_exec_block_write_exec_event_code (block, gc, GUM_CODE_INTERRUPTIBLE);
}
Expand Down Expand Up @@ -2952,6 +2946,14 @@ gum_stalker_iterator_keep (GumStalkerIterator * self)
self->requirements = requirements;
}

GumMemoryAccess
gum_stalker_iterator_get_memory_access (GumStalkerIterator * self)
{
return ((self->exec_block->flags & GUM_EXEC_BLOCK_USES_EXCLUSIVE_ACCESS) != 0)
? GUM_MEMORY_ACCESS_EXCLUSIVE
: GUM_MEMORY_ACCESS_OPEN;
}

static void
gum_exec_ctx_emit_call_event (GumExecCtx * ctx,
gpointer location,
Expand Down Expand Up @@ -4381,15 +4383,8 @@ gum_exec_block_virtualize_branch_insn (GumExecBlock * block,

gum_arm64_writer_put_label (cw, is_false);

if (gc->exclusive_load_offset == GUM_INSTRUCTION_OFFSET_NONE)
{
gum_exec_block_write_jmp_transfer_code (block, &cond_target,
cond_entry_func, gc);
}
else
{
return GUM_REQUIRE_EXCLUSIVE_STORE;
}
gum_exec_block_write_jmp_transfer_code (block, &cond_target,
cond_entry_func, gc);
}

break;
Expand Down Expand Up @@ -5199,6 +5194,63 @@ gum_exec_block_backpatch_slab (GumExecBlock * block,
gum_spinlock_release (&ctx->code_lock);
}

static void
gum_exec_block_maybe_inherit_exclusive_access_state (GumExecBlock * block,
GumExecBlock * reference)
{
const guint8 * real_address = block->real_start;
GumExecBlock * cur;

for (cur = reference; cur != NULL; cur = cur->next)
{
if ((cur->flags & GUM_EXEC_BLOCK_USES_EXCLUSIVE_ACCESS) == 0)
return;

if (real_address >= cur->real_start &&
real_address < cur->real_start + cur->real_size)
{
block->flags |= GUM_EXEC_BLOCK_USES_EXCLUSIVE_ACCESS;
return;
}
}
}

static void
gum_exec_block_propagate_exclusive_access_state (GumExecBlock * block)
{
GumExecBlock * block_containing_load, * cur;
guint i;

if ((block->flags & GUM_EXEC_BLOCK_USES_EXCLUSIVE_ACCESS) != 0)
return;

if ((block->flags & GUM_EXEC_BLOCK_HAS_EXCLUSIVE_STORE) == 0)
return;

block_containing_load = NULL;
for (cur = block, i = 0;
cur != NULL && i != GUM_EXCLUSIVE_ACCESS_MAX_DEPTH;
cur = cur->next, i++)
{
if ((cur->flags & GUM_EXEC_BLOCK_HAS_EXCLUSIVE_LOAD) != 0)
{
block_containing_load = cur;
break;
}
}
if (block_containing_load == NULL)
return;

for (cur = block; TRUE; cur = cur->next)
{
cur->flags |= GUM_EXEC_BLOCK_USES_EXCLUSIVE_ACCESS;
gum_exec_block_invalidate (cur);

if (cur == block_containing_load)
break;
}
}

static void
gum_exec_ctx_write_adjust_depth (GumExecCtx * ctx,
GumArm64Writer * cw,
Expand Down
8 changes: 7 additions & 1 deletion gum/gumdefs.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2008-2022 Ole André Vadla Ravnås <oleavr@nowsecure.com>
* Copyright (C) 2008-2023 Ole André Vadla Ravnås <oleavr@nowsecure.com>
* Copyright (C) 2023 Håvard Sørbø <havard@hsorbo.no>
*
* Licence: wxWindows Library Licence, Version 3.1
*/
Expand Down Expand Up @@ -178,6 +179,11 @@ enum _GumCpuFeatures
GUM_CPU_PTRAUTH = 1 << 5,
};

typedef enum {
GUM_MEMORY_ACCESS_OPEN,
GUM_MEMORY_ACCESS_EXCLUSIVE,
} GumMemoryAccess;

enum _GumInstructionEncoding
{
GUM_INSTRUCTION_DEFAULT,
Expand Down
3 changes: 3 additions & 0 deletions gum/gumstalker.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2009-2023 Ole André Vadla Ravnås <oleavr@nowsecure.com>
* Copyright (C) 2010 Karl Trygve Kalleberg <karltk@boblycat.org>
* Copyright (C) 2023 Håvard Sørbø <havard@hsorbo.no>
*
* Licence: wxWindows Library Licence, Version 3.1
*/
Expand Down Expand Up @@ -222,6 +223,8 @@ GUM_API void gum_stalker_transformer_transform_block (
GUM_API gboolean gum_stalker_iterator_next (GumStalkerIterator * self,
const cs_insn ** insn);
GUM_API void gum_stalker_iterator_keep (GumStalkerIterator * self);
GUM_API GumMemoryAccess gum_stalker_iterator_get_memory_access (
GumStalkerIterator * self);
GUM_API void gum_stalker_iterator_put_callout (GumStalkerIterator * self,
GumStalkerCallout callout, gpointer data, GDestroyNotify data_destroy);
GUM_API csh gum_stalker_iterator_get_capstone (GumStalkerIterator * self);
Expand Down
Loading

0 comments on commit 0ff142c

Please sign in to comment.