Re: [PATCHv7 2/3] kernel: add support for live patching
From: Balbir Singh
Date: Tue Dec 16 2014 - 13:46:24 EST
On Tue, Dec 16, 2014 at 11:28 PM, Seth Jennings <sjenning@xxxxxxxxxx> wrote:
> This commit introduces code for the live patching core. It implements
> an ftrace-based mechanism and kernel interface for doing live patching
> of kernel and kernel module functions.
>
> It represents the greatest common functionality set between kpatch and
> kgraft and can accept patches built using either method.
>
> This first version does not implement any consistency mechanism that
> ensures that old and new code do not run together. In practice, ~90% of
> CVEs are safe to apply in this way, since they simply add a conditional
> check. However, any function change that can not execute safely with
> the old version of the function can _not_ be safely applied in this
> version.
>
> Signed-off-by: Seth Jennings <sjenning@xxxxxxxxxx>
> Signed-off-by: Josh Poimboeuf <jpoimboe@xxxxxxxxxx>
snip
> diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
> index ce8dcdf..5c57181 100644
> --- a/arch/x86/Kconfig
> +++ b/arch/x86/Kconfig
> @@ -17,6 +17,7 @@ config X86_64
> depends on 64BIT
> select X86_DEV_DMA_OPS
> select ARCH_USE_CMPXCHG_LOCKREF
> + select ARCH_HAVE_LIVE_PATCHING
>
> ### Arch settings
> config X86
> @@ -1986,6 +1987,8 @@ config CMDLINE_OVERRIDE
> This is used to work around broken boot loaders. This should
> be set to 'N' under normal conditions.
>
> +source "kernel/livepatch/Kconfig"
> +
> endmenu
>
> config ARCH_ENABLE_MEMORY_HOTPLUG
> diff --git a/arch/x86/include/asm/livepatch.h b/arch/x86/include/asm/livepatch.h
> new file mode 100644
> index 0000000..c2ae592
> --- /dev/null
> +++ b/arch/x86/include/asm/livepatch.h
> @@ -0,0 +1,36 @@
> +/*
> + * livepatch.h - x86-specific Kernel Live Patching Core
> + *
> + * Copyright (C) 2014 Seth Jennings <sjenning@xxxxxxxxxx>
> + *
> + * 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 <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef _ASM_X86_LIVEPATCH_H
> +#define _ASM_X86_LIVEPATCH_H
> +
> +#include <linux/module.h>
> +
> +#ifdef CONFIG_LIVE_PATCHING
> +#ifndef CC_USING_FENTRY
> +#error Your compiler must support -mfentry for live patching to work
> +#endif
> +extern int klp_write_module_reloc(struct module *mod, unsigned long type,
> + unsigned long loc, unsigned long value);
> +
> +#else
> +#error Live patching support is disabled; check CONFIG_LIVE_PATCHING
> +#endif
> +
> +#endif /* _ASM_X86_LIVEPATCH_H */
> diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile
> index 5d4502c..316b34e 100644
> --- a/arch/x86/kernel/Makefile
> +++ b/arch/x86/kernel/Makefile
> @@ -63,6 +63,7 @@ obj-$(CONFIG_X86_MPPARSE) += mpparse.o
> obj-y += apic/
> obj-$(CONFIG_X86_REBOOTFIXUPS) += reboot_fixups_32.o
> obj-$(CONFIG_DYNAMIC_FTRACE) += ftrace.o
> +obj-$(CONFIG_LIVE_PATCHING) += livepatch.o
> obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o
> obj-$(CONFIG_FTRACE_SYSCALLS) += ftrace.o
> obj-$(CONFIG_X86_TSC) += trace_clock.o
> diff --git a/arch/x86/kernel/livepatch.c b/arch/x86/kernel/livepatch.c
> new file mode 100644
> index 0000000..4b0ed7b
> --- /dev/null
> +++ b/arch/x86/kernel/livepatch.c
> @@ -0,0 +1,89 @@
> +/*
> + * livepatch.c - x86-specific Kernel Live Patching Core
> + *
> + * Copyright (C) 2014 Seth Jennings <sjenning@xxxxxxxxxx>
> + *
> + * 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 <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/uaccess.h>
> +#include <asm/cacheflush.h>
> +#include <asm/page_types.h>
> +#include <asm/elf.h>
> +#include <asm/livepatch.h>
> +
> +/**
> + * klp_write_module_reloc() - write a relocation in a module
> + * @mod: module in which the section to be modified is found
> + * @type: ELF relocation type (see asm/elf.h)
> + * @loc: address that the relocation should be written to
> + * @value: relocation value (sym address + addend)
> + *
> + * This function writes a relocation to the specified location for
> + * a particular module.
This is more of the what then why?
> + */
> +int klp_write_module_reloc(struct module *mod, unsigned long type,
> + unsigned long loc, unsigned long value)
> +{
> + int ret, numpages, size = 4;
> + bool readonly;
> + unsigned long val;
> + unsigned long core = (unsigned long)mod->module_core;
> + unsigned long core_ro_size = mod->core_ro_size;
> + unsigned long core_size = mod->core_size;
> +
> + switch (type) {
> + case R_X86_64_NONE:
> + return 0;
> + case R_X86_64_64:
> + val = value;
> + size = 8;
> + break;
> + case R_X86_64_32:
> + val = (u32)value;
> + break;
> + case R_X86_64_32S:
> + val = (s32)value;
> + break;
> + case R_X86_64_PC32:
> + val = (u32)(value - loc);
> + break;
> + default:
> + /* unsupported relocation type */
> + return -EINVAL;
> + }
> +
> + if (loc < core || loc >= core + core_size)
> + /* loc does not point to any symbol inside the module */
> + return -EINVAL;
> +
> + if (loc < core + core_ro_size)
> + readonly = true;
> + else
> + readonly = false;
> +
> + /* determine if the relocation spans a page boundary */
> + numpages = ((loc & PAGE_MASK) == ((loc + size) & PAGE_MASK)) ? 1 : 2;
> +
> + if (readonly)
> + set_memory_rw(loc & PAGE_MASK, numpages);
> +
> + ret = probe_kernel_write((void *)loc, &val, size);
> +
> + if (readonly)
> + set_memory_ro(loc & PAGE_MASK, numpages);
> +
> + return ret;
> +}
> diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h
> new file mode 100644
> index 0000000..b61fe74
> --- /dev/null
> +++ b/include/linux/livepatch.h
> @@ -0,0 +1,132 @@
> +/*
> + * livepatch.h - Kernel Live Patching Core
> + *
> + * Copyright (C) 2014 Seth Jennings <sjenning@xxxxxxxxxx>
> + *
> + * 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 <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef _LINUX_LIVEPATCH_H_
> +#define _LINUX_LIVEPATCH_H_
> +
> +#include <linux/module.h>
> +#include <linux/ftrace.h>
> +
> +#if IS_ENABLED(CONFIG_LIVE_PATCHING)
> +
> +#include <asm/livepatch.h>
> +
> +enum klp_state {
> + KLP_DISABLED,
> + KLP_ENABLED
> +};
> +
> +/**
> + * struct klp_func - function structure for live patching
> + * @old_name: name of the function to be patched
> + * @new_func: pointer to the patched function code
> + * @old_addr: a hint conveying at what address the old function
> + * can be found (optional, vmlinux patches only)
> + * @kobj: kobject for sysfs resources
> + * @fops: ftrace operations structure
> + * @state: tracks function-level patch application state
> + */
> +struct klp_func {
> + /* external */
> + const char *old_name;
> + void *new_func;
> + /*
> + * The old_addr field is optional and can be used to resolve
> + * duplicate symbol names in the vmlinux object. If this
> + * information is not present, the symbol is located by name
> + * with kallsyms. If the name is not unique and old_addr is
> + * not provided, the patch application fails as there is no
> + * way to resolve the ambiguity.
> + */
> + unsigned long old_addr;
> +
> + /* internal */
> + struct kobject kobj;
> + struct ftrace_ops *fops;
> + enum klp_state state;
> +};
> +
> +/**
> + * struct klp_reloc - relocation structure for live patching
> + * @loc: address where the relocation will be written
> + * @val: address of the referenced symbol (optional,
> + * vmlinux patches only)
> + * @type: ELF relocation type
> + * @name: name of the referenced symbol (for lookup/verification)
> + * @addend: offset from the referenced symbol
> + * @external: symbol is either exported or within the live patch module itself
> + */
> +struct klp_reloc {
> + unsigned long loc;
> + unsigned long val;
> + unsigned long type;
> + const char *name;
> + int addend;
> + int external;
> +};
> +
> +/**
> + * struct klp_object - kernel object structure for live patching
> + * @name: module name (or NULL for vmlinux)
> + * @relocs: relocation entries to be applied at load time
> + * @funcs: function entries for functions to be patched in the object
> + * @kobj: kobject for sysfs resources
> + * @mod: kernel module associated with the patched object
> + * (NULL for vmlinux)
> + * @state: tracks object-level patch application state
> + */
> +struct klp_object {
> + /* external */
> + const char *name;
> + struct klp_reloc *relocs;
> + struct klp_func *funcs;
> +
> + /* internal */
> + struct kobject *kobj;
> + struct module *mod;
> + enum klp_state state;
> +};
> +
> +/**
> + * struct klp_patch - patch structure for live patching
> + * @mod: reference to the live patch module
> + * @objs: object entries for kernel objects to be patched
> + * @list: list node for global list of registered patches
> + * @kobj: kobject for sysfs resources
> + * @state: tracks patch-level application state
> + */
> +struct klp_patch {
> + /* external */
> + struct module *mod;
> + struct klp_object *objs;
> +
> + /* internal */
> + struct list_head list;
> + struct kobject kobj;
> + enum klp_state state;
> +};
> +
> +extern int klp_register_patch(struct klp_patch *);
> +extern int klp_unregister_patch(struct klp_patch *);
> +extern int klp_enable_patch(struct klp_patch *);
> +extern int klp_disable_patch(struct klp_patch *);
> +
> +#endif /* CONFIG_LIVE_PATCHING */
> +
> +#endif /* _LINUX_LIVEPATCH_H_ */
> diff --git a/kernel/Makefile b/kernel/Makefile
> index a59481a..616994f 100644
> --- a/kernel/Makefile
> +++ b/kernel/Makefile
> @@ -26,6 +26,7 @@ obj-y += power/
> obj-y += printk/
> obj-y += irq/
> obj-y += rcu/
> +obj-y += livepatch/
>
> obj-$(CONFIG_CHECKPOINT_RESTORE) += kcmp.o
> obj-$(CONFIG_FREEZER) += freezer.o
> diff --git a/kernel/livepatch/Kconfig b/kernel/livepatch/Kconfig
> new file mode 100644
> index 0000000..96da00f
> --- /dev/null
> +++ b/kernel/livepatch/Kconfig
> @@ -0,0 +1,18 @@
> +config ARCH_HAVE_LIVE_PATCHING
> + boolean
> + help
> + Arch supports kernel live patching
> +
> +config LIVE_PATCHING
> + boolean "Kernel Live Patching"
> + depends on DYNAMIC_FTRACE_WITH_REGS
> + depends on MODULES
> + depends on SYSFS
> + depends on KALLSYMS_ALL
> + depends on ARCH_HAVE_LIVE_PATCHING
> + help
> + Say Y here if you want to support kernel live patching.
> + This option has no runtime impact until a kernel "patch"
> + module uses the interface provided by this option to register
> + a patch, causing calls to patched functions to be redirected
> + to new function code contained in the patch module.
> diff --git a/kernel/livepatch/Makefile b/kernel/livepatch/Makefile
> new file mode 100644
> index 0000000..7c1f008
> --- /dev/null
> +++ b/kernel/livepatch/Makefile
> @@ -0,0 +1,3 @@
> +obj-$(CONFIG_LIVE_PATCHING) += livepatch.o
> +
> +livepatch-objs := core.o
> diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
> new file mode 100644
> index 0000000..0004a71
> --- /dev/null
> +++ b/kernel/livepatch/core.c
> @@ -0,0 +1,929 @@
> +/*
> + * core.c - Kernel Live Patching Core
> + *
> + * Copyright (C) 2014 Seth Jennings <sjenning@xxxxxxxxxx>
> + *
> + * 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 <http://www.gnu.org/licenses/>.
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/mutex.h>
> +#include <linux/slab.h>
> +#include <linux/ftrace.h>
> +#include <linux/list.h>
> +#include <linux/kallsyms.h>
> +#include <linux/livepatch.h>
> +
> +/*
> + * The klp_mutex protects the klp_patches list and state transitions of any
> + * structure reachable from the patches list. References to any structure must
> + * be obtained under mutex protection.
> + */
> +
> +static DEFINE_MUTEX(klp_mutex);
> +static LIST_HEAD(klp_patches);
> +
> +static struct kobject *klp_root_kobj;
> +
> +static bool klp_is_module(struct klp_object *obj)
> +{
> + return obj->name;
> +}
> +
> +static bool klp_is_object_loaded(struct klp_object *obj)
> +{
> + return !obj->name || obj->mod;
> +}
> +
> +/* sets obj->mod if object is not vmlinux and module is found */
> +static void klp_find_object_module(struct klp_object *obj)
> +{
> + if (!klp_is_module(obj))
> + return;
> +
> + mutex_lock(&module_mutex);
> + /*
> + * We don't need to take a reference on the module here because we have
> + * the klp_mutex, which is also taken by the module notifier. This
> + * prevents any module from unloading until we release the klp_mutex.
> + */
> + obj->mod = find_module(obj->name);
> + mutex_unlock(&module_mutex);
> +}
> +
> +/* klp_mutex must be held by caller */
> +static bool klp_is_patch_registered(struct klp_patch *patch)
> +{
> + struct klp_patch *mypatch;
> +
> + list_for_each_entry(mypatch, &klp_patches, list)
> + if (mypatch == patch)
> + return true;
> +
> + return false;
> +}
> +
> +static bool klp_initialized(void)
> +{
> + return klp_root_kobj;
> +}
> +
> +struct klp_find_arg {
> + const char *objname;
> + const char *name;
> + unsigned long addr;
> + /*
> + * If count == 0, the symbol was not found. If count == 1, a unique
> + * match was found and addr is set. If count > 1, there is
> + * unresolvable ambiguity among "count" number of symbols with the same
> + * name in the same object.
> + */
> + unsigned long count;
> +};
> +
> +static int klp_find_callback(void *data, const char *name,
> + struct module *mod, unsigned long addr)
> +{
> + struct klp_find_arg *args = data;
> +
> + if ((mod && !args->objname) || (!mod && args->objname))
> + return 0;
> +
> + if (strcmp(args->name, name))
> + return 0;
> +
> + if (args->objname && strcmp(args->objname, mod->name))
> + return 0;
> +
> + /*
> + * args->addr might be overwritten if another match is found
> + * but klp_find_object_symbol() handles this and only returns the
> + * addr if count == 1.
> + */
> + args->addr = addr;
> + args->count++;
> +
> + return 0;
> +}
> +
> +static int klp_find_object_symbol(const char *objname, const char *name,
> + unsigned long *addr)
> +{
> + struct klp_find_arg args = {
> + .objname = objname,
> + .name = name,
> + .addr = 0,
> + .count = 0
> + };
> +
> + kallsyms_on_each_symbol(klp_find_callback, &args);
> +
> + if (args.count == 0)
> + pr_err("symbol '%s' not found in symbol table\n", name);
> + else if (args.count > 1)
> + pr_err("unresolvable ambiguity (%lu matches) on symbol '%s' in object '%s'\n",
> + args.count, name, objname);
> + else {
> + *addr = args.addr;
> + return 0;
> + }
> +
> + *addr = 0;
> + return -EINVAL;
> +}
> +
> +struct klp_verify_args {
> + const char *name;
> + const unsigned long addr;
> +};
> +
> +static int klp_verify_callback(void *data, const char *name,
> + struct module *mod, unsigned long addr)
> +{
> + struct klp_verify_args *args = data;
> +
> + if (!mod &&
> + !strcmp(args->name, name) &&
> + args->addr == addr)
> + return 1;
> +
> + return 0;
> +}
> +
> +static int klp_verify_vmlinux_symbol(const char *name, unsigned long addr)
> +{
> + struct klp_verify_args args = {
> + .name = name,
> + .addr = addr,
> + };
> +
> + if (kallsyms_on_each_symbol(klp_verify_callback, &args))
> + return 0;
> +
> + pr_err("symbol '%s' not found at specified address 0x%016lx, kernel mismatch?",
> + name, addr);
> + return -EINVAL;
> +}
> +
> +static int klp_find_verify_func_addr(struct klp_object *obj,
> + struct klp_func *func)
> +{
> + int ret;
> +
> +#if defined(CONFIG_RANDOMIZE_BASE)
> + /* KASLR is enabled, disregard old_addr from user */
> + func->old_addr = 0;
> +#endif
> +
> + if (!func->old_addr || klp_is_module(obj))
> + ret = klp_find_object_symbol(obj->name, func->old_name,
> + &func->old_addr);
> + else
> + ret = klp_verify_vmlinux_symbol(func->old_name,
> + func->old_addr);
> +
> + return ret;
> +}
> +
> +/*
> + * external symbols are located outside the parent object (where the parent
> + * object is either vmlinux or the kmod being patched).
> + */
> +static int klp_find_external_symbol(struct module *pmod, const char *name,
> + unsigned long *addr)
> +{
> + const struct kernel_symbol *sym;
> +
> + /* first, check if it's an exported symbol */
> + preempt_disable();
> + sym = find_symbol(name, NULL, NULL, true, true);
> + preempt_enable();
> + if (sym) {
> + *addr = sym->value;
> + return 0;
> + }
> +
> + /* otherwise check if it's in another .o within the patch module */
> + return klp_find_object_symbol(pmod->name, name, addr);
> +}
> +
Shouldn't the code above be asbtracted out for anyone searching for
kernel symbols? Its only KLP at the moment, but I am sure these are
reusable bits
> +static int klp_write_object_relocations(struct module *pmod,
> + struct klp_object *obj)
> +{
> + int ret;
> + struct klp_reloc *reloc;
> +
> + if (WARN_ON(!klp_is_object_loaded(obj)))
> + return -EINVAL;
> +
> + if (WARN_ON(!obj->relocs))
> + return -EINVAL;
> +
> + for (reloc = obj->relocs; reloc->name; reloc++) {
> + if (!klp_is_module(obj)) {
> + ret = klp_verify_vmlinux_symbol(reloc->name,
> + reloc->val);
> + if (ret)
> + return ret;
> + } else {
> + /* module, reloc->val needs to be discovered */
> + if (reloc->external)
> + ret = klp_find_external_symbol(pmod,
> + reloc->name,
> + &reloc->val);
> + else
> + ret = klp_find_object_symbol(obj->mod->name,
> + reloc->name,
> + &reloc->val);
> + if (ret)
> + return ret;
> + }
> + ret = klp_write_module_reloc(pmod, reloc->type, reloc->loc,
> + reloc->val + reloc->addend);
An iterative loop can be expensive -- state transitions from page
rw/ro frequently! Can we optimize?
> + if (ret) {
> + pr_err("relocation failed for symbol '%s' at 0x%016lx (%d)\n",
> + reloc->name, reloc->val, ret);
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static void notrace klp_ftrace_handler(unsigned long ip,
> + unsigned long parent_ip,
> + struct ftrace_ops *ops,
> + struct pt_regs *regs)
> +{
> + struct klp_func *func = ops->private;
> +
> + regs->ip = (unsigned long)func->new_func;
> +}
> +
> +static int klp_disable_func(struct klp_func *func)
> +{
> + int ret;
> +
> + if (WARN_ON(func->state != KLP_ENABLED))
> + return -EINVAL;
> +
> + if (WARN_ON(!func->old_addr))
> + return -EINVAL;
> +
> + ret = unregister_ftrace_function(func->fops);
> + if (ret) {
> + pr_err("failed to unregister ftrace handler for function '%s' (%d)\n",
> + func->old_name, ret);
> + return ret;
> + }
> +
> + ret = ftrace_set_filter_ip(func->fops, func->old_addr, 1, 0);
> + if (ret)
> + pr_warn("function unregister succeeded but failed to clear the filter\n");
> +
> + func->state = KLP_DISABLED;
> +
> + return 0;
> +}
> +
> +static int klp_enable_func(struct klp_func *func)
> +{
> + int ret;
> +
> + if (WARN_ON(!func->old_addr))
> + return -EINVAL;
> +
> + if (WARN_ON(func->state != KLP_DISABLED))
> + return -EINVAL;
> +
> + ret = ftrace_set_filter_ip(func->fops, func->old_addr, 0, 0);
> + if (ret) {
> + pr_err("failed to set ftrace filter for function '%s' (%d)\n",
> + func->old_name, ret);
> + return ret;
> + }
> +
> + ret = register_ftrace_function(func->fops);
> + if (ret) {
> + pr_err("failed to register ftrace handler for function '%s' (%d)\n",
> + func->old_name, ret);
> + ftrace_set_filter_ip(func->fops, func->old_addr, 1, 0);
> + } else {
> + func->state = KLP_ENABLED;
> + }
> +
> + return ret;
> +}
> +
> +static int klp_disable_object(struct klp_object *obj)
> +{
> + struct klp_func *func;
> + int ret;
> +
> + for (func = obj->funcs; func->old_name; func++) {
> + if (func->state != KLP_ENABLED)
> + continue;
> +
> + ret = klp_disable_func(func);
> + if (ret)
> + return ret;
> + }
> +
> + obj->state = KLP_DISABLED;
> +
> + return 0;
> +}
> +
> +static int klp_enable_object(struct klp_object *obj)
> +{
> + struct klp_func *func;
> + int ret;
> +
> + if (WARN_ON(obj->state != KLP_DISABLED))
> + return -EINVAL;
> +
> + if (WARN_ON(!klp_is_object_loaded(obj)))
> + return -EINVAL;
> +
> + for (func = obj->funcs; func->old_name; func++) {
> + ret = klp_enable_func(func);
> + if (ret)
> + goto unregister;
> + }
> + obj->state = KLP_ENABLED;
> +
> + return 0;
> +
> +unregister:
> + WARN_ON(klp_disable_object(obj));
> + return ret;
> +}
> +
> +static int __klp_disable_patch(struct klp_patch *patch)
> +{
> + struct klp_object *obj;
> + int ret;
> +
> + pr_notice("disabling patch '%s'\n", patch->mod->name);
> +
> + for (obj = patch->objs; obj->funcs; obj++) {
> + if (obj->state != KLP_ENABLED)
> + continue;
> +
> + ret = klp_disable_object(obj);
> + if (ret)
> + return ret;
> + }
> +
> + patch->state = KLP_DISABLED;
> +
> + return 0;
> +}
> +
> +/**
> + * klp_disable_patch() - disables a registered patch
> + * @patch: The registered, enabled patch to be disabled
> + *
> + * Unregisters the patched functions from ftrace.
> + *
> + * Return: 0 on success, otherwise error
> + */
> +int klp_disable_patch(struct klp_patch *patch)
> +{
> + int ret;
> +
> + mutex_lock(&klp_mutex);
> +
> + if (!klp_is_patch_registered(patch)) {
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + if (patch->state == KLP_DISABLED) {
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + ret = __klp_disable_patch(patch);
> +
> +err:
> + mutex_unlock(&klp_mutex);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(klp_disable_patch);
> +
> +static int __klp_enable_patch(struct klp_patch *patch)
> +{
> + struct klp_object *obj;
> + int ret;
> +
> + if (WARN_ON(patch->state != KLP_DISABLED))
> + return -EINVAL;
> +
> + pr_notice_once("tainting kernel with TAINT_LIVEPATCH\n");
> + add_taint(TAINT_LIVEPATCH, LOCKDEP_STILL_OK);
> +
> + pr_notice("enabling patch '%s'\n", patch->mod->name);
> +
> + for (obj = patch->objs; obj->funcs; obj++) {
> + klp_find_object_module(obj);
> +
> + if (!klp_is_object_loaded(obj))
> + continue;
> +
> + ret = klp_enable_object(obj);
> + if (ret)
> + goto unregister;
> + }
> +
> + patch->state = KLP_ENABLED;
> +
> + return 0;
> +
> +unregister:
> + WARN_ON(__klp_disable_patch(patch));
> + return ret;
> +}
> +
> +/**
> + * klp_enable_patch() - enables a registered patch
> + * @patch: The registered, disabled patch to be enabled
> + *
> + * Performs the needed symbol lookups and code relocations,
> + * then registers the patched functions with ftrace.
> + *
> + * Return: 0 on success, otherwise error
> + */
> +int klp_enable_patch(struct klp_patch *patch)
> +{
> + int ret;
> +
> + mutex_lock(&klp_mutex);
> +
> + if (!klp_is_patch_registered(patch)) {
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + ret = __klp_enable_patch(patch);
> +
> +err:
> + mutex_unlock(&klp_mutex);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(klp_enable_patch);
> +
> +/*
> + * Sysfs Interface
> + *
> + * /sys/kernel/livepatch
> + * /sys/kernel/livepatch/<patch>
> + * /sys/kernel/livepatch/<patch>/enabled
> + * /sys/kernel/livepatch/<patch>/<object>
> + * /sys/kernel/livepatch/<patch>/<object>/<func>
> + */
> +
> +static ssize_t enabled_store(struct kobject *kobj, struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct klp_patch *patch;
> + int ret;
> + unsigned long val;
> +
> + ret = kstrtoul(buf, 10, &val);
> + if (ret)
> + return -EINVAL;
> +
> + if (val != KLP_DISABLED && val != KLP_ENABLED)
> + return -EINVAL;
> +
> + patch = container_of(kobj, struct klp_patch, kobj);
> +
> + mutex_lock(&klp_mutex);
> +
> + if (val == patch->state) {
> + /* already in requested state */
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + if (val == KLP_ENABLED) {
> + ret = __klp_enable_patch(patch);
> + if (ret)
> + goto err;
> + } else {
> + ret = __klp_disable_patch(patch);
> + if (ret)
> + goto err;
> + }
> +
> + mutex_unlock(&klp_mutex);
> +
> + return count;
> +
> +err:
> + mutex_unlock(&klp_mutex);
> + return ret;
> +}
> +
> +static ssize_t enabled_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct klp_patch *patch;
> +
> + patch = container_of(kobj, struct klp_patch, kobj);
> + return snprintf(buf, PAGE_SIZE-1, "%d\n", patch->state);
> +}
> +
> +static struct kobj_attribute enabled_kobj_attr = __ATTR_RW(enabled);
> +static struct attribute *klp_patch_attrs[] = {
> + &enabled_kobj_attr.attr,
> + NULL
> +};
> +
> +static void klp_kobj_release_patch(struct kobject *kobj)
> +{
> + /*
> + * Once we have a consistency model we'll need to module_put() the
> + * patch module here. See klp_register_patch() for more details.
> + */
> +}
> +
> +static struct kobj_type klp_ktype_patch = {
> + .release = klp_kobj_release_patch,
> + .sysfs_ops = &kobj_sysfs_ops,
> + .default_attrs = klp_patch_attrs,
> +};
> +
> +static void klp_kobj_release_func(struct kobject *kobj)
> +{
> + struct klp_func *func;
> +
> + func = container_of(kobj, struct klp_func, kobj);
> + kfree(func->fops);
> +}
> +
> +static struct kobj_type klp_ktype_func = {
> + .release = klp_kobj_release_func,
> + .sysfs_ops = &kobj_sysfs_ops,
> +};
> +
> +/*
> + * Free all functions' kobjects in the array up to some limit. When limit is
> + * NULL, all kobjects are freed.
> + */
> +static void klp_free_funcs_limited(struct klp_object *obj,
> + struct klp_func *limit)
> +{
> + struct klp_func *func;
> +
> + for (func = obj->funcs; func->old_name && func != limit; func++)
> + kobject_put(&func->kobj);
> +}
> +
> +/* Clean up when a patched object is unloaded */
> +static void klp_free_object_loaded(struct klp_object *obj)
> +{
> + struct klp_func *func;
> +
> + obj->mod = NULL;
> +
> + for (func = obj->funcs; func->old_name; func++)
> + func->old_addr = 0;
> +}
> +
> +/*
> + * Free all objects' kobjects in the array up to some limit. When limit is
> + * NULL, all kobjects are freed.
> + */
> +static void klp_free_objects_limited(struct klp_patch *patch,
> + struct klp_object *limit)
> +{
> + struct klp_object *obj;
> +
> + for (obj = patch->objs; obj->funcs && obj != limit; obj++) {
> + klp_free_funcs_limited(obj, NULL);
> + kobject_put(obj->kobj);
> + }
> +}
> +
> +static void klp_free_patch(struct klp_patch *patch)
> +{
> + klp_free_objects_limited(patch, NULL);
> + if (!list_empty(&patch->list))
> + list_del(&patch->list);
> + kobject_put(&patch->kobj);
> +}
> +
> +static int klp_init_func(struct klp_object *obj, struct klp_func *func)
> +{
> + struct ftrace_ops *ops;
> + int ret;
> +
> + ops = kzalloc(sizeof(*ops), GFP_KERNEL);
> + if (!ops)
> + return -ENOMEM;
> +
> + ops->private = func;
> + ops->func = klp_ftrace_handler;
> + ops->flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_DYNAMIC;
> + func->fops = ops;
> + func->state = KLP_DISABLED;
> +
> + ret = kobject_init_and_add(&func->kobj, &klp_ktype_func,
> + obj->kobj, func->old_name);
> + if (ret) {
> + kfree(func->fops);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/* parts of the initialization that is done only when the object is loaded */
> +static int klp_init_object_loaded(struct klp_patch *patch,
> + struct klp_object *obj)
> +{
> + struct klp_func *func;
> + int ret;
> +
> + if (obj->relocs) {
> + ret = klp_write_object_relocations(patch->mod, obj);
> + if (ret)
> + return ret;
> + }
> +
> + for (func = obj->funcs; func->old_name; func++) {
> + ret = klp_find_verify_func_addr(obj, func);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int klp_init_object(struct klp_patch *patch, struct klp_object *obj)
> +{
> + struct klp_func *func;
> + int ret;
> + const char *name;
> +
> + if (!obj->funcs)
> + return -EINVAL;
> +
> + obj->state = KLP_DISABLED;
> +
> + klp_find_object_module(obj);
> +
> + name = klp_is_module(obj) ? obj->name : "vmlinux";
> + obj->kobj = kobject_create_and_add(name, &patch->kobj);
> + if (!obj->kobj)
> + return -ENOMEM;
> +
> + for (func = obj->funcs; func->old_name; func++) {
> + ret = klp_init_func(obj, func);
> + if (ret)
> + goto free;
> + }
> +
> + if (klp_is_object_loaded(obj)) {
> + ret = klp_init_object_loaded(patch, obj);
> + if (ret)
> + goto free;
> + }
> +
> + return 0;
> +
> +free:
> + klp_free_funcs_limited(obj, func);
> + kobject_put(obj->kobj);
> + return ret;
> +}
> +
> +static int klp_init_patch(struct klp_patch *patch)
> +{
> + struct klp_object *obj;
> + int ret;
> +
> + if (!patch->objs)
> + return -EINVAL;
> +
> + mutex_lock(&klp_mutex);
> +
> + patch->state = KLP_DISABLED;
> +
> + ret = kobject_init_and_add(&patch->kobj, &klp_ktype_patch,
> + klp_root_kobj, patch->mod->name);
> + if (ret)
> + goto unlock;
> +
> + for (obj = patch->objs; obj->funcs; obj++) {
> + ret = klp_init_object(patch, obj);
> + if (ret)
> + goto free;
> + }
> +
> + list_add(&patch->list, &klp_patches);
> +
> + mutex_unlock(&klp_mutex);
> +
> + return 0;
> +
> +free:
> + klp_free_objects_limited(patch, obj);
> + kobject_put(&patch->kobj);
> +unlock:
> + mutex_unlock(&klp_mutex);
> + return ret;
> +}
> +
> +/**
> + * klp_unregister_patch() - unregisters a patch
> + * @patch: Disabled patch to be unregistered
> + *
> + * Frees the data structures and removes the sysfs interface.
> + *
> + * Return: 0 on success, otherwise error
> + */
> +int klp_unregister_patch(struct klp_patch *patch)
> +{
> + int ret = 0;
> +
> + mutex_lock(&klp_mutex);
> +
> + if (!klp_is_patch_registered(patch)) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + if (patch->state == KLP_ENABLED) {
> + ret = -EBUSY;
> + goto out;
> + }
> +
> + klp_free_patch(patch);
> +
> +out:
> + mutex_unlock(&klp_mutex);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(klp_unregister_patch);
> +
> +/**
> + * klp_register_patch() - registers a patch
> + * @patch: Patch to be registered
> + *
> + * Initializes the data structure associated with the patch and
> + * creates the sysfs interface.
> + *
> + * Return: 0 on success, otherwise error
> + */
> +int klp_register_patch(struct klp_patch *patch)
> +{
> + int ret;
> +
> + if (!klp_initialized())
> + return -ENODEV;
> +
> + if (!patch || !patch->mod)
> + return -EINVAL;
> +
> + /*
> + * A reference is taken on the patch module to prevent it from being
> + * unloaded. Right now, we don't allow patch modules to unload since
> + * there is currently no method to determine if a thread is still
> + * running in the patched code contained in the patch module once
> + * the ftrace registration is successful.
> + */
> + if (!try_module_get(patch->mod))
> + return -ENODEV;
> +
> + ret = klp_init_patch(patch);
> + if (ret)
> + module_put(patch->mod);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(klp_register_patch);
> +
> +static void klp_module_notify_coming(struct klp_patch *patch,
> + struct klp_object *obj)
> +{
> + struct module *pmod = patch->mod;
> + struct module *mod = obj->mod;
> + int ret;
> +
> + ret = klp_init_object_loaded(patch, obj);
> + if (ret)
> + goto err;
> +
> + if (patch->state == KLP_DISABLED)
> + return;
> +
> + pr_notice("applying patch '%s' to loading module '%s'\n",
> + pmod->name, mod->name);
> +
> + ret = klp_enable_object(obj);
> + if (!ret)
> + return;
> +
> +err:
> + pr_warn("failed to apply patch '%s' to module '%s' (%d)\n",
> + pmod->name, mod->name, ret);
> +}
> +
> +static void klp_module_notify_going(struct klp_patch *patch,
> + struct klp_object *obj)
> +{
> + struct module *pmod = patch->mod;
> + struct module *mod = obj->mod;
> + int ret;
> +
> + if (patch->state == KLP_DISABLED)
> + goto disabled;
> +
> + pr_notice("reverting patch '%s' on unloading module '%s'\n",
> + pmod->name, mod->name);
> +
> + ret = klp_disable_object(obj);
> + if (ret)
> + pr_warn("failed to revert patch '%s' on module '%s' (%d)\n",
> + pmod->name, mod->name, ret);
> +
> +disabled:
> + klp_free_object_loaded(obj);
> +}
> +
> +static int klp_module_notify(struct notifier_block *nb, unsigned long action,
> + void *data)
> +{
> + struct module *mod = data;
> + struct klp_patch *patch;
> + struct klp_object *obj;
> +
> + if (action != MODULE_STATE_COMING && action != MODULE_STATE_GOING)
> + return 0;
> +
> + mutex_lock(&klp_mutex);
> +
> + list_for_each_entry(patch, &klp_patches, list) {
> + for (obj = patch->objs; obj->funcs; obj++) {
> + if (!klp_is_module(obj) || strcmp(obj->name, mod->name))
> + continue;
> +
> + if (action == MODULE_STATE_COMING) {
> + obj->mod = mod;
> + klp_module_notify_coming(patch, obj);
> + } else /* MODULE_STATE_GOING */
> + klp_module_notify_going(patch, obj);
> +
> + break;
> + }
> + }
> +
> + mutex_unlock(&klp_mutex);
> +
> + return 0;
> +}
> +
> +static struct notifier_block klp_module_nb = {
> + .notifier_call = klp_module_notify,
> + .priority = INT_MIN+1, /* called late but before ftrace notifier */
> +};
> +
> +static int klp_init(void)
> +{
> + int ret;
> +
> + ret = register_module_notifier(&klp_module_nb);
> + if (ret)
> + return ret;
> +
> + klp_root_kobj = kobject_create_and_add("livepatch", kernel_kobj);
> + if (!klp_root_kobj) {
> + ret = -ENOMEM;
> + goto unregister;
> + }
> +
> + return 0;
> +
> +unregister:
> + unregister_module_notifier(&klp_module_nb);
> + return ret;
> +}
> +
> +module_init(klp_init);
Looks good otherwise
Reviewed-by: Balbir Singh <bsingharora@xxxxxxxxx>
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/