]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
objtool/klp: Fix position-dependent checksums for non-relocated jumps/calls
authorJosh Poimboeuf <jpoimboe@kernel.org>
Fri, 3 Apr 2026 18:57:02 +0000 (11:57 -0700)
committerJosh Poimboeuf <jpoimboe@kernel.org>
Tue, 5 May 2026 04:16:06 +0000 (21:16 -0700)
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.]

Fixes: 0d83da43b1e1 ("objtool/klp: Add --checksum option to generate per-function checksums")
Acked-by: Song Liu <song@kernel.org>
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
tools/objtool/arch/x86/decode.c
tools/objtool/include/objtool/arch.h
tools/objtool/include/objtool/check.h
tools/objtool/klp-checksum.c

index 350b8ee6e7769f85ebe178ebdb7f6e3b700e0755..1b387d5a195baba2d0f36b52bfab37281e8f6e64 100644 (file)
@@ -805,14 +805,27 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec
                break;
        }
 
-       if (ins.immediate.nbytes)
+       if (ins.immediate.nbytes) {
                insn->immediate = ins.immediate.value;
-       else if (ins.displacement.nbytes)
+               insn->immediate_len = ins.immediate.nbytes;
+       } else if (ins.displacement.nbytes) {
                insn->immediate = ins.displacement.value;
+               insn->immediate_len = ins.displacement.nbytes;
+       }
 
        return 0;
 }
 
+size_t arch_jump_opcode_bytes(struct objtool_file *file, struct instruction *insn,
+                             unsigned char *buf)
+{
+       size_t len;
+
+       len = insn->len - insn->immediate_len;
+       memcpy(buf, insn->sec->data->d_buf + insn->offset, len);
+       return len;
+}
+
 void arch_initial_func_cfi_state(struct cfi_init_state *state)
 {
        int i;
index 8866158975fcbf9ec896d54e03a5894991279725..96d828a8401fd5cb8be3a2d282ca391bc39d587e 100644 (file)
@@ -79,6 +79,9 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec
                            unsigned long offset, unsigned int maxlen,
                            struct instruction *insn);
 
+size_t arch_jump_opcode_bytes(struct objtool_file *file, struct instruction *insn,
+                             unsigned char *buf);
+
 bool arch_callee_saved_reg(unsigned char reg);
 
 unsigned long arch_jump_destination(struct instruction *insn);
index fe08205d8eb16d90bec9262dd589d538524130eb..063f5985fecd0f0db58dfd286c15bfd26848ad04 100644 (file)
@@ -68,6 +68,7 @@ struct instruction {
        s8 instr;
 
        u32 idx                 : INSN_CHUNK_BITS,
+           immediate_len       : 4,
            dead_end            : 1,
            ignore_alts         : 1,
            hint                : 1,
@@ -81,7 +82,7 @@ struct instruction {
            hole                : 1,
            fake                : 1,
            trace               : 1;
-               /* 9 bit hole */
+               /* 4 bit hole */
 
        struct alt_group *alt_group;
        struct instruction *jump_dest;
index 19653dbe109db245c4eeb5255509d9897ac600c9..b8e47f28997e924d9da79b1ef902857c0e44b904 100644 (file)
@@ -66,17 +66,58 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func,
        if (insn->fake)
                return;
 
-       __checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len);
-
        if (!reloc) {
                struct symbol *call_dest = insn_call_dest(insn);
+               struct instruction *jump_dest = insn->jump_dest;
 
-               if (call_dest)
-                       __checksum_update_insn(func, insn, call_dest->demangled_name,
-                                              strlen(call_dest->demangled_name));
-               goto alts;
+               /*
+                * For a jump/call non-relocated dest offset embedded in the
+                * instruction, the offset may vary due to changes in
+                * surrounding code.  Just hash the opcode and a
+                * position-independent representation of the destination.
+                */
+
+               if (call_dest || jump_dest) {
+                       unsigned char buf[16];
+                       size_t len;
+
+                       len = arch_jump_opcode_bytes(file, insn, buf);
+                       __checksum_update_insn(func, insn, buf, len);
+
+                       if (call_dest) {
+                               __checksum_update_insn(func, insn, call_dest->demangled_name,
+                                                      strlen(call_dest->demangled_name));
+
+                       } else if (jump_dest) {
+                               struct symbol *dest_sym;
+                               unsigned long offset;
+
+                               /*
+                                * use insn->_sym instead of insn_sym() here.
+                                * For alternative replacements, the latter
+                                * would give the function of the code being
+                                * replaced.
+                                */
+                               dest_sym = jump_dest->_sym;
+                               if (!dest_sym)
+                                       goto alts;
+
+                               __checksum_update_insn(func, insn, dest_sym->demangled_name,
+                                                      strlen(dest_sym->demangled_name));
+
+                               offset = jump_dest->offset - dest_sym->offset;
+                               __checksum_update_insn(func, insn, &offset, sizeof(offset));
+                       }
+
+                       goto alts;
+               }
        }
 
+       __checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len);
+
+       if (!reloc)
+               goto alts;
+
        sym = reloc->sym;
        offset = arch_insn_adjusted_addend(insn, reloc);