diff --git a/kpatch-build/create-diff-object.c b/kpatch-build/create-diff-object.c index ab0d86762ae6a06d10a3977901542c7cce32e0f0..d0290409b3b80671c3f4c4209d07ce9d8e65a7f8 100644 --- a/kpatch-build/create-diff-object.c +++ b/kpatch-build/create-diff-object.c @@ -72,6 +72,8 @@ enum loglevel loglevel = NORMAL; bool KLP_ARCH; bool multi_pfe; +int jump_label_errors; + /******************* * Data structures * ****************/ @@ -79,6 +81,9 @@ struct special_section { char *name; enum architecture arch; int (*group_size)(struct kpatch_elf *kelf, int offset); + bool (*group_filter)(struct lookup_table *lookup, + struct section *relasec, unsigned int offset, + unsigned int size); }; /************* @@ -2296,6 +2301,107 @@ static int fixup_group_size(struct kpatch_elf *kelf, int offset) return (int)(rela->addend - offset); } +static bool jump_table_group_filter(struct lookup_table *lookup, + struct section *relasec, + unsigned int group_offset, + unsigned int group_size) +{ + struct rela *code = NULL, *key = NULL, *rela; + bool tracepoint = false, dynamic_debug = false; + struct lookup_result symbol; + int i = 0; + + /* + * Here we hard-code knowledge about the contents of the jump_entry + * struct. It has three fields: code, target, and key. Each field has + * a relocation associated with it. + */ + list_for_each_entry(rela, &relasec->relas, list) { + if (rela->offset >= group_offset && + rela->offset < group_offset + group_size) { + if (i == 0) + code = rela; + else if (i == 2) + key = rela; + i++; + } + } + + if (i != 3 || !key || !code) + ERROR("BUG: __jump_table has an unexpected format"); + + if (!strncmp(key->sym->name, "__tracepoint_", 13)) + tracepoint = true; + + if (is_dynamic_debug_symbol(key->sym)) + dynamic_debug = true; + + if (KLP_ARCH) { + /* + * On older kernels (with .klp.arch support), jump labels + * aren't supported at all. Error out when they occur in a + * replacement function, with the exception of tracepoints and + * dynamic debug printks. An inert tracepoint or printk is + * harmless enough, but a broken jump label can cause + * unexpected behavior. + */ + if (tracepoint || dynamic_debug) + return false; + + /* + * This will be upgraded to an error after all jump labels have + * been reported. + */ + log_normal("Found a jump label at %s()+0x%lx, using key %s. Jump labels aren't supported with this kernel. Use static_key_enabled() instead.\n", + code->sym->name, code->addend, key->sym->name); + jump_label_errors++; + return false; + } + + /* + * On newer (5.8+) kernels, jump labels are supported in the case where + * the corresponding static key lives in vmlinux. That's because such + * kernels apply vmlinux-specific .klp.rela sections at the same time + * (in the klp module load) as normal relas, before jump label init. + * On the other hand, jump labels based on static keys which are + * defined in modules aren't supported, because late module patching + * can result in the klp relas getting applied *after* the klp module's + * jump label init. + */ + + if (lookup_symbol(lookup, key->sym, &symbol) && + strcmp(symbol.objname, "vmlinux")) { + + /* The static key lives in a module -- not supported */ + + /* Inert tracepoints and dynamic debug printks are harmless */ + if (tracepoint || dynamic_debug) + return false; + + /* + * This will be upgraded to an error after all jump label + * errors have been reported. + */ + log_normal("Found a jump label at %s()+0x%lx, using key %s, which is defined in a module. Use static_key_enabled() instead.\n", + code->sym->name, code->addend, key->sym->name); + jump_label_errors++; + return false; + } + + /* The static key lives in vmlinux or the patch module itself */ + + /* + * If the jump label key lives in the '__dyndbg' section, make sure + * the section gets included, because we don't use klp relocs for + * dynamic debug symbols. For an example of such a key, see + * DYNAMIC_DEBUG_BRANCH(). + */ + if (dynamic_debug) + kpatch_include_symbol(key->sym); + + return true; +} + static struct special_section special_sections[] = { { .name = "__bug_table", @@ -2316,6 +2422,7 @@ static struct special_section special_sections[] = { .name = "__jump_table", .arch = AARCH64 | X86_64 | PPC64 | S390, .group_size = jump_table_group_size, + .group_filter = jump_table_group_filter, }, { .name = ".printk_index", @@ -2405,111 +2512,9 @@ static struct special_section special_sections[] = { {}, }; -static bool should_keep_jump_label(struct lookup_table *lookup, - struct section *relasec, - unsigned int group_offset, - unsigned int group_size, - int *jump_labels_found) -{ - struct rela *code = NULL, *key = NULL, *rela; - bool tracepoint = false, dynamic_debug = false; - struct lookup_result symbol; - int i = 0; - - /* - * Here we hard-code knowledge about the contents of the jump_entry - * struct. It has three fields: code, target, and key. Each field has - * a relocation associated with it. - */ - list_for_each_entry(rela, &relasec->relas, list) { - if (rela->offset >= group_offset && - rela->offset < group_offset + group_size) { - if (i == 0) - code = rela; - else if (i == 2) - key = rela; - i++; - } - } - - if (i != 3 || !key || !code) - ERROR("BUG: __jump_table has an unexpected format"); - - if (!strncmp(key->sym->name, "__tracepoint_", 13)) - tracepoint = true; - - if (is_dynamic_debug_symbol(key->sym)) - dynamic_debug = true; - - if (KLP_ARCH) { - /* - * On older kernels (with .klp.arch support), jump labels - * aren't supported at all. Error out when they occur in a - * replacement function, with the exception of tracepoints and - * dynamic debug printks. An inert tracepoint or printk is - * harmless enough, but a broken jump label can cause - * unexpected behavior. - */ - if (tracepoint || dynamic_debug) - return false; - - /* - * This will be upgraded to an error after all jump labels have - * been reported. - */ - log_normal("Found a jump label at %s()+0x%lx, using key %s. Jump labels aren't supported with this kernel. Use static_key_enabled() instead.\n", - code->sym->name, code->addend, key->sym->name); - (*jump_labels_found)++; - return false; - } - - /* - * On newer (5.8+) kernels, jump labels are supported in the case where - * the corresponding static key lives in vmlinux. That's because such - * kernels apply vmlinux-specific .klp.rela sections at the same time - * (in the klp module load) as normal relas, before jump label init. - * On the other hand, jump labels based on static keys which are - * defined in modules aren't supported, because late module patching - * can result in the klp relas getting applied *after* the klp module's - * jump label init. - */ - - if (lookup_symbol(lookup, key->sym, &symbol) && - strcmp(symbol.objname, "vmlinux")) { - - /* The static key lives in a module -- not supported */ - - /* Inert tracepoints and dynamic debug printks are harmless */ - if (tracepoint || dynamic_debug) - return false; - - /* - * This will be upgraded to an error after all jump labels have - * been reported. - */ - log_normal("Found a jump label at %s()+0x%lx, using key %s, which is defined in a module. Use static_key_enabled() instead.\n", - code->sym->name, code->addend, key->sym->name); - (*jump_labels_found)++; - return false; - } - - /* The static key lives in vmlinux or the patch module itself */ - - /* - * If the jump label key lives in the '__dyndbg' section, make sure - * the section gets included, because we don't use klp relocs for - * dynamic debug symbols. For an example of such a key, see - * DYNAMIC_DEBUG_BRANCH(). - */ - if (dynamic_debug) - kpatch_include_symbol(key->sym); - - return true; -} - static bool should_keep_rela_group(struct lookup_table *lookup, struct section *relasec, unsigned int offset, - unsigned int size, int *jump_labels_found) + unsigned int size) { struct rela *rela; bool found = false; @@ -2529,10 +2534,6 @@ static bool should_keep_rela_group(struct lookup_table *lookup, if (!found) return false; - if (!strcmp(relasec->name, ".rela__jump_table")) - return should_keep_jump_label(lookup, relasec, offset, size, - jump_labels_found); - return true; } @@ -2569,7 +2570,6 @@ static void kpatch_regenerate_special_section(struct kpatch_elf *kelf, struct rela *rela, *safe; char *src, *dest; unsigned int group_size, src_offset, dest_offset; - int jump_labels_found = 0; LIST_HEAD(newrelas); @@ -2604,8 +2604,11 @@ static void kpatch_regenerate_special_section(struct kpatch_elf *kelf, if (src_offset + group_size > relasec->base->sh.sh_size) group_size = (unsigned int)(relasec->base->sh.sh_size - src_offset); - if (!should_keep_rela_group(lookup, relasec, src_offset, group_size, - &jump_labels_found)) + if (!should_keep_rela_group(lookup, relasec, src_offset, group_size)) + continue; + + if (special->group_filter && + !special->group_filter(lookup, relasec, src_offset, group_size)) continue; /* @@ -2638,9 +2641,9 @@ static void kpatch_regenerate_special_section(struct kpatch_elf *kelf, dest_offset += group_size; } - if (jump_labels_found) - ERROR("Found %d jump label(s) in the patched code. Jump labels aren't currently supported. Use static_key_enabled() instead.", - jump_labels_found); + if (jump_label_errors) + ERROR("Found %d unsupported jump label(s) in the patched code. Use static_key_enabled() instead.", + jump_label_errors); if (!dest_offset) { /* no changed or global functions referenced */