diff --git a/.gitignore b/.gitignore index dd50ef5e9..1b5b1e63d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,9 @@ -kpatch-diff-gen/kpatch-diff-gen *.o *.o.cmd *.ko *.ko.cmd *.mod.c *.swp -kmod/.tmp_versions/ -kmod/Module.symvers -kmod/modules.order -tools/add-patches-section -tools/create-diff-object -tools/link-vmlinux-syms +kpatch-build/add-patches-section +kpatch-build/create-diff-object +kpatch-build/link-vmlinux-syms diff --git a/Makefile b/Makefile deleted file mode 100644 index f248f062b..000000000 --- a/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -SUBDIRS = kpatch-kmod tools - -.PHONY: clean -clean: - for dir in $(SUBDIRS); do \ - $(MAKE) -C $$dir $@; \ - done - diff --git a/README b/README index 6059be4f9..0c337d504 100644 --- a/README +++ b/README @@ -12,6 +12,20 @@ scale-out, run on a single machine and are very downtime sensitive or require a heavyweight approval process and notification of workload users in the event of downtime. +QUICK START + +NOTE: While kpatch is designed to work with any recent Linux +kernel on any distribution, these quick start instructions +currently only work on Fedora. + +First make a patch against the kernel tree, e.g. foo.patch. +Then: + + sudo kpatch-build/kpatch-build ~/foo.patch + insmod kpatch.ko kpatch-foo.ko + +Voila, your kernel is patched. + LICENSE kpatch is under the GPLv2 license. @@ -38,7 +52,7 @@ patches and environments is conducted. DEPENDENCIES -kpatch tools require libelf library and development headers to be installed. +kpatch-build tools require libelf library and development headers to be installed. See GOTCHAS below. GOTCHAS @@ -54,11 +68,11 @@ git://git.fedorahosted.org/git/elfutils.git HOWTO -An example script for automating the hotfix module generation is in -scripts/kpatch-build. The script is written for Fedora but should +An example script for automating the patch module generation is +kpatch-build/kpatch-build. The script is written for Fedora but should be adaptable to other distributions with limited changes. -The primary steps in the hotfix module generation process are: +The primary steps in the patch module generation process are: - Building the unstripped vmlinux for the kernel - Patching the source tree - Rebuilding vmlinux and monitoring which objects are building rebuilt. @@ -67,17 +81,17 @@ The primary steps in the hotfix module generation process are: resulting in the changed patched objects - Unpatch the source tree - Recompile each changed object with -ffunction-sections -fdata-sections - resulting in the changed base objects -- Use tools/create-diff-object to analyze each base/patched object pair + resulting in the changed original objects +- Use create-diff-object to analyze each original/patched object pair for patchability and generate an output object containing modified sections - Link all the output objects in a into a cumulative object -- Use tools/add-patches-section to add the .patches section that the +- Use add-patches-section to add the .patches section that the core kpatch module uses to determine the list of functions that need to be redirected using ftrace -- Generate the hotfix kernel module -- Use tools/link-vmlinux-syms to hardcode non-exported kernel symbols - into the symbol table of the hotfix kernel module +- Generate the patch kernel module +- Use link-vmlinux-syms to hardcode non-exported kernel symbols + into the symbol table of the patch kernel module DEMONSTRATION diff --git a/scripts/kpatch.service b/contrib/kpatch.service similarity index 100% rename from scripts/kpatch.service rename to contrib/kpatch.service diff --git a/kpatch-kmod/Makefile b/kmod/core/Makefile similarity index 80% rename from kpatch-kmod/Makefile rename to kmod/core/Makefile index 21d200a69..ac83fe421 100644 --- a/kpatch-kmod/Makefile +++ b/kmod/core/Makefile @@ -2,9 +2,9 @@ KPATCH_BUILD ?= /usr/lib/modules/$(shell uname -r)/build KPATCH_MAKE = $(MAKE) -C $(KPATCH_BUILD) M=$(PWD) obj-m := kpatch.o -kpatch-y := base.o trampoline.o +kpatch-y := core.o trampoline.o -kpatch.ko: base.c trampoline.S +kpatch.ko: core.c trampoline.S $(KPATCH_MAKE) kpatch.ko all: kpatch.ko diff --git a/kpatch-kmod/base.c b/kmod/core/core.c similarity index 96% rename from kpatch-kmod/base.c rename to kmod/core/core.c index 57f9ee5ea..5c91b11fb 100644 --- a/kpatch-kmod/base.c +++ b/kmod/core/core.c @@ -1,17 +1,15 @@ /* - * kpatch-kmod/base.c - * * Copyright (C) 2014 Seth Jennings * Copyright (C) 2013 Josh Poimboeuf * * Contains the code for the core kpatch module. This module reads - * information from the hotfix modules, find new patched functions, + * information from the patch modules, find new patched functions, * and register those functions in the ftrace handlers the redirects * the old function call to the new function code. * - * Each hotfix can contain one or more patched functions. This information - * is contained in the .patches section of the hotfix module. For each - * function patched by the module we must: + * Each patch module can contain one or more patched functions. This + * information is contained in the .patches section of the patch module. For + * each function patched by the module we must: * - Call stop_machine * - Ensure that no execution thread is currently in the function to be * patched (or has the function in the call stack) diff --git a/kpatch-kmod/kpatch.h b/kmod/core/kpatch.h similarity index 90% rename from kpatch-kmod/kpatch.h rename to kmod/core/kpatch.h index 68cb88ee2..15e0f3e2f 100644 --- a/kpatch-kmod/kpatch.h +++ b/kmod/core/kpatch.h @@ -4,7 +4,7 @@ * Copyright (C) 2014 Seth Jennings * Copyright (C) 2013 Josh Poimboeuf * - * Contains the API for the core kpatch module used by the hotfix modules + * Contains the API for the core kpatch module used by the patch modules */ #ifndef _KPATCH_H_ diff --git a/kpatch-kmod/trampoline.S b/kmod/core/trampoline.S similarity index 100% rename from kpatch-kmod/trampoline.S rename to kmod/core/trampoline.S diff --git a/kmod/patch/Makefile b/kmod/patch/Makefile new file mode 100644 index 000000000..d27819129 --- /dev/null +++ b/kmod/patch/Makefile @@ -0,0 +1,20 @@ +KPATCH_NAME ?= patch +KPATCH_BUILD ?= /lib/modules/$(shell uname -r)/build +KPATCH_MAKE = $(MAKE) -C $(KPATCH_BUILD) M=$(PWD) +ccflags-y += -I$(KPATCH_BASEDIR) + +obj-m += kpatch-$(KPATCH_NAME).o + +kpatch-$(KPATCH_NAME)-objs += kpatch-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 + +clean: + $(RM) -Rf .*.o.cmd .*.ko.cmd .tmp_versions *.o *.ko *.mod.c \ + Module.symvers diff --git a/kpatch-files/kpatch-patch-hook.c b/kmod/patch/kpatch-patch-hook.c similarity index 93% rename from kpatch-files/kpatch-patch-hook.c rename to kmod/patch/kpatch-patch-hook.c index dca0fd0db..f0589341d 100644 --- a/kpatch-files/kpatch-patch-hook.c +++ b/kmod/patch/kpatch-patch-hook.c @@ -2,7 +2,7 @@ #include #include -#include "../kpatch-kmod/kpatch.h" +#include "kpatch.h" extern char __kpatch_patches, __kpatch_patches_end; diff --git a/kpatch-files/kpatch.lds b/kmod/patch/kpatch.lds similarity index 100% rename from kpatch-files/kpatch.lds rename to kmod/patch/kpatch.lds diff --git a/tools/Makefile b/kpatch-build/Makefile similarity index 93% rename from tools/Makefile rename to kpatch-build/Makefile index 1d9685b3f..de0f15ff9 100644 --- a/tools/Makefile +++ b/kpatch-build/Makefile @@ -1,3 +1,5 @@ +CFLAGS=-I../kmod/core + all: create-diff-object add-patches-section link-vmlinux-syms create-diff-object: create-diff-object.c diff --git a/tools/add-patches-section.c b/kpatch-build/add-patches-section.c similarity index 99% rename from tools/add-patches-section.c rename to kpatch-build/add-patches-section.c index c76d42996..cb425537a 100644 --- a/tools/add-patches-section.c +++ b/kpatch-build/add-patches-section.c @@ -26,7 +26,7 @@ #include #include -#include "../kpatch-kmod/kpatch.h" +#include "kpatch.h" #define ERROR(format, ...) \ error(1, 0, "%s: %d: " format, __FUNCTION__, __LINE__, ##__VA_ARGS__) diff --git a/tools/create-diff-object.c b/kpatch-build/create-diff-object.c similarity index 99% rename from tools/create-diff-object.c rename to kpatch-build/create-diff-object.c index 9f9b98517..6f993be48 100644 --- a/tools/create-diff-object.c +++ b/kpatch-build/create-diff-object.c @@ -39,7 +39,7 @@ #include #include -#include "../kpatch-kmod/kpatch.h" +#include "kpatch.h" #define ERROR(format, ...) \ error(1, 0, "%s: %d: " format, __FUNCTION__, __LINE__, ##__VA_ARGS__) diff --git a/scripts/kpatch-build b/kpatch-build/kpatch-build similarity index 50% rename from scripts/kpatch-build rename to kpatch-build/kpatch-build index 5bbdf3ded..deac7112f 100755 --- a/scripts/kpatch-build +++ b/kpatch-build/kpatch-build @@ -1,4 +1,4 @@ -#/bin/bash +#!/bin/bash # kpatch build script for Fedora @@ -24,25 +24,29 @@ BASE=$PWD LOGFILE=$PWD/kpatch-build.log -KPATCHPATH=/usr/local/share/kpatch +TOOLSDIR=$BASE/kpatch-build ARCHVERSION=$(uname -r) DISTROVERSION=${ARCHVERSION%*.*} CPUS=$(grep -c ^processor /proc/cpuinfo) LOCALVERSION=$(uname -r) LOCALVERSION=-${LOCALVERSION##*-} +KSRCDIR=~/.kpatch/$ARCHVERSION +KSRCDIR_DIR=$(dirname $KSRCDIR) +KSRCDIR_CACHE=$KSRCDIR.tgz cleanup() { - rm -Rf kernel-$DISTROVERSION.src.rpm rpmbuild $LOGFILE $TEMPDIR > /dev/null 2>/dev/null + rm -Rf $KSRCDIR kernel-$DISTROVERSION.src.rpm $LOGFILE $TEMPDIR > /dev/null 2>/dev/null } die() { echo "kpatch encountered an error" + echo "check kpatch-build.log" exit 1 } if [ $# -ne 1 ] then - echo "kpatch patchfile" + echo "usage: $0 PATCH" exit 2 fi @@ -53,34 +57,56 @@ then exit 3 fi -cleanup +PATCHNAME=`basename $PATCHFILE` +if [[ $PATCHNAME =~ \.patch ]] || [[ $PATCHNAME =~ \.diff ]] +then + PATCHNAME=${PATCHNAME%.*} +fi -echo "Verifying required development tools" -yum install rpmdevtools yum-utils || die +cleanup -echo "Downloading kernel source for $ARCHVERSION" -yumdownloader --source kernel-$ARCHVERSION || die +echo "Building tools" +make -C $TOOLSDIR >> $LOGFILE 2>&1 || die -echo "Verifying build dependencies for kernel package" -yum-builddep kernel-$DISTROVERSION.src.rpm || die +TEMPDIR=`mktemp -d` || die -echo "Unpacking kernel source" -rpmdev-setuptree >> $LOGFILE 2>&1 || die -rpm -Uvh kernel-$DISTROVERSION.src.rpm >> $LOGFILE 2>&1 || die -cd ~/rpmbuild/SPECS || die -rpmbuild -bp --target=$(uname -m) kernel.spec >> $LOGFILE 2>&1 || die -cd ~/rpmbuild/BUILD/kernel-* || die -cd linux-$ARCHVERSION || die -BUILD=$PWD -echo $LOCALVERSION > localversion +if [ -f "$KSRCDIR_CACHE" ] +then + echo "Using cache at $KSRCDIR_CACHE" + rm -rf $KSRCDIR + tar xzf $KSRCDIR_CACHE -C $KSRCDIR_DIR || die + cd $KSRCDIR || die +else + echo "Verifying required development tools" + yum install rpmdevtools yum-utils || die + + echo "Downloading kernel source for $ARCHVERSION" + yumdownloader --source kernel-$ARCHVERSION || die + + echo "Verifying build dependencies for kernel package" + yum-builddep kernel-$DISTROVERSION.src.rpm || die + + echo "Unpacking kernel source" + rpmdev-setuptree >> $LOGFILE 2>&1 || die + rpm -Uvh kernel-$DISTROVERSION.src.rpm >> $LOGFILE 2>&1 || die + rpmbuild -bp --target=$(uname -m) ~/rpmbuild/SPECS/kernel.spec >> $LOGFILE 2>&1 || die + rm -rf $KSRCDIR + mkdir -p $KSRCDIR_DIR + mv ~/rpmbuild/BUILD/kernel-*/linux-$ARCHVERSION $KSRCDIR >> $LOGFILE 2>&1 || die + + echo "Building original kernel" + cd $KSRCDIR + echo $LOCALVERSION > localversion + make -j$CPUS vmlinux >> $LOGFILE 2>&1 || die + + echo "Creating cache" + tar czf $KSRCDIR_CACHE -C $KSRCDIR_DIR $ARCHVERSION +fi -echo "Building the base kernel" -make -j$CPUS vmlinux >> $LOGFILE 2>&1 || die -TEMPDIR=`mktemp -d` || die -cp -R $KPATCHPATH/* $TEMPDIR || die +cp -R $BASE/kmod/patch/* $BASE/kmod/core $TEMPDIR || die cp vmlinux $TEMPDIR || die -echo "Building the patched kernel" +echo "Building patched kernel" patch -p1 < $PATCHFILE >> $LOGFILE 2>&1 make -j$CPUS vmlinux > $TEMPDIR/patched_build.log 2>&1 || die @@ -103,34 +129,36 @@ do done patch -R -p1 < $PATCHFILE >> $LOGFILE 2>&1 -mkdir $TEMPDIR/base +mkdir $TEMPDIR/orig for i in $(cat $TEMPDIR/changed_objs) do rm -f $i KCFLAGS="-ffunction-sections -fdata-sections" make $i >> $LOGFILE 2>&1 || die strip -d $i - cp -f $i $TEMPDIR/base/ + cp -f $i $TEMPDIR/orig/ done echo "Extracting new and modified ELF sections" cd $TEMPDIR mkdir output -for i in base/* +for i in orig/* do FILE=`basename $i` - ./tools/create-diff-object base/$FILE patched/$FILE output/$FILE >> $LOGFILE 2>&1 + $TOOLSDIR/create-diff-object orig/$FILE patched/$FILE output/$FILE >> $LOGFILE 2>&1 || die done -echo "Building hotfix module" +echo "Building core module: kpatch.ko" +cd core +KPATCH_BUILD=$KSRCDIR make >> $LOGFILE 2>&1 || die +cd .. + +echo "Building patch module: kpatch-$PATCHNAME.ko" ld -r -o output.o output/* >> $LOGFILE 2>&1 || die -./tools/add-patches-section output.o vmlinux >> $LOGFILE 2>&1 || die -KPATCH_BUILD=$BUILD make >> $LOGFILE 2>&1 || die -strip -d kpatch-patch.ko >> $LOGFILE 2>&1 || die -./tools/link-vmlinux-syms kpatch-patch.ko vmlinux >> $LOGFILE 2>&1 || die - -echo "Patch generation complete" -cp -f $TEMPDIR/kpatch-patch.ko $BASE -echo "Cleaning up" -cleanup -echo "Done" +$TOOLSDIR/add-patches-section output.o vmlinux >> $LOGFILE 2>&1 || die +KPATCH_BASEDIR=$TEMPDIR/core KPATCH_BUILD=$KSRCDIR KPATCH_NAME=$PATCHNAME make >> $LOGFILE 2>&1 || die +strip -d kpatch-$PATCHNAME.ko >> $LOGFILE 2>&1 || die +$TOOLSDIR/link-vmlinux-syms kpatch-$PATCHNAME.ko vmlinux >> $LOGFILE 2>&1 || die +echo "SUCCESS" +cp -f $TEMPDIR/kpatch-$PATCHNAME.ko $TEMPDIR/core/kpatch.ko $BASE +cleanup diff --git a/tools/link-vmlinux-syms.c b/kpatch-build/link-vmlinux-syms.c similarity index 99% rename from tools/link-vmlinux-syms.c rename to kpatch-build/link-vmlinux-syms.c index 6295763dd..8e8f2a944 100644 --- a/tools/link-vmlinux-syms.c +++ b/kpatch-build/link-vmlinux-syms.c @@ -23,7 +23,7 @@ #include #include -#include "../kpatch-kmod/kpatch.h" +#include "kpatch.h" #define ERROR(format, ...) \ error(1, 0, "%s: %d: " format, __FUNCTION__, __LINE__, ##__VA_ARGS__) diff --git a/kpatch-files/Makefile b/kpatch-files/Makefile deleted file mode 100644 index ddeffb99f..000000000 --- a/kpatch-files/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -KPATCH_BUILD = /lib/modules/$(shell uname -r)/build -KPATCH_MAKE = $(MAKE) -C $(KPATCH_BUILD) M=$(PWD) - -obj-m += kpatch-patch.o - -kpatch-patch-objs += kpatch-patch-hook.o kpatch.lds output.o - -all: kpatch-patch.ko - -kpatch-patch.ko: - $(KPATCH_MAKE) kpatch-patch.ko - -kpatch-patch-hook.o: kpatch-patch-hook.c - $(KPATCH_MAKE) kpatch-patch-hook.o - -clean: - $(RM) -Rf .*.o.cmd .*.ko.cmd .tmp_versions *.o *.ko *.mod.c \ - Module.symvers diff --git a/scripts/kpatch b/kpatch/kpatch similarity index 78% rename from scripts/kpatch rename to kpatch/kpatch index 874ffa16c..6fcb06a33 100755 --- a/scripts/kpatch +++ b/kpatch/kpatch @@ -1,7 +1,7 @@ #!/bin/bash # This is the primary kpatch user script that manages loading, unloading -# and displaying information on the hotfixes that are installed on the +# and displaying information on the patches that are installed on the # system. # Subcommands: @@ -31,16 +31,16 @@ usage () { } # return either VARDIR or USRDIR (or exits if not found) -find_hotfix () { +find_patch () { [ -e $VARDIR/available/$1 ] && DIR=$VARDIR && return [ -e $USRDIR/available/$1 ] && DIR=$USRDIR && return echo "Hotfix $2 is not installed" - echo "Use "kpatch list" for available hotfixes" + echo "Use "kpatch list" for available patches" exit 2 } -# takes full path to hotfix module -load_hotfix () { +# takes full path to patch module +load_patch () { NAME=$(basename $1) NAME=${NAME%.*} insmod $1 @@ -53,7 +53,7 @@ load_hotfix () { } # takes only the module filename -unload_hotfix () { +unload_patch () { NAME=$(basename $1) rmmod ${NAME%.*} if [ $? -ne 0 ] @@ -70,7 +70,7 @@ MODFILE=$2.ko case $1 in "enable") [ $# -ne 2 ] && usage - find_hotfix $MODFILE + find_patch $MODFILE if [ -e $DIR/enabled/$MODFILE ] then echo "Hotfix $2 is already enabled" @@ -79,7 +79,7 @@ case $1 in ln -s $DIR/available/$MODFILE $DIR/enabled/$MODFILE if [ $? -ne 0 ] then - echo "Failed to enable hotfix $2" + echo "Failed to enable patch $2" exit 3 else echo "Hotfix $2 enabled" @@ -87,7 +87,7 @@ case $1 in ;; "disable") [ $# -ne 2 ] && usage - find_hotfix $MODFILE + find_patch $MODFILE if [ ! -e $DIR/enabled/$MODFILE ] then echo "Hotfix $2 is already disabled" @@ -96,7 +96,7 @@ case $1 in rm -f $DIR/enabled/$MODFILE if [ $? -ne 0 ] then - echo "Failed to disable hotfix $2" + echo "Failed to disable patch $2" exit 3 else echo "Hotfix $2 disabled" @@ -108,16 +108,16 @@ case $1 in "--all") for i in $(ls $VARDIR/enabled) do - load_hotfix $VARDIR/enabled/$i + load_patch $VARDIR/enabled/$i done for i in $(ls $USRDIR/enabled) do - load_hotfix $USRDIR/enabled/$i + load_patch $USRDIR/enabled/$i done ;; *) - find_hotfix $MODFILE - load_hotfix $DIR/available/$MODFILE + find_patch $MODFILE + load_patch $DIR/available/$MODFILE ;; esac ;; @@ -127,40 +127,40 @@ case $1 in "--all") for i in $(ls $VARDIR/available) do - unload_hotfix ${i%.*} + unload_patch ${i%.*} done for i in $(ls $USRDIR/available) do - unload_hotfix ${i%.*} + unload_patch ${i%.*} done ;; *) - find_hotfix $MODFILE - unload_hotfix $2 + find_patch $MODFILE + unload_patch $2 ;; esac ;; "list") [ $# -ne 1 ] && usage - echo "User hotfixes available:" + echo "User patches available:" for i in $(ls $VARDIR/available) do echo ${i%.*} done echo "" - echo "User hotfixes enabled:" + echo "User patches enabled:" for i in $(ls $VARDIR/enabled) do echo ${i%.*} done echo "" - echo "System hotfixes available:" + echo "System patches available:" for i in $(ls $USRDIR/available) do echo ${i%.*} done echo "" - echo "System hotfixes enabled:" + echo "System patches enabled:" for i in $(ls $USRDIR/enabled) do echo ${i%.*} @@ -168,7 +168,7 @@ case $1 in ;; "info") [ $# -ne 2 ] && usage - find_hotfix $MODFILE + find_patch $MODFILE echo "Hotfix information for ${2%.*}:" modinfo $DIR/available/$MODFILE ;;