#!/bin/bash # kpatch build script # 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" TOOLSDIR="$(readlink -f $(dirname $0))" ARCHVERSION="$(uname -r)" DISTROVERSION="${ARCHVERSION%*.*}" CPUS="$(grep -c ^processor /proc/cpuinfo)" LOCALVERSION="$(uname -r)" LOCALVERSION="-${LOCALVERSION##*-}" KSRCDIR="$HOME/.kpatch/$ARCHVERSION" KSRCDIR_DIR="$(dirname $KSRCDIR)" TEMPDIR= STRIPCMD="strip -d --keep-file-symbols" APPLIEDPATCHFILE="applied-patch" die() { if [[ -z $1 ]]; then echo "ERROR: kpatch build failed. Check $LOGFILE for more details." >&2 else echo "ERROR: $1" >&2 fi exit 1 } find_data_dir() { # git repo DATADIR="$(readlink -f $TOOLSDIR/../kmod)" [[ -e "$DATADIR" ]] && return # installation path DATADIR="$(readlink -f $TOOLSDIR/../../share/kpatch)" [[ -e "$DATADIR" ]] && return return 1 } if [[ "$#" -ne 1 ]]; then echo "usage: $0 patchfile" >&2 exit 2 fi PATCHFILE="$(readlink -f $1)" if [[ ! -f "$PATCHFILE" ]]; then echo "ERROR: patch file $PATCHFILE not found" >&2 exit 3 fi PATCHNAME="$(basename $PATCHFILE)" if [[ "$PATCHNAME" =~ \.patch ]] || [[ "$PATCHNAME" =~ \.diff ]]; then PATCHNAME="${PATCHNAME%.*}" fi TEMPDIR="$(mktemp -d)" || die "mktemp failed" trap "rm -rf $TEMPDIR" EXIT INT TERM if [[ -d "$KSRCDIR" ]]; then echo "Using cache at $KSRCDIR" cd "$KSRCDIR" || die if [[ -f "$APPLIEDPATCHFILE" ]]; then patch -R -p1 < "$APPLIEDPATCHFILE" || die "the kpatch cache is corrupted. \"rm -rf $KSRCDIR\" and try again" rm -f "$APPLIEDPATCHFILE" fi make "-j$CPUS" vmlinux >> "$LOGFILE" 2>&1 || die 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." mkdir -p "$KSRCDIR_DIR" mv "$HOME"/rpmbuild/BUILD/kernel-*/linux-"$ARCHVERSION" "$KSRCDIR" >> "$LOGFILE" 2>&1 || die echo "Building original kernel" cd "$KSRCDIR" echo "$LOCALVERSION" > localversion || die make "-j$CPUS" vmlinux >> "$LOGFILE" 2>&1 || die fi find_data_dir || (echo "can't find data dir" >&2 && die) cp -LR "$DATADIR/patch" "$TEMPDIR" || die cp vmlinux "$TEMPDIR" || die echo "Building patched kernel" cp "$PATCHFILE" "$APPLIEDPATCHFILE" || die patch -p1 < "$APPLIEDPATCHFILE" >> "$LOGFILE" 2>&1 || die make "-j$CPUS" vmlinux > "$TEMPDIR/patched_build.log" 2>&1 || die echo "Detecting changed objects" grep CC "$TEMPDIR/patched_build.log" | grep -v init/version.o | awk '{print $2}' >> "$TEMPDIR/changed_objs" if [[ $? -ne 0 ]]; then echo "No changed objects" exit 1 fi echo "Rebuilding changed objects" mkdir "$TEMPDIR/patched" for i in $(cat $TEMPDIR/changed_objs); do rm -f "$i" KCFLAGS="-ffunction-sections -fdata-sections" make "$i" >> "$LOGFILE" 2>&1 || die $STRIPCMD "$i" >> "$LOGFILE" 2>&1 || die mkdir -p "$TEMPDIR/patched/$(dirname $i)" cp -f "$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" >> "$LOGFILE" 2>&1 || die $STRIPCMD -d "$i" >> "$LOGFILE" 2>&1 || die mkdir -p "$TEMPDIR/orig/$(dirname $i)" cp -f "$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" >> "$LOGFILE" 2>&1 || die done echo "Building patch module: kpatch-$PATCHNAME.ko" 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="$KSRCDIR" KPATCH_NAME="$PATCHNAME" make >> "$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"