855 lines · 19.2 KB · Text
RawDownload
#!/bin/bash# SPDX-License-Identifier: GPL-2.0## Build a livepatch module # shellcheck disable=SC1090,SC2155 if (( BASH_VERSINFO[0]  < 4 || \     (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4) )); then		echo "error: this script requires bash 4.4+" >&2	exit 1fi set -o errexitset -o errtraceset -o pipefailset -o nounset # Allow doing 'cmd | mapfile -t array' instead of 'mapfile -t array < <(cmd)'.# This helps keep execution in pipes so pipefail+errexit can catch errors.shopt -s lastpipe unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP XTRACE REPLACE=1SHORT_CIRCUIT=0JOBS="$(getconf _NPROCESSORS_ONLN)"VERBOSE="-s"shopt -o xtrace | grep -q 'on' && XTRACE=1 # Avoid removing the previous $TMP_DIR until args have been fully processed.KEEP_TMP=1 SCRIPT="$(basename "$0")"SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"FIX_PATCH_LINES="$SCRIPT_DIR/fix-patch-lines" SRC="$(pwd)"OBJ="$(pwd)" CONFIG="$OBJ/.config"TMP_DIR="$OBJ/klp-tmp" ORIG_DIR="$TMP_DIR/orig"PATCHED_DIR="$TMP_DIR/patched"DIFF_DIR="$TMP_DIR/diff"KMOD_DIR="$TMP_DIR/kmod" STASH_DIR="$TMP_DIR/stash"TIMESTAMP="$TMP_DIR/timestamp"PATCH_TMP_DIR="$TMP_DIR/tmp" KLP_DIFF_LOG="$DIFF_DIR/diff.log" # Terminal output colorsread -r COLOR_RESET COLOR_BOLD COLOR_ERROR COLOR_WARN <<< ""if [[ -t 1 && -t 2 ]]; then	COLOR_RESET="\033[0m"	COLOR_BOLD="\033[1m"	COLOR_ERROR="\033[0;31m"	COLOR_WARN="\033[0;33m"fi grep0() {	# shellcheck disable=SC2317	command grep "$@" || true} # Because pipefail is enabled, the grep0 helper should be used instead of# grep, otherwise a failed match can propagate to an error.grep() {	echo "error: $SCRIPT: use grep0 or 'command grep' instead of bare grep" >&2	exit 1} status() {	echo -e "${COLOR_BOLD}$*${COLOR_RESET}"} warn() {	echo -e "${COLOR_WARN}warning${COLOR_RESET}: $SCRIPT: $*" >&2} die() {	echo -e "${COLOR_ERROR}error${COLOR_RESET}: $SCRIPT: $*" >&2	exit 1} declare -a STASHED_FILES stash_file() {	local file="$1"	local rel_file="${file#"$SRC"/}" 	[[ ! -e "$file" ]] && die "no file to stash: $file" 	mkdir -p "$STASH_DIR/$(dirname "$rel_file")"	cp -f "$file" "$STASH_DIR/$rel_file" 	STASHED_FILES+=("$rel_file")} restore_files() {	local file 	for file in "${STASHED_FILES[@]}"; do		mv -f "$STASH_DIR/$file" "$SRC/$file" || warn "can't restore file: $file"	done 	STASHED_FILES=()} cleanup() {	set +o nounset	revert_patches	restore_files	[[ "$KEEP_TMP" -eq 0 ]] && rm -rf "$TMP_DIR"	return 0} trap_err() {	die "line ${BASH_LINENO[0]}: '$BASH_COMMAND'"} trap cleanup  EXIT INT TERM HUPtrap trap_err ERR __usage() {	cat <<EOFUsage: $SCRIPT [OPTIONS] PATCH_FILE(s)Generate a livepatch module. Options:   -f, --show-first-changed	Show address of first changed instruction   -j, --jobs=<jobs>		Build jobs to run simultaneously [default: $JOBS]   -o, --output=<file.ko>	Output file [default: livepatch-<patch-name>.ko]       --no-replace		Disable livepatch atomic replace   -v, --verbose		Pass V=1 to kernel/module builds Advanced Options:   -d, --debug			Show symbol/reloc cloning decisions   -S, --short-circuit=STEP	Start at build step (requires prior --keep-tmp)				   1|orig	Build original kernel (default)				   2|patched	Build patched kernel				   3|diff	Diff objects				   4|kmod	Build patch module   -T, --keep-tmp		Preserve tmp dir on exit EOF} usage() {	__usage >&2} process_args() {	local keep_tmp=0	local short	local long	local args 	short="hfj:o:vdS:T"	long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp" 	args=$(getopt --options "$short" --longoptions "$long" -- "$@") || {		echo; usage; exit	}	eval set -- "$args" 	while true; do		case "$1" in			-h | --help)				usage				exit 0				;;			-f | --show-first-changed)				DIFF_CHECKSUM=1				shift				;;			-j | --jobs)				JOBS="$2"				shift 2				;;			-o | --output)				[[ "$2" != *.ko ]] && die "output filename should end with .ko"				OUTFILE="$2"				NAME="$(basename "$OUTFILE")"				NAME="${NAME%.ko}"				NAME="$(module_name_string "$NAME")"				shift 2				;;			--no-replace)				REPLACE=0				shift				;;			-v | --verbose)				VERBOSE="V=1"				shift				;;			-d | --debug)				DEBUG_CLONE=1				keep_tmp=1				shift				;;			-S | --short-circuit)				[[ ! -d "$TMP_DIR" ]] && die "--short-circuit requires preserved klp-tmp dir"				keep_tmp=1				case "$2" in					1 | orig)	SHORT_CIRCUIT=1; ;;					2 | patched)	SHORT_CIRCUIT=2; ;;					3 | diff)	SHORT_CIRCUIT=3; ;;					4 | mod)	SHORT_CIRCUIT=4; ;;					*)		die "invalid short-circuit step '$2'" ;;				esac				shift 2				;;			-T | --keep-tmp)				keep_tmp=1				shift				;;			--)				shift				break				;;			*)				usage				exit 1				;;		esac	done 	if [[ $# -eq 0 ]] && (( SHORT_CIRCUIT <= 2 )); then		usage		exit 1	fi 	KEEP_TMP="$keep_tmp"	PATCHES=("$@")} # temporarily disable xtrace for especially verbose codextrace_save() {	[[ -v XTRACE ]] && set +x	return 0} xtrace_restore() {	[[ -v XTRACE ]] && set -x	return 0} validate_config() {	xtrace_save "reading .config"	source "$CONFIG" || die "no .config file in $(dirname "$CONFIG")"	xtrace_restore 	[[ -v CONFIG_LIVEPATCH ]] ||			\		die "CONFIG_LIVEPATCH not enabled" 	[[ -v CONFIG_KLP_BUILD ]] ||			\		die "CONFIG_KLP_BUILD not enabled" 	[[ -v CONFIG_GCC_PLUGIN_LATENT_ENTROPY ]] &&	\		die "kernel option 'CONFIG_GCC_PLUGIN_LATENT_ENTROPY' not supported" 	[[ -v CONFIG_GCC_PLUGIN_RANDSTRUCT ]] &&	\		die "kernel option 'CONFIG_GCC_PLUGIN_RANDSTRUCT' not supported" 	[[ -v CONFIG_AS_IS_LLVM ]] &&				\		[[ "$CONFIG_AS_VERSION" -lt 200000 ]] &&	\		die "Clang assembler version < 20 not supported" 	return 0} # Only allow alphanumerics and '_' and '-' in the module name.  Everything else# is replaced with '-'.  Also truncate to 55 chars so the full name + NUL# terminator fits in the kernel's 56-byte module name array.module_name_string() {	echo "${1//[^a-zA-Z0-9_-]/-}" | cut -c 1-55} # If the module name wasn't specified on the cmdline with --output, give it a# name based on the patch name.set_module_name() {	[[ -v NAME ]] && return 0 	if [[ "${#PATCHES[@]}" -eq 1 ]]; then		NAME="$(basename "${PATCHES[0]}")"		NAME="${NAME%.*}"	else		NAME="patch"	fi 	NAME="livepatch-$NAME"	NAME="$(module_name_string "$NAME")" 	OUTFILE="$NAME.ko"} # Hardcode the value printed by the localversion script to prevent patch# application from appending it with '+' due to a dirty working tree.set_kernelversion() {	local file="$SRC/scripts/setlocalversion"	local kernelrelease 	stash_file "$file" 	kernelrelease="$(cd "$SRC" && make syncconfig &>/dev/null && make -s kernelrelease)"	[[ -z "$kernelrelease" ]] && die "failed to get kernel version" 	sed -i "2i echo $kernelrelease; exit 0" scripts/setlocalversion} get_patch_input_files() {	local patch="$1" 	grep0 -E '^--- ' "$patch"				\		| grep0 -v -e '/dev/null' -e '1969-12-31' -e '1970-01-01' \		| gawk '{print $2}'				\		| sed 's|^[^/]*/||'				\		| sort -u} get_patch_output_files() {	local patch="$1" 	grep0 -E '^\+\+\+ ' "$patch"				\		| grep0 -v -e '/dev/null' -e '1969-12-31' -e '1970-01-01' \		| gawk '{print $2}'				\		| sed 's|^[^/]*/||'				\		| sort -u} get_patch_files() {	local patch="$1" 	{ get_patch_input_files "$patch"; get_patch_output_files "$patch"; } \		| sort -u} check_unsupported_patches() {	local patch 	for patch in "${PATCHES[@]}"; do		local files=() 		get_patch_files "$patch" | mapfile -t files 		for file in "${files[@]}"; do			case "$file" in				lib/*|*.S)					die "${patch}: unsupported patch to $file"					;;			esac		done	done} apply_patch() {	local patch="$1"	shift	local extra_args=("$@")	local drift_regex="with fuzz|offset [0-9]+ line"	local output	local status 	[[ ! -f "$patch" ]] && die "$patch doesn't exist"	status=0	output=$(patch -d "$SRC" -p1 --dry-run --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" < "$patch" 2>&1) || status=$?	if [[ "$status" -ne 0 ]]; then		echo "$output" >&2		die "$patch did not apply"	elif [[ "$output" =~ $drift_regex ]]; then		echo "$output" >&2		warn "${patch} applied with fuzz"	fi 	patch -d "$SRC" -p1 --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" --silent < "$patch"	APPLIED_PATCHES+=("$patch")} revert_patch() {	local patch="$1"	local tmp=() 	patch -d "$SRC" -p1 -R --silent --no-backup-if-mismatch -r /dev/null < "$patch" 	for p in "${APPLIED_PATCHES[@]}"; do		[[ "$p" == "$patch" ]] && continue		tmp+=("$p")	done 	APPLIED_PATCHES=("${tmp[@]}")} apply_patches() {	local extra_args=("$@")	local patch 	for patch in "${PATCHES[@]}"; do		apply_patch "$patch" "${extra_args[@]}"	done} revert_patches() {	local patches=("${APPLIED_PATCHES[@]}") 	for (( i=${#patches[@]}-1 ; i>=0 ; i-- )) ; do		revert_patch "${patches[$i]}"	done 	APPLIED_PATCHES=()} validate_patches() {	check_unsupported_patches	apply_patches	revert_patches} do_init() {	# We're not yet smart enough to handle anything other than in-tree	# builds in pwd.	[[ ! "$SRC" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory"	[[ ! "$OBJ" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory" 	(( SHORT_CIRCUIT <= 1 )) && rm -rf "$TMP_DIR"	mkdir -p "$TMP_DIR" 	APPLIED_PATCHES=() 	[[ -x "$FIX_PATCH_LINES" ]] || die "can't find fix-patch-lines"	command -v recountdiff &>/dev/null || die "recountdiff not found (install patchutils)" 	validate_config	set_module_name	set_kernelversion} # Refresh the patch hunk headers, specifically the line numbers and counts.refresh_patch() {	local patch="$1"	local tmpdir="$PATCH_TMP_DIR"	local input_files=()	local output_files=() 	rm -rf "$tmpdir"	mkdir -p "$tmpdir/a"	mkdir -p "$tmpdir/b" 	# Get all source files affected by the patch	get_patch_input_files "$patch" | mapfile -t input_files	get_patch_output_files "$patch" | mapfile -t output_files 	# Copy orig source files to 'a'	( cd "$SRC" && echo "${input_files[@]}" | xargs cp --parents --target-directory="$tmpdir/a" ) 	# Copy patched source files to 'b'	apply_patch "$patch" "--silent"	( cd "$SRC" && echo "${output_files[@]}" | xargs cp --parents --target-directory="$tmpdir/b" )	revert_patch "$patch" 	# Diff 'a' and 'b' to make a clean patch	( cd "$tmpdir" && diff -Nupr a b > "$patch" ) || true} # Copy the patches to a temporary directory, fix their lines so as not to# affect the __LINE__ macro for otherwise unchanged functions further down the# file, and update $PATCHES to point to the fixed patches.fix_patches() {	local idx	local i 	rm -f "$TMP_DIR"/*.patch 	idx=0001	for i in "${!PATCHES[@]}"; do		local old_patch="${PATCHES[$i]}"		local tmp_patch="$TMP_DIR/tmp.patch"		local patch="${PATCHES[$i]}"		local new_patch 		new_patch="$TMP_DIR/$idx-fixed-$(basename "$patch")" 		cp -f "$old_patch" "$tmp_patch"		refresh_patch "$tmp_patch"		"$FIX_PATCH_LINES" "$tmp_patch" | recountdiff > "$new_patch" 		PATCHES[i]="$new_patch" 		rm -f "$tmp_patch"		idx=$(printf "%04d" $(( 10#$idx + 1 )))	done} clean_kernel() {	local cmd=() 	cmd=("make")

Lines 1–500 of 855.