diff --git a/kpatch-build/create-diff-object.c b/kpatch-build/create-diff-object.c index df413e9..9570340 100644 --- a/kpatch-build/create-diff-object.c +++ b/kpatch-build/create-diff-object.c @@ -171,6 +171,32 @@ static int is_gcc6_localentry_bundled_sym(struct symbol *sym) } #endif +/* + * On ppc64le, when a function references data, it does so indirectly, via the + * .toc section. So there are *two* levels of relas: + * + * 1) the original function rela, referring to the .toc section; and + * + * 2) the .toc section rela, referring to the data needed by the function. + * + * For example: + * + * Relocation section '.rela.text.netlink_release' at offset 0xcadf0 contains 44 entries: + * ... + * 0000000000000398 0000007300000032 R_PPC64_TOC16_HA 0000000000000000 .toc + 138 + * 00000000000003a0 0000007300000040 R_PPC64_TOC16_LO_DS 0000000000000000 .toc + 138 + * + * Relocation section '.rela.toc' at offset 0xcc6b0 contains 46 entries: + * ... + * 0000000000000138 0000002a00000026 R_PPC64_ADDR64 0000000000000000 .text.deferred_put_nlk_sk + 8 + * + * The below function takes the "first level" rela as input, and, if it refers + * to .toc, returns the "second level" rela, which is the one that refers to + * the actual data symbol. + * + * In some rare cases, a .toc entry has constant data, and thus has no + * corresponding rela. In that case, NULL is returned. + */ static struct rela *toc_rela(const struct rela *rela) { if (rela->type != R_PPC64_TOC16_HA && @@ -2746,16 +2772,15 @@ static void kpatch_create_patches_sections(struct kpatch_elf *kelf, sym->parent) continue; - if (sym->bind == STB_LOCAL) { - if (!lookup_local_symbol(table, sym->name, &symbol)) - ERROR("lookup_local_symbol %s", sym->name); - } else { - if (!lookup_global_symbol(table, sym->name, &symbol)) - ERROR("lookup_global_symbol %s", sym->name); - } + if (!lookup_symbol(table, sym->name, &symbol)) + ERROR("can't find symbol '%s' in symbol table", sym->name); - log_debug("lookup for %s @ 0x%016lx len %lu\n", - sym->name, symbol.addr, symbol.size); + if (sym->bind == STB_LOCAL && symbol.global) + ERROR("can't find local symbol '%s' in symbol table", sym->name); + + log_debug("lookup for %s: obj=%s sympos=%lu size=%lu", + sym->name, symbol.objname, symbol.sympos, + symbol.size); /* * Convert global symbols to local so other objects in the @@ -2821,76 +2846,178 @@ static int kpatch_is_core_module_symbol(char *name) !strcmp(name, "kpatch_shadow_get")); } -/* - * If the patched code refers to a symbol, for example, calls a function - * or stores a pointer to a function somewhere, the address of that symbol - * must be resolved somehow before the patch is applied. The symbol may be - * present in the original code too, so the patch may refer either to that - * version of the symbol (dynrela is used for that) or to its patched - * version directly (with a normal relocation). - * - * Dynrelas may be needed for the symbols not present in this object file - * (rela->sym->sec is NULL), because it is unknown if the patched versions - * of these symbols exist and where they are. - * - * The patched code can usually refer to a symbol from this object file - * directly. If it is a function, this may also improve performance because - * it will not be needed to call the original function first, find the - * patched one and then use Ftrace to pass control to it. - * - * There is an exception though, at least on x86. It is safer to use - * a dynrela if the patched code stores a pointer to a function somewhere - * (relocation of type R_X86_64_32S). The function could be used as - * a callback and some kinds of callbacks are called asynchronously. If - * the patch module sets such callback and is unloaded shortly after, - * the kernel could try to call the function via an invalid pointer and - * would crash. With dynrela, the kernel would call the original function - * in that case. - */ static int function_ptr_rela(const struct rela *rela) { const struct rela *rela_toc = toc_rela(rela); return (rela_toc && rela_toc->sym->type == STT_FUNC && !rela_toc->sym->parent && - /* skip switch table on PowerPC */ rela_toc->addend == (int)rela_toc->sym->sym.st_value && (rela->type == R_X86_64_32S || rela->type == R_PPC64_TOC16_HA || rela->type == R_PPC64_TOC16_LO_DS)); } -static int may_need_dynrela(const struct rela *rela) +static bool need_dynrela(struct lookup_table *table, const struct rela *rela) { + struct lookup_result symbol; + /* - * References to .TOC. are treated specially by the module loader and + * These references are treated specially by the module loader and * should never be converted to dynrelas. */ if (rela->type == R_PPC64_REL16_HA || rela->type == R_PPC64_REL16_LO || - rela->type == R_PPC64_REL64) - return 0; - - if (!rela->sym->sec) - return 1; + rela->type == R_PPC64_REL64 || rela->type == R_PPC64_ENTRY) + return false; /* - * Nested functions used as callbacks are a special case. - * They are not supposed to be visible outside of the - * function that defines them. Their names may differ in - * the original and the patched kernels which makes it - * difficult to use dynrelas. Fortunately, nested functions - * are rare and are unlikely to be used as asynchronous - * callbacks, so the patched code can refer to them directly. - * It seems, one can only distinguish such functions by their - * names containing a dot. Other kinds of functions with - * such names (e.g. optimized copies of functions) are - * unlikely to be used as callbacks. + * On powerpc, the function prologue generated by GCC 6 has the + * sequence: + * + * .globl my_func + * .type my_func, @function + * .quad .TOC.-my_func + * my_func: + * .reloc ., R_PPC64_ENTRY ; optional + * ld r2,-8(r12) + * add r2,r2,r12 + * .localentry my_func, .-my_func + * + * The R_PPC64_ENTRY is optional and its symbol might have an empty + * name. Leave it as a normal rela. */ - return (function_ptr_rela(rela) && - toc_rela(rela)->sym->status != NEW && - !strchr(toc_rela(rela)->sym->name, '.')); + if (rela->type == R_PPC64_ENTRY) + return false; + + /* + * Allow references to core module symbols to remain as normal + * relas. They should be exported. + */ + if (kpatch_is_core_module_symbol(rela->sym->name)) + return false; + + if (rela->sym->sec) { + /* + * Internal symbols usually don't need dynrelas, because they + * live in the patch module and can be relocated normally. + * + * There's one exception: function pointers. + * + * If the rela references a function pointer, we convert it to + * a dynrela, so that the function pointer will refer to the + * original function rather than the patched function. This + * can prevent crashes in cases where the function pointer is + * called asynchronously after the patch module has been + * unloaded. + */ + if (!function_ptr_rela(rela)) + return false; + + /* + * Function pointers which refer to _nested_ functions are a + * special case. They are not supposed to be visible outside + * of the function that defines them. Their names may differ + * in the original and the patched kernels which makes it + * difficult to use dynrelas. Fortunately, nested functions + * are rare and are unlikely to be used as asynchronous + * callbacks, so the patched code can refer to them directly. + * It seems, one can only distinguish such functions by their + * names containing a dot. Other kinds of functions with such + * names (e.g. optimized copies of functions) are unlikely to + * be used as callbacks. + * + * Function pointers to *new* functions don't have this issue, + * just use a normal rela for them. + */ + return toc_rela(rela)->sym->status != NEW && + !strchr(toc_rela(rela)->sym->name, '.'); + } + + if (!lookup_symbol(table, rela->sym->name, &symbol)) { + /* + * Assume the symbol lives in another .o in the patch module. + * A normal rela should work. + */ + return false; + } + + if (rela->sym->bind == STB_LOCAL) { + + if (symbol.global) + ERROR("can't find local symbol '%s' in symbol table", + rela->sym->name); + + /* + * The symbol is (formerly) local. Use a dynrela to access the + * original version of the symbol in the patched object. + */ + return true; + } + + if (symbol.exported) { + + if (is_gcc6_localentry_bundled_sym(rela->sym)) { + /* + * On powerpc, the symbol is global and exported, but + * it was also in the changed object file. In this + * case the rela refers to the 'localentry' point, so a + * normal rela wouldn't work. Force a dynrela so it + * can be handled correctly by the livepatch relocation + * code. + */ + return true; + } + + if (!strcmp(symbol.objname, "vmlinux")) { + /* + * The symbol is exported by vmlinux. Use a normal + * rela. + */ + return false; + } + + /* + * The symbol is exported by the to-be-patched module, or by + * another module which the patched module depends on. Use a + * dynrela because of late module loading: the patch module may + * be loaded before the to-be-patched (or other) module. + */ + return true; + } + + if (symbol.global) { + /* + * The symbol is global in the to-be-patched object, but not + * exported. Use a dynrela to work around the fact that it's + * an unexported sybmbol. + */ + return true; + } + + /* + * The symbol is global and not exported, but it's not in the parent + * object. The only explanation is that it's defined in another object + * in the patch module. A normal rela should resolve it. + */ + return false; } +/* + * kpatch_create_intermediate_sections() + * + * The primary purpose of this function is to convert some relas (also known as + * relocations) to dynrelas (also known as dynamic relocations or livepatch + * relocations or klp relas). + * + * If the patched code refers to a symbol, for example, if it calls a function + * or stores a pointer to a function somewhere or accesses some global data, + * the address of that symbol must be resolved somehow before the patch is + * applied. + * + * If the symbol lives outside the patch module, and if it's not exported by + * vmlinux (e.g., with EXPORT_SYMBOL) then the rela needs to be converted to a + * dynrela so the livepatch code can resolve it at runtime. + */ static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf, struct lookup_table *table, char *objname, @@ -2903,8 +3030,7 @@ static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf, struct kpatch_symbol *ksyms; struct kpatch_relocation *krelas; struct lookup_result symbol; - char *sym_objname; - int vmlinux, external; + bool vmlinux; vmlinux = !strcmp(objname, "vmlinux"); @@ -2916,25 +3042,22 @@ static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf, if (!strcmp(sec->name, ".rela.kpatch.funcs")) continue; list_for_each_entry(rela, &sec->relas, list) { - nr++; /* upper bound on number of kpatch relas and symbols */ + + /* upper bound on number of kpatch relas and symbols */ + nr++; + /* - * Relocation section '.rela.toc' at offset 0xcc6b0 contains 46 entries: - * ... - * 0000000000000138 0000002a00000026 R_PPC64_ADDR64 0000000000000000 .text.deferred_put_nlk_sk + 8 + * We set 'need_dynrela' here in the first pass because + * the .toc section's 'need_dynrela' values are + * dependent on all the other sections. Otherwise, if + * we did this analysis in the second pass, we'd have + * to convert .toc dynrelas at the very end. * - * Relocation section '.rela.text.netlink_release' at offset 0xcadf0 contains 44 entries: - * ... - * 0000000000000398 0000007300000032 R_PPC64_TOC16_HA 0000000000000000 .toc + 138 - * 00000000000003a0 0000007300000040 R_PPC64_TOC16_LO_DS 0000000000000000 .toc + 138 - * - * On PowerPC, may_need_dynrela() should be using rela's reference in .rela.toc for - * the rela like in the example, where the sym name is .toc + offset. In such case, - * the checks are performed on both rela and its reference in .rela.toc. Where the - * rela is checked for rela->type and its corresponding rela in .rela.toc for function - * pointer/switch label. If rela->need_dynrela needs to be set, it's referenced rela - * in (.rela.toc)->need_dynrela is set, as they represent the function sym. + * Specifically, this is needed for the powerpc + * internal symbol function pointer check which is done + * via .toc indirection in need_dynrela(). */ - if (may_need_dynrela(rela)) + if (need_dynrela(table, rela)) toc_rela(rela)->need_dynrela = 1; } } @@ -2973,129 +3096,13 @@ static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf, if (!rela->need_dynrela) continue; - /* - * Allow references to core module symbols to remain as - * normal relas, since the core module may not be - * compiled into the kernel, and they should be - * exported anyway. - */ - if (kpatch_is_core_module_symbol(rela->sym->name)) - continue; + if (!lookup_symbol(table, rela->sym->name, &symbol)) + ERROR("can't find symbol '%s' in symbol table", + rela->sym->name); - external = 0; - /* - * sym_objname is the name of the object to which - * rela->sym belongs. We'll need this to build - * ".klp.sym." symbol names later on. - * - * By default sym_objname is the name of the - * component being patched (vmlinux or module). - * If it's an external symbol, sym_objname - * will get reassigned appropriately. - */ - sym_objname = objname; - - /* - * On ppc64le, the function prologue generated by GCC 6 - * has the sequence: - * - * .globl my_func - * .type my_func, @function - * .quad .TOC.-my_func - * my_func: - * .reloc ., R_PPC64_ENTRY ; optional - * ld r2,-8(r12) - * add r2,r2,r12 - * .localentry my_func, .-my_func - * - * The R_PPC64_ENTRY is optional and its symbol might - * have an empty name. Leave it as a normal rela. - */ - if (rela->type == R_PPC64_ENTRY) - continue; - - if (rela->sym->bind == STB_LOCAL) { - /* An unchanged local symbol */ - if (!lookup_local_symbol(table, rela->sym->name, - &symbol)) - ERROR("lookup_local_symbol %s needed for %s", - rela->sym->name, sec->base->name); - - } - else if (vmlinux) { - /* - * We have a patch to vmlinux which references - * a global symbol. Use a normal rela for - * exported symbols and a dynrela otherwise. - */ -#ifdef __powerpc64__ - /* - * An exported symbol might be local to an - * object file and any access to the function - * might be through localentry (toc+offset) - * instead of global offset. - * - * fs/proc/proc_sysctl::sysctl_head_grab: - * 166: 0000000000000000 256 FUNC GLOBAL DEFAULT [: 8] 42 unregister_sysctl_table - * 167: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND .TOC. - * - * These type of symbols have a type of - * STT_FUNC. Treat them like local symbols. - * They will be handled by the livepatch - * relocation code. - */ - if (lookup_is_exported_symbol(table, rela->sym->name)) { - if (rela->sym->type != STT_FUNC) - continue; - } -#else - if (lookup_is_exported_symbol(table, rela->sym->name)) - continue; -#endif - /* - * If lookup_global_symbol() fails, assume the - * symbol is defined in another object in the - * patch module. - */ - if (!lookup_global_symbol(table, rela->sym->name, - &symbol)) - continue; - } else { - /* - * We have a patch to a module which references - * a global symbol. Try to find the symbol in - * the module being patched. - */ - if (!lookup_global_symbol(table, rela->sym->name, - &symbol)) { - /* - * Not there, see if the symbol is - * exported, and set sym_objname to the - * object the exported symbol belongs - * to. If it's not exported, assume sym - * is provided by another .o in the - * patch module. - */ - sym_objname = lookup_exported_symbol_objname(table, rela->sym->name); - if (!sym_objname) - sym_objname = pmod_name; - - /* - * For a symbol exported by vmlinux, use - * the original rela. - * - * For a symbol exported by a module, - * convert to a dynrela because the - * module might not be loaded yet. - */ - if (!strcmp(sym_objname, "vmlinux")) - continue; - - external = 1; - } - } - log_debug("lookup for %s @ 0x%016lx len %lu\n", - rela->sym->name, symbol.addr, symbol.size); + log_debug("lookup for %s: obj=%s sympos=%lu", + rela->sym->name, symbol.objname, + symbol.sympos); /* Fill in ksyms[index] */ if (vmlinux) @@ -3119,7 +3126,7 @@ static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf, ALLOC_LINK(rela2, &ksym_sec->rela->relas); rela2->sym = strsym; rela2->type = ABSOLUTE_RELA_TYPE; - rela2->addend = offset_of_string(&kelf->strings, sym_objname); + rela2->addend = offset_of_string(&kelf->strings, symbol.objname); rela2->offset = (unsigned int)(index * sizeof(*ksyms) + \ offsetof(struct kpatch_symbol, objname)); @@ -3129,7 +3136,7 @@ static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf, rela->addend -= rela->sym->sym.st_value; krelas[index].addend = rela->addend; krelas[index].type = rela->type; - krelas[index].external = external; + krelas[index].external = !vmlinux && symbol.exported; /* add rela to fill in krelas[index].dest field */ ALLOC_LINK(rela2, &krela_sec->rela->relas); diff --git a/kpatch-build/lookup.c b/kpatch-build/lookup.c index 4a601a7..3c7f9e6 100644 --- a/kpatch-build/lookup.c +++ b/kpatch-build/lookup.c @@ -399,8 +399,8 @@ void lookup_close(struct lookup_table *table) free(table); } -bool lookup_local_symbol(struct lookup_table *table, char *name, - struct lookup_result *result) +static bool lookup_local_symbol(struct lookup_table *table, char *name, + struct lookup_result *result) { struct object_symbol *sym; unsigned long sympos = 0; @@ -434,14 +434,51 @@ bool lookup_local_symbol(struct lookup_table *table, char *name, result->addr = sym->addr; result->size = sym->size; result->sympos = sympos; + result->global = false; + result->exported = false; } } return !!result->objname; } -bool lookup_global_symbol(struct lookup_table *table, char *name, - struct lookup_result *result) +static bool lookup_exported_symbol(struct lookup_table *table, char *name, + struct lookup_result *result) +{ + struct export_symbol *sym; + int i; + + if (result) + memset(result, 0, sizeof(*result)); + + for_each_exp_symbol(i, sym, table) { + if (!strcmp(sym->name, name)) { + + if (!result) + return true; + + if (result->objname) + ERROR("duplicate exported symbol found for %s", name); + + result->objname = sym->objname; + result->addr = 0; /* determined at runtime */ + result->size = 0; /* not used for exported symbols */ + result->sympos = 0; /* always 0 for exported symbols */ + result->global = true; + result->exported = true; + } + } + + return result && result->objname; +} + +bool is_exported(struct lookup_table *table, char *name) +{ + return lookup_exported_symbol(table, name, NULL); +} + +static bool lookup_global_symbol(struct lookup_table *table, char *name, + struct lookup_result *result) { struct object_symbol *sym; int i; @@ -458,47 +495,22 @@ bool lookup_global_symbol(struct lookup_table *table, char *name, result->addr = sym->addr; result->size = sym->size; result->sympos = 0; /* always 0 for global symbols */ + result->global = true; + result->exported = is_exported(table, name); } } return !!result->objname; } -bool lookup_is_exported_symbol(struct lookup_table *table, char *name) +bool lookup_symbol(struct lookup_table *table, char *name, + struct lookup_result *result) { - struct export_symbol *sym, *match = NULL; - int i; + if (lookup_local_symbol(table, name, result)) + return true; - for_each_exp_symbol(i, sym, table) { - if (!strcmp(sym->name, name)) { - if (match) - ERROR("duplicate exported symbol found for %s", name); - match = sym; - } - } + if (lookup_global_symbol(table, name, result)) + return true; - return !!match; -} - -/* - * lookup_exported_symbol_objname - find the object/module an exported - * symbol belongs to. - */ -char *lookup_exported_symbol_objname(struct lookup_table *table, char *name) -{ - struct export_symbol *sym, *match = NULL; - int i; - - for_each_exp_symbol(i, sym, table) { - if (!strcmp(sym->name, name)) { - if (match) - ERROR("duplicate exported symbol found for %s", name); - match = sym; - } - } - - if (match) - return match->objname; - - return NULL; + return lookup_exported_symbol(table, name, result); } diff --git a/kpatch-build/lookup.h b/kpatch-build/lookup.h index 59f7f1e..8d29900 100644 --- a/kpatch-build/lookup.h +++ b/kpatch-build/lookup.h @@ -10,6 +10,7 @@ struct lookup_result { unsigned long addr; unsigned long size; unsigned long sympos; + bool global, exported; }; struct sym_compare_type { @@ -21,11 +22,7 @@ struct lookup_table *lookup_open(char *symtab_path, char *objname, char *symvers_path, char *hint, struct sym_compare_type *locals); void lookup_close(struct lookup_table *table); -bool lookup_local_symbol(struct lookup_table *table, char *name, - struct lookup_result *result); -bool lookup_global_symbol(struct lookup_table *table, char *name, - struct lookup_result *result); -bool lookup_is_exported_symbol(struct lookup_table *table, char *name); -char *lookup_exported_symbol_objname(struct lookup_table *table, char *name); +bool lookup_symbol(struct lookup_table *table, char *name, + struct lookup_result *result); #endif /* _LOOKUP_H_ */