From 4dee89269c835ad6c3d0241825d575b44774cca2 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 29 Aug 2014 11:27:33 -0500 Subject: [PATCH] add support for shadow variables This adds support for shadow variables, which allow you to add new "shadow" fields to existing data structures. To allow patches to call the shadow functions in the core module, I had to add a funky hack to use --warn-unresolved-symbols when linking, which allows the patched vmlinux to link with the missing symbols. I also added greps to the log file to ensure that only unresolved symbols to kpatch_shadow_* are allowed. We can remove this hack once the core module gets moved into the kernel tree. Fixes #314. --- kmod/core/Makefile | 2 +- kmod/core/kpatch.h | 4 + kmod/core/shadow.c | 139 +++++++++++++++++++++ kpatch-build/create-diff-object.c | 15 +++ kpatch-build/kpatch-build | 8 +- test/integration/shadow-newpid-LOADED.test | 3 + test/integration/shadow-newpid.patch | 72 +++++++++++ 7 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 kmod/core/shadow.c create mode 100755 test/integration/shadow-newpid-LOADED.test create mode 100644 test/integration/shadow-newpid.patch 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 34177a8..8e2b157 100644 --- a/kpatch-build/create-diff-object.c +++ b/kpatch-build/create-diff-object.c @@ -2039,6 +2039,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) @@ -2088,6 +2095,14 @@ 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; + /* + * 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 207a597..a4b612f 100755 --- a/kpatch-build/kpatch-build +++ b/kpatch-build/kpatch-build @@ -398,8 +398,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.