add livepatch API support

Adds a new patch module scaffold for use when building against a kernel
with CONFIG_LIVE_PATCHING=y.

Signed-off-by: Seth Jennings <sjenning@redhat.com>
This commit is contained in:
Seth Jennings 2015-01-21 18:28:31 -06:00
parent 4c6e54b2d2
commit f1903de1eb
3 changed files with 352 additions and 3 deletions

View File

@ -4,15 +4,15 @@ KPATCH_MAKE = $(MAKE) -C $(KPATCH_BUILD) M=$(PWD)
obj-m += kpatch-$(KPATCH_NAME).o
kpatch-$(KPATCH_NAME)-objs += kpatch-patch-hook.o kpatch.lds output.o
kpatch-$(KPATCH_NAME)-objs += patch-hook.o kpatch.lds output.o
all: kpatch-$(KPATCH_NAME).ko
kpatch-$(KPATCH_NAME).ko:
$(KPATCH_MAKE) kpatch-$(KPATCH_NAME).ko
kpatch-patch-hook.o: kpatch-patch-hook.c
$(KPATCH_MAKE) kpatch-patch-hook.o
patch-hook.o: patch-hook.c
$(KPATCH_MAKE) patch-hook.o
clean:
$(RM) -Rf .*.o.cmd .*.ko.cmd .tmp_versions *.o *.ko *.mod.c \

View File

@ -0,0 +1,324 @@
/*
* Copyright (C) 2013-2014 Josh Poimboeuf <jpoimboe@redhat.com>
* Copyright (C) 2014 Seth Jennings <sjenning@redhat.com>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA,
* 02110-1301, USA.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/livepatch.h>
#include "kpatch-patch.h"
struct klp_patch *patch;
static LIST_HEAD(patch_objects);
static int patch_objects_nr;
struct patch_object {
struct list_head list;
struct list_head funcs;
struct list_head relocs;
const char *name;
int funcs_nr, relocs_nr;
};
struct patch_func {
struct list_head list;
struct kpatch_patch_func *kfunc;
};
struct patch_reloc {
struct list_head list;
struct kpatch_patch_dynrela *kdynrela;
};
static struct patch_object *patch_alloc_new_object(const char *name)
{
struct patch_object *object;
object = kzalloc(sizeof(*object), GFP_KERNEL);
if (!object)
return NULL;
INIT_LIST_HEAD(&object->funcs);
INIT_LIST_HEAD(&object->relocs);
if (strcmp(name, "vmlinux"))
object->name = name;
list_add(&object->list, &patch_objects);
patch_objects_nr++;
return object;
}
static struct patch_object *patch_find_object_by_name(const char *name)
{
struct patch_object *object;
list_for_each_entry(object, &patch_objects, list)
if ((!strcmp(name, "vmlinux") && !object->name) ||
!strcmp(object->name, name))
return object;
return patch_alloc_new_object(name);
}
static int patch_add_func_to_object(struct kpatch_patch_func *kfunc)
{
struct patch_func *func;
struct patch_object *object;
func = kzalloc(sizeof(*func), GFP_KERNEL);
if (!func)
return -ENOMEM;
INIT_LIST_HEAD(&func->list);
func->kfunc = kfunc;
object = patch_find_object_by_name(kfunc->objname);
if (!object)
return -ENOMEM;
list_add(&func->list, &object->funcs);
object->funcs_nr++;
return 0;
}
static int patch_add_reloc_to_object(struct kpatch_patch_dynrela *dynrela)
{
struct patch_reloc *reloc;
struct patch_object *object;
reloc = kzalloc(sizeof(*reloc), GFP_KERNEL);
if (!reloc)
return -ENOMEM;
INIT_LIST_HEAD(&reloc->list);
reloc->kdynrela = dynrela;
object = patch_find_object_by_name(dynrela->objname);
if (!object)
return -ENOMEM;
list_add(&reloc->list, &object->relocs);
object->relocs_nr++;
return 0;
}
static void patch_free_scaffold(void) {
struct patch_func *func, *safefunc;
struct patch_reloc *reloc, *safereloc;
struct patch_object *object, *safeobject;
list_for_each_entry_safe(object, safeobject, &patch_objects, list) {
list_for_each_entry_safe(func, safefunc,
&object->funcs, list) {
list_del(&func->list);
kfree(func);
}
list_for_each_entry_safe(reloc, safereloc,
&object->relocs, list) {
list_del(&reloc->list);
kfree(reloc);
}
list_del(&object->list);
kfree(object);
}
}
static void patch_free_livepatch(struct klp_patch *patch)
{
struct klp_object *object;
if (patch) {
object = patch->objs;
while (object && object->funcs) {
if (object->funcs)
kfree(object->funcs);
if (object->relocs)
kfree(object->relocs);
object++;
}
if (patch->objs)
kfree(patch->objs);
kfree(patch);
}
}
/* debug */
#if 0
static void print_object(struct klp_object *object)
{
pr_info("object '%s':\n", (object->name) ? object->name : "vmlinux");
}
static void print_func(struct klp_func *func)
{
pr_info(" func: '%s' @ 0x%016lx, new @ 0x%016lx\n",
func->old_name, func->old_addr,
(unsigned long)func->new_func);
}
static void print_reloc(struct klp_reloc *reloc)
{
pr_info(" reloc: sym '%s', dest 0x%016lx, src 0x%016lx\n",
reloc->name, reloc->loc, reloc->val);
}
static void print_livepatch(struct klp_patch *patch)
{
struct klp_object *object;
struct klp_func *func;
struct klp_reloc *reloc;
if (patch) {
object = patch->objs;
while (object && object->funcs) {
print_object(object);
func = object->funcs;
while (func && func->old_name) {
print_func(func);
func++;
}
reloc = object->relocs;
while (reloc && reloc->name) {
print_reloc(reloc);
reloc++;
}
object++;
}
}
}
#endif
extern struct kpatch_patch_func __kpatch_funcs[], __kpatch_funcs_end[];
extern struct kpatch_patch_dynrela __kpatch_dynrelas[], __kpatch_dynrelas_end[];
static int __init patch_init(void)
{
struct kpatch_patch_func *kfunc;
struct kpatch_patch_dynrela *dynrela;
struct klp_object *lobjects, *lobject;
struct klp_func *lfuncs, *lfunc;
struct klp_reloc *lrelocs, *lreloc;
struct patch_object *object;
struct patch_func *func;
struct patch_reloc *reloc;
int ret = 0, i, j;
/* organize functions and relocs by object in scaffold */
for (kfunc = __kpatch_funcs;
kfunc != __kpatch_funcs_end;
kfunc++) {
ret = patch_add_func_to_object(kfunc);
if (ret)
goto out;
}
for (dynrela = __kpatch_dynrelas;
dynrela != __kpatch_dynrelas_end;
dynrela++) {
ret = patch_add_reloc_to_object(dynrela);
if (ret)
goto out;
}
/* past this point, only possible return code is -ENOMEM */
ret = -ENOMEM;
/* allocate and fill livepatch structures */
patch = kzalloc(sizeof(*patch), GFP_KERNEL);
if (!patch)
goto out;
lobjects = kzalloc(sizeof(*lobjects) * (patch_objects_nr+1),
GFP_KERNEL);
if (!lobjects)
goto out;
patch->mod = THIS_MODULE;
patch->objs = lobjects;
i = 0;
list_for_each_entry(object, &patch_objects, list) {
lobject = &lobjects[i];
lobject->name = object->name;
lfuncs = kzalloc(sizeof(struct klp_func) *
(object->funcs_nr+1), GFP_KERNEL);
if (!lfuncs)
goto out;
lobject->funcs = lfuncs;
j = 0;
list_for_each_entry(func, &object->funcs, list) {
lfunc = &lfuncs[j];
lfunc->old_name = func->kfunc->name;
lfunc->new_func = (void *)func->kfunc->new_addr;
lfunc->old_addr = func->kfunc->old_addr;
j++;
}
lrelocs = kzalloc(sizeof(struct klp_reloc) *
(object->relocs_nr+1), GFP_KERNEL);
if (!lrelocs)
goto out;
lobject->relocs = lrelocs;
j = 0;
list_for_each_entry(reloc, &object->relocs, list) {
lreloc = &lrelocs[j];
lreloc->loc = reloc ->kdynrela->dest;
lreloc->val = reloc->kdynrela->src;
lreloc->type = reloc->kdynrela->type;
lreloc->name = reloc->kdynrela->name;
lreloc->addend = reloc->kdynrela->addend;
lreloc->external = reloc->kdynrela->external;
j++;
}
i++;
}
/*
* Once the patch structure that the live patching API expects
* has been built, we can release the scaffold structure.
*/
patch_free_scaffold();
ret = klp_register_patch(patch);
if (ret) {
patch_free_livepatch(patch);
return ret;
}
ret = klp_enable_patch(patch);
if (ret) {
WARN_ON(klp_unregister_patch(patch));
patch_free_livepatch(patch);
return ret;
}
return 0;
out:
patch_free_livepatch(patch);
patch_free_scaffold();
return ret;
}
static void __exit patch_exit(void)
{
WARN_ON(klp_disable_patch(patch));
WARN_ON(klp_unregister_patch(patch));
}
module_init(patch_init);
module_exit(patch_exit);
MODULE_LICENSE("GPL");

25
kmod/patch/patch-hook.c Normal file
View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2015 Seth Jennings <sjenning@redhat.com>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA,
* 02110-1301, USA.
*/
#if IS_ENABLED(CONFIG_LIVE_PATCHING)
#include "livepatch-patch-hook.c"
#else
#include "kpatch-patch-hook.c"
#endif