Messages in this thread Patch in this message | | | From | Alexandre Chartre <> | Subject | [PATCH V3 4/9] objtool: Handle return instruction with intra-function call | Date | Tue, 14 Apr 2020 12:36:13 +0200 |
| |
Intra-function calls are implemented in objtool like unconditional jumps. Keep track of intra-functin calls return addresses so that objtool can make a return instruction continues the flow at the right location.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com> --- tools/objtool/arch/x86/decode.c | 7 +++ tools/objtool/check.c | 104 ++++++++++++++++++++++++++++++-- tools/objtool/check.h | 3 + 3 files changed, 110 insertions(+), 4 deletions(-)
diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index f4d70b8835c4..76b593bb2e4f 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -427,6 +427,13 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, case 0xc2: case 0xc3: *type = INSN_RETURN; + /* + * For the impact on the stack, a ret behaves like + * a pop of the return address. + */ + op->src.type = OP_SRC_POP; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_RA; break; case 0xca: /* retf */ diff --git a/tools/objtool/check.c b/tools/objtool/check.c index ad362c5de281..8b1df659cd68 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -26,9 +26,50 @@ struct alternative { bool skip_orig; }; +/* + * List to keep track of intra-function call return addresses. + * The list is a simple static array because we don't expect + * to have a lot of nested intra-function calls. + */ +#define RADDR_COUNT_MAX 32 +#define RADDR_ALTERED ((void *)-1) + +static struct instruction *raddr_list[RADDR_COUNT_MAX]; +static int raddr_count; + const char *objname; struct cfi_state initial_func_cfi; +static void raddr_clear(void) +{ + raddr_count = 0; +} + +static bool raddr_push(struct instruction *insn) +{ + if (raddr_count == RADDR_COUNT_MAX) { + WARN_FUNC("return address list is full", + insn->sec, insn->offset); + return false; + } + + raddr_list[raddr_count++] = insn; + return true; +} + +static bool raddr_pop(struct instruction **insn) +{ + if (raddr_count == 0) + return false; + + *insn = raddr_list[--raddr_count]; + return true; +} + +static int validate_branch(struct objtool_file *file, struct symbol *func, + struct instruction *from, + struct instruction *first, struct insn_state state); + struct instruction *find_insn(struct objtool_file *file, struct section *sec, unsigned long offset) { @@ -2039,8 +2080,52 @@ static int validate_sibling_call(struct instruction *insn, struct insn_state *st return validate_call(insn, state); } -static int validate_return(struct symbol *func, struct instruction *insn, struct insn_state *state) +static int validate_return_address(struct objtool_file *file, + struct symbol *func, + struct instruction *insn, + struct insn_state *state) { + struct instruction *raddr_insn; + int ret; + + while (raddr_pop(&raddr_insn)) { + /* + * We are branching somewhere and so processing + * a return instruction. So update the stack + * state for this instruction. + */ + update_insn_state(insn, state); + + /* + * If the return address has no instruction then + * that's the end of the function. + */ + if (!raddr_insn) + break; + + /* + * If we are branching to a defined address then + * just do an unconditional jump there. + */ + ret = validate_branch(file, func, insn, + raddr_insn, *state); + if (ret) { + if (backtrace) + BT_FUNC("(ret-branch)", insn); + return ret; + } + + return 0; + } + + return -1; +} + +static int validate_return(struct objtool_file *file, struct symbol *func, + struct instruction *insn, struct insn_state *state) +{ + int ret; + if (state->uaccess && !func_uaccess_safe(func)) { WARN_FUNC("return with UACCESS enabled", insn->sec, insn->offset); @@ -2059,6 +2144,11 @@ static int validate_return(struct symbol *func, struct instruction *insn, struct return 1; } + /* check if we have return address to branch to */ + ret = validate_return_address(file, func, insn, state); + if (ret >= 0) + return ret; + if (func && has_modified_stack_frame(state)) { WARN_FUNC("return with modified stack frame", insn->sec, insn->offset); @@ -2200,7 +2290,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, switch (insn->type) { case INSN_RETURN: - return validate_return(func, insn, &state); + return validate_return(file, func, insn, &state); case INSN_CALL: case INSN_CALL_DYNAMIC: @@ -2223,12 +2313,17 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, /* * The call instruction can update the stack * state. Then make the intra-function call - * behaves like and unconditional jump. + * behaves like and unconditional jump. We + * track the return address to handle any + * return instruction. */ ret = update_insn_state(insn, &state); if (ret) return ret; + if (!raddr_push(next_insn)) + return 1; + ret = validate_branch(file, func, insn, insn->jump_dest, state); if (ret) { @@ -2383,6 +2478,7 @@ static int validate_unwind_hints(struct objtool_file *file) for_each_insn(file, insn) { if (insn->hint && !insn->visited) { + raddr_clear(); ret = validate_branch(file, insn->func, NULL, insn, state); if (ret && backtrace) @@ -2522,7 +2618,7 @@ static int validate_section(struct objtool_file *file, struct section *sec) continue; state.uaccess = func->uaccess_safe; - + raddr_clear(); ret = validate_branch(file, func, NULL, insn, state); if (ret && backtrace) BT_FUNC("<=== (func)", insn); diff --git a/tools/objtool/check.h b/tools/objtool/check.h index 6a80903fc4aa..f7dbecd46bed 100644 --- a/tools/objtool/check.h +++ b/tools/objtool/check.h @@ -23,6 +23,7 @@ struct insn_state { unsigned int uaccess_stack; int drap_reg, drap_offset; struct cfi_reg vals[CFI_NUM_REGS]; + bool stack_altered; }; struct instruction { @@ -39,6 +40,8 @@ struct instruction { bool intra_function_call; bool retpoline_safe; u8 visited; + u8 raddr_delete; + u8 raddr_alter; struct symbol *call_dest; struct instruction *jump_dest; struct instruction *first_jump_src; -- 2.18.2
| |