Skip to content

Commit 3c6f9f7

Browse files
jpoimboePeter Zijlstra
authored andcommitted
objtool: Rework ibt and extricate from stack validation
Extricate ibt from validate_branch() so they can be executed (or ported) independently from each other. While shuffling code around, simplify and improve the ibt logic: - Ignore an explicit list of known sections which reference functions for reasons other than indirect branching to them. This helps prevent unnnecesary sealing. - Warn on missing !ENDBR for all other sections, not just .data and .rodata. This finds additional warnings, because there are sections other than .[ro]data which reference function pointers. For example, the ksymtab sections which are used for exporting symbols. Signed-off-by: Josh Poimboeuf <[email protected]> Signed-off-by: Peter Zijlstra (Intel) <[email protected]> Reviewed-by: Miroslav Benes <[email protected]> Link: https://lkml.kernel.org/r/fd1435e46bb95f81031b8fb1fa360f5f787e4316.1650300597.git.jpoimboe@redhat.com
1 parent 7dce620 commit 3c6f9f7

File tree

1 file changed

+147
-133
lines changed

1 file changed

+147
-133
lines changed

tools/objtool/check.c

Lines changed: 147 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -3182,114 +3182,6 @@ static struct instruction *next_insn_to_validate(struct objtool_file *file,
31823182
return next_insn_same_sec(file, insn);
31833183
}
31843184

3185-
static struct instruction *
3186-
validate_ibt_reloc(struct objtool_file *file, struct reloc *reloc)
3187-
{
3188-
struct instruction *dest;
3189-
struct section *sec;
3190-
unsigned long off;
3191-
3192-
sec = reloc->sym->sec;
3193-
off = reloc->sym->offset;
3194-
3195-
if ((reloc->sec->base->sh.sh_flags & SHF_EXECINSTR) &&
3196-
(reloc->type == R_X86_64_PC32 || reloc->type == R_X86_64_PLT32))
3197-
off += arch_dest_reloc_offset(reloc->addend);
3198-
else
3199-
off += reloc->addend;
3200-
3201-
dest = find_insn(file, sec, off);
3202-
if (!dest)
3203-
return NULL;
3204-
3205-
if (dest->type == INSN_ENDBR) {
3206-
if (!list_empty(&dest->call_node))
3207-
list_del_init(&dest->call_node);
3208-
3209-
return NULL;
3210-
}
3211-
3212-
if (reloc->sym->static_call_tramp)
3213-
return NULL;
3214-
3215-
return dest;
3216-
}
3217-
3218-
static void warn_noendbr(const char *msg, struct section *sec, unsigned long offset,
3219-
struct instruction *dest)
3220-
{
3221-
WARN_FUNC("%srelocation to !ENDBR: %s", sec, offset, msg,
3222-
offstr(dest->sec, dest->offset));
3223-
}
3224-
3225-
static void validate_ibt_dest(struct objtool_file *file, struct instruction *insn,
3226-
struct instruction *dest)
3227-
{
3228-
if (dest->func && dest->func == insn->func) {
3229-
/*
3230-
* Anything from->to self is either _THIS_IP_ or IRET-to-self.
3231-
*
3232-
* There is no sane way to annotate _THIS_IP_ since the compiler treats the
3233-
* relocation as a constant and is happy to fold in offsets, skewing any
3234-
* annotation we do, leading to vast amounts of false-positives.
3235-
*
3236-
* There's also compiler generated _THIS_IP_ through KCOV and
3237-
* such which we have no hope of annotating.
3238-
*
3239-
* As such, blanket accept self-references without issue.
3240-
*/
3241-
return;
3242-
}
3243-
3244-
if (dest->noendbr)
3245-
return;
3246-
3247-
warn_noendbr("", insn->sec, insn->offset, dest);
3248-
}
3249-
3250-
static void validate_ibt_insn(struct objtool_file *file, struct instruction *insn)
3251-
{
3252-
struct instruction *dest;
3253-
struct reloc *reloc;
3254-
3255-
switch (insn->type) {
3256-
case INSN_CALL:
3257-
case INSN_CALL_DYNAMIC:
3258-
case INSN_JUMP_CONDITIONAL:
3259-
case INSN_JUMP_UNCONDITIONAL:
3260-
case INSN_JUMP_DYNAMIC:
3261-
case INSN_JUMP_DYNAMIC_CONDITIONAL:
3262-
case INSN_RETURN:
3263-
/*
3264-
* We're looking for code references setting up indirect code
3265-
* flow. As such, ignore direct code flow and the actual
3266-
* dynamic branches.
3267-
*/
3268-
return;
3269-
3270-
case INSN_NOP:
3271-
/*
3272-
* handle_group_alt() will create INSN_NOP instruction that
3273-
* don't belong to any section, ignore all NOP since they won't
3274-
* carry a (useful) relocation anyway.
3275-
*/
3276-
return;
3277-
3278-
default:
3279-
break;
3280-
}
3281-
3282-
for (reloc = insn_reloc(file, insn);
3283-
reloc;
3284-
reloc = find_reloc_by_dest_range(file->elf, insn->sec,
3285-
reloc->offset + 1,
3286-
(insn->offset + insn->len) - (reloc->offset + 1))) {
3287-
dest = validate_ibt_reloc(file, reloc);
3288-
if (dest)
3289-
validate_ibt_dest(file, insn, dest);
3290-
}
3291-
}
3292-
32933185
/*
32943186
* Follow the branch starting at the given instruction, and recursively follow
32953187
* any other branches (jumps). Meanwhile, track the frame pointer state at
@@ -3499,9 +3391,6 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
34993391
break;
35003392
}
35013393

3502-
if (opts.ibt)
3503-
validate_ibt_insn(file, insn);
3504-
35053394
if (insn->dead_end)
35063395
return 0;
35073396

@@ -3787,48 +3676,173 @@ static int validate_functions(struct objtool_file *file)
37873676
return warnings;
37883677
}
37893678

3790-
static int validate_ibt(struct objtool_file *file)
3679+
static void mark_endbr_used(struct instruction *insn)
37913680
{
3792-
struct section *sec;
3681+
if (!list_empty(&insn->call_node))
3682+
list_del_init(&insn->call_node);
3683+
}
3684+
3685+
static int validate_ibt_insn(struct objtool_file *file, struct instruction *insn)
3686+
{
3687+
struct instruction *dest;
37933688
struct reloc *reloc;
3689+
unsigned long off;
3690+
int warnings = 0;
37943691

3795-
for_each_sec(file, sec) {
3796-
bool is_data;
3692+
/*
3693+
* Looking for function pointer load relocations. Ignore
3694+
* direct/indirect branches:
3695+
*/
3696+
switch (insn->type) {
3697+
case INSN_CALL:
3698+
case INSN_CALL_DYNAMIC:
3699+
case INSN_JUMP_CONDITIONAL:
3700+
case INSN_JUMP_UNCONDITIONAL:
3701+
case INSN_JUMP_DYNAMIC:
3702+
case INSN_JUMP_DYNAMIC_CONDITIONAL:
3703+
case INSN_RETURN:
3704+
case INSN_NOP:
3705+
return 0;
3706+
default:
3707+
break;
3708+
}
37973709

3798-
/* already done in validate_branch() */
3799-
if (sec->sh.sh_flags & SHF_EXECINSTR)
3800-
continue;
3710+
for (reloc = insn_reloc(file, insn);
3711+
reloc;
3712+
reloc = find_reloc_by_dest_range(file->elf, insn->sec,
3713+
reloc->offset + 1,
3714+
(insn->offset + insn->len) - (reloc->offset + 1))) {
38013715

3802-
if (!sec->reloc)
3716+
/*
3717+
* static_call_update() references the trampoline, which
3718+
* doesn't have (or need) ENDBR. Skip warning in that case.
3719+
*/
3720+
if (reloc->sym->static_call_tramp)
38033721
continue;
38043722

3805-
if (!strncmp(sec->name, ".orc", 4))
3723+
off = reloc->sym->offset;
3724+
if (reloc->type == R_X86_64_PC32 || reloc->type == R_X86_64_PLT32)
3725+
off += arch_dest_reloc_offset(reloc->addend);
3726+
else
3727+
off += reloc->addend;
3728+
3729+
dest = find_insn(file, reloc->sym->sec, off);
3730+
if (!dest)
38063731
continue;
38073732

3808-
if (!strncmp(sec->name, ".discard", 8))
3733+
if (dest->type == INSN_ENDBR) {
3734+
mark_endbr_used(dest);
38093735
continue;
3736+
}
38103737

3811-
if (!strncmp(sec->name, ".debug", 6))
3738+
if (dest->func && dest->func == insn->func) {
3739+
/*
3740+
* Anything from->to self is either _THIS_IP_ or
3741+
* IRET-to-self.
3742+
*
3743+
* There is no sane way to annotate _THIS_IP_ since the
3744+
* compiler treats the relocation as a constant and is
3745+
* happy to fold in offsets, skewing any annotation we
3746+
* do, leading to vast amounts of false-positives.
3747+
*
3748+
* There's also compiler generated _THIS_IP_ through
3749+
* KCOV and such which we have no hope of annotating.
3750+
*
3751+
* As such, blanket accept self-references without
3752+
* issue.
3753+
*/
38123754
continue;
3755+
}
38133756

3814-
if (!strcmp(sec->name, "_error_injection_whitelist"))
3757+
if (dest->noendbr)
38153758
continue;
38163759

3817-
if (!strcmp(sec->name, "_kprobe_blacklist"))
3760+
WARN_FUNC("relocation to !ENDBR: %s",
3761+
insn->sec, insn->offset,
3762+
offstr(dest->sec, dest->offset));
3763+
3764+
warnings++;
3765+
}
3766+
3767+
return warnings;
3768+
}
3769+
3770+
static int validate_ibt_data_reloc(struct objtool_file *file,
3771+
struct reloc *reloc)
3772+
{
3773+
struct instruction *dest;
3774+
3775+
dest = find_insn(file, reloc->sym->sec,
3776+
reloc->sym->offset + reloc->addend);
3777+
if (!dest)
3778+
return 0;
3779+
3780+
if (dest->type == INSN_ENDBR) {
3781+
mark_endbr_used(dest);
3782+
return 0;
3783+
}
3784+
3785+
if (dest->noendbr)
3786+
return 0;
3787+
3788+
WARN_FUNC("data relocation to !ENDBR: %s",
3789+
reloc->sec->base, reloc->offset,
3790+
offstr(dest->sec, dest->offset));
3791+
3792+
return 1;
3793+
}
3794+
3795+
/*
3796+
* Validate IBT rules and remove used ENDBR instructions from the seal list.
3797+
* Unused ENDBR instructions will be annotated for sealing (i.e., replaced with
3798+
* NOPs) later, in create_ibt_endbr_seal_sections().
3799+
*/
3800+
static int validate_ibt(struct objtool_file *file)
3801+
{
3802+
struct section *sec;
3803+
struct reloc *reloc;
3804+
struct instruction *insn;
3805+
int warnings = 0;
3806+
3807+
for_each_insn(file, insn)
3808+
warnings += validate_ibt_insn(file, insn);
3809+
3810+
for_each_sec(file, sec) {
3811+
3812+
/* Already done by validate_ibt_insn() */
3813+
if (sec->sh.sh_flags & SHF_EXECINSTR)
38183814
continue;
38193815

3820-
is_data = strstr(sec->name, ".data") || strstr(sec->name, ".rodata");
3816+
if (!sec->reloc)
3817+
continue;
38213818

3822-
list_for_each_entry(reloc, &sec->reloc->reloc_list, list) {
3823-
struct instruction *dest;
3819+
/*
3820+
* These sections can reference text addresses, but not with
3821+
* the intent to indirect branch to them.
3822+
*/
3823+
if (!strncmp(sec->name, ".discard", 8) ||
3824+
!strncmp(sec->name, ".debug", 6) ||
3825+
!strcmp(sec->name, ".altinstructions") ||
3826+
!strcmp(sec->name, ".ibt_endbr_seal") ||
3827+
!strcmp(sec->name, ".orc_unwind_ip") ||
3828+
!strcmp(sec->name, ".parainstructions") ||
3829+
!strcmp(sec->name, ".retpoline_sites") ||
3830+
!strcmp(sec->name, ".smp_locks") ||
3831+
!strcmp(sec->name, ".static_call_sites") ||
3832+
!strcmp(sec->name, "_error_injection_whitelist") ||
3833+
!strcmp(sec->name, "_kprobe_blacklist") ||
3834+
!strcmp(sec->name, "__bug_table") ||
3835+
!strcmp(sec->name, "__ex_table") ||
3836+
!strcmp(sec->name, "__jump_table") ||
3837+
!strcmp(sec->name, "__mcount_loc") ||
3838+
!strcmp(sec->name, "__tracepoints"))
3839+
continue;
38243840

3825-
dest = validate_ibt_reloc(file, reloc);
3826-
if (is_data && dest && !dest->noendbr)
3827-
warn_noendbr("data ", sec, reloc->offset, dest);
3828-
}
3841+
list_for_each_entry(reloc, &sec->reloc->reloc_list, list)
3842+
warnings += validate_ibt_data_reloc(file, reloc);
38293843
}
38303844

3831-
return 0;
3845+
return warnings;
38323846
}
38333847

38343848
static int validate_reachable_instructions(struct objtool_file *file)
@@ -3899,7 +3913,7 @@ int check(struct objtool_file *file)
38993913
warnings += ret;
39003914
}
39013915

3902-
if (opts.stackval || opts.orc || opts.uaccess || opts.ibt || opts.sls) {
3916+
if (opts.stackval || opts.orc || opts.uaccess || opts.sls) {
39033917
ret = validate_functions(file);
39043918
if (ret < 0)
39053919
goto out;

0 commit comments

Comments
 (0)