History

objtool/klp: Fix position-dependent checksums for non-relocated jumps/calls

When computing klp checksums, instructions with non-relocated jump/call destination offsets are problematic because the offset values can change when surrounding code has moved, causing the function to be incorrectly marked as changed. Specifically, that includes jumps from alternatives to the end of the alternative, which from objtool's perspective are jumps to the end of the alternative instruction block in the original function. Note that 'jump_dest' jumps don't include sibling calls (those use call_dest), nor do they include jumps to/from .cold sub functions (those are cross-section and need a reloc). Fix it by hashing the opcode bytes (excluding the immediate operand) along with a position-independent representation of the destination. For calls, use the function name, and for jumps, use the destination's offset within its function. [Note the "9 bit hole" comment was wrong: it has been 8 bits since commit 70589843b36f ("objtool: Add option to trace function validation") added the 'trace' field. Adding the 4-bit 'immediate_len' field now leaves a 4-bit hole.]

Josh Poimboeuf committed 7 months ago

Commit cca84cb1 parent 3ee67624 files · +67 −9Browse files

Unified · Split

4 files
  1. tools/objtool/arch/x86/decode.c +15 −2
  2. tools/objtool/include/objtool/arch.h +3 −0
  3. tools/objtool/include/objtool/check.h +2 −1
  4. tools/objtool/klp-checksum.c +47 −6
tools/objtool/arch/x86/decode.c+15−2
@@ -805,14 +805,27 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec · 804 unmodified lines
805 break;805 break;
806 }806 }
807 807
808 if (ins.immediate.nbytes)808 if (ins.immediate.nbytes) {
809 insn->immediate = ins.immediate.value;809 insn->immediate = ins.immediate.value;
810 else if (ins.displacement.nbytes)810 insn->immediate_len = ins.immediate.nbytes;
811 } else if (ins.displacement.nbytes) {
811 insn->immediate = ins.displacement.value;812 insn->immediate = ins.displacement.value;
813 insn->immediate_len = ins.displacement.nbytes;
814 }
812 815
813 return 0;816 return 0;
814}817}
815 818
819size_t arch_jump_opcode_bytes(struct objtool_file *file, struct instruction *insn,
820 unsigned char *buf)
821{
822 size_t len;
823
824 len = insn->len - insn->immediate_len;
825 memcpy(buf, insn->sec->data->d_buf + insn->offset, len);
826 return len;
827}
828
816void arch_initial_func_cfi_state(struct cfi_init_state *state)829void arch_initial_func_cfi_state(struct cfi_init_state *state)
817{830{
818 int i;831 int i;
tools/objtool/include/objtool/arch.h+3−0
@@ -79,6 +79,9 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec · 78 unmodified lines
79 unsigned long offset, unsigned int maxlen,79 unsigned long offset, unsigned int maxlen,
80 struct instruction *insn);80 struct instruction *insn);
81 81
82size_t arch_jump_opcode_bytes(struct objtool_file *file, struct instruction *insn,
83 unsigned char *buf);
84
82bool arch_callee_saved_reg(unsigned char reg);85bool arch_callee_saved_reg(unsigned char reg);
83 86
84unsigned long arch_jump_destination(struct instruction *insn);87unsigned long arch_jump_destination(struct instruction *insn);
tools/objtool/include/objtool/check.h+2−1
@@ -68,6 +68,7 @@ struct instruction { · 67 unmodified lines
68 s8 instr;68 s8 instr;
69 69
70 u32 idx : INSN_CHUNK_BITS,70 u32 idx : INSN_CHUNK_BITS,
71 immediate_len : 4,
71 dead_end : 1,72 dead_end : 1,
72 ignore_alts : 1,73 ignore_alts : 1,
73 hint : 1,74 hint : 1,
@@ -81,7 +82,7 @@ struct instruction { · 7 unmodified lines
81 hole : 1,82 hole : 1,
82 fake : 1,83 fake : 1,
83 trace : 1;84 trace : 1;
84 /* 9 bit hole */85 /* 4 bit hole */
85 86
86 struct alt_group *alt_group;87 struct alt_group *alt_group;
87 struct instruction *jump_dest;88 struct instruction *jump_dest;
tools/objtool/klp-checksum.c+47−6
@@ -66,17 +66,58 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func, · 65 unmodified lines
66 if (insn->fake)66 if (insn->fake)
67 return;67 return;
68 68
69 __checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len);
70
71 if (!reloc) {69 if (!reloc) {
72 struct symbol *call_dest = insn_call_dest(insn);70 struct symbol *call_dest = insn_call_dest(insn);
71 struct instruction *jump_dest = insn->jump_dest;
73 72
74 if (call_dest)73 /*
75 __checksum_update_insn(func, insn, call_dest->demangled_name,74 * For a jump/call non-relocated dest offset embedded in the
76 strlen(call_dest->demangled_name));75 * instruction, the offset may vary due to changes in
77 goto alts;76 * surrounding code. Just hash the opcode and a
77 * position-independent representation of the destination.
78 */
79
80 if (call_dest || jump_dest) {
81 unsigned char buf[16];
82 size_t len;
83
84 len = arch_jump_opcode_bytes(file, insn, buf);
85 __checksum_update_insn(func, insn, buf, len);
86
87 if (call_dest) {
88 __checksum_update_insn(func, insn, call_dest->demangled_name,
89 strlen(call_dest->demangled_name));
90
91 } else if (jump_dest) {
92 struct symbol *dest_sym;
93 unsigned long offset;
94
95 /*
96 * use insn->_sym instead of insn_sym() here.
97 * For alternative replacements, the latter
98 * would give the function of the code being
99 * replaced.
100 */
101 dest_sym = jump_dest->_sym;
102 if (!dest_sym)
103 goto alts;
104
105 __checksum_update_insn(func, insn, dest_sym->demangled_name,
106 strlen(dest_sym->demangled_name));
107
108 offset = jump_dest->offset - dest_sym->offset;
109 __checksum_update_insn(func, insn, &offset, sizeof(offset));
110 }
111
112 goto alts;
113 }
78 }114 }
79 115
116 __checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len);
117
118 if (!reloc)
119 goto alts;
120
80 sym = reloc->sym;121 sym = reloc->sym;
81 offset = arch_insn_adjusted_addend(insn, reloc);122 offset = arch_insn_adjusted_addend(insn, reloc);
82 123