Joe Lawrence 083416
From cdee6bd650a35075515d4fe2bb67657811c9640c Mon Sep 17 00:00:00 2001
Joe Lawrence 083416
From: Joe Lawrence <joe.lawrence@redhat.com>
Joe Lawrence 083416
Date: Mon, 16 Nov 2020 15:21:59 -0500
Joe Lawrence 083416
Subject: [PATCH] kpatch: wait for module ref counts on unload
Joe Lawrence 083416
Joe Lawrence 083416
There exists a very small timing window in which "kpatch unload" gets to
Joe Lawrence 083416
its "rmmod" step before the kpatch-patch module's reference count has
Joe Lawrence 083416
cleared and the "rmmod" fails.
Joe Lawrence 083416
Joe Lawrence 083416
This is only a transient problem, but we can adopt code from upstream
Joe Lawrence 083416
livepatch kselftests which wait for the module refcounts to settle
Joe Lawrence 083416
before moving onto "rmmod".
Joe Lawrence 083416
Joe Lawrence 083416
A small wrinkle is that this is not supported by the older kpatch.ko
Joe Lawrence 083416
core.  The price for circumventing the activeness safety check via
Joe Lawrence 083416
KPATCH_FORCE_UNSAFE is that it must leave the kpatch patch modules in
Joe Lawrence 083416
place (see e1890e627a9b ("prevent rmmod of forced modules")).
Joe Lawrence 083416
Joe Lawrence 083416
Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
Joe Lawrence 083416
---
Joe Lawrence 083416
 kpatch/kpatch | 40 ++++++++++++++++++++++++++++++++++++++--
Joe Lawrence 083416
 1 file changed, 38 insertions(+), 2 deletions(-)
Joe Lawrence 083416
Joe Lawrence 083416
diff --git a/kpatch/kpatch b/kpatch/kpatch
Joe Lawrence 083416
index bca8f41..b35b742 100755
Joe Lawrence 083416
--- a/kpatch/kpatch
Joe Lawrence 083416
+++ b/kpatch/kpatch
Joe Lawrence 083416
@@ -28,6 +28,7 @@ SCRIPTDIR="$(readlink -f "$(dirname "$(type -p "$0")")")"
Joe Lawrence 083416
 VERSION="0.9.2"
Joe Lawrence 083416
 POST_ENABLE_WAIT=15	# seconds
Joe Lawrence 083416
 POST_SIGNAL_WAIT=60	# seconds
Joe Lawrence 083416
+MODULE_REF_WAIT=15	# seconds
Joe Lawrence 083416
 
Joe Lawrence 083416
 # How many times to try loading the patch if activeness safety check fails.
Joe Lawrence 083416
 MAX_LOAD_ATTEMPTS=5
Joe Lawrence 083416
@@ -125,6 +126,10 @@ find_core_module() {
Joe Lawrence 083416
 	return 1
Joe Lawrence 083416
 }
Joe Lawrence 083416
 
Joe Lawrence 083416
+kpatch_core_loaded() {
Joe Lawrence 083416
+	grep -q -e "T kpatch_register" /proc/kallsyms
Joe Lawrence 083416
+}
Joe Lawrence 083416
+
Joe Lawrence 083416
 core_loaded () {
Joe Lawrence 083416
 	grep -q -e "T klp_enable_patch" -e "T kpatch_register" /proc/kallsyms
Joe Lawrence 083416
 }
Joe Lawrence 083416
@@ -265,6 +270,31 @@ wait_for_patch_transition() {
Joe Lawrence 083416
 	return 1
Joe Lawrence 083416
 }
Joe Lawrence 083416
 
Joe Lawrence 083416
+module_ref_count() {
Joe Lawrence 083416
+	local modname="$1"
Joe Lawrence 083416
+	[[ $(cat "/sys/module/$modname/refcnt" 2>/dev/null) != "0" ]]
Joe Lawrence 083416
+}
Joe Lawrence 083416
+
Joe Lawrence 083416
+wait_for_zero_module_ref_count() {
Joe Lawrence 083416
+	local modname="$1"
Joe Lawrence 083416
+	local i=0
Joe Lawrence 083416
+
Joe Lawrence 083416
+	# We can't rely on a zero refcount with kpatch.ko as it
Joe Lawrence 083416
+	# implements KPATCH_FORCE_UNSAFE with an additional reference on
Joe Lawrence 083416
+	# kpatch-patch modules to avoid potential crashes.
Joe Lawrence 083416
+	kpatch_core_loaded && return 0
Joe Lawrence 083416
+
Joe Lawrence 083416
+	module_ref_count "$modname" || return 0
Joe Lawrence 083416
+
Joe Lawrence 083416
+	echo "waiting (up to $MODULE_REF_WAIT seconds) for module refcount..."
Joe Lawrence 083416
+	for (( i=0; i
Joe Lawrence 083416
+		module_ref_count "$modname" || return 0
Joe Lawrence 083416
+		sleep 1s
Joe Lawrence 083416
+	done
Joe Lawrence 083416
+
Joe Lawrence 083416
+	return 1
Joe Lawrence 083416
+}
Joe Lawrence 083416
+
Joe Lawrence 083416
 load_module () {
Joe Lawrence 083416
 	local module="$1"
Joe Lawrence 083416
 
Joe Lawrence 083416
@@ -381,10 +411,16 @@ disable_patch_strict () {
Joe Lawrence 083416
 }
Joe Lawrence 083416
 
Joe Lawrence 083416
 remove_module () {
Joe Lawrence 083416
-	echo "unloading patch module: $1"
Joe Lawrence 083416
+	local modname="$1"
Joe Lawrence 083416
+
Joe Lawrence 083416
+	if ! wait_for_zero_module_ref_count "$modname"; then
Joe Lawrence 083416
+		die "failed to unload module $modname (refcnt)"
Joe Lawrence 083416
+	fi
Joe Lawrence 083416
+
Joe Lawrence 083416
+	echo "unloading patch module: $modname"
Joe Lawrence 083416
 	# ignore any error here because rmmod can fail if the module used
Joe Lawrence 083416
 	# KPATCH_FORCE_UNSAFE.
Joe Lawrence 083416
-	rmmod "$1" 2> /dev/null || return 0
Joe Lawrence 083416
+	rmmod "$modname" 2> /dev/null || return 0
Joe Lawrence 083416
 }
Joe Lawrence 083416
 
Joe Lawrence 083416
 unload_module () {
Joe Lawrence 083416
-- 
Joe Lawrence 083416
2.25.4
Joe Lawrence 083416