diff --git a/kmod/core/Makefile b/kmod/core/Makefile index 3e69b2a..53ad8e4 100644 --- a/kmod/core/Makefile +++ b/kmod/core/Makefile @@ -20,4 +20,4 @@ clean: # kbuild rules obj-m := kpatch.o -kpatch-y := core.o +kpatch-y := core.o shadow.o diff --git a/kmod/core/kpatch.h b/kmod/core/kpatch.h index 7523227..c00203e 100644 --- a/kmod/core/kpatch.h +++ b/kmod/core/kpatch.h @@ -91,4 +91,8 @@ extern struct kobject *kpatch_patches_kobj; extern int kpatch_register(struct kpatch_module *kpmod, bool replace); extern int kpatch_unregister(struct kpatch_module *kpmod); +extern void *kpatch_shadow_alloc(void *obj, char *var, size_t size, gfp_t gfp); +extern void kpatch_shadow_free(void *obj, char *var); +extern void *kpatch_shadow_get(void *obj, char *var); + #endif /* _KPATCH_H_ */ diff --git a/kmod/core/shadow.c b/kmod/core/shadow.c new file mode 100644 index 0000000..89bbfa5 --- /dev/null +++ b/kmod/core/shadow.c @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2014 Josh Poimboeuf + * Copyright (C) 2014 Seth Jennings + * + * 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 . + */ + +/* + * kpatch shadow variables + * + * These functions can be used to add new "shadow" fields to existing data + * structures. For example, to allocate a "newpid" variable associated with an + * instance of task_struct, and assign it a value of 1000: + * + * struct task_struct *tsk = current; + * int *newpid; + * newpid = kpatch_shadow_alloc(tsk, "newpid", sizeof(int), GFP_KERNEL); + * if (newpid) + * *newpid = 1000; + * + * To retrieve a pointer to the variable: + * + * struct task_struct *tsk = current; + * int *newpid; + * newpid = kpatch_shadow_get(tsk, "newpid"); + * if (newpid) + * printk("task newpid = %d\n", *newpid); // prints "task newpid = 1000" + * + * To free it: + * + * kpatch_shadow_free(tsk, "newpid"); + */ + +#include +#include +#include "kpatch.h" + +static DEFINE_HASHTABLE(kpatch_shadow_hash, 12); +static DEFINE_SPINLOCK(kpatch_shadow_lock); + +struct kpatch_shadow { + struct hlist_node node; + struct rcu_head rcu_head; + void *obj; + char *var; + void *data; +}; + +void *kpatch_shadow_alloc(void *obj, char *var, size_t size, gfp_t gfp) +{ + unsigned long flags; + struct kpatch_shadow *shadow; + + shadow = kmalloc(sizeof(*shadow), gfp); + if (!shadow) + return NULL; + + shadow->obj = obj; + + shadow->var = kstrdup(var, gfp); + if (!shadow->var) + return NULL; + + shadow->data = kmalloc(size, gfp); + if (!shadow->data) { + kfree(shadow->var); + return NULL; + } + + spin_lock_irqsave(&kpatch_shadow_lock, flags); + hash_add_rcu(kpatch_shadow_hash, &shadow->node, (unsigned long)obj); + spin_unlock_irqrestore(&kpatch_shadow_lock, flags); + + return shadow->data; +} +EXPORT_SYMBOL_GPL(kpatch_shadow_alloc); + +static void kpatch_shadow_rcu_free(struct rcu_head *head) +{ + struct kpatch_shadow *shadow; + + shadow = container_of(head, struct kpatch_shadow, rcu_head); + + kfree(shadow->data); + kfree(shadow->var); + kfree(shadow); +} + +void kpatch_shadow_free(void *obj, char *var) +{ + unsigned long flags; + struct kpatch_shadow *shadow; + + spin_lock_irqsave(&kpatch_shadow_lock, flags); + + hash_for_each_possible(kpatch_shadow_hash, shadow, node, + (unsigned long)obj) { + if (shadow->obj == obj && !strcmp(shadow->var, var)) { + hash_del_rcu(&shadow->node); + spin_unlock_irqrestore(&kpatch_shadow_lock, flags); + call_rcu(&shadow->rcu_head, kpatch_shadow_rcu_free); + return; + } + } + + spin_unlock_irqrestore(&kpatch_shadow_lock, flags); +} +EXPORT_SYMBOL_GPL(kpatch_shadow_free); + +void *kpatch_shadow_get(void *obj, char *var) +{ + struct kpatch_shadow *shadow; + + rcu_read_lock(); + + hash_for_each_possible_rcu(kpatch_shadow_hash, shadow, node, + (unsigned long)obj) { + if (shadow->obj == obj && !strcmp(shadow->var, var)) { + rcu_read_unlock(); + return shadow->data; + } + } + + rcu_read_unlock(); + + return NULL; +} +EXPORT_SYMBOL_GPL(kpatch_shadow_get); diff --git a/kpatch-build/create-diff-object.c b/kpatch-build/create-diff-object.c index a577ebb..dbfee0b 100644 --- a/kpatch-build/create-diff-object.c +++ b/kpatch-build/create-diff-object.c @@ -2094,6 +2094,13 @@ void kpatch_create_patches_sections(struct kpatch_elf *kelf, } +static int kpatch_is_core_module_symbol(char *name) +{ + return (!strcmp(name, "kpatch_shadow_alloc") || + !strcmp(name, "kpatch_shadow_free") || + !strcmp(name, "kpatch_shadow_get")); +} + void kpatch_create_dynamic_rela_sections(struct kpatch_elf *kelf, struct lookup_table *table, char *hint, char *objname) @@ -2143,6 +2150,14 @@ void kpatch_create_dynamic_rela_sections(struct kpatch_elf *kelf, list_for_each_entry_safe(rela, safe, &sec->relas, list) { if (rela->sym->sec) 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; exported = 0; diff --git a/kpatch-build/kpatch-build b/kpatch-build/kpatch-build index e20155a..c84d2b3 100755 --- a/kpatch-build/kpatch-build +++ b/kpatch-build/kpatch-build @@ -401,8 +401,14 @@ echo "Building patched kernel" patch -N -p1 < "$APPLIEDPATCHFILE" >> "$LOGFILE" 2>&1 || die mkdir -p "$TEMPDIR/orig" "$TEMPDIR/patched" export TEMPDIR -CROSS_COMPILE="$TOOLSDIR/kpatch-gcc " make "-j$CPUS" $TARGETS "O=$OBJDIR" >> "$LOGFILE" 2>&1 || die +# TODO: remove custom LDFLAGS and ugly "undefined reference" grep when core +# module gets moved to the kernel tree +CROSS_COMPILE="$TOOLSDIR/kpatch-gcc " \ + LDFLAGS_vmlinux="--warn-unresolved-symbols" \ + LDFLAGS_MODULE="--warn-unresolved-symbols" \ + make "-j$CPUS" $TARGETS "O=$OBJDIR" >> "$LOGFILE" 2>&1 || die [[ "${PIPESTATUS[0]}" -eq 0 ]] || die +grep -q "undefined reference" "$LOGFILE" | grep -qv kpatch_shadow && die if [[ ! -e "$TEMPDIR/changed_objs" ]]; then die "no changed objects found" diff --git a/test/integration/shadow-newpid-LOADED.test b/test/integration/shadow-newpid-LOADED.test new file mode 100755 index 0000000..c07d112 --- /dev/null +++ b/test/integration/shadow-newpid-LOADED.test @@ -0,0 +1,3 @@ +#!/bin/bash + +grep -q newpid: /proc/$$/status diff --git a/test/integration/shadow-newpid.patch b/test/integration/shadow-newpid.patch new file mode 100644 index 0000000..5652c37 --- /dev/null +++ b/test/integration/shadow-newpid.patch @@ -0,0 +1,72 @@ +Index: src/kernel/fork.c +=================================================================== +--- src.orig/kernel/fork.c ++++ src/kernel/fork.c +@@ -1572,6 +1572,7 @@ struct task_struct *fork_idle(int cpu) + * It copies the process, and if successful kick-starts + * it and waits for it to finish using the VM if required. + */ ++#include "kpatch.h" + long do_fork(unsigned long clone_flags, + unsigned long stack_start, + unsigned long stack_size, +@@ -1622,6 +1623,13 @@ long do_fork(unsigned long clone_flags, + if (!IS_ERR(p)) { + struct completion vfork; + struct pid *pid; ++ int *newpid; ++ static int ctr = 0; ++ ++ newpid = kpatch_shadow_alloc(p, "newpid", sizeof(*newpid), ++ GFP_KERNEL); ++ if (newpid) ++ *newpid = ctr++; + + trace_sched_process_fork(current, p); + +Index: src/fs/proc/array.c +=================================================================== +--- src.orig/fs/proc/array.c ++++ src/fs/proc/array.c +@@ -337,13 +337,20 @@ static inline void task_seccomp(struct s + #endif + } + ++#include "kpatch.h" + static inline void task_context_switch_counts(struct seq_file *m, + struct task_struct *p) + { ++ int *newpid; ++ + seq_printf(m, "voluntary_ctxt_switches:\t%lu\n" + "nonvoluntary_ctxt_switches:\t%lu\n", + p->nvcsw, + p->nivcsw); ++ ++ newpid = kpatch_shadow_get(p, "newpid"); ++ if (newpid) ++ seq_printf(m, "newpid:\t%d\n", *newpid); + } + + static void task_cpus_allowed(struct seq_file *m, struct task_struct *task) +Index: src/kernel/exit.c +=================================================================== +--- src.orig/kernel/exit.c ++++ src/kernel/exit.c +@@ -694,6 +694,7 @@ static void check_stack_usage(void) + static inline void check_stack_usage(void) {} + #endif + ++#include "kpatch.h" + void do_exit(long code) + { + struct task_struct *tsk = current; +@@ -790,6 +791,8 @@ void do_exit(long code) + exit_task_work(tsk); + exit_thread(); + ++ kpatch_shadow_free(tsk, "newpid"); ++ + /* + * Flush inherited counters to the parent - before the parent + * gets woken up by child-exit notifications.