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
805805 break;
806806 }
807807
808− if (ins.immediate.nbytes)
808+ if (ins.immediate.nbytes) {
809809 insn->immediate = ins.immediate.value;
810− else if (ins.displacement.nbytes)
810+ insn->immediate_len = ins.immediate.nbytes;
811+ } else if (ins.displacement.nbytes) {
811812 insn->immediate = ins.displacement.value;
813+ insn->immediate_len = ins.displacement.nbytes;
814+ }
812815
813816 return 0;
814817 }
815818
819+size_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+
816829 void arch_initial_func_cfi_state(struct cfi_init_state *state)
817830 {
818831 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
7979 unsigned long offset, unsigned int maxlen,
8080 struct instruction *insn);
8181
82+size_t arch_jump_opcode_bytes(struct objtool_file *file, struct instruction *insn,
83+ unsigned char *buf);
84+
8285 bool arch_callee_saved_reg(unsigned char reg);
8386
8487 unsigned long arch_jump_destination(struct instruction *insn);
tools/objtool/include/objtool/check.h+2−1
@@ -68,6 +68,7 @@ struct instruction { · 67 unmodified lines
6868 s8 instr;
6969
7070 u32 idx : INSN_CHUNK_BITS,
71+ immediate_len : 4,
7172 dead_end : 1,
7273 ignore_alts : 1,
7374 hint : 1,
@@ -81,7 +82,7 @@ struct instruction { · 7 unmodified lines
8182 hole : 1,
8283 fake : 1,
8384 trace : 1;
84− /* 9 bit hole */
85+ /* 4 bit hole */
8586
8687 struct alt_group *alt_group;
8788 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
6666 if (insn->fake)
6767 return;
6868
69− __checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len);
70−
7169 if (!reloc) {
7270 struct symbol *call_dest = insn_call_dest(insn);
71+ struct instruction *jump_dest = insn->jump_dest;
7372
74− if (call_dest)
75− __checksum_update_insn(func, insn, call_dest->demangled_name,
76− strlen(call_dest->demangled_name));
77− goto alts;
73+ /*
74+ * For a jump/call non-relocated dest offset embedded in the
75+ * instruction, the offset may vary due to changes in
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+ }
78114 }
79115
116+ __checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len);
117+
118+ if (!reloc)
119+ goto alts;
120+
80121 sym = reloc->sym;
81122 offset = arch_insn_adjusted_addend(insn, reloc);
82123