mirror of
https://github.com/dynup/kpatch
synced 2025-01-11 07:39:24 +00:00
fix undefined symbols for future loaded modules
When patching module A, if one of the new function's relas reference a symbol in module B, we currently just leave it as a normal rela. But if module B hasn't been loaded yet, the patch module will fail to load due to the rela's reference to an undefined symbol. The fix is to convert these relas to dynrelas, which can be resolved later in the module notifier when A is loaded. Also added support for the R_X86_64_NONE relocation type, needed for dynrelas which reference __fentry__.
This commit is contained in:
parent
f4bba70412
commit
34cc258a31
@ -526,11 +526,15 @@ static int kpatch_write_relocations(struct kpatch_module *kpmod,
|
||||
} else {
|
||||
/* module, dynrela->src needs to be discovered */
|
||||
|
||||
src = kpatch_find_module_symbol(object->mod,
|
||||
dynrela->name);
|
||||
if (dynrela->exported)
|
||||
src = (unsigned long)__symbol_get(dynrela->name);
|
||||
else
|
||||
src = kpatch_find_module_symbol(object->mod,
|
||||
dynrela->name);
|
||||
|
||||
if (!src) {
|
||||
pr_err("unable to find symbol '%s' in module '%s'\n",
|
||||
dynrela->name, object->name);
|
||||
pr_err("unable to find symbol '%s'\n",
|
||||
dynrela->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@ -538,6 +542,8 @@ static int kpatch_write_relocations(struct kpatch_module *kpmod,
|
||||
}
|
||||
|
||||
switch (dynrela->type) {
|
||||
case R_X86_64_NONE:
|
||||
continue;
|
||||
case R_X86_64_PC32:
|
||||
loc = dynrela->dest;
|
||||
val = (u32)(dynrela->src + dynrela->addend -
|
||||
@ -592,6 +598,7 @@ static int kpatch_write_relocations(struct kpatch_module *kpmod,
|
||||
static int kpatch_unlink_object(struct kpatch_object *object)
|
||||
{
|
||||
struct kpatch_func *func;
|
||||
struct kpatch_dynrela *dynrela;
|
||||
int ret;
|
||||
|
||||
list_for_each_entry(func, &object->funcs, list) {
|
||||
@ -605,6 +612,10 @@ static int kpatch_unlink_object(struct kpatch_object *object)
|
||||
}
|
||||
}
|
||||
|
||||
list_for_each_entry(dynrela, &object->dynrelas, list)
|
||||
if (dynrela->src && dynrela->exported)
|
||||
__symbol_put(dynrela->name);
|
||||
|
||||
if (object->mod)
|
||||
module_put(object->mod);
|
||||
|
||||
|
@ -54,6 +54,7 @@ struct kpatch_dynrela {
|
||||
const char *name;
|
||||
const char *objname;
|
||||
int addend;
|
||||
bool exported;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
|
@ -287,6 +287,7 @@ static int patch_make_dynrelas_list(struct list_head *objects)
|
||||
dynrela->type = p_dynrela->type;
|
||||
dynrela->name = p_dynrela->name;
|
||||
dynrela->objname = p_dynrela->objname;
|
||||
dynrela->exported = p_dynrela->exported;
|
||||
dynrela->addend = p_dynrela->addend;
|
||||
list_add_tail(&dynrela->list, &object->dynrelas);
|
||||
}
|
||||
|
@ -19,6 +19,10 @@
|
||||
* Contains the structs used for the patch module special sections
|
||||
*/
|
||||
|
||||
#ifndef __KERNEL__
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
struct kpatch_patch_func {
|
||||
unsigned long new_addr;
|
||||
unsigned long new_size;
|
||||
@ -34,5 +38,6 @@ struct kpatch_patch_dynrela {
|
||||
unsigned long type;
|
||||
char *name;
|
||||
char *objname;
|
||||
bool exported;
|
||||
int addend;
|
||||
};
|
||||
|
@ -1654,6 +1654,9 @@ void kpatch_create_dynamic_rela_sections(struct kpatch_elf *kelf,
|
||||
struct symbol *strsym;
|
||||
struct lookup_result result;
|
||||
struct kpatch_patch_dynrela *dynrelas;
|
||||
bool vmlinux, exported;
|
||||
|
||||
vmlinux = !strcmp(objname, "vmlinux");
|
||||
|
||||
/* count rela entries that need to be dynamic */
|
||||
nr = 0;
|
||||
@ -1726,48 +1729,55 @@ void kpatch_create_dynamic_rela_sections(struct kpatch_elf *kelf,
|
||||
list_for_each_entry_safe(rela, safe, &sec2->relas, list) {
|
||||
if (rela->sym->sec)
|
||||
continue;
|
||||
|
||||
exported = false;
|
||||
|
||||
if (rela->sym->bind == STB_LOCAL) {
|
||||
/* An unchanged local symbol */
|
||||
if (lookup_local_symbol(table, rela->sym->name,
|
||||
hint, &result))
|
||||
ERROR("lookup_local_symbol %s (%s) needed for %s",
|
||||
rela->sym->name, hint, sec2->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.
|
||||
*/
|
||||
if (lookup_is_exported_symbol(table, rela->sym->name))
|
||||
continue;
|
||||
if (lookup_global_symbol(table, rela->sym->name,
|
||||
&result))
|
||||
ERROR("lookup_global_symbol failed for %s, needed for %s\n",
|
||||
rela->sym->name,
|
||||
sec2->base->name);
|
||||
} else {
|
||||
/*
|
||||
* Symbol is global, could be an object-level
|
||||
* global not defined in this file or exported
|
||||
* by another object.
|
||||
*/
|
||||
if (!strcmp(objname, "vmlinux") &&
|
||||
lookup_is_exported_symbol(table, rela->sym->name))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Try to find global symbol. If none is
|
||||
* found, assume it is exported and no dynrela
|
||||
* is needed. If the symbol is not exported,
|
||||
* the final patch module will fail to load.
|
||||
* We have a patch to a module which references
|
||||
* a global symbol. First try to find it in
|
||||
* the module being patched.
|
||||
*/
|
||||
if (lookup_global_symbol(table, rela->sym->name,
|
||||
&result))
|
||||
&result))
|
||||
/*
|
||||
* Symbol is not a kernel object
|
||||
* global. Assume symbol is exported by
|
||||
* vmlinux or another kernel module.
|
||||
* Not there, assume it's exported by
|
||||
* another object.
|
||||
*/
|
||||
continue;
|
||||
exported = true;
|
||||
}
|
||||
log_debug("lookup for %s @ 0x%016lx len %lu\n",
|
||||
rela->sym->name, result.value, result.size);
|
||||
|
||||
/* dest filed in by rela entry below */
|
||||
if (!strcmp(objname, "vmlinux"))
|
||||
if (vmlinux)
|
||||
dynrelas[index].src = result.value;
|
||||
else
|
||||
/* for modules, src is discovered at runtime */
|
||||
dynrelas[index].src = 0;
|
||||
dynrelas[index].addend = rela->addend;
|
||||
dynrelas[index].type = rela->type;
|
||||
dynrelas[index].exported = exported;
|
||||
|
||||
/* add rela to fill in dest field */
|
||||
ALLOC_LINK(dynrela, &relasec->relas);
|
||||
|
Loading…
Reference in New Issue
Block a user