diff --git a/kmod/core/core.c b/kmod/core/core.c index 905e49d19..2b09c848e 100644 --- a/kmod/core/core.c +++ b/kmod/core/core.c @@ -72,10 +72,24 @@ struct kpatch_backtrace_args { }; struct kpatch_kallsyms_args { - char *name; + const char *name; unsigned long addr; }; +/* this is a double loop, use goto instead of break */ +#define do_for_each_linked_func(kpmod, func) { \ + struct kpatch_object *_object; \ + list_for_each_entry(_object, &kpmod->objects, list) { \ + if (!kpatch_object_linked(_object)) \ + continue; \ + list_for_each_entry(func, &_object->funcs, list) { + +#define while_for_each_linked_func() \ + } \ + } \ +} + + /* * The kpatch core module has a state machine which allows for proper * synchronization with kpatch_ftrace_handler() when it runs in NMI context. @@ -114,21 +128,6 @@ enum { }; static atomic_t kpatch_state; -enum kpatch_op { - KPATCH_OP_NONE, - KPATCH_OP_PATCH, - KPATCH_OP_UNPATCH, -}; - -struct kpatch_func { - struct kpatch_patch *patch; - unsigned long old_addr; - struct hlist_node node; - struct kpatch_module *kpmod; - enum kpatch_op op; - struct module *mod; -}; - static inline void kpatch_state_idle(void) { int state = atomic_read(&kpatch_state); @@ -171,10 +170,15 @@ static struct kpatch_func *kpatch_get_prev_func(struct kpatch_func *f, return NULL; } +static inline bool kpatch_object_linked(struct kpatch_object *object) +{ + return object->mod || !strcmp(object->name, "vmlinux"); +} + static inline int kpatch_compare_addresses(unsigned long stack_addr, unsigned long func_addr, unsigned long func_size, - char *func_name) + const char *func_name) { if (stack_addr >= func_addr && stack_addr < func_addr + func_size) { pr_err("activeness safety check failed for %s\n", func_name); @@ -195,43 +199,37 @@ static void kpatch_backtrace_address_verify(void *data, unsigned long address, return; /* check kpmod funcs */ - for (i = 0; i < kpmod->patches_nr; i++) { + do_for_each_linked_func(kpmod, func) { unsigned long func_addr, func_size; - char *func_name; + const char *func_name; struct kpatch_func *active_func; - func = &kpmod->funcs[i]; - - /* check if patch depends on a future module load */ - if (!func->old_addr) - continue; - active_func = kpatch_get_func(func->old_addr); if (!active_func) { /* patching an unpatched func */ func_addr = func->old_addr; - func_size = func->patch->old_size; - func_name = func->patch->name; + func_size = func->old_size; + func_name = func->name; } else { /* repatching or unpatching */ - func_addr = active_func->patch->new_addr; - func_size = active_func->patch->new_size; - func_name = active_func->patch->name; + func_addr = active_func->new_addr; + func_size = active_func->new_size; + func_name = active_func->name; } args->ret = kpatch_compare_addresses(address, func_addr, func_size, func_name); if (args->ret) return; - } + } while_for_each_linked_func(); /* in the replace case, need to check the func hash as well */ hash_for_each_rcu(kpatch_func_hash, i, func, node) { if (func->op == KPATCH_OP_UNPATCH) { args->ret = kpatch_compare_addresses(address, - func->patch->new_addr, - func->patch->new_size, - func->patch->name); + func->new_addr, + func->new_size, + func->name); if (args->ret) return; } @@ -282,9 +280,8 @@ static int kpatch_verify_activeness_safety(struct kpatch_module *kpmod) static int kpatch_apply_patch(void *data) { struct kpatch_module *kpmod = data; - struct kpatch_func *funcs = kpmod->funcs; - int num_funcs = kpmod->patches_nr; - int i, ret; + struct kpatch_func *func; + int ret; ret = kpatch_verify_activeness_safety(kpmod); if (ret) { @@ -293,10 +290,9 @@ static int kpatch_apply_patch(void *data) } /* tentatively add the new funcs to the global func hash */ - for (i = 0; i < num_funcs; i++) - if (funcs[i].old_addr) - hash_add_rcu(kpatch_func_hash, &funcs[i].node, - funcs[i].old_addr); + do_for_each_linked_func(kpmod, func) { + hash_add_rcu(kpatch_func_hash, &func->node, func->old_addr); + } while_for_each_linked_func(); /* memory barrier between func hash add and state change */ smp_wmb(); @@ -310,9 +306,9 @@ static int kpatch_apply_patch(void *data) pr_err("NMI activeness safety check failed\n"); /* Failed, we have to rollback patching process */ - for (i = 0; i < num_funcs; i++) - if (funcs[i].old_addr) - hash_del_rcu(&funcs[i].node); + do_for_each_linked_func(kpmod, func) { + hash_del_rcu(&func->node); + } while_for_each_linked_func(); return -EBUSY; } @@ -324,9 +320,8 @@ static int kpatch_apply_patch(void *data) static int kpatch_remove_patch(void *data) { struct kpatch_module *kpmod = data; - struct kpatch_func *funcs = kpmod->funcs; - int num_funcs = kpmod->patches_nr; - int ret, i; + struct kpatch_func *func; + int ret; ret = kpatch_verify_activeness_safety(kpmod); if (ret) { @@ -340,9 +335,9 @@ static int kpatch_remove_patch(void *data) return -EBUSY; /* Succeeded, remove all updating funcs from hash table */ - for (i = 0; i < num_funcs; i++) - if (funcs[i].old_addr) - hash_del_rcu(&funcs[i].node); + do_for_each_linked_func(kpmod, func) { + hash_del_rcu(&func->node); + } while_for_each_linked_func(); return 0; } @@ -396,7 +391,7 @@ kpatch_ftrace_handler(unsigned long ip, unsigned long parent_ip, } done: if (func) - regs->ip = func->patch->new_addr; + regs->ip = func->new_addr; preempt_enable_notrace(); } @@ -460,18 +455,6 @@ static int kpatch_ftrace_remove_func(unsigned long ip) return 0; } -static void kpatch_put_modules(struct kpatch_func *funcs, int num_funcs) -{ - int i; - - for (i = 0; i < num_funcs; i++) { - struct kpatch_func *func = &funcs[i]; - - if (func->mod) - module_put(func->mod); - } -} - static int kpatch_kallsyms_callback(void *data, const char *name, struct module *mod, unsigned long addr) @@ -484,7 +467,7 @@ static int kpatch_kallsyms_callback(void *data, const char *name, return 0; } -static int kpatch_verify_symbol_match(char *name, unsigned long addr) +static int kpatch_verify_symbol_match(const char *name, unsigned long addr) { int ret; @@ -503,7 +486,8 @@ static int kpatch_verify_symbol_match(char *name, unsigned long addr) return 0; } -static unsigned long kpatch_find_module_symbol(struct module *mod, char *name) +static unsigned long kpatch_find_module_symbol(struct module *mod, + const char *name) { char buf[KSYM_SYMBOL_LEN]; @@ -522,27 +506,19 @@ static unsigned long kpatch_find_module_symbol(struct module *mod, char *name) return kallsyms_lookup_name(buf); } -static int kpatch_write_relocations(struct kpatch_module *kpmod) +static int kpatch_write_relocations(struct kpatch_module *kpmod, + struct kpatch_object *object) { - int ret, i, size, readonly = 0; + int ret, size, readonly = 0; struct kpatch_dynrela *dynrela; u64 loc, val; unsigned long core = (unsigned long)kpmod->mod->module_core; unsigned long core_ro_size = kpmod->mod->core_ro_size; unsigned long core_size = kpmod->mod->core_size; unsigned long src; - struct module *mod; - - for (i = 0; i < kpmod->dynrelas_nr; i++) { - dynrela = &kpmod->dynrelas[i]; - - if (!strcmp(dynrela->objname, "vmlinux")) { - /* vmlinux */ - - /* check if reloc has already been written */ - if (kpmod->enabled) - continue; + list_for_each_entry(dynrela, &object->dynrelas, list) { + if (!strcmp(object->name, "vmlinux")) { ret = kpatch_verify_symbol_match(dynrela->name, dynrela->src); if (ret) @@ -550,21 +526,11 @@ static int kpatch_write_relocations(struct kpatch_module *kpmod) } else { /* module, dynrela->src needs to be discovered */ - /* check if reloc has already been written */ - if (dynrela->src) - continue; - - /* check if dynrela depends on a future loaded module */ - mutex_lock(&module_mutex); - mod = find_module(dynrela->objname); - mutex_unlock(&module_mutex); - if (!mod) - continue; - - src = kpatch_find_module_symbol(mod, dynrela->name); + src = kpatch_find_module_symbol(object->mod, + dynrela->name); if (!src) { pr_err("unable to find symbol '%s' in module '%s'\n", - dynrela->name, mod->name); + dynrela->name, object->name); return -EINVAL; } @@ -589,9 +555,9 @@ static int kpatch_write_relocations(struct kpatch_module *kpmod) size = 8; break; default: - printk("unsupported rela type %ld for source %s (0x%lx <- 0x%lx) at index %d\n", + printk("unsupported rela type %ld for source %s (0x%lx <- 0x%lx)\n", dynrela->type, dynrela->name, - dynrela->dest, dynrela->src, i); + dynrela->dest, dynrela->src); return -EINVAL; } @@ -623,33 +589,93 @@ static int kpatch_write_relocations(struct kpatch_module *kpmod) return 0; } -static void kpatch_calculate_old_addr(struct kpatch_func *func) +static int kpatch_unlink_object(struct kpatch_object *object) { - struct module *module; + struct kpatch_func *func; + int ret; - if (!strcmp(func->patch->objname, "vmlinux")) { - func->old_addr = func->patch->old_offset; - return; + list_for_each_entry(func, &object->funcs, list) { + if (!func->old_addr) + continue; + ret = kpatch_ftrace_remove_func(func->old_addr); + if (ret) { + WARN(1, "can't unregister ftrace for address 0x%lx\n", + func->old_addr); + return ret; + } } - /* - * If the module hasn't been loaded yet, we'll patch it later in - * kpatch_module_notify(). - */ - mutex_lock(&module_mutex); - module = find_module(func->patch->objname); - if (!module) { + if (object->mod) + module_put(object->mod); + + return 0; +} + +/* + * Link to a to-be-patched object in preparation for patching it. + * + * - Find the object module + * - Write patch module relocations which reference the object + * - Calculate the patched functions' addresses + * - Register them with ftrace + */ +static int kpatch_link_object(struct kpatch_module *kpmod, + struct kpatch_object *object) +{ + struct module *mod = NULL; + struct kpatch_func *func; + int ret; + bool vmlinux = !strcmp(object->name, "vmlinux"); + + if (!vmlinux) { + mutex_lock(&module_mutex); + mod = find_module(object->name); + if (!mod) { + /* + * The module hasn't been loaded yet. We can patch it + * later in kpatch_module_notify(). + */ + mutex_unlock(&module_mutex); + return 0; + } + + /* should never fail because we have the mutex */ + WARN_ON(!try_module_get(mod)); mutex_unlock(&module_mutex); - return; + object->mod = mod; + } + + ret = kpatch_write_relocations(kpmod, object); + if (ret) + goto err_unlink; + + list_for_each_entry(func, &object->funcs, list) { + unsigned long old_addr; + + /* calculate actual old location */ + if (vmlinux) { + old_addr = func->old_offset; + ret = kpatch_verify_symbol_match(func->name, + old_addr); + if (ret) + goto err_unlink; + } else + old_addr = (unsigned long)mod->module_core + + func->old_offset; + + /* add to ftrace filter and register handler if needed */ + ret = kpatch_ftrace_add_func(old_addr); + if (ret) + goto err_unlink; + + func->old_addr = old_addr; } - /* should never fail because we have the mutex */ - WARN_ON(!try_module_get(module)); - mutex_unlock(&module_mutex); + return 0; - func->mod = module; - func->old_addr = (unsigned long)module->module_core + - func->patch->old_offset; +err_unlink: + kpatch_unlink_object(object); + return ret; } static int kpatch_module_notify(struct notifier_block *nb, unsigned long action, @@ -657,83 +683,63 @@ static int kpatch_module_notify(struct notifier_block *nb, unsigned long action, { struct module *mod = data; struct kpatch_module *kpmod; - int printed = 0, ret = 0, i; + struct kpatch_object *object; + struct kpatch_func *func; + int ret = 0; + bool found = false; if (action != MODULE_STATE_COMING) - return ret; + return 0; down(&kpatch_mutex); list_for_each_entry(kpmod, &kpmod_list, list) { - if (!kpmod->pending) - continue; - - /* temporarily clear pending, can be set again below */ - kpmod->pending = 0; - - for (i = 0; i < kpmod->patches_nr; i++) { - struct kpatch_func *func = &kpmod->funcs[i]; - - /* check if it has already been patched */ - if (func->old_addr) - continue; - - /* calculate actual old location (refs if module) */ - kpatch_calculate_old_addr(func); - - if (!func->old_addr) { - kpmod->pending = 1; + list_for_each_entry(object, &kpmod->objects, list) { + if (kpatch_object_linked(object)) continue; + if (!strcmp(object->name, mod->name)) { + found = true; + goto done; } + } + } +done: + if (!found) + goto out; - if (!printed) { - pr_notice("patching newly loaded module '%s'\n", - mod->name); - printed = 1; - } + ret = kpatch_link_object(kpmod, object); + if (ret) + goto out; - ret = kpatch_ftrace_add_func(func->old_addr); - if (ret) - goto out; + BUG_ON(!object->mod); - /* add to the global func hash */ - hash_add_rcu(kpatch_func_hash, &func->node, - func->old_addr); - } + pr_notice("patching newly loaded module '%s'\n", object->name); - /* write any newly valid relocations */ - ret = kpatch_write_relocations(kpmod); - if (ret) - goto out; - } + /* add to the global func hash */ + list_for_each_entry(func, &object->funcs, list) + hash_add_rcu(kpatch_func_hash, &func->node, func->old_addr); out: up(&kpatch_mutex); /* no way to stop the module load on error */ WARN(ret, "error (%d) patching newly loaded module '%s'\n", ret, - mod->name); + object->name); return 0; } int kpatch_register(struct kpatch_module *kpmod, bool replace) { int ret, i; - struct kpatch_func *funcs, *func; - int num_funcs = kpmod->patches_nr; + struct kpatch_object *object; + struct kpatch_func *func; - if (!kpmod->mod || !kpmod->patches || !num_funcs) + if (!kpmod->mod || list_empty(&kpmod->objects)) return -EINVAL; - funcs = kzalloc(sizeof(*funcs) * num_funcs, GFP_KERNEL); - if (!funcs) - return -ENOMEM; - down(&kpatch_mutex); kpmod->enabled = false; - kpmod->pending = 0; - kpmod->funcs = funcs; list_add_tail(&kpmod->list, &kpmod_list); if (!try_module_get(kpmod->mod)) { @@ -741,40 +747,24 @@ int kpatch_register(struct kpatch_module *kpmod, bool replace) goto err_up; } - for (i = 0; i < num_funcs; i++) { - func = &funcs[i]; - - func->op = KPATCH_OP_PATCH; - func->kpmod = kpmod; - func->patch = &kpmod->patches[i]; + list_for_each_entry(object, &kpmod->objects, list) { - /* calculate actual old location (refs if module) */ - kpatch_calculate_old_addr(func); + ret = kpatch_link_object(kpmod, object); + if (ret) + goto err_unlink; - /* check if patch depends on a future loaded module */ - if (!func->old_addr) { - kpmod->pending = 1; + if (!object->mod) { + pr_notice("delaying patch of unloaded module '%s'\n", + object->name); continue; } - ret = kpatch_verify_symbol_match(func->patch->name, - func->old_addr); - if (ret) { - num_funcs = i; - goto err_rollback; - } + pr_notice("patching module '%s\n", object->name); - ret = kpatch_ftrace_add_func(func->old_addr); - if (ret) { - num_funcs = i; - goto err_rollback; - } + list_for_each_entry(func, &object->funcs, list) + func->op = KPATCH_OP_PATCH; } - ret = kpatch_write_relocations(kpmod); - if (ret) - goto err_rollback; - if (replace) hash_for_each_rcu(kpatch_func_hash, i, func, node) func->op = KPATCH_OP_UNPATCH; @@ -836,8 +826,9 @@ int kpatch_register(struct kpatch_module *kpmod, bool replace) if (ret) goto err_ops; - for (i = 0; i < num_funcs; i++) - funcs[i].op = KPATCH_OP_NONE; + do_for_each_linked_func(kpmod, func) { + func->op = KPATCH_OP_NONE; + } while_for_each_linked_func(); /* TODO: need TAINT_KPATCH */ pr_notice_once("tainting kernel with TAINT_USER\n"); @@ -854,32 +845,32 @@ int kpatch_register(struct kpatch_module *kpmod, bool replace) if (replace) hash_for_each_rcu(kpatch_func_hash, i, func, node) func->op = KPATCH_OP_NONE; -err_rollback: - for (i = 0; i < num_funcs; i++) - kpatch_ftrace_remove_func(funcs[i].old_addr); - kpatch_put_modules(funcs, kpmod->patches_nr); +err_unlink: + list_for_each_entry(object, &kpmod->objects, list) + if (kpatch_object_linked(object)) + kpatch_unlink_object(object); module_put(kpmod->mod); err_up: list_del(&kpmod->list); up(&kpatch_mutex); - kfree(kpmod->funcs); return ret; } EXPORT_SYMBOL(kpatch_register); int kpatch_unregister(struct kpatch_module *kpmod) { - struct kpatch_func *funcs = kpmod->funcs; - int num_funcs = kpmod->patches_nr; - int i, ret; + struct kpatch_object *object; + struct kpatch_func *func; + int ret; if (!kpmod->enabled) return -EINVAL; down(&kpatch_mutex); - for (i = 0; i < num_funcs; i++) - funcs[i].op = KPATCH_OP_UNPATCH; + do_for_each_linked_func(kpmod, func) { + func->op = KPATCH_OP_UNPATCH; + } while_for_each_linked_func(); /* memory barrier between func hash and state write */ smp_wmb(); @@ -901,26 +892,20 @@ int kpatch_unregister(struct kpatch_module *kpmod) synchronize_rcu(); if (ret) { - for (i = 0; i < num_funcs; i++) - funcs[i].op = KPATCH_OP_NONE; + do_for_each_linked_func(kpmod, func) { + func->op = KPATCH_OP_NONE; + } while_for_each_linked_func(); goto out; } - for (i = 0; i < num_funcs; i++) { - if (!funcs[i].old_addr) + list_for_each_entry(object, &kpmod->objects, list) { + if (!kpatch_object_linked(object)) continue; - ret = kpatch_ftrace_remove_func(funcs[i].old_addr); - if (ret) { - WARN(1, "can't unregister ftrace for address 0x%lx\n", - funcs[i].old_addr); + ret = kpatch_unlink_object(object); + if (ret) goto out; - } } - kpatch_put_modules(funcs, num_funcs); - - kfree(kpmod->funcs); - pr_notice("unloaded patch module '%s'\n", kpmod->mod->name); kpmod->enabled = false; diff --git a/kmod/core/kpatch.h b/kmod/core/kpatch.h index 90d3aca2b..3f5a639a0 100644 --- a/kmod/core/kpatch.h +++ b/kmod/core/kpatch.h @@ -23,42 +23,60 @@ #ifndef _KPATCH_H_ #define _KPATCH_H_ -struct kpatch_patch { +#include +#include + +enum kpatch_op { + KPATCH_OP_NONE, + KPATCH_OP_PATCH, + KPATCH_OP_UNPATCH, +}; + +struct kpatch_func { + /* public */ unsigned long new_addr; unsigned long new_size; unsigned long old_offset; unsigned long old_size; - char *name; - char *objname; + const char *name; + struct list_head list; + + /* private */ + struct hlist_node node; + unsigned long old_addr; + enum kpatch_op op; }; struct kpatch_dynrela { unsigned long dest; unsigned long src; unsigned long type; - char *name; - char *objname; + const char *name; + const char *objname; int addend; + struct list_head list; }; -#ifdef __KERNEL__ +struct kpatch_object { + struct list_head list; + const char *name; + struct list_head funcs; + struct list_head dynrelas; -#include -#include + /* private */ + struct module *mod; +}; struct kpatch_module { /* public */ struct module *mod; - struct kpatch_patch *patches; - struct kpatch_dynrela *dynrelas; - int patches_nr; - int dynrelas_nr; + struct list_head objects; + + /* public read-only */ bool enabled; /* private */ struct list_head list; - struct kpatch_func *funcs; - bool pending; }; extern struct kobject *kpatch_patches_kobj; @@ -66,6 +84,4 @@ extern struct kobject *kpatch_patches_kobj; extern int kpatch_register(struct kpatch_module *kpmod, bool replace); extern int kpatch_unregister(struct kpatch_module *kpmod); -#endif /* __KERNEL__ */ - #endif /* _KPATCH_H_ */ diff --git a/kmod/patch/kpatch-patch-hook.c b/kmod/patch/kpatch-patch-hook.c index 8524685e6..60400d7af 100644 --- a/kmod/patch/kpatch-patch-hook.c +++ b/kmod/patch/kpatch-patch-hook.c @@ -24,13 +24,14 @@ #include #include #include "kpatch.h" +#include "kpatch-patch.h" static bool replace; module_param(replace, bool, S_IRUGO); MODULE_PARM_DESC(replace, "replace all previously loaded patch modules"); -extern char __kpatch_patches, __kpatch_patches_end; -extern char __kpatch_dynrelas, __kpatch_dynrelas_end; +extern struct kpatch_patch_func __kpatch_funcs[], __kpatch_funcs_end[]; +extern struct kpatch_patch_dynrela __kpatch_dynrelas[], __kpatch_dynrelas_end[]; static struct kpatch_module kpmod; static struct kobject *patch_kobj; @@ -38,11 +39,11 @@ static struct kobject *functions_kobj; struct kpatch_func_obj { struct kobject kobj; - struct kpatch_patch *patch; + struct kpatch_patch_func *func; char name[KSYM_NAME_LEN]; }; -static struct kpatch_func_obj **funcs = NULL; +static struct kpatch_func_obj **func_objs = NULL; static ssize_t patch_enabled_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) @@ -87,7 +88,7 @@ static ssize_t func_old_addr_show(struct kobject *kobj, struct kpatch_func_obj *func = container_of(kobj, struct kpatch_func_obj, kobj); - return sprintf(buf, "0x%lx\n", func->patch->old_offset); + return sprintf(buf, "0x%lx\n", func->func->old_offset); } static ssize_t func_new_addr_show(struct kobject *kobj, @@ -96,7 +97,7 @@ static ssize_t func_new_addr_show(struct kobject *kobj, struct kpatch_func_obj *func = container_of(kobj, struct kpatch_func_obj, kobj); - return sprintf(buf, "0x%lx\n", func->patch->new_addr); + return sprintf(buf, "0x%lx\n", func->func->new_addr); } static struct kobj_attribute old_addr_attr = @@ -149,37 +150,166 @@ static struct kpatch_func_obj *func_kobj_alloc(void) return func; } -static int __init patch_init(void) +static struct kpatch_object *patch_find_or_add_object(struct list_head *head, + const char *name) { + struct kpatch_object *object; + + list_for_each_entry(object, head, list) { + if (!strcmp(object->name, name)) + return object; + } + + object = kzalloc(sizeof(*object), GFP_KERNEL); + if (!object) + return NULL; + + object->name = name; + INIT_LIST_HEAD(&object->funcs); + INIT_LIST_HEAD(&object->dynrelas); + + list_add_tail(&object->list, head); + + return object; +} + +static void patch_free_objects(void) +{ + struct kpatch_object *object, *object_safe; + struct kpatch_func *func, *func_safe; + struct kpatch_dynrela *dynrela, *dynrela_safe; + int i; + + for (i = 0; i < __kpatch_funcs_end - __kpatch_funcs; i++) + if (func_objs[i]) + kobject_put(&func_objs[i]->kobj); + kfree(func_objs); + + list_for_each_entry_safe(object, object_safe, &kpmod.objects, list) { + list_for_each_entry_safe(func, func_safe, &object->funcs, + list) { + list_del(&func->list); + kfree(func); + } + list_for_each_entry_safe(dynrela, dynrela_safe, + &object->dynrelas, list) { + list_del(&dynrela->list); + kfree(dynrela); + } + list_del(&object->list); + kfree(object); + } + +} + +static int patch_make_funcs_list(struct list_head *objects) +{ + struct kpatch_object *object; + struct kpatch_patch_func *p_func; + struct kpatch_func *func; + struct kpatch_func_obj *func_obj; + int i = 0, funcs_nr, ret; + + funcs_nr = __kpatch_funcs_end - __kpatch_funcs; + func_objs = kzalloc(funcs_nr * sizeof(struct kpatch_func_obj*), + GFP_KERNEL); + if (!func_objs) + return -ENOMEM; + + for (p_func = __kpatch_funcs; p_func < __kpatch_funcs_end; p_func++) { + object = patch_find_or_add_object(&kpmod.objects, + p_func->objname); + if (!object) { + ret = -ENOMEM; + goto err; + } + + func = kzalloc(sizeof(*func), GFP_KERNEL); + if (!func) { + ret = -ENOMEM; + goto err; + } + + func->new_addr = p_func->new_addr; + func->new_size = p_func->new_size; + func->old_offset = p_func->old_offset; + func->old_size = p_func->old_size; + func->name = p_func->name; + list_add_tail(&func->list, &object->funcs); + + func_obj = func_kobj_alloc(); + if (!func_obj) { + ret = -ENOMEM; + goto err; + } + + func_obj->func = p_func; + func_objs[i++] = func_obj; + sprint_symbol_no_offset(func_obj->name, + p_func->old_offset); + + ret = kobject_add(&func_obj->kobj, functions_kobj, + "%s", func_obj->name); + if (ret) + goto err; + } + + return 0; + +err: + patch_free_objects(); + return ret; +} + +static int patch_make_dynrelas_list(struct list_head *objects) +{ + struct kpatch_object *object; + struct kpatch_patch_dynrela *p_dynrela; + struct kpatch_dynrela *dynrela; int ret; - int i = 0; - struct kpatch_func_obj *func = NULL; - kpmod.mod = THIS_MODULE; - kpmod.patches = (struct kpatch_patch *)&__kpatch_patches; - kpmod.patches_nr = (&__kpatch_patches_end - &__kpatch_patches) / - sizeof(*kpmod.patches); - kpmod.dynrelas = (struct kpatch_dynrela *)&__kpatch_dynrelas; - kpmod.dynrelas_nr = (&__kpatch_dynrelas_end - &__kpatch_dynrelas) / - sizeof(*kpmod.dynrelas); - - funcs = kzalloc(kpmod.patches_nr * sizeof(struct kpatch_func_obj*), - GFP_KERNEL); - if (!funcs) { - ret = -ENOMEM; - goto err_ret; + for (p_dynrela = __kpatch_dynrelas; p_dynrela < __kpatch_dynrelas_end; + p_dynrela++) { + object = patch_find_or_add_object(objects, p_dynrela->objname); + if (!object) { + ret = -ENOMEM; + goto err; + } + + dynrela = kzalloc(sizeof(*dynrela), GFP_KERNEL); + if (!dynrela) { + ret = -ENOMEM; + goto err; + } + + dynrela->dest = p_dynrela->dest; + dynrela->src = p_dynrela->src; + dynrela->type = p_dynrela->type; + dynrela->name = p_dynrela->name; + dynrela->objname = p_dynrela->objname; + dynrela->addend = p_dynrela->addend; + list_add_tail(&dynrela->list, &object->dynrelas); } + return 0; + +err: + patch_free_objects(); + return ret; +} + +static int __init patch_init(void) +{ + int ret; + patch_kobj = kobject_create_and_add(THIS_MODULE->name, kpatch_patches_kobj); - if (!patch_kobj) { - ret = -ENOMEM; - goto err_free; - } + if (!patch_kobj) + return -ENOMEM; ret = sysfs_create_file(patch_kobj, &patch_enabled_attr.attr); if (ret) - goto err_put; + goto err_patch; functions_kobj = kobject_create_and_add("functions", patch_kobj); if (!functions_kobj) { @@ -187,55 +317,39 @@ static int __init patch_init(void) goto err_sysfs; } - for (i = 0; i < kpmod.patches_nr; i++) { - func = func_kobj_alloc(); - if (!func) { - ret = -ENOMEM; - goto err_sysfs2; - } - funcs[i] = func; - - sprint_symbol_no_offset(func->name, kpmod.patches[i].old_offset); + kpmod.mod = THIS_MODULE; + INIT_LIST_HEAD(&kpmod.objects); - ret = kobject_add(&func->kobj, functions_kobj, - "%s", func->name); - if (ret) - goto err_sysfs2; + ret = patch_make_funcs_list(&kpmod.objects); + if (ret) + goto err_functions; - func->patch = &kpmod.patches[i]; - } + ret = patch_make_dynrelas_list(&kpmod.objects); + if (ret) + goto err_objects; ret = kpatch_register(&kpmod, replace); if (ret) - goto err_sysfs2; + goto err_objects; return 0; -err_sysfs2: - for (i = 0; i < kpmod.patches_nr; i++) { - if (funcs[i] != NULL) - kobject_put(&funcs[i]->kobj); - } +err_objects: + patch_free_objects(); +err_functions: kobject_put(functions_kobj); err_sysfs: sysfs_remove_file(patch_kobj, &patch_enabled_attr.attr); -err_put: +err_patch: kobject_put(patch_kobj); -err_free: - kfree(funcs); -err_ret: return ret; } static void __exit patch_exit(void) { - int i; WARN_ON(kpmod.enabled); - for (i = 0; i < kpmod.patches_nr; i++) { - kobject_put(&funcs[i]->kobj); - } - kfree(funcs); + patch_free_objects(); kobject_put(functions_kobj); sysfs_remove_file(patch_kobj, &patch_enabled_attr.attr); kobject_put(patch_kobj); diff --git a/kmod/patch/kpatch-patch.h b/kmod/patch/kpatch-patch.h new file mode 100644 index 000000000..0a73c1775 --- /dev/null +++ b/kmod/patch/kpatch-patch.h @@ -0,0 +1,38 @@ +/* + * kpatch-patch.h + * + * Copyright (C) 2014 Josh Poimboeuf + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * Contains the structs used for the patch module special sections + */ + +struct kpatch_patch_func { + unsigned long new_addr; + unsigned long new_size; + unsigned long old_offset; + unsigned long old_size; + char *name; + char *objname; +}; + +struct kpatch_patch_dynrela { + unsigned long dest; + unsigned long src; + unsigned long type; + char *name; + char *objname; + int addend; +}; diff --git a/kmod/patch/kpatch.lds b/kmod/patch/kpatch.lds index 7faef5da5..01c72ab1b 100644 --- a/kmod/patch/kpatch.lds +++ b/kmod/patch/kpatch.lds @@ -1,4 +1,4 @@ -__kpatch_patches = ADDR(.kpatch.patches); -__kpatch_patches_end = ADDR(.kpatch.patches) + SIZEOF(.kpatch.patches); +__kpatch_funcs = ADDR(.kpatch.funcs); +__kpatch_funcs_end = ADDR(.kpatch.funcs) + SIZEOF(.kpatch.funcs); __kpatch_dynrelas = ADDR(.kpatch.dynrelas); __kpatch_dynrelas_end = ADDR(.kpatch.dynrelas) + SIZEOF(.kpatch.dynrelas); diff --git a/kpatch-build/create-diff-object.c b/kpatch-build/create-diff-object.c index a63e3a15f..9db01f51b 100644 --- a/kpatch-build/create-diff-object.c +++ b/kpatch-build/create-diff-object.c @@ -49,7 +49,7 @@ #include "list.h" #include "lookup.h" #include "asm/insn.h" -#include "kpatch.h" +#include "kpatch-patch.h" #define ERROR(format, ...) \ error(1, 0, "%s: %d: " format, __FUNCTION__, __LINE__, ##__VA_ARGS__) @@ -1492,7 +1492,7 @@ void kpatch_create_patches_sections(struct kpatch_elf *kelf, struct symbol *sym, *strsym; struct rela *rela; struct lookup_result result; - struct kpatch_patch *patches; + struct kpatch_patch_func *funcs; /* count patched functions */ nr = 0; @@ -1500,36 +1500,36 @@ void kpatch_create_patches_sections(struct kpatch_elf *kelf, if (sym->type == STT_FUNC && sym->status == CHANGED) nr++; - /* create .kpatch.patches */ + /* create .kpatch.funcs */ /* allocate section resources */ ALLOC_LINK(sec, &kelf->sections); - size = nr * sizeof(*patches); - patches = malloc(size); - if (!patches) + size = nr * sizeof(*funcs); + funcs = malloc(size); + if (!funcs) ERROR("malloc"); - sec->name = ".kpatch.patches"; + sec->name = ".kpatch.funcs"; /* set data */ sec->data = malloc(sizeof(*sec->data)); if (!sec->data) ERROR("malloc"); - sec->data->d_buf = patches; + sec->data->d_buf = funcs; sec->data->d_size = size; sec->data->d_type = ELF_T_BYTE; /* set section header */ sec->sh.sh_type = SHT_PROGBITS; - sec->sh.sh_entsize = sizeof(*patches); + sec->sh.sh_entsize = sizeof(*funcs); sec->sh.sh_addralign = 8; sec->sh.sh_flags = SHF_ALLOC; sec->sh.sh_size = size; - /* create .rela.patches */ + /* create .rela.funcs */ /* allocate section resources */ ALLOC_LINK(relasec, &kelf->sections); - relasec->name = ".rela.kpatch.patches"; + relasec->name = ".rela.kpatch.funcs"; relasec->base = sec; INIT_LIST_HEAD(&relasec->relas); @@ -1570,42 +1570,42 @@ void kpatch_create_patches_sections(struct kpatch_elf *kelf, sym->name, result.value, result.size); /* add entry in text section */ - patches[index].old_offset = result.value; - patches[index].old_size = result.size; - patches[index].new_size = sym->sym.st_size; + funcs[index].old_offset = result.value; + funcs[index].old_size = result.size; + funcs[index].new_size = sym->sym.st_size; /* * Add a relocation that will populate - * the patches[index].new_addr field at + * the funcs[index].new_addr field at * module load time. */ ALLOC_LINK(rela, &relasec->relas); rela->sym = sym; rela->type = R_X86_64_64; rela->addend = 0; - rela->offset = index * sizeof(*patches); + rela->offset = index * sizeof(*funcs); /* * Add a relocation that will populate - * the patches[index].name field. + * the funcs[index].name field. */ ALLOC_LINK(rela, &relasec->relas); rela->sym = strsym; rela->type = R_X86_64_64; rela->addend = offset_of_string(&kelf->strings, sym->name); - rela->offset = index * sizeof(*patches) + - offsetof(struct kpatch_patch, name); + rela->offset = index * sizeof(*funcs) + + offsetof(struct kpatch_patch_func, name); /* * Add a relocation that will populate - * the patches[index].objname field. + * the funcs[index].objname field. */ ALLOC_LINK(rela, &relasec->relas); rela->sym = strsym; rela->type = R_X86_64_64; rela->addend = objname_offset; - rela->offset = index * sizeof(*patches) + - offsetof(struct kpatch_patch, objname); + rela->offset = index * sizeof(*funcs) + + offsetof(struct kpatch_patch_func,objname); index++; } @@ -1613,7 +1613,7 @@ void kpatch_create_patches_sections(struct kpatch_elf *kelf, /* sanity check, index should equal nr */ if (index != nr) - ERROR("size mismatch in patches sections"); + ERROR("size mismatch in funcs sections"); } @@ -1626,14 +1626,14 @@ void kpatch_create_dynamic_rela_sections(struct kpatch_elf *kelf, struct rela *rela, *dynrela, *safe; struct symbol *strsym; struct lookup_result result; - struct kpatch_dynrela *dynrelas; + struct kpatch_patch_dynrela *dynrelas; /* count rela entries that need to be dynamic */ nr = 0; list_for_each_entry(sec, &kelf->sections, list) { if (!is_rela_section(sec)) continue; - if (!strcmp(sec->name, ".rela.kpatch.patches")) + if (!strcmp(sec->name, ".rela.kpatch.funcs")) continue; list_for_each_entry(rela, &sec->relas, list) nr++; /* upper bound on number of dynrelas */ @@ -1757,7 +1757,7 @@ void kpatch_create_dynamic_rela_sections(struct kpatch_elf *kelf, dynrela->sym = strsym; dynrela->type = R_X86_64_64; dynrela->addend = offset_of_string(&kelf->strings, rela->sym->name); - dynrela->offset = index * sizeof(*dynrelas) + offsetof(struct kpatch_dynrela, name); + dynrela->offset = index * sizeof(*dynrelas) + offsetof(struct kpatch_patch_dynrela, name); /* add rela to fill in objname field */ ALLOC_LINK(dynrela, &relasec->relas); @@ -1765,7 +1765,7 @@ void kpatch_create_dynamic_rela_sections(struct kpatch_elf *kelf, dynrela->type = R_X86_64_64; dynrela->addend = objname_offset; dynrela->offset = index * sizeof(*dynrelas) + - offsetof(struct kpatch_dynrela, objname); + offsetof(struct kpatch_patch_dynrela, objname); list_del(&rela->list); free(rela); @@ -1776,7 +1776,7 @@ void kpatch_create_dynamic_rela_sections(struct kpatch_elf *kelf, } /* set size to actual number of dynrelas */ - sec->data->d_size = index * sizeof(struct kpatch_dynrela); + sec->data->d_size = index * sizeof(struct kpatch_patch_dynrela); sec->sh.sh_size = sec->data->d_size; }