objtool: Grow __cfi_* prefix symbols for all CFI+CALL_PADDING

For all CONFIG_CFI+CONFIG_CALL_PADDING configs, for C functions, the
__cfi_ symbols only cover the 5-byte kCFI type hash.  After that there
also N bytes of NOP padding between the hash and the function entry
which aren't associated with any symbol.

The NOPs can be replaced with actual code at runtime.  Without a symbol,
unwinders and tooling have no way of knowing where those bytes belong.

Grow the existing __cfi_* symbols to fill that gap.

Note that assembly functions with SYM_TYPED_FUNC_START() aren't affected
by this issue, their __cfi_ symbols also cover the padding.

Also, CONFIG_PREFIX_SYMBOLS has no reason to exist: CONFIG_CALL_PADDING
is what causes the compiler to emit NOP padding before function entry
(via -fpatchable-function-entry), so it's the right condition for
creating prefix symbols.

Remove CONFIG_PREFIX_SYMBOLS, as it's no longer needed.  Simplify the
LONGEST_SYM_KUNIT_TEST dependency accordingly.  Rework objtool's
arguments a bit to handle the variety of prefix/cfi-related cases.

Suggested-by: Peter Zijlstra <peterz@infradead.org>
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
This commit is contained in:
Josh Poimboeuf
2026-04-23 15:53:17 -07:00
parent cca84cb129
commit fc0bb9915b
8 changed files with 84 additions and 21 deletions

View File

@@ -2437,10 +2437,6 @@ config CALL_THUNKS
def_bool n
select CALL_PADDING
config PREFIX_SYMBOLS
def_bool y
depends on CALL_PADDING && !CFI
menuconfig CPU_MITIGATIONS
bool "Mitigations for CPU vulnerabilities"
default y

View File

@@ -3070,7 +3070,7 @@ config FORTIFY_KUNIT_TEST
config LONGEST_SYM_KUNIT_TEST
tristate "Test the longest symbol possible" if !KUNIT_ALL_TESTS
depends on KUNIT && KPROBES
depends on !PREFIX_SYMBOLS && !CFI && !GCOV_KERNEL
depends on !CALL_PADDING && !CFI && !GCOV_KERNEL
default KUNIT_ALL_TESTS
help
Tests the longest symbol possible

View File

@@ -187,7 +187,11 @@ objtool-args-$(CONFIG_HAVE_JUMP_LABEL_HACK) += --hacks=jump_label
objtool-args-$(CONFIG_HAVE_NOINSTR_HACK) += --hacks=noinstr
objtool-args-$(CONFIG_MITIGATION_CALL_DEPTH_TRACKING) += --hacks=skylake
objtool-args-$(CONFIG_X86_KERNEL_IBT) += --ibt
objtool-args-$(CONFIG_FINEIBT) += --cfi
objtool-args-$(CONFIG_CALL_PADDING) += --prefix=$(CONFIG_FUNCTION_PADDING_BYTES)
ifdef CONFIG_CALL_PADDING
objtool-args-$(CONFIG_CFI) += --cfi
objtool-args-$(CONFIG_FINEIBT) += --fineibt
endif
objtool-args-$(CONFIG_FTRACE_MCOUNT_USE_OBJTOOL) += --mcount
ifdef CONFIG_FTRACE_MCOUNT_USE_OBJTOOL
objtool-args-$(CONFIG_HAVE_OBJTOOL_NOP_MCOUNT) += --mnop
@@ -200,7 +204,6 @@ objtool-args-$(CONFIG_STACK_VALIDATION) += --stackval
objtool-args-$(CONFIG_HAVE_STATIC_CALL_INLINE) += --static-call
objtool-args-$(CONFIG_HAVE_UACCESS_VALIDATION) += --uaccess
objtool-args-$(or $(CONFIG_GCOV_KERNEL),$(CONFIG_KCOV)) += --no-unreachable
objtool-args-$(CONFIG_PREFIX_SYMBOLS) += --prefix=$(CONFIG_FUNCTION_PADDING_BYTES)
objtool-args-$(CONFIG_OBJTOOL_WERROR) += --werror
objtool-args = $(objtool-args-y) \

View File

@@ -73,7 +73,6 @@ static int parse_hacks(const struct option *opt, const char *str, int unset)
static const struct option check_options[] = {
OPT_GROUP("Actions:"),
OPT_BOOLEAN(0, "cfi", &opts.cfi, "annotate kernel control flow integrity (kCFI) function preambles"),
OPT_STRING_OPTARG('d', "disas", &opts.disas, "function-pattern", "disassemble functions", "*"),
OPT_CALLBACK_OPTARG('h', "hacks", NULL, NULL, "jump_label,noinstr,skylake", "patch toolchain bugs/limitations", parse_hacks),
OPT_BOOLEAN('i', "ibt", &opts.ibt, "validate and annotate IBT"),
@@ -84,7 +83,7 @@ static const struct option check_options[] = {
OPT_BOOLEAN('r', "retpoline", &opts.retpoline, "validate and annotate retpoline usage"),
OPT_BOOLEAN(0, "rethunk", &opts.rethunk, "validate and annotate rethunk usage"),
OPT_BOOLEAN(0, "unret", &opts.unret, "validate entry unret placement"),
OPT_INTEGER(0, "prefix", &opts.prefix, "generate prefix symbols"),
OPT_INTEGER(0, "prefix", &opts.prefix, "generate or grow prefix symbols for N-byte function padding"),
OPT_BOOLEAN('l', "sls", &opts.sls, "validate straight-line-speculation mitigations"),
OPT_BOOLEAN('s', "stackval", &opts.stackval, "validate frame pointer rules"),
OPT_BOOLEAN('t', "static-call", &opts.static_call, "annotate static calls"),
@@ -92,6 +91,8 @@ static const struct option check_options[] = {
OPT_CALLBACK_OPTARG(0, "dump", NULL, NULL, "orc", "dump metadata", parse_dump),
OPT_GROUP("Options:"),
OPT_BOOLEAN(0, "cfi", &opts.cfi, "grow kCFI preamble symbols (use with --prefix)"),
OPT_BOOLEAN(0, "fineibt", &opts.fineibt, "create .cfi_sites section for FineIBT"),
OPT_BOOLEAN(0, "backtrace", &opts.backtrace, "unwind on error"),
OPT_BOOLEAN(0, "backup", &opts.backup, "create backup (.orig) file on warning/error"),
OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"),
@@ -163,6 +164,16 @@ static bool opts_valid(void)
return false;
}
if (opts.cfi && !opts.prefix) {
ERROR("--cfi requires --prefix");
return false;
}
if (opts.fineibt && !opts.cfi) {
ERROR("--fineibt requires --cfi");
return false;
}
if (opts.disas ||
opts.hack_jump_label ||
opts.hack_noinstr ||

View File

@@ -881,6 +881,31 @@ static int create_ibt_endbr_seal_sections(struct objtool_file *file)
return 0;
}
/*
* Grow __cfi_ symbols to fill the NOP gap between the 'mov <hash>, %rax' and
* the start of the function.
*/
static int grow_cfi_symbols(struct objtool_file *file)
{
struct symbol *sym;
for_each_sym(file->elf, sym) {
if (!is_func_sym(sym) || !strstarts(sym->name, "__cfi_") ||
sym->len != 5)
continue;
if (!find_func_by_offset(sym->sec, sym->offset + sym->len + opts.prefix))
continue;
sym->len += opts.prefix;
sym->sym.st_size = sym->len;
if (elf_write_symbol(file->elf, sym))
return -1;
}
return 0;
}
static int create_cfi_sections(struct objtool_file *file)
{
struct section *sec;
@@ -4903,12 +4928,6 @@ int check(struct objtool_file *file)
goto out;
}
if (opts.cfi) {
ret = create_cfi_sections(file);
if (ret)
goto out;
}
if (opts.rethunk) {
ret = create_return_sites_sections(file);
if (ret)
@@ -4928,9 +4947,21 @@ int check(struct objtool_file *file)
}
if (opts.prefix) {
ret = create_prefix_symbols(file);
if (ret)
goto out;
if (!opts.cfi) {
ret = create_prefix_symbols(file);
if (ret)
goto out;
} else {
ret = grow_cfi_symbols(file);
if (ret)
goto out;
if (opts.fineibt) {
ret = create_cfi_sections(file);
if (ret)
goto out;
}
}
}
if (opts.ibt) {

View File

@@ -997,6 +997,26 @@ struct symbol *elf_create_symbol(struct elf *elf, const char *name,
return sym;
}
int elf_write_symbol(struct elf *elf, struct symbol *sym)
{
struct section *symtab, *symtab_shndx;
symtab = find_section_by_name(elf, ".symtab");
if (!symtab) {
ERROR("no .symtab");
return -1;
}
symtab_shndx = find_section_by_name(elf, ".symtab_shndx");
if (elf_update_symbol(elf, symtab, symtab_shndx, sym))
return -1;
mark_sec_changed(elf, symtab, true);
return 0;
}
struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec)
{
struct symbol *sym = calloc(1, sizeof(*sym));

View File

@@ -9,8 +9,8 @@
struct opts {
/* actions: */
bool cfi;
bool checksum;
const char *disas;
bool dump_orc;
bool hack_jump_label;
bool hack_noinstr;
@@ -20,6 +20,7 @@ struct opts {
bool noabs;
bool noinstr;
bool orc;
int prefix;
bool retpoline;
bool rethunk;
bool unret;
@@ -27,14 +28,14 @@ struct opts {
bool stackval;
bool static_call;
bool uaccess;
int prefix;
const char *disas;
/* options: */
bool backtrace;
bool backup;
bool cfi;
const char *debug_checksum;
bool dryrun;
bool fineibt;
bool link;
bool mnop;
bool module;

View File

@@ -199,6 +199,7 @@ struct reloc *elf_init_reloc_data_sym(struct elf *elf, struct section *sec,
struct symbol *sym,
s64 addend);
int elf_write_symbol(struct elf *elf, struct symbol *sym);
int elf_write_insn(struct elf *elf, struct section *sec, unsigned long offset,
unsigned int len, const char *insn);