| @@ -418,8 +418,16 @@ static int decode_instructions(struct objtool_file *file) · 417 unmodified lines |
| 418 | return -1; | 418 | return -1; |
| 419 | } | 419 | } |
| 420 | | 420 | |
| 421 | sym_for_each_insn(file, func, insn) | 421 | sym_for_each_insn(file, func, insn) { |
| 422 | insn->func = func; | 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 | annotate_call_site(file, insn, false); | 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 | * Find the destination instructions for all jumps. | 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 | insn->func->cfunc = insn->jump_dest->func; | 1272 | insn->func->cfunc = insn->jump_dest->func; |
| 1252 | insn->jump_dest->func->pfunc = insn->func; | 1273 | insn->jump_dest->func->pfunc = insn->func; |
| 1253 | | 1274 | |
| 1254 | } else if (insn->jump_dest->func->pfunc != insn->func->pfunc && | 1275 | } else if (!same_function(insn, insn->jump_dest) && |
| 1255 | insn->jump_dest->offset == insn->jump_dest->func->offset) { | 1276 | is_first_func_insn(insn->jump_dest)) { |
| 1256 | /* internal sibling call (without reloc) */ | 1277 | /* internal sibling call (without reloc) */ |
| 1257 | add_call_dest(file, insn, insn->jump_dest->func, true); | 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 | insn->hint = true; | 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 | if (hint->type == UNWIND_HINT_TYPE_FUNC) { | 1876 | if (hint->type == UNWIND_HINT_TYPE_FUNC) { |
| 1846 | insn->cfi = &func_cfi; | 1877 | insn->cfi = &func_cfi; |
| 1847 | continue; | 1878 | continue; |
| @@ -1883,6 +1914,9 @@ static int read_noendbr_hints(struct objtool_file *file) · 35 unmodified lines |
| 1883 | return -1; | 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 | insn->noendbr = 1; | 1920 | insn->noendbr = 1; |
| 1887 | } | 1921 | } |
| 1888 | | 1922 | |
| @@ -2122,6 +2156,9 @@ static int decode_sections(struct objtool_file *file) · 233 unmodified lines |
| 2122 | if (ret) | 2156 | if (ret) |
| 2123 | return ret; | 2157 | return ret; |
| 2124 | | 2158 | |
| | 2159 | /* |
| | 2160 | * Must be before read_unwind_hints() since that needs insn->noendbr. |
| | 2161 | */ |
| 2125 | ret = read_noendbr_hints(file); | 2162 | ret = read_noendbr_hints(file); |
| 2126 | if (ret) | 2163 | if (ret) |
| 2127 | return ret; | 2164 | return ret; |
| @@ -3063,6 +3100,111 @@ static struct instruction *next_insn_to_validate(struct objtool_file *file, · 935 unmodified lines |
| 3063 | return next_insn_same_sec(file, insn); | 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 | * Follow the branch starting at the given instruction, and recursively follow | 3209 | * Follow the branch starting at the given instruction, and recursively follow |
| 3068 | * any other branches (jumps). Meanwhile, track the frame pointer state at | 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 | break; | 3414 | break; |
| 3273 | } | 3415 | } |
| 3274 | | 3416 | |
| | 3417 | if (ibt) |
| | 3418 | validate_ibt_insn(file, insn); |
| | 3419 | |
| 3275 | if (insn->dead_end) | 3420 | if (insn->dead_end) |
| 3276 | return 0; | 3421 | return 0; |
| 3277 | | 3422 | |
| @@ -3557,6 +3702,53 @@ static int validate_functions(struct objtool_file *file) · 279 unmodified lines |
| 3557 | return warnings; | 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 | static int validate_reachable_instructions(struct objtool_file *file) | 3752 | static int validate_reachable_instructions(struct objtool_file *file) |
| 3561 | { | 3753 | { |
| 3562 | struct instruction *insn; | 3754 | struct instruction *insn; |
| @@ -3584,6 +3776,11 @@ int check(struct objtool_file *file) · 21 unmodified lines |
| 3584 | return 1; | 3776 | return 1; |
| 3585 | } | 3777 | } |
| 3586 | | 3778 | |
| | 3779 | if (ibt && !lto) { |
| | 3780 | fprintf(stderr, "--ibt requires: --lto\n"); |
| | 3781 | return 1; |
| | 3782 | } |
| | 3783 | |
| 3587 | arch_initial_func_cfi_state(&initial_func_cfi); | 3784 | arch_initial_func_cfi_state(&initial_func_cfi); |
| 3588 | init_cfi_state(&init_cfi); | 3785 | init_cfi_state(&init_cfi); |
| 3589 | init_cfi_state(&func_cfi); | 3786 | init_cfi_state(&func_cfi); |
| @@ -3630,6 +3827,13 @@ int check(struct objtool_file *file) · 40 unmodified lines |
| 3630 | goto out; | 3827 | goto out; |
| 3631 | warnings += ret; | 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 | if (!warnings) { | 3837 | if (!warnings) { |
| 3634 | ret = validate_reachable_instructions(file); | 3838 | ret = validate_reachable_instructions(file); |
| 3635 | if (ret < 0) | 3839 | if (ret < 0) |