objtool: Introduce HINT_RET_OFFSET
authorPeter Zijlstra <peterz@infradead.org>
Wed, 1 Apr 2020 14:38:19 +0000 (16:38 +0200)
committerIngo Molnar <mingo@kernel.org>
Wed, 22 Apr 2020 08:53:50 +0000 (10:53 +0200)
Normally objtool ensures a function keeps the stack layout invariant.
But there is a useful exception, it is possible to stuff the return
stack in order to 'inject' a 'call':

push $fun
ret

In this case the invariant mentioned above is violated.

Add an objtool HINT to annotate this and allow a function exit with a
modified stack frame.

Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Reviewed-by: Miroslav Benes <mbenes@suse.cz>
Reviewed-by: Alexandre Chartre <alexandre.chartre@oracle.com>
Acked-by: Josh Poimboeuf <jpoimboe@redhat.com>
Link: https://lkml.kernel.org/r/20200416115118.690601403@infradead.org
Signed-off-by: Ingo Molnar <mingo@kernel.org>
arch/x86/include/asm/orc_types.h
arch/x86/include/asm/unwind_hints.h
tools/arch/x86/include/asm/orc_types.h
tools/objtool/check.c
tools/objtool/check.h

index 6e060907c163bfdf98e1e200d731a2c181c08732..5f18ca7ac51a86da9bba9775c5282b660875688d 100644 (file)
@@ -60,6 +60,7 @@
 #define ORC_TYPE_REGS_IRET             2
 #define UNWIND_HINT_TYPE_SAVE          3
 #define UNWIND_HINT_TYPE_RESTORE       4
+#define UNWIND_HINT_TYPE_RET_OFFSET    5
 
 #ifndef __ASSEMBLY__
 /*
index f5e2eb12cb71e2d168c1bf525caf609e7dcf1d9a..aabf7ace0476d8f3ec58f9d8432c211073dd65c1 100644 (file)
        UNWIND_HINT type=UNWIND_HINT_TYPE_RESTORE
 .endm
 
+
+/*
+ * RET_OFFSET: Used on instructions that terminate a function; mostly RETURN
+ * and sibling calls. On these, sp_offset denotes the expected offset from
+ * initial_func_cfi.
+ */
+.macro UNWIND_HINT_RET_OFFSET sp_offset=8
+       UNWIND_HINT type=UNWIND_HINT_TYPE_RET_OFFSET sp_offset=\sp_offset
+.endm
+
 #else /* !__ASSEMBLY__ */
 
 #define UNWIND_HINT(sp_reg, sp_offset, type, end)              \
index 6e060907c163bfdf98e1e200d731a2c181c08732..5f18ca7ac51a86da9bba9775c5282b660875688d 100644 (file)
@@ -60,6 +60,7 @@
 #define ORC_TYPE_REGS_IRET             2
 #define UNWIND_HINT_TYPE_SAVE          3
 #define UNWIND_HINT_TYPE_RESTORE       4
+#define UNWIND_HINT_TYPE_RET_OFFSET    5
 
 #ifndef __ASSEMBLY__
 /*
index 781b3a3c2ba6b1b21d57ca4d9099564654ec3f81..93c88ac51f0fd04efbc0707f30e0d6d48fe11804 100644 (file)
@@ -1261,6 +1261,9 @@ static int read_unwind_hints(struct objtool_file *file)
                } else if (hint->type == UNWIND_HINT_TYPE_RESTORE) {
                        insn->restore = true;
                        insn->hint = true;
+
+               } else if (hint->type == UNWIND_HINT_TYPE_RET_OFFSET) {
+                       insn->ret_offset = hint->sp_offset;
                        continue;
                }
 
@@ -1424,20 +1427,25 @@ static bool is_fentry_call(struct instruction *insn)
        return false;
 }
 
-static bool has_modified_stack_frame(struct insn_state *state)
+static bool has_modified_stack_frame(struct instruction *insn, struct insn_state *state)
 {
+       u8 ret_offset = insn->ret_offset;
        int i;
 
-       if (state->cfa.base != initial_func_cfi.cfa.base ||
-           state->cfa.offset != initial_func_cfi.cfa.offset ||
-           state->stack_size != initial_func_cfi.cfa.offset ||
-           state->drap)
+       if (state->cfa.base != initial_func_cfi.cfa.base || state->drap)
+               return true;
+
+       if (state->cfa.offset != initial_func_cfi.cfa.offset + ret_offset)
                return true;
 
-       for (i = 0; i < CFI_NUM_REGS; i++)
+       if (state->stack_size != initial_func_cfi.cfa.offset + ret_offset)
+               return true;
+
+       for (i = 0; i < CFI_NUM_REGS; i++) {
                if (state->regs[i].base != initial_func_cfi.regs[i].base ||
                    state->regs[i].offset != initial_func_cfi.regs[i].offset)
                        return true;
+       }
 
        return false;
 }
@@ -2014,7 +2022,7 @@ static int validate_call(struct instruction *insn, struct insn_state *state)
 
 static int validate_sibling_call(struct instruction *insn, struct insn_state *state)
 {
-       if (has_modified_stack_frame(state)) {
+       if (has_modified_stack_frame(insn, state)) {
                WARN_FUNC("sibling call from callable instruction with modified stack frame",
                                insn->sec, insn->offset);
                return 1;
@@ -2043,7 +2051,7 @@ static int validate_return(struct symbol *func, struct instruction *insn, struct
                return 1;
        }
 
-       if (func && has_modified_stack_frame(state)) {
+       if (func && has_modified_stack_frame(insn, state)) {
                WARN_FUNC("return with modified stack frame",
                          insn->sec, insn->offset);
                return 1;
index 2c55f7591443b44a4c43a2b5768ad4b3011aacda..81ce27e62c04f05fdcdf0a774ae399a9c498cbcd 100644 (file)
@@ -33,9 +33,11 @@ struct instruction {
        unsigned int len;
        enum insn_type type;
        unsigned long immediate;
-       bool alt_group, dead_end, ignore, hint, save, restore, ignore_alts;
+       bool alt_group, dead_end, ignore, ignore_alts;
+       bool hint, save, restore;
        bool retpoline_safe;
        u8 visited;
+       u8 ret_offset;
        struct symbol *call_dest;
        struct instruction *jump_dest;
        struct instruction *first_jump_src;