Blame SOURCES/0002-backport-kpatch-script-livepatch-fixups-753.patch

997f8e
From 2d9606d9b864eec8d81de6952b8816284dec0032 Mon Sep 17 00:00:00 2001
997f8e
From: Joe Lawrence <joe.lawrence@redhat.com>
997f8e
Date: Thu, 16 Nov 2017 14:21:22 -0500
997f8e
Subject: [PATCH] backport "kpatch script - livepatch fixups #753"
997f8e
997f8e
This patchset improves livepatch support, most notably adding a
997f8e
wait-for-transition loop when (un)loading patch modules and signaling of
997f8e
tasks (with a SIGSTOP/SIGCONT combo) that are stalling the transition.
997f8e
997f8e
commit a2fbce15872167cfeb2bee4dcf66c33e29e4dfe2
997f8e
Author: Joe Lawrence <joe.lawrence@redhat.com>
997f8e
Date:   Wed Nov 15 10:44:42 2017 -0500
997f8e
997f8e
    kpatch: don't complain about missing livepatch .kpatch.checksum
997f8e
997f8e
    The verify_module_checksum() function reads a kpatch-specific ELF
997f8e
    section to compare on-disk and in-memory kernel modules.  The function
997f8e
    only reports a miscompare if the .kpatch.checksum section actually
997f8e
    exists.  Livepatches don't have such section, so throw away any "Section
997f8e
    '.kpatch.checksum' was not dumped because it does not exist!" warnings
997f8e
    from readelf.
997f8e
997f8e
    Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
997f8e
997f8e
commit fb0bc53eb7540460bc8222313d436b7537aa9952
997f8e
Author: Joe Lawrence <joe.lawrence@redhat.com>
997f8e
Date:   Wed Nov 15 10:44:42 2017 -0500
997f8e
997f8e
    kpatch: show transitioning patches and stalled tasks
997f8e
997f8e
    In 'kpatch list' output, show the current patch state: enabled,
997f8e
    disabled, and livepatch mid-transition states enabling... and
997f8e
    disabling...
997f8e
997f8e
    Also provide a list of any tasks that are stalling a livepatch
997f8e
    transition.
997f8e
997f8e
    Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
997f8e
997f8e
commit 3582e10e42fed7a08b5cf1ce0b470723428b1386
997f8e
Author: Joe Lawrence <joe.lawrence@redhat.com>
997f8e
Date:   Wed Nov 15 10:44:42 2017 -0500
997f8e
997f8e
    kpatch: signal stalled processes
997f8e
997f8e
    Add a "signal" command line option that iterates over all processes that
997f8e
    may be holding up the current livepatch transition.  Send such processes
997f8e
    a SIGSTOP / SIGCONT combination to try and expedite the transition.
997f8e
997f8e
    Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
997f8e
997f8e
commit 52c12cbad6cd0ca35f892a0b07519b41326ea91e
997f8e
Author: Joe Lawrence <joe.lawrence@redhat.com>
997f8e
Date:   Wed Nov 15 10:44:42 2017 -0500
997f8e
997f8e
    kpatch: wait for livepatch transitions, poke stragglers
997f8e
997f8e
    When loading a livepatch, wait for the patching transition to complete
997f8e
    within a reasonable timeframe, then poke any stalled tasks with a
997f8e
    signal.  If the transition is still taking too long, reverse the patch
997f8e
    and unload the livepatch.
997f8e
997f8e
    When re-enabling a livepatch, do the same wait and signaling.  If the
997f8e
    expected time expires, disable the livepatch.
997f8e
997f8e
    When unloading a livepatch, perform the wait/signaling, but only emit an
997f8e
    error message if the transition exceeds the time limit.
997f8e
997f8e
    Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
997f8e
997f8e
Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
997f8e
diff --git a/kpatch/kpatch b/kpatch/kpatch
997f8e
index 5998fbc1ba72..a2ac607f6e31 100755
997f8e
--- a/kpatch/kpatch
997f8e
+++ b/kpatch/kpatch
997f8e
@@ -26,6 +26,8 @@
997f8e
 INSTALLDIR=/var/lib/kpatch
997f8e
 SCRIPTDIR="$(readlink -f $(dirname $(type -p $0)))"
997f8e
 VERSION="0.4.0"
997f8e
+POST_ENABLE_WAIT=5	# seconds
997f8e
+POST_SIGNAL_WAIT=60	# seconds
997f8e
 
997f8e
 usage_cmd() {
997f8e
 	printf '   %-20s\n      %s\n' "$1" "$2" >&2
997f8e
@@ -49,6 +51,8 @@ usage () {
997f8e
 	echo >&2
997f8e
 	usage_cmd "list" "list installed patch modules"
997f8e
 	echo >&2
997f8e
+	usage_cmd "signal" "signal/poke any process stalling the current patch transition"
997f8e
+	echo >&2
997f8e
 	usage_cmd "version" "display the kpatch version"
997f8e
 	exit 1
997f8e
 }
997f8e
@@ -145,7 +149,7 @@ verify_module_checksum () {
997f8e
 	modname=$(get_module_name $1)
997f8e
 	[[ -z $modname ]] && return 1
997f8e
 
997f8e
-	checksum=$(readelf -p .kpatch.checksum $1 | grep '\[.*\]' | awk '{print $3}')
997f8e
+	checksum="$(readelf -p .kpatch.checksum "$1" 2>&1 | grep '\[.*\]' | awk '{print $3}')"
997f8e
 
997f8e
 	# Fail checksum match only if both exist and diverge
997f8e
 	if [[ ! -z $checksum ]] && [[ -e "$SYSFS/${modname}/checksum" ]] ; then
997f8e
@@ -156,6 +160,119 @@ verify_module_checksum () {
997f8e
 	return 0
997f8e
 }
997f8e
 
997f8e
+in_transition() {
997f8e
+	local moddir="$SYSFS/$1"
997f8e
+	[[ $(cat "$moddir/transition" 2>/dev/null) == "1" ]] && return 0
997f8e
+	return 1
997f8e
+}
997f8e
+
997f8e
+is_stalled() {
997f8e
+	local module="$1"
997f8e
+	local pid="$2"
997f8e
+	local patch_enabled
997f8e
+	local patch_state
997f8e
+
997f8e
+	patch_enabled="$(cat "$SYSFS/$module/enabled" 2>/dev/null)"
997f8e
+	patch_state="$(cat "/proc/$pid/patch_state" 2>/dev/null)"
997f8e
+
997f8e
+	# No patch transition in progress
997f8e
+	[[ "$patch_state" == "-1" ]] && return 1
997f8e
+
997f8e
+	[[ -z "$patch_enabled" ]] || [[ -z "$patch_state" ]] && return 1
997f8e
+
997f8e
+	# Stalls can be determined if the process state does not match
997f8e
+	# the transition target (ie, "enabled" and "patched", "disabled"
997f8e
+	# and "unpatched").  The state value enumerations match, so we
997f8e
+	# can just compare them directly:
997f8e
+	[[ "$patch_enabled" != "$patch_state" ]] && return 0
997f8e
+	return 1
997f8e
+}
997f8e
+
997f8e
+get_transition_patch() {
997f8e
+	local module
997f8e
+	local modname
997f8e
+	for module in "$SYSFS"/*; do
997f8e
+		modname=$(basename "$module")
997f8e
+		if in_transition "$modname" ; then
997f8e
+			echo "$modname"
997f8e
+			return
997f8e
+		fi
997f8e
+	done
997f8e
+}
997f8e
+
997f8e
+show_stalled_processes() {
997f8e
+	local module
997f8e
+	local proc_task
997f8e
+	local tid
997f8e
+
997f8e
+	module=$(get_transition_patch)
997f8e
+	[[ -z "$module" ]] && return
997f8e
+
997f8e
+	echo ""
997f8e
+	echo "Stalled processes:"
997f8e
+	for proc_task in /proc/[0-9]*/task/[0-9]*; do
997f8e
+		tid=${proc_task#*/task/}
997f8e
+		is_stalled "$module" "$tid" && echo "$tid $(cat "$proc_task"/comm 2>/dev/null)"
997f8e
+	done
997f8e
+}
997f8e
+
997f8e
+signal_stalled_processes() {
997f8e
+	local module
997f8e
+	local proc_task
997f8e
+	local tid
997f8e
+
997f8e
+	module=$(get_transition_patch)
997f8e
+	[[ -z "$module" ]] && return
997f8e
+
997f8e
+	if [[ -e "/sys/kernel/livepatch/$module/signal" ]] ; then
997f8e
+		echo 1 > "/sys/kernel/livepatch/$module/signal"
997f8e
+	else
997f8e
+		for proc_task in /proc/[0-9]*/task/[0-9]*; do
997f8e
+			tid=${proc_task#*/task/}
997f8e
+			if is_stalled "$module" "$tid" ; then
997f8e
+				if [[ "$tid" -eq "$$" ]] ; then
997f8e
+					echo "skipping pid $tid $(cat "$proc_task"/comm 2>/dev/null)"
997f8e
+				else
997f8e
+					echo "signaling pid $tid $(cat "$proc_task"/comm 2>/dev/null)"
997f8e
+					kill -SIGSTOP "$tid"
997f8e
+					sleep .1
997f8e
+					kill -SIGCONT "$tid"
997f8e
+				fi
997f8e
+			fi
997f8e
+		done
997f8e
+	fi
997f8e
+}
997f8e
+
997f8e
+wait_for_patch_transition() {
997f8e
+	local module="$1"
997f8e
+	local i
997f8e
+
997f8e
+	in_transition "$module" || return 0
997f8e
+
997f8e
+	echo "waiting (up to $POST_ENABLE_WAIT seconds) for patch transition to complete..."
997f8e
+	for (( i=0; i
997f8e
+		if ! in_transition "$module" ; then
997f8e
+			echo "transition complete ($i seconds)"
997f8e
+			return 0
997f8e
+		fi
997f8e
+		sleep 1s
997f8e
+	done
997f8e
+
997f8e
+	echo "patch transition has stalled, signaling stalled process(es):"
997f8e
+	signal_stalled_processes
997f8e
+
997f8e
+	echo "waiting (up to $POST_SIGNAL_WAIT seconds) for patch transition to complete..."
997f8e
+	for (( i=0; i
997f8e
+		if ! in_transition "$module" ; then
997f8e
+			echo "transition complete ($i seconds)"
997f8e
+			return 0
997f8e
+		fi
997f8e
+		sleep 1s
997f8e
+	done
997f8e
+
997f8e
+	return 1
997f8e
+}
997f8e
+
997f8e
 load_module () {
997f8e
 	local module="$1"
997f8e
 
997f8e
@@ -180,6 +297,13 @@ load_module () {
997f8e
 			if verify_module_checksum $module; then # same checksum
997f8e
 				echo "module already loaded, re-enabling"
997f8e
 				echo 1 > ${moddir}/enabled || die "failed to re-enable module $modname"
997f8e
+				if ! wait_for_patch_transition "$modname" ; then
997f8e
+					echo "module $modname did not complete its transition, disabling..."
997f8e
+					echo 0 > "${moddir}/enabled" || die "failed to disable module $modname"
997f8e
+					wait_for_patch_transition "$modname"
997f8e
+					die "error: failed to re-enable module $modname (transition stalled), patch disabled"
997f8e
+				fi
997f8e
+
997f8e
 				return
997f8e
 			else
997f8e
 				die "error: cannot re-enable patch module $modname, cannot verify checksum match"
997f8e
@@ -210,6 +334,12 @@ load_module () {
997f8e
 		fi
997f8e
 	done
997f8e
 
997f8e
+	if ! wait_for_patch_transition "$modname" ; then
997f8e
+		echo "module $modname did not complete its transition, unloading..."
997f8e
+		unload_module "$modname"
997f8e
+		die "error: failed to load module $modname (transition stalled)"
997f8e
+	fi
997f8e
+
997f8e
 	return 0
997f8e
 }
997f8e
 
997f8e
@@ -223,6 +353,11 @@ unload_module () {
997f8e
 		echo 0 > $ENABLED || die "can't disable $PATCH"
997f8e
 	fi
997f8e
 
997f8e
+	if ! wait_for_patch_transition "$PATCH" ; then
997f8e
+		die "error: failed to unload module $PATCH (transition stalled)"
997f8e
+	fi
997f8e
+
997f8e
+
997f8e
 	echo "unloading patch module: $PATCH"
997f8e
 	# ignore any error here because rmmod can fail if the module used
997f8e
 	# KPATCH_FORCE_UNSAFE.
997f8e
@@ -352,10 +487,19 @@ case "$1" in
997f8e
 	[[ "$#" -ne 1 ]] && usage
997f8e
 	echo "Loaded patch modules:"
997f8e
 	for module in $SYSFS/*; do
997f8e
-		if [[ -e $module ]] && [[ $(cat $module/enabled) -eq 1 ]]; then
997f8e
-			echo $(basename "$module")
997f8e
+		if [[ -e "$module" ]]; then
997f8e
+			modname=$(basename "$module")
997f8e
+			if [[ "$(cat "$module/enabled" 2>/dev/null)" -eq 1 ]]; then
997f8e
+				in_transition "$modname" && state="enabling..." \
997f8e
+							 || state="enabled"
997f8e
+			else
997f8e
+				in_transition "$modname" && state="disabling..." \
997f8e
+							 || state="disabled"
997f8e
+			fi
997f8e
+			echo "$modname [$state]"
997f8e
 		fi
997f8e
 	done
997f8e
+	show_stalled_processes
997f8e
 	echo ""
997f8e
 	echo "Installed patch modules:"
997f8e
 	for kdir in $INSTALLDIR/*; do
997f8e
@@ -376,6 +520,11 @@ case "$1" in
997f8e
 	modinfo "$MODULE" || die "failed to get info for module $PATCH"
997f8e
 	;;
997f8e
 
997f8e
+"signal")
997f8e
+	[[ "$#" -ne 1 ]] && usage
997f8e
+	signal_stalled_processes
997f8e
+	;;
997f8e
+
997f8e
 "help"|"-h"|"--help")
997f8e
 	usage
997f8e
 	;;
997f8e
-- 
997f8e
1.8.3.1