#!/bin/bash # # kpatch build script # # Copyright (C) 2014 Seth Jennings # Copyright (C) 2013,2014 Josh Poimboeuf # # 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. # This script takes a patch based on the version of the kernel # currently running and creates a kernel module that will # replace modified functions in the kernel such that the # patched code takes effect. # This script currently only works on Fedora and will need to be adapted to # work on other distros. # This script: # - Downloads the kernel src rpm for the currently running kernel # - Unpacks and prepares the src rpm for building # - Builds the base kernel (vmlinux) # - Builds the patched kernel and monitors changed objects # - Builds the patched objects with gcc flags -f[function|data]-sections # - Runs kpatch tools to create and link the patch kernel module BASE="$PWD" LOGFILE="/tmp/kpatch-build-$(date +%s).log" SCRIPTDIR="$(readlink -f $(dirname $(type -p $0)))" ARCHVERSION="$(uname -r)" DISTROVERSION="${ARCHVERSION%*.*}" LOCALVERSION="-${ARCHVERSION##*-}" CPUS="$(grep -c ^processor /proc/cpuinfo)" CACHEDIR="$HOME/.kpatch" SRCDIR="$CACHEDIR/$ARCHVERSION/src" OBJDIR="$CACHEDIR/$ARCHVERSION/obj" OBJDIR2="$CACHEDIR/$ARCHVERSION/obj2" TEMPDIR= STRIPCMD="strip -d --keep-file-symbols" APPLIEDPATCHFILE="applied-patch" DEBUG=0 die() { if [[ -z $1 ]]; then echo "ERROR: kpatch build failed. Check $LOGFILE for more details." >&2 else echo "ERROR: $1" >&2 fi exit 1 } cleanup() { [[ "$DEBUG" -eq 0 ]] && rm -rf "$TEMPDIR" if [[ -e "$SRCDIR/$APPLIEDPATCHFILE" ]]; then patch -p1 -R -d "$SRCDIR" < "$SRCDIR/$APPLIEDPATCHFILE" &> /dev/null rm -f "$SRCDIR/$APPLIEDPATCHFILE" fi } find_dirs() { if [[ -e "$SCRIPTDIR/create-diff-object" ]]; then # git repo TOOLSDIR="$SCRIPTDIR" DATADIR="$(readlink -f $SCRIPTDIR/../kmod)" SYMVERSFILE="$DATADIR/core/Module.symvers" return elif [[ -e "$SCRIPTDIR/../libexec/kpatch/create-diff-object" ]]; then # installation path TOOLSDIR="$(readlink -f $SCRIPTDIR/../libexec/kpatch)" DATADIR="$(readlink -f $SCRIPTDIR/../share/kpatch)" SYMVERSFILE="$(readlink -f $SCRIPTDIR/../lib/modules/$(uname -r)/kpatch/Module.symvers)" return fi return 1 } usage() { echo "usage: $0 [options] " >&2 echo " -h, --help Show this help message" >&2 echo " -s, --sourcedir Specify kernel source directory" >&2 echo " -c, --config Specify kernel config file" >&2 echo " -d, --debug Keep scratch files in /tmp" >&2 } options=$(getopt -o hs:c:d -l "help,sourcedir:,config:,debug" -- "$@") || die "getopt failed" eval set -- "$options" while [[ $# -gt 0 ]]; do case "$1" in -h|--help) usage exit 0 ;; -s|--sourcedir) USERSRCDIR=$(readlink -f "$2") shift [[ ! -d "$USERSRCDIR" ]] && die "source dir $USERSRCDIR not found" ;; -c|--config) CONFIGFILE=$(readlink -f "$2") shift [[ ! -f "$CONFIGFILE" ]] && die "config file $CONFIGFILE not found" ;; -d|--debug) echo "DEBUG mode enabled" DEBUG=1 ;; --) if [[ -z "$2" ]]; then usage exit 1 fi PATCHFILE=$(readlink -f "$2") [[ ! -f "$PATCHFILE" ]] && die "patch file $PATCHFILE not found" break ;; esac shift done PATCHNAME="$(basename $PATCHFILE)" if [[ "$PATCHNAME" =~ \.patch ]] || [[ "$PATCHNAME" =~ \.diff ]]; then PATCHNAME="${PATCHNAME%.*}" fi TEMPDIR="$(mktemp -d)" || die "mktemp failed" trap cleanup EXIT INT TERM find_dirs || die "can't find supporting tools" [[ -e "$SYMVERSFILE" ]] || die "can't find core module Module.symvers" if [[ -d "$SRCDIR" ]] || [[ -n "$USERSRCDIR" ]]; then if [[ -n "$USERSRCDIR" ]]; then SRCDIR="$CACHEDIR/src" OBJDIR="$CACHEDIR/obj" OBJDIR2="$CACHEDIR/obj2" rm -rf "$CACHEDIR" mkdir -p "$CACHEDIR" mkdir -p "$OBJDIR" "$OBJDIR2" if [[ -n "$CONFIGFILE" ]]; then cp "$CONFIGFILE" "$OBJDIR/.config" || die "config file is missing" else cp "$USERSRCDIR/.config" "$OBJDIR" || die "source dir is missing a .config file" fi echo "Copying source to $SRCDIR" cp -a "$USERSRCDIR" "$SRCDIR" || die "copy failed" else echo "Using cache at $SRCDIR" fi else rpm -q --quiet rpmdevtools || die "rpmdevtools not installed" rpm -q --quiet yum-utils || die "yum-utils not installed" echo "Downloading kernel source for $ARCHVERSION" yumdownloader --source --destdir "$TEMPDIR" "kernel-$ARCHVERSION" >> "$LOGFILE" 2>&1 || die echo "Unpacking kernel source" rpmdev-setuptree >> "$LOGFILE" 2>&1 || die rpm -ivh "$TEMPDIR/kernel-$DISTROVERSION.src.rpm" >> "$LOGFILE" 2>&1 || die rpmbuild -bp "--target=$(uname -m)" "$HOME/rpmbuild/SPECS/kernel.spec" >> "$LOGFILE" 2>&1 || die "rpmbuild -bp failed. you may need to run 'yum-builddep kernel' first." rm -rf "$CACHEDIR" mkdir -p "$OBJDIR" mv "$HOME"/rpmbuild/BUILD/kernel-*/linux-"$ARCHVERSION" "$SRCDIR" >> "$LOGFILE" 2>&1 || die cp "$SRCDIR/.config" "$OBJDIR" || die echo "$LOCALVERSION" > "$SRCDIR/localversion" || die fi echo "Testing patch file" cd "$SRCDIR" || die cp "$PATCHFILE" "$APPLIEDPATCHFILE" || die patch -p1 < "$APPLIEDPATCHFILE" || die "source patch file failed to apply" patch -p1 -R < "$APPLIEDPATCHFILE" &> /dev/null || die "reverse patch apply failed" echo "Building original kernel" make mrproper >> "$LOGFILE" 2>&1 || die make "-j$CPUS" vmlinux "O=$OBJDIR" >> "$LOGFILE" 2>&1 || die cp -LR "$DATADIR/patch" "$TEMPDIR" || die cp "$OBJDIR/vmlinux" "$TEMPDIR" || die echo "Building patched kernel" patch -p1 < "$APPLIEDPATCHFILE" >> "$LOGFILE" 2>&1 || die make "-j$CPUS" vmlinux "O=$OBJDIR" 2>&1 | tee -a "$TEMPDIR/patched_build.log" >> "$LOGFILE" [[ "${PIPESTATUS[0]}" -eq 0 ]] || die echo "Detecting changed objects" grep CC "$TEMPDIR/patched_build.log" | grep -v -e init/version.o -e scripts/mod/devicetable-offsets.s -e scripts/mod/file2alias.o | awk '{print $2}' > "$TEMPDIR/changed_objs" [[ ! -s "$TEMPDIR/changed_objs" ]] && die "no changed objects were detected" echo "Rebuilding changed objects" rm -rf "$OBJDIR2" mkdir -p "$OBJDIR2" cp "$OBJDIR/.config" "$OBJDIR2" || die mkdir "$TEMPDIR/patched" for i in $(cat $TEMPDIR/changed_objs); do KCFLAGS="-ffunction-sections -fdata-sections" make "$i" "O=$OBJDIR2" >> "$LOGFILE" 2>&1 || die $STRIPCMD "$OBJDIR2/$i" >> "$LOGFILE" 2>&1 || die mkdir -p "$TEMPDIR/patched/$(dirname $i)" cp -f "$OBJDIR2/$i" "$TEMPDIR/patched/$i" || die done patch -R -p1 < "$APPLIEDPATCHFILE" >> "$LOGFILE" 2>&1 rm -f "$APPLIEDPATCHFILE" mkdir "$TEMPDIR/orig" for i in $(cat $TEMPDIR/changed_objs); do rm -f "$i" KCFLAGS="-ffunction-sections -fdata-sections" make "$i" "O=$OBJDIR2" >> "$LOGFILE" 2>&1 || die $STRIPCMD -d "$OBJDIR2/$i" >> "$LOGFILE" 2>&1 || die mkdir -p "$TEMPDIR/orig/$(dirname $i)" cp -f "$OBJDIR2/$i" "$TEMPDIR/orig/$i" || die done echo "Extracting new and modified ELF sections" cd "$TEMPDIR/orig" FILES="$(find * -type f)" cd "$TEMPDIR" mkdir output for i in $FILES; do mkdir -p "output/$(dirname $i)" "$TOOLSDIR"/create-diff-object "orig/$i" "patched/$i" "output/$i" 2>&1 |tee -a "$LOGFILE" [[ "${PIPESTATUS[0]}" -eq 0 ]] || die done echo "Building patch module: kpatch-$PATCHNAME.ko" cp "$OBJDIR/.config" "$SRCDIR" cd "$SRCDIR" make prepare >> "$LOGFILE" 2>&1 || die cd "$TEMPDIR/output" ld -r -o ../patch/output.o $FILES >> "$LOGFILE" 2>&1 || die cd "$TEMPDIR/patch" "$TOOLSDIR"/add-patches-section output.o ../vmlinux >> "$LOGFILE" 2>&1 || die KPATCH_BUILD="$SRCDIR" KPATCH_NAME="$PATCHNAME" KBUILD_EXTRA_SYMBOLS="$SYMVERSFILE" make "O=$OBJDIR" >> "$LOGFILE" 2>&1 || die $STRIPCMD "kpatch-$PATCHNAME.ko" >> "$LOGFILE" 2>&1 || die "$TOOLSDIR"/link-vmlinux-syms "kpatch-$PATCHNAME.ko" ../vmlinux >> "$LOGFILE" 2>&1 || die cp -f "$TEMPDIR/patch/kpatch-$PATCHNAME.ko" "$BASE" || die rm -f "$LOGFILE" echo "SUCCESS"