| @@ -418,8 +418,16 @@ static int decode_instructions(struct objtool_file *file) · 417 unmodified lines |
| 418 | 418 | return -1; |
| 419 | 419 | } |
| 420 | 420 | |
| 421 | | − sym_for_each_insn(file, func, insn) |
| 421 | + sym_for_each_insn(file, func, insn) { |
| 422 | 422 | insn->func = func; |
| 423 | + if (insn->type == INSN_ENDBR) { |
| 424 | + if (insn->offset == insn->func->offset) { |
| 425 | + file->nr_endbr++; |
| 426 | + } else { |
| 427 | + file->nr_endbr_int++; |
| 428 | + } |
| 429 | + } |
| 430 | + } |
| 423 | 431 | } |
| 424 | 432 | } |
| 425 | 433 | |
| @@ -1171,6 +1179,19 @@ static void add_retpoline_call(struct objtool_file *file, struct instruction *in · 745 unmodified lines |
| 1171 | 1179 | |
| 1172 | 1180 | annotate_call_site(file, insn, false); |
| 1173 | 1181 | } |
| 1182 | + |
| 1183 | +static bool same_function(struct instruction *insn1, struct instruction *insn2) |
| 1184 | +{ |
| 1185 | + return insn1->func->pfunc == insn2->func->pfunc; |
| 1186 | +} |
| 1187 | + |
| 1188 | +static bool is_first_func_insn(struct instruction *insn) |
| 1189 | +{ |
| 1190 | + return insn->offset == insn->func->offset || |
| 1191 | + (insn->type == INSN_ENDBR && |
| 1192 | + insn->offset == insn->func->offset + insn->len); |
| 1193 | +} |
| 1194 | + |
| 1174 | 1195 | /* |
| 1175 | 1196 | * Find the destination instructions for all jumps. |
| 1176 | 1197 | */ |
| @@ -1251,8 +1272,8 @@ static int add_jump_destinations(struct objtool_file *file) · 74 unmodified lines |
| 1251 | 1272 | insn->func->cfunc = insn->jump_dest->func; |
| 1252 | 1273 | insn->jump_dest->func->pfunc = insn->func; |
| 1253 | 1274 | |
| 1254 | | − } else if (insn->jump_dest->func->pfunc != insn->func->pfunc && |
| 1255 | | − insn->jump_dest->offset == insn->jump_dest->func->offset) { |
| 1275 | + } else if (!same_function(insn, insn->jump_dest) && |
| 1276 | + is_first_func_insn(insn->jump_dest)) { |
| 1256 | 1277 | /* internal sibling call (without reloc) */ |
| 1257 | 1278 | add_call_dest(file, insn, insn->jump_dest->func, true); |
| 1258 | 1279 | } |
| @@ -1842,6 +1863,16 @@ static int read_unwind_hints(struct objtool_file *file) · 583 unmodified lines |
| 1842 | 1863 | |
| 1843 | 1864 | insn->hint = true; |
| 1844 | 1865 | |
| 1866 | + if (ibt && hint->type == UNWIND_HINT_TYPE_REGS_PARTIAL) { |
| 1867 | + struct symbol *sym = find_symbol_by_offset(insn->sec, insn->offset); |
| 1868 | + |
| 1869 | + if (sym && sym->bind == STB_GLOBAL && |
| 1870 | + insn->type != INSN_ENDBR && !insn->noendbr) { |
| 1871 | + WARN_FUNC("UNWIND_HINT_IRET_REGS without ENDBR", |
| 1872 | + insn->sec, insn->offset); |
| 1873 | + } |
| 1874 | + } |
| 1875 | + |
| 1845 | 1876 | if (hint->type == UNWIND_HINT_TYPE_FUNC) { |
| 1846 | 1877 | insn->cfi = &func_cfi; |
| 1847 | 1878 | continue; |
| @@ -1883,6 +1914,9 @@ static int read_noendbr_hints(struct objtool_file *file) · 35 unmodified lines |
| 1883 | 1914 | return -1; |
| 1884 | 1915 | } |
| 1885 | 1916 | |
| 1917 | + if (insn->type == INSN_ENDBR) |
| 1918 | + WARN_FUNC("ANNOTATE_NOENDBR on ENDBR", insn->sec, insn->offset); |
| 1919 | + |
| 1886 | 1920 | insn->noendbr = 1; |
| 1887 | 1921 | } |
| 1888 | 1922 | |
| @@ -2122,6 +2156,9 @@ static int decode_sections(struct objtool_file *file) · 233 unmodified lines |
| 2122 | 2156 | if (ret) |
| 2123 | 2157 | return ret; |
| 2124 | 2158 | |
| 2159 | + /* |
| 2160 | + * Must be before read_unwind_hints() since that needs insn->noendbr. |
| 2161 | + */ |
| 2125 | 2162 | ret = read_noendbr_hints(file); |
| 2126 | 2163 | if (ret) |
| 2127 | 2164 | return ret; |
| @@ -3063,6 +3100,111 @@ static struct instruction *next_insn_to_validate(struct objtool_file *file, · 935 unmodified lines |
| 3063 | 3100 | return next_insn_same_sec(file, insn); |
| 3064 | 3101 | } |
| 3065 | 3102 | |
| 3103 | +static struct instruction * |
| 3104 | +validate_ibt_reloc(struct objtool_file *file, struct reloc *reloc) |
| 3105 | +{ |
| 3106 | + struct instruction *dest; |
| 3107 | + struct section *sec; |
| 3108 | + unsigned long off; |
| 3109 | + |
| 3110 | + sec = reloc->sym->sec; |
| 3111 | + off = reloc->sym->offset; |
| 3112 | + |
| 3113 | + if ((reloc->sec->base->sh.sh_flags & SHF_EXECINSTR) && |
| 3114 | + (reloc->type == R_X86_64_PC32 || reloc->type == R_X86_64_PLT32)) |
| 3115 | + off += arch_dest_reloc_offset(reloc->addend); |
| 3116 | + else |
| 3117 | + off += reloc->addend; |
| 3118 | + |
| 3119 | + dest = find_insn(file, sec, off); |
| 3120 | + if (!dest) |
| 3121 | + return NULL; |
| 3122 | + |
| 3123 | + if (dest->type == INSN_ENDBR) |
| 3124 | + return NULL; |
| 3125 | + |
| 3126 | + if (reloc->sym->static_call_tramp) |
| 3127 | + return NULL; |
| 3128 | + |
| 3129 | + return dest; |
| 3130 | +} |
| 3131 | + |
| 3132 | +static void warn_noendbr(const char *msg, struct section *sec, unsigned long offset, |
| 3133 | + struct instruction *dest) |
| 3134 | +{ |
| 3135 | + WARN_FUNC("%srelocation to !ENDBR: %s+0x%lx", sec, offset, msg, |
| 3136 | + dest->func ? dest->func->name : dest->sec->name, |
| 3137 | + dest->func ? dest->offset - dest->func->offset : dest->offset); |
| 3138 | +} |
| 3139 | + |
| 3140 | +static void validate_ibt_dest(struct objtool_file *file, struct instruction *insn, |
| 3141 | + struct instruction *dest) |
| 3142 | +{ |
| 3143 | + if (dest->func && dest->func == insn->func) { |
| 3144 | + /* |
| 3145 | + * Anything from->to self is either _THIS_IP_ or IRET-to-self. |
| 3146 | + * |
| 3147 | + * There is no sane way to annotate _THIS_IP_ since the compiler treats the |
| 3148 | + * relocation as a constant and is happy to fold in offsets, skewing any |
| 3149 | + * annotation we do, leading to vast amounts of false-positives. |
| 3150 | + * |
| 3151 | + * There's also compiler generated _THIS_IP_ through KCOV and |
| 3152 | + * such which we have no hope of annotating. |
| 3153 | + * |
| 3154 | + * As such, blanket accept self-references without issue. |
| 3155 | + */ |
| 3156 | + return; |
| 3157 | + } |
| 3158 | + |
| 3159 | + if (dest->noendbr) |
| 3160 | + return; |
| 3161 | + |
| 3162 | + warn_noendbr("", insn->sec, insn->offset, dest); |
| 3163 | +} |
| 3164 | + |
| 3165 | +static void validate_ibt_insn(struct objtool_file *file, struct instruction *insn) |
| 3166 | +{ |
| 3167 | + struct instruction *dest; |
| 3168 | + struct reloc *reloc; |
| 3169 | + |
| 3170 | + switch (insn->type) { |
| 3171 | + case INSN_CALL: |
| 3172 | + case INSN_CALL_DYNAMIC: |
| 3173 | + case INSN_JUMP_CONDITIONAL: |
| 3174 | + case INSN_JUMP_UNCONDITIONAL: |
| 3175 | + case INSN_JUMP_DYNAMIC: |
| 3176 | + case INSN_JUMP_DYNAMIC_CONDITIONAL: |
| 3177 | + case INSN_RETURN: |
| 3178 | + /* |
| 3179 | + * We're looking for code references setting up indirect code |
| 3180 | + * flow. As such, ignore direct code flow and the actual |
| 3181 | + * dynamic branches. |
| 3182 | + */ |
| 3183 | + return; |
| 3184 | + |
| 3185 | + case INSN_NOP: |
| 3186 | + /* |
| 3187 | + * handle_group_alt() will create INSN_NOP instruction that |
| 3188 | + * don't belong to any section, ignore all NOP since they won't |
| 3189 | + * carry a (useful) relocation anyway. |
| 3190 | + */ |
| 3191 | + return; |
| 3192 | + |
| 3193 | + default: |
| 3194 | + break; |
| 3195 | + } |
| 3196 | + |
| 3197 | + for (reloc = insn_reloc(file, insn); |
| 3198 | + reloc; |
| 3199 | + reloc = find_reloc_by_dest_range(file->elf, insn->sec, |
| 3200 | + reloc->offset + 1, |
| 3201 | + (insn->offset + insn->len) - (reloc->offset + 1))) { |
| 3202 | + dest = validate_ibt_reloc(file, reloc); |
| 3203 | + if (dest) |
| 3204 | + validate_ibt_dest(file, insn, dest); |
| 3205 | + } |
| 3206 | +} |
| 3207 | + |
| 3066 | 3208 | /* |
| 3067 | 3209 | * Follow the branch starting at the given instruction, and recursively follow |
| 3068 | 3210 | * any other branches (jumps). Meanwhile, track the frame pointer state at |
| @@ -3272,6 +3414,9 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, · 203 unmodified lines |
| 3272 | 3414 | break; |
| 3273 | 3415 | } |
| 3274 | 3416 | |
| 3417 | + if (ibt) |
| 3418 | + validate_ibt_insn(file, insn); |
| 3419 | + |
| 3275 | 3420 | if (insn->dead_end) |
| 3276 | 3421 | return 0; |
| 3277 | 3422 | |
| @@ -3557,6 +3702,53 @@ static int validate_functions(struct objtool_file *file) · 279 unmodified lines |
| 3557 | 3702 | return warnings; |
| 3558 | 3703 | } |
| 3559 | 3704 | |
| 3705 | +static int validate_ibt(struct objtool_file *file) |
| 3706 | +{ |
| 3707 | + struct section *sec; |
| 3708 | + struct reloc *reloc; |
| 3709 | + |
| 3710 | + for_each_sec(file, sec) { |
| 3711 | + bool is_data; |
| 3712 | + |
| 3713 | + /* already done in validate_branch() */ |
| 3714 | + if (sec->sh.sh_flags & SHF_EXECINSTR) |
| 3715 | + continue; |
| 3716 | + |
| 3717 | + if (!sec->reloc) |
| 3718 | + continue; |
| 3719 | + |
| 3720 | + if (!strncmp(sec->name, ".orc", 4)) |
| 3721 | + continue; |
| 3722 | + |
| 3723 | + if (!strncmp(sec->name, ".discard", 8)) |
| 3724 | + continue; |
| 3725 | + |
| 3726 | + if (!strncmp(sec->name, ".debug", 6)) |
| 3727 | + continue; |
| 3728 | + |
| 3729 | + if (!strcmp(sec->name, "_error_injection_whitelist")) |
| 3730 | + continue; |
| 3731 | + |
| 3732 | + if (!strcmp(sec->name, "_kprobe_blacklist")) |
| 3733 | + continue; |
| 3734 | + |
| 3735 | + is_data = strstr(sec->name, ".data") || strstr(sec->name, ".rodata"); |
| 3736 | + |
| 3737 | + list_for_each_entry(reloc, &sec->reloc->reloc_list, list) { |
| 3738 | + struct instruction *dest; |
| 3739 | + |
| 3740 | + dest = validate_ibt_reloc(file, reloc); |
| 3741 | + if (is_data && dest && !dest->noendbr) { |
| 3742 | + warn_noendbr("data ", reloc->sym->sec, |
| 3743 | + reloc->sym->offset + reloc->addend, |
| 3744 | + dest); |
| 3745 | + } |
| 3746 | + } |
| 3747 | + } |
| 3748 | + |
| 3749 | + return 0; |
| 3750 | +} |
| 3751 | + |
| 3560 | 3752 | static int validate_reachable_instructions(struct objtool_file *file) |
| 3561 | 3753 | { |
| 3562 | 3754 | struct instruction *insn; |
| @@ -3584,6 +3776,11 @@ int check(struct objtool_file *file) · 21 unmodified lines |
| 3584 | 3776 | return 1; |
| 3585 | 3777 | } |
| 3586 | 3778 | |
| 3779 | + if (ibt && !lto) { |
| 3780 | + fprintf(stderr, "--ibt requires: --lto\n"); |
| 3781 | + return 1; |
| 3782 | + } |
| 3783 | + |
| 3587 | 3784 | arch_initial_func_cfi_state(&initial_func_cfi); |
| 3588 | 3785 | init_cfi_state(&init_cfi); |
| 3589 | 3786 | init_cfi_state(&func_cfi); |
| @@ -3630,6 +3827,13 @@ int check(struct objtool_file *file) · 40 unmodified lines |
| 3630 | 3827 | goto out; |
| 3631 | 3828 | warnings += ret; |
| 3632 | 3829 | |
| 3830 | + if (ibt) { |
| 3831 | + ret = validate_ibt(file); |
| 3832 | + if (ret < 0) |
| 3833 | + goto out; |
| 3834 | + warnings += ret; |
| 3835 | + } |
| 3836 | + |
| 3633 | 3837 | if (!warnings) { |
| 3634 | 3838 | ret = validate_reachable_instructions(file); |
| 3635 | 3839 | if (ret < 0) |