Skip to content

Commit

Permalink
[Mono]: Add PLT entry index validation mode for MONO_ARCH_CODE_EXEC_O…
Browse files Browse the repository at this point in the history
…NLY. (dotnet#75464)

* Add plt entry index validation mode for MONO_ARCH_CODE_EXEC_ONLY.

When running with MONO_ATCH_CODE_EXEC_ONLY PLT entry index can't
be read from call site (code only marked for execute), instead
index is loaded into RAX and then used by patch trampolines.

It is important that sequences of trampolines leading up to plt patch
call won't clobber RAX, or it will cause incorrect patching with
very hard to debug side effects. One such scenario was hit and solved
by dotnet#70449 and to
automatically detect these errors, this fix adds ability to validate
that the index put into RAX has not been clobbered when hitting plt
patch location.

Fix adds a mode under MONO_ARCH_CODE_EXEC_ONLY define:

MONO_VALIDATE_PLT_ENTRY_INDEX

That will always load the index using full 64-bit RAX, were
both lower and upper 32-bit is a copy of the index, and then
AOT compiler will xor the complete 64-bit value with a xor key.

At runtime, the plt patch logic could then validate that the value
in RAX appears to be the anticipated index, by reverse the process
and then compare the upper 32-bit with the lower 32-bit and if they
don't match, assert. It is very unlikely that a random change of value
in RAX would pass this test.

The issue hitting dotnet#70449 was
run using validation mode, and the assert trapped the error with
the incorrect sequence of trampolines, directly pin point the issue
to the location of the error, instead of patching wrong method
in wrong plt slot leading to complete random behaviour later in
execution when code would call through the incorrect patched plt.
  • Loading branch information
lateralusX authored Sep 13, 2022
1 parent f70fd22 commit b875fdf
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 1 deletion.
10 changes: 9 additions & 1 deletion src/mono/mono/mini/aot-compiler.c
Original file line number Diff line number Diff line change
Expand Up @@ -1840,6 +1840,9 @@ arch_emit_objc_selector_ref (MonoAotCompile *acfg, guint8 *code, int index, int
/* Keep in sync with tramp-amd64.c, aot_arch_get_plt_entry_index. */
#define PLT_ENTRY_OFFSET_REG AMD64_RAX
#endif
#ifdef MONO_VALIDATE_PLT_ENTRY_INDEX
extern guint64 mono_arch_plt_entry_index_xor_key;
#endif
#endif

/*
Expand Down Expand Up @@ -1867,7 +1870,11 @@ arch_emit_plt_entry (MonoAotCompile *acfg, const char *got_symbol, guint32 plt_i
#ifdef MONO_ARCH_CODE_EXEC_ONLY
guint8 buf [16];
guint8 *code = buf;

#ifdef MONO_VALIDATE_PLT_ENTRY_INDEX
guint64 plt_index_debug = ((guint64)plt_index << 32) | (guint64)plt_index;
plt_index_debug = plt_index_debug ^ mono_arch_plt_entry_index_xor_key;
amd64_mov_reg_imm_size (code, PLT_ENTRY_OFFSET_REG, plt_index_debug, sizeof(plt_index_debug));
#else
/* Emit smallest possible imm size 1, 2 or 4 bytes based on total number of PLT entries. */
if (acfg->plt_offset <= (guint32)0xFF) {
amd64_emit_rex(code, sizeof (guint8), 0, 0, PLT_ENTRY_OFFSET_REG);
Expand All @@ -1881,6 +1888,7 @@ arch_emit_plt_entry (MonoAotCompile *acfg, const char *got_symbol, guint32 plt_i
} else {
amd64_mov_reg_imm_size (code, PLT_ENTRY_OFFSET_REG, plt_index, sizeof(plt_index));
}
#endif
emit_bytes (acfg, buf, code - buf);
acfg->stats.plt_size += code - buf;

Expand Down
15 changes: 15 additions & 0 deletions src/mono/mono/mini/tramp-amd64.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
guint8* mono_aot_arch_get_plt_entry_exec_only (gpointer amodule_info, host_mgreg_t *regs, guint8 *code, guint8 *plt);
guint32 mono_arch_get_plt_info_offset_exec_only (gpointer amodule_info, guint8 *plt_entry, host_mgreg_t *regs, guint8 *code, MonoAotResolvePltInfoOffset resolver, gpointer amodule);
void mono_arch_patch_plt_entry_exec_only (gpointer amodule_info, guint8 *code, gpointer *got, host_mgreg_t *regs, guint8 *addr);
#ifdef MONO_VALIDATE_PLT_ENTRY_INDEX
const guint64 mono_arch_plt_entry_index_xor_key = 0xBA284AA0910FB367;
#endif
#endif

#define IS_REX(inst) (((inst) >= 0x40) && ((inst) <= 0x4f))
Expand Down Expand Up @@ -810,11 +813,16 @@ mono_arch_get_call_target (guint8 *code)
#define PLT_MOV_REG_IMM8_SIZE (1 + sizeof (guint8))
#define PLT_MOV_REG_IMM16_SIZE (2 + sizeof (guint16))
#define PLT_MOV_REG_IMM32_SIZE (1 + sizeof (guint32))
#define PLT_MOV_REG_IMM64_SIZE (2 + sizeof (guint64))
#define PLT_JMP_INST_SIZE 6

static guchar
aot_arch_get_plt_entry_size (MonoAotFileInfo *info, host_mgreg_t *regs, guint8 *code, guint8 *plt)
{
#ifdef MONO_VALIDATE_PLT_ENTRY_INDEX
return PLT_MOV_REG_IMM64_SIZE + PLT_JMP_INST_SIZE;
#endif

if (info->plt_size <= 0xFF)
return PLT_MOV_REG_IMM8_SIZE + PLT_JMP_INST_SIZE;
else if (info->plt_size <= 0xFFFF)
Expand All @@ -826,6 +834,13 @@ aot_arch_get_plt_entry_size (MonoAotFileInfo *info, host_mgreg_t *regs, guint8 *
static guint32
aot_arch_get_plt_entry_index (MonoAotFileInfo *info, host_mgreg_t *regs, guint8 *code, guint8 *plt)
{
#ifdef MONO_VALIDATE_PLT_ENTRY_INDEX
guint64 plt_index = regs[PLT_ENTRY_OFFSET_REG];
plt_index ^= mono_arch_plt_entry_index_xor_key;
g_assert ((plt_index & 0xFFFFFFFF) == ((plt_index >> 32) & 0xFFFFFFFF));
return (guint32)plt_index;
#endif

if (info->plt_size <= 0xFF)
return regs[PLT_ENTRY_OFFSET_REG] & 0xFF;
else if (info->plt_size <= 0xFFFF)
Expand Down

0 comments on commit b875fdf

Please sign in to comment.