diff --git a/SPECS/kpatch/kpatch.spec b/SPECS/kpatch/kpatch.spec index 87652ddfc9..549e7e79fc 100644 --- a/SPECS/kpatch/kpatch.spec +++ b/SPECS/kpatch/kpatch.spec @@ -1,13 +1,16 @@ Summary: Kpatch tooling Name: kpatch Version: 0.9.6 -Release: 2%{?dist} +Release: 4%{?dist} License: GPLv2 Vendor: Microsoft Corporation Distribution: Mariner Group: System Environment/Base URL: https://github.com/dynup/kpatch Source0: %{url}/archive/refs/tags/v%{version}.tar.gz#/%{name}-%{version}.tar.gz +Patch0: mariner_update.patch + +ExclusiveArch: x86_64 BuildRequires: binutils BuildRequires: dnf-utils @@ -17,7 +20,8 @@ BuildRequires: gcc BuildRequires: glibc-devel BuildRequires: kernel-headers -ExclusiveArch: x86_64 +Requires: binutils +Requires: gawk %description Kpatch is a Linux dynamic kernel patching infrastructure which allows you to patch @@ -31,9 +35,11 @@ It gives more control over uptime without sacrificing security or stability. Summary: Tools for building livepatches with kpatch. Group: Development/Tools +Requires: mariner-release Requires: numactl-devel Requires: openssl Requires: patch +Requires: perl Requires: rpm-build Requires: wget @@ -41,7 +47,7 @@ Requires: wget %{summary} %prep -%autosetup +%autosetup -p1 %build %make_build @@ -72,6 +78,12 @@ rm -rf %{buildroot}%{_mandir} %{_bindir}/kpatch-build %changelog +* Mon Aug 01 2022 Pawel Winogrodzki - 0.9.6-4 +- Adding missing dependency on "awk" and "binutils". + +* Thu Jun 30 2022 Pawel Winogrodzki - 0.9.6-3 +- Add Mariner-specific steps to 'kpatch-build'. + * Fri Jun 17 2022 Jon Slobodzian - 0.9.6-2 - Fix ARM64 build break (exclusive to AMD64) diff --git a/SPECS/kpatch/mariner_update.patch b/SPECS/kpatch/mariner_update.patch new file mode 100644 index 0000000000..7585207d30 --- /dev/null +++ b/SPECS/kpatch/mariner_update.patch @@ -0,0 +1,139 @@ +From 4f97bfb7590a10b6e8b267dc1f595a57a8d9659f Mon Sep 17 00:00:00 2001 +From: Pawel Winogrodzki +Date: Wed, 15 Jun 2022 10:51:00 -0700 +Subject: [PATCH] Adjustments for CBL-Mariner. + +--- + kpatch-build/kpatch-build | 61 ++++++++++++++++++++------------------- + 1 file changed, 31 insertions(+), 30 deletions(-) + +diff --git a/kpatch-build/kpatch-build b/kpatch-build/kpatch-build +index 634095d..7766e0d 100755 +--- a/kpatch-build/kpatch-build ++++ b/kpatch-build/kpatch-build +@@ -205,21 +205,6 @@ rhel_kernel_version_gte() { + [ "${ARCHVERSION}" = "$(echo -e "${ARCHVERSION}\\n$1" | sort -rV | head -n1)" ] + } + +-# klp.arch relocations were supported prior to v5.8 +-# and prior to 4.18.0-240.el8 +-use_klp_arch() +-{ +- if kernel_is_rhel; then +- ! rhel_kernel_version_gte 4.18.0-240.el8 +- else +- ! kernel_version_gte 5.8.0 +- fi +-} +- +-rhel_kernel_version_gte() { +- [ "${ARCHVERSION}" = "$(echo -e "${ARCHVERSION}\\n$1" | sort -rV | head -n1)" ] +-} +- + # klp.arch relocations were supported prior to v5.8 + # and prior to 4.18.0-284.el8 + use_klp_arch() +@@ -638,13 +623,23 @@ if [[ $DEBUG -eq 1 ]] || [[ $DEBUG -ge 3 ]]; then + set -o xtrace + fi + ++# Don't check external file. ++# shellcheck disable=SC1090 ++if [[ -z "$USERSRCDIR" ]] && [[ -f "$RELEASE_FILE" ]]; then ++ source "$RELEASE_FILE" ++ DISTRO="$ID" ++fi ++ + if [[ -n "$SRCRPM" ]]; then + if [[ -n "$ARCHVERSION" ]]; then + warn "--archversion is incompatible with --sourcerpm" + exit 1 + fi + rpmname="$(basename "$SRCRPM")" +- ARCHVERSION="${rpmname%.src.rpm}.$(uname -m)" ++ ARCHVERSION="${rpmname%.src.rpm}" ++ if [[ "$DISTRO" != mariner ]]; then ++ ARCHVERSION="${ARCHVERSION}.$(uname -m)" ++ fi + ARCHVERSION="${ARCHVERSION#kernel-}" + ARCHVERSION="${ARCHVERSION#alt-}" + fi +@@ -699,20 +694,15 @@ fi + KVER="${ARCHVERSION%%-*}" + if [[ "$ARCHVERSION" =~ - ]]; then + KREL="${ARCHVERSION##*-}" +- KREL="${KREL%.*}" ++ if [[ "$DISTRO" != mariner ]]; then ++ KREL="${KREL%.*}" ++ fi + fi + [[ "$ARCHVERSION" =~ .el7a. ]] && ALT="-alt" + + [[ -z "$TARGETS" ]] && TARGETS="vmlinux modules" + +-# Don't check external file. +-# shellcheck disable=SC1090 +-if [[ -z "$USERSRCDIR" ]] && [[ -f "$RELEASE_FILE" ]]; then +- source "$RELEASE_FILE" +- DISTRO="$ID" +-fi +- +-if [[ "$DISTRO" = fedora ]] || [[ "$DISTRO" = rhel ]] || [[ "$DISTRO" = ol ]] || [[ "$DISTRO" = centos ]] || [[ "$DISTRO" = openEuler ]]; then ++if [[ "$DISTRO" = fedora ]] || [[ "$DISTRO" = rhel ]] || [[ "$DISTRO" = ol ]] || [[ "$DISTRO" = centos ]] || [[ "$DISTRO" = openEuler ]] || [[ "$DISTRO" = mariner ]]; then + [[ -z "$VMLINUX" ]] && VMLINUX="/usr/lib/debug/lib/modules/$ARCHVERSION/vmlinux" + [[ -e "$VMLINUX" ]] || die "kernel-debuginfo-$ARCHVERSION not installed" + +@@ -748,9 +738,8 @@ elif [[ -n "$OOT_MODULE" ]]; then + fi + elif [[ -e "$KERNEL_SRCDIR"/.config ]] && [[ -e "$VERSIONFILE" ]] && [[ "$(cat "$VERSIONFILE")" = "$ARCHVERSION" ]]; then + echo "Using cache at $KERNEL_SRCDIR" +- + else +- if [[ "$DISTRO" = fedora ]] || [[ "$DISTRO" = rhel ]] || [[ "$DISTRO" = ol ]] || [[ "$DISTRO" = centos ]]; then ++ if [[ "$DISTRO" = fedora ]] || [[ "$DISTRO" = rhel ]] || [[ "$DISTRO" = ol ]] || [[ "$DISTRO" = centos ]] || [[ "$DISTRO" = mariner ]]; then + + echo "Fedora/Red Hat distribution detected" + +@@ -760,6 +749,9 @@ else + if [[ -z "$SRCRPM" ]]; then + if [[ "$DISTRO" = fedora ]]; then + wget -P "$TEMPDIR" "http://kojipkgs.fedoraproject.org/packages/kernel/$KVER/$KREL/src/kernel-$KVER-$KREL.src.rpm" 2>&1 | logger || die ++ elif [[ "$DISTRO" = mariner ]]; then ++ source "$RELEASE_FILE" ++ wget -P "$TEMPDIR" "https://packages.microsoft.com/cbl-mariner/$VERSION_ID/prod/base/srpms/kernel-$KVER-$KREL.src.rpm" 2>&1 | logger || die + else + command -v yumdownloader &>/dev/null || die "yumdownloader (yum-utils or dnf-utils) not installed" + yumdownloader --source --destdir "$TEMPDIR" "kernel$ALT-$KVER-$KREL" 2>&1 | logger || die +@@ -773,17 +765,26 @@ else + rpmbuild -D "_topdir $RPMTOPDIR" -bp --nodeps "--target=$(uname -m)" "$RPMTOPDIR"/SPECS/kernel$ALT.spec 2>&1 | logger || + die "rpmbuild -bp failed. you may need to run 'yum-builddep kernel' first." + +- mv "$RPMTOPDIR"/BUILD/kernel-*/linux-* "$KERNEL_SRCDIR" 2>&1 | logger || die ++ if [[ "$DISTRO" = mariner ]]; then ++ mv "$RPMTOPDIR"/BUILD/CBL-Mariner-Linux-Kernel-* "$KERNEL_SRCDIR" 2>&1 | logger || die ++ else ++ mv "$RPMTOPDIR"/BUILD/kernel-*/linux-* "$KERNEL_SRCDIR" 2>&1 | logger || die ++ fi + rm -rf "$RPMTOPDIR" + rm -rf "$KERNEL_SRCDIR/.git" + +- if [[ "$ARCHVERSION" == *-* ]]; then ++ if [[ "$DISTRO" != mariner ]] && [[ "$ARCHVERSION" == *-* ]]; then + sed -i "s/^EXTRAVERSION.*/EXTRAVERSION = -${ARCHVERSION##*-}/" "$KERNEL_SRCDIR/Makefile" || die + fi + + echo "$ARCHVERSION" > "$VERSIONFILE" || die + +- [[ -z "$CONFIGFILE" ]] && CONFIGFILE="$KERNEL_SRCDIR/configs/kernel$ALT-$KVER-$ARCH.config" ++ if [[ "$DISTRO" = mariner ]]; then ++ [[ -z "$CONFIGFILE" ]] && CONFIGFILE="$KERNEL_SRCDIR/new_config" ++ sed -i "s/CONFIG_LOCALVERSION=\"\"/CONFIG_LOCALVERSION=\"-$KREL\"/" "$CONFIGFILE" ++ else ++ [[ -z "$CONFIGFILE" ]] && CONFIGFILE="$KERNEL_SRCDIR/configs/kernel$ALT-$KVER-$ARCH.config" ++ fi + + (cd "$KERNEL_SRCDIR" && make mrproper 2>&1 | logger) || die + +-- +2.34.1 + diff --git a/toolkit/scripts/bump_kernel_release.sh b/toolkit/scripts/bump_kernel_release.sh new file mode 100755 index 0000000000..c7506996a0 --- /dev/null +++ b/toolkit/scripts/bump_kernel_release.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +set -e + +REPO_ROOT="$(git rev-parse --show-toplevel)" +COMMON_SCRIPTS_FOLDER="$REPO_ROOT/toolkit/scripts" + +export PATH="$PATH:$COMMON_SCRIPTS_FOLDER" + +# shellcheck source=../../toolkit/scripts/specs/specs_tools.sh +source "$COMMON_SCRIPTS_FOLDER/specs/specs_tools.sh" + +function update_manifests { + local kernel_release_number + local package_manifests_dir + + kernel_release_number="$1" + + echo "Updating package manifests." + + package_manifests_dir="$REPO_ROOT/toolkit/resources/manifests/package" + sed -i -E "s/(kernel-headers-.*-)[0-9]+(\.cm.*)/\1$kernel_release_number\2/" "$package_manifests_dir"/{pkggen,toolchain}*.txt +} + +function bump_spec_releases { + local kernel_release_number + local specs_dir_path + local specs_signed_dir_path + local specs_to_bump + + kernel_release_number="$1" + + echo "Bumping kernel specs releases to ($kernel_release_number)." + + specs_dir_path="$REPO_ROOT/SPECS" + specs_signed_dir_path="$REPO_ROOT/SPECS-SIGNED" + + specs_to_bump="$specs_dir_path/kernel-headers/kernel-headers.spec $specs_signed_dir_path/kernel-signed/kernel-signed.spec" + for spec_to_bump in $specs_to_bump + do + spec_release_number="$(spec_read_release_number "$spec_to_bump")" + if [[ "$kernel_release_number" != "$spec_release_number" ]] + then + update_spec.sh "Bump release number to match kernel release." "$spec_to_bump" + fi + done +} + +KERNEL_RELEASE_NUMBER="$(spec_read_release_number "$REPO_ROOT/SPECS/kernel/kernel.spec")" + +bump_spec_releases "$KERNEL_RELEASE_NUMBER" +update_manifests "$KERNEL_RELEASE_NUMBER" +"$COMMON_SCRIPTS_FOLDER"/livepatching/update_livepatches.sh diff --git a/toolkit/scripts/livepatching/generate_livepatch-signed_spec.sh b/toolkit/scripts/livepatching/generate_livepatch-signed_spec.sh new file mode 100755 index 0000000000..1df5c2a91d --- /dev/null +++ b/toolkit/scripts/livepatching/generate_livepatch-signed_spec.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +set -e + +REPO_ROOT="$(git rev-parse --show-toplevel)" +COMMON_SCRIPTS_FOLDER="$REPO_ROOT/toolkit/scripts" +SCRIPT_FOLDER="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" + +export PATH="$PATH:$COMMON_SCRIPTS_FOLDER" + +# shellcheck source=../../../toolkit/scripts/specs/specs_tools.sh +source "$COMMON_SCRIPTS_FOLDER/specs/specs_tools.sh" + +LIVEPATCH_SPEC_PATH="$1" + +if [[ ! -f "$LIVEPATCH_SPEC_PATH" ]] +then + echo "Must provide a livepatch spec file as first argument." >&2 + exit 1 +fi + +echo "Generating signed spec for ($LIVEPATCH_SPEC_PATH)." + +KERNEL_VERSION_RELEASE="$(grep -oP "(?<=kernel_version_release ).*" "$LIVEPATCH_SPEC_PATH")" + +DESCRIPTION="$(spec_query_srpm "$LIVEPATCH_SPEC_PATH" "%{DESCRIPTION}\n")" + +RELEASE_TAG="$(spec_read_release_tag "$LIVEPATCH_SPEC_PATH")" + +CHANGELOG="$(dump_changelog "$LIVEPATCH_SPEC_PATH")" + +# shellcheck disable=SC2034 # Variable used indirectly inside 'create_new_file_from_template'. +declare -A TEMPLATE_PLACEHOLDERS=( + ["@KERNEL_VERSION_RELEASE@"]="$KERNEL_VERSION_RELEASE" + ["@DESCRIPTION@"]="$DESCRIPTION" + ["@RELEASE_TAG@"]="$RELEASE_TAG" + ["@CHANGELOG@"]="$CHANGELOG" +) + +LIVEPATCH_SIGNED_SPEC_PATH="$REPO_ROOT/SPECS-SIGNED/livepatch-signed/livepatch-signed-$KERNEL_VERSION_RELEASE.spec" +create_new_file_from_template "$SCRIPT_FOLDER/template_livepatch-signed.spec" "$LIVEPATCH_SIGNED_SPEC_PATH" TEMPLATE_PLACEHOLDERS + +# Cgmanifest.json update skipped - already handled by the unsigned version. + +echo "Updating licensing info." + +license_map.py --no_check --update \ + SPECS/LICENSES-AND-NOTICES/data/licenses.json \ + SPECS/LICENSES-AND-NOTICES/LICENSES-MAP.md \ + "$LIVEPATCH_SIGNED_SPEC_PATH" diff --git a/toolkit/scripts/livepatching/generate_livepatch_spec.sh b/toolkit/scripts/livepatching/generate_livepatch_spec.sh new file mode 100755 index 0000000000..085d355d5d --- /dev/null +++ b/toolkit/scripts/livepatching/generate_livepatch_spec.sh @@ -0,0 +1,111 @@ +#!/bin/bash +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +set -e + +REPO_ROOT="$(git rev-parse --show-toplevel)" +COMMON_SCRIPTS_FOLDER="$REPO_ROOT/toolkit/scripts" +SCRIPT_FOLDER="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" + +export PATH="$PATH:$COMMON_SCRIPTS_FOLDER" + +# shellcheck source=../../../toolkit/scripts/specs/specs_tools.sh +source "$COMMON_SCRIPTS_FOLDER/specs/specs_tools.sh" + +function copy_kernel_sources { + echo "Copying kernel config ($LIVEPATCH_CONFIG_FILE) and trusted key ($LIVEPATCH_PUBLIC_KEY_FILE)." + + cp "$KERNEL_CONFIG_PATH" "$LIVEPATCH_SPECS_DIR/$LIVEPATCH_CONFIG_FILE" + cp "$KERNEL_PUBLIC_KEY_PATH" "$LIVEPATCH_SPECS_DIR/$LIVEPATCH_PUBLIC_KEY_FILE" +} + +function generate_livepatch_signatures { + local config_hash + local kernel_hash + local kernel_tarball_file + local public_key_hash + + echo "Generating livepatch signatures." + + config_hash="$(jq -r ".Signatures.config" "$KERNEL_SIGNATURES_PATH")" + kernel_tarball_file="kernel-$KERNEL_VERSION.tar.gz" + kernel_hash="$(jq -r ".Signatures.\"$kernel_tarball_file\"" "$KERNEL_SIGNATURES_PATH")" + public_key_hash="$(jq -r ".Signatures.\"$KERNEL_PUBLIC_KEY_FILE\"" "$KERNEL_SIGNATURES_PATH")" + + jq -n \ + --arg config_hash "$config_hash" \ + --arg config_name "$LIVEPATCH_CONFIG_FILE" \ + --arg kernel_hash "$kernel_hash" \ + --arg kernel_name "$kernel_tarball_file" \ + --arg pem_hash "$public_key_hash" \ + --arg pem_name "$LIVEPATCH_PUBLIC_KEY_FILE" \ + '{"Signatures": {($config_name): $config_hash, ($kernel_name): $kernel_hash, ($pem_name): $pem_hash}}' \ + > "$LIVEPATCH_SPECS_DIR/livepatch-$KERNEL_VERSION_RELEASE.signatures.json" +} + +function generate_livepatch_spec { + local -A template_placeholders + local latest_existing_release + local patches + local same_version_livepatches + + same_version_livepatches="$(find "$LIVEPATCH_SPECS_DIR" -name "livepatch-$KERNEL_VERSION*spec")" + if [[ -n "$same_version_livepatches" ]] + then + echo "Detected older livepatch for the current kernel version - re-using any existing patches." + + latest_existing_release=$(find "$LIVEPATCH_SPECS_DIR" -name "livepatch-$KERNEL_VERSION*spec" | grep -oP "(?<=$KERNEL_VERSION-)\d+" | sort -n | tail -1) + patches="$(grep -P "^\s*Patch\d*:.*" "$LIVEPATCH_SPECS_DIR/livepatch-$KERNEL_VERSION-$latest_existing_release".cm*spec)" + fi + + # shellcheck disable=SC2034 # Variable used indirectly inside 'create_new_file_from_template'. + template_placeholders=( + ["@KERNEL_VERSION_RELEASE@"]="$KERNEL_VERSION_RELEASE" + ["@PATCHES@"]="$patches" + ) + create_new_file_from_template "$SCRIPT_FOLDER/template_livepatch.spec" "$LIVEPATCH_SPEC_PATH" template_placeholders + + update_spec.sh "Original version for CBL-Mariner.\n- License verified." "$LIVEPATCH_SPEC_PATH" 1>/dev/null +} + +KERNEL_SPECS_DIR="$REPO_ROOT/SPECS/kernel" +KERNEL_CONFIG_PATH="$KERNEL_SPECS_DIR/config" +KERNEL_PUBLIC_KEYS=("$KERNEL_SPECS_DIR"/cbl-mariner-ca-*.pem) +KERNEL_PUBLIC_KEY_PATH="${KERNEL_PUBLIC_KEYS[0]}" +KERNEL_PUBLIC_KEY_FILE="$(basename "$KERNEL_PUBLIC_KEY_PATH")" +KERNEL_SIGNATURES_PATH="$KERNEL_SPECS_DIR/kernel.signatures.json" +KERNEL_SPEC_PATH="$KERNEL_SPECS_DIR/kernel.spec" + +KERNEL_VERSION="$(spec_read_version "$KERNEL_SPEC_PATH")" +KERNEL_VERSION_RELEASE="$(spec_query_srpm "$KERNEL_SPEC_PATH" "%{VERSION}-%{RELEASE}\n")" + +LIVEPATCH_CONFIG_FILE="config-$KERNEL_VERSION_RELEASE" +LIVEPATCH_PUBLIC_KEY_FILE="mariner-$KERNEL_VERSION_RELEASE.pem" +LIVEPATCH_SPECS_DIR="$REPO_ROOT/SPECS/livepatch" +LIVEPATCH_SPEC_PATH="$LIVEPATCH_SPECS_DIR/livepatch-$KERNEL_VERSION_RELEASE.spec" + +if [[ -f "$LIVEPATCH_SPEC_PATH" ]] +then + echo "Livepatch spec ($LIVEPATCH_SPEC_PATH) alread exists. Exiting." + exit 0 +fi + +echo "Generating empty livepatch spec for kernel ($KERNEL_VERSION_RELEASE) under ($LIVEPATCH_SPEC_PATH)." + +mkdir -p "$LIVEPATCH_SPECS_DIR" + +copy_kernel_sources +generate_livepatch_signatures +generate_livepatch_spec + +echo "Updating licensing info." + +license_map.py --no_check --update \ + SPECS/LICENSES-AND-NOTICES/data/licenses.json \ + SPECS/LICENSES-AND-NOTICES/LICENSES-MAP.md \ + "$LIVEPATCH_SPEC_PATH" + +echo "Updating the cgmanifest.json." + +update_cgmanifest.py last "$REPO_ROOT/cgmanifest.json" "$LIVEPATCH_SPEC_PATH" diff --git a/toolkit/scripts/livepatching/template_livepatch-signed.spec b/toolkit/scripts/livepatching/template_livepatch-signed.spec new file mode 100644 index 0000000000..cbc065a712 --- /dev/null +++ b/toolkit/scripts/livepatching/template_livepatch-signed.spec @@ -0,0 +1,98 @@ +%global debug_package %{nil} + +%define kernel_version_release @KERNEL_VERSION_RELEASE@ +%define kernel_version %(echo %{kernel_version_release} | grep -oP "^[^-]+") +%define kernel_release %(echo %{kernel_version_release} | grep -oP "(?<=-).+") + +# Kpatch module names allow only alphanumeric characters and '_'. +%define livepatch_name %(value="%{name}-%{version}-%{release}"; echo "${value//[^a-zA-Z0-9_]/_}") +%define livepatch_install_dir %{_libdir}/livepatching/%{kernel_version_release} +%define livepatch_module_name %{livepatch_name}.ko +%define livepatch_module_path %{livepatch_install_dir}/%{livepatch_module_name} + +%define patch_applicable_for_kernel [[ -f "%{livepatch_module_path}" && "$(uname -r)" == "%{kernel_version_release}" ]] +%define patch_installed kpatch list | grep -qP "%{livepatch_name}.*%{kernel_version_release}" +%define patch_loaded kpatch list | grep -qP "%{livepatch_name}.*enabled" + +# Install patch if the RUNNING kernel matches. +# No-op for initial (empty) livepatch. +%define install_if_should \ +if %{patch_applicable_for_kernel} && ! %{patch_installed} \ +then \ + kpatch install %{livepatch_module_path} \ +fi + +# Load patch, if the RUNNING kernel matches. +# No-op for initial (empty) livepatch. +%define load_if_should \ +if %{patch_applicable_for_kernel} && ! %{patch_loaded} \ +then \ + kpatch load %{livepatch_module_path} \ +fi + +%define uninstall_if_should \ +if %{patch_installed} \ +then \ + kpatch uninstall %{livepatch_name} \ +fi + +%define unload_if_should \ +if %{patch_loaded} \ +then \ + kpatch unload %{livepatch_name} \ +fi + +Summary: Set of livepatches for kernel %{kernel_version_release} +Name: livepatch-%{kernel_version_release} +Version: 1.0.0 +Release: @RELEASE_TAG@ +License: MIT +Vendor: Microsoft Corporation +Distribution: Mariner +Group: System Environment/Base +URL: https://github.com/microsoft/CBL-Mariner +Source0: https://github.com/microsoft/CBL-Mariner-Linux-Kernel/archive/rolling-lts/mariner-2/%{kernel_version}.tar.gz#/%{livepatch_module_name} + +ExclusiveArch: x86_64 + +Requires: coreutils +Requires: livepatching-filesystem + +Requires(post): coreutils +Requires(post): kpatch + +Requires(preun): kpatch + +Provides: livepatch = %{kernel_version_release} + +%description +@DESCRIPTION@ + +%install +install -dm 755 %{buildroot}%{livepatch_install_dir} +install -m 744 %{SOURCE0} %{buildroot}%{livepatch_module_path} + +%post +%load_if_should +%install_if_should + +%preun +%uninstall_if_should +%unload_if_should + +# Re-enable patch on rollbacks to supported kernel. +%triggerin -- kernel = %{kernel_version_release} +%load_if_should +%install_if_should + +# Prevent the patch from being loaded after a reboot to a different kernel. +# Previous kernel is still running, do NOT unload the livepatch. +%triggerin -- kernel > %{kernel_version_release}, kernel < %{kernel_version_release} +%uninstall_if_should + +%files +%defattr(-,root,root) +%dir %{livepatch_install_dir} +%{livepatch_module_path} + +@CHANGELOG@ diff --git a/toolkit/scripts/livepatching/template_livepatch.spec b/toolkit/scripts/livepatching/template_livepatch.spec new file mode 100644 index 0000000000..4f8c3374c8 --- /dev/null +++ b/toolkit/scripts/livepatching/template_livepatch.spec @@ -0,0 +1,127 @@ +%define kernel_version_release @KERNEL_VERSION_RELEASE@ +%define kernel_version %(echo %{kernel_version_release} | grep -oP "^[^-]+") +%define kernel_release %(echo %{kernel_version_release} | grep -oP "(?<=-).+") + +%define builds_module %([[ -n "$(echo "%{patches}" | grep -oP "CVE-\\d+-\\d+(?=\\.patch)")" ]] && echo 1 || echo 0) + +# Kpatch module names allow only alphanumeric characters and '_'. +%define livepatch_name %(value="%{name}-%{version}-%{release}"; echo "${value//[^a-zA-Z0-9_]/_}") +%define livepatch_install_dir %{_libdir}/livepatching/%{kernel_version_release} +%define livepatch_module_name %{livepatch_name}.ko +%define livepatch_module_path %{livepatch_install_dir}/%{livepatch_module_name} + +%define patches_description \ +%( + echo "Patches list ('*' - fixed, '!' - unfixable through livepatching, kernel update required):" + for patch in %{patches} + do + patch_file=$(basename "$patch") + + cve_number="${patch_file%.*}" + patch_suffix="${patch_file#*.}" + + if [ "$patch_suffix" = "patch" ] + then + echo "*$cve_number" + else + echo "\!$cve_number: $(cat "$patch")" + fi + done +) + +Summary: Set of livepatches for kernel %{kernel_version_release} +Name: livepatch-%{kernel_version_release} +Version: 1.0.0 +Release: 0%{?dist} +License: MIT +Vendor: Microsoft Corporation +Distribution: Mariner +Group: System Environment/Base +URL: https://github.com/microsoft/CBL-Mariner +Source0: https://github.com/microsoft/CBL-Mariner-Linux-Kernel/archive/rolling-lts/mariner-2/%{kernel_version}.tar.gz#/kernel-%{kernel_version}.tar.gz +Source1: config-%{kernel_version_release} +Source2: mariner-%{kernel_version_release}.pem +@PATCHES@ + +ExclusiveArch: x86_64 + +# Must be kept below the "Patch" tags to correctly evaluate %%builds_module. +%if %{builds_module} +BuildRequires: audit-devel +BuildRequires: bash +BuildRequires: bc +BuildRequires: binutils +BuildRequires: bison +BuildRequires: diffutils +BuildRequires: dwarves +BuildRequires: elfutils-libelf-devel +BuildRequires: flex +BuildRequires: gcc +BuildRequires: glib-devel +BuildRequires: glibc-devel +BuildRequires: kbd +BuildRequires: kernel-debuginfo = %{kernel_version_release} +BuildRequires: kernel-headers = %{kernel_version_release} +BuildRequires: kmod-devel +BuildRequires: kpatch-build +BuildRequires: libdnet-devel +BuildRequires: libmspack-devel +BuildRequires: make +BuildRequires: openssl +BuildRequires: openssl-devel +BuildRequires: pam-devel +BuildRequires: procps-ng-devel +BuildRequires: python3-devel +BuildRequires: rpm-build +%else +%global debug_package %{nil} +# endif builds_module +%endif + +Provides: livepatch = %{kernel_version_release} + +%description +A set of kernel livepatches addressing CVEs present in Mariner's +kernel version %{kernel_version_release}. +%{patches_description} + +%if %{builds_module} + +%prep +%setup -q -n CBL-Mariner-Linux-Kernel-rolling-lts-mariner-2-%{kernel_version} + +cp %{SOURCE1} .config +cp %{SOURCE2} certs/mariner.pem + +sed -i 's#CONFIG_SYSTEM_TRUSTED_KEYS=""#CONFIG_SYSTEM_TRUSTED_KEYS="certs/mariner.pem"#' .config +sed -i 's/CONFIG_LOCALVERSION=""/CONFIG_LOCALVERSION="-%{kernel_release}"/' .config + +%build +# Building cumulative patch. +all_patches_file=all.patch +for patch in %{patches} +do + [[ "$patch" == *.patch ]] && cat "$patch" >> $all_patches_file +done + +kpatch-build -ddd \ + --sourcedir . \ + --vmlinux %{_libdir}/debug/lib/modules/%{kernel_version_release}/vmlinux \ + --name %{livepatch_name} \ + $all_patches_file + +%install +install -dm 755 %{buildroot}%{livepatch_install_dir} +install -m 744 %{livepatch_module_name} %{buildroot}%{livepatch_module_path} + +# endif builds_module +%endif + +%files +%defattr(-,root,root) +%if %{builds_module} +%dir %{livepatch_install_dir} +%{livepatch_module_path} +%endif + +%changelog \ No newline at end of file diff --git a/toolkit/scripts/livepatching/update_livepatches.sh b/toolkit/scripts/livepatching/update_livepatches.sh new file mode 100755 index 0000000000..e98ab57a6a --- /dev/null +++ b/toolkit/scripts/livepatching/update_livepatches.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +set -e + +SPECS_DIR="$(git rev-parse --show-toplevel)/SPECS" +SCRIPT_FOLDER="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" + +echo "Updating livepatch specs." + +"$SCRIPT_FOLDER"/generate_livepatch_spec.sh + +for livepatch_spec in "$SPECS_DIR/livepatch/"livepatch-*.spec +do + "$SCRIPT_FOLDER"/generate_livepatch-signed_spec.sh "$livepatch_spec" +done diff --git a/toolkit/scripts/specs/specs_tools.sh b/toolkit/scripts/specs/specs_tools.sh new file mode 100644 index 0000000000..a7ab8ed132 --- /dev/null +++ b/toolkit/scripts/specs/specs_tools.sh @@ -0,0 +1,221 @@ +#!/bin/bash + +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +set -e + +# shellcheck source=../../../toolkit/scripts/rpmops.sh +source "$(git rev-parse --show-toplevel)"/toolkit/scripts/rpmops.sh + +USER_EMAIL="" +USER_NAME="" + +init() { + local init_failed=false + + if [[ -z $USER_EMAIL ]] + then + USER_EMAIL="$(git config user.email)" + fi + + if [[ -z $USER_NAME ]] + then + USER_NAME="$(git config user.name)" + fi + + if [[ -z $USER_EMAIL ]] + then + echo "ERROR: must set git user e-mail. Try running 'git config --local user.email [USER_EMAIL]'." >&2 + init_failed=true + fi + + if [[ -z $USER_NAME ]] + then + echo "ERROR: must set git user name. Try running 'git config --local user.name [USER_NAME]'." >&2 + init_failed=true + fi + + if $init_failed + then + exit 1 + fi +} + +add_changelog_entry() { + local changelog_header + local changelog_indents + local changelog_message + local epoch + local next_release + local spec_path + local version + + spec_path="$1" + changelog_message="$2" + + next_release=$(spec_read_release_number "$spec_path") + next_release=$((next_release+1)) + + version=$(spec_read_version "$spec_path") + + epoch="$(spec_read_epoch "$spec_path"):" + if [[ "$epoch" == "(none):" ]] + then + epoch="" + fi + + changelog_header=$(date "+%a %b %d %Y $USER_NAME <$USER_EMAIL> - $epoch$version-$next_release") + + changelog_indents=$(grep -m 1 -P "^\*.*@.*>" "$spec_path" | sed -E "s/^\*(\s+).*/\1/") + if [[ -z "$changelog_indents" ]] + then + changelog_indents=" " + fi + + spec_set_release_number "$spec_path" $next_release + sed -i -E "/\s*^%changelog.*/a *$changelog_indents$changelog_header\n-$changelog_indents$changelog_message\n" "$spec_path" + # Remove excessive empty lines, if present. + sed -i -e :a -e '/^\n*$/{$d;N;ba' -e '}' "$spec_path" +} + +create_new_file_from_template() { + local -n placeholders + local key + local target_path + local template_path + local value + + template_path="$1" + target_path="$2" + placeholders="$3" + + echo "Creating a new file under \"$target_path\" from template \"$template_path\"." + + mkdir -p "$(dirname "$target_path")" + + cp "$template_path" "$target_path" + + for key in "${!placeholders[@]}" + do + value="${placeholders[$key]}" + awk -i inplace -v r="$value" "{gsub(/$key/,r)}1" "$target_path" + done +} + +dump_changelog() { + local spec_path + + spec_path="$1" + + sed -n '/%changelog/,$p' "$spec_path" +} + +parsed_spec_read_patches() { + parsed_spec_read_tags "$1" "Patch" "$2" +} + +parsed_spec_read_tag() { + local spec_path + local tag + + spec_path="$1" + tag="$2" + + mariner_rpmspec --query --queryformat="%{$tag}\n" --srpm "$spec_path" 2>/dev/null +} + +parsed_spec_read_tags() { + local -n results_array + local spec_path + local tag + + spec_path="$1" + tag="$2" + results_array="$3" + + for result in $(mariner_rpmspec --query --queryformat="[%{$tag}\n]" --srpm "$spec_path" 2>/dev/null | tac) + do + results_array+=("$result") + done +} + +spec_query_srpm() { + local query_format + local spec_path + + spec_path="$1" + query_format="$2" + + mariner_rpmspec -q --queryformat="$query_format" --srpm "$spec_path" 2>/dev/null +} + +spec_read_release_number() { + spec_read_tag_skip_macros "$1" "Release" +} + +spec_read_release_tag() { + spec_read_tag "$1" "Release" +} + +spec_read_tag() { + local spec_path + local tag + + spec_path="$1" + tag="$2" + + grep "^$tag:" "$spec_path" | sed -E "s/$tag:\s+([^#]+)/\1/" +} + +spec_read_tag_skip_macros() { + local spec_path + local tag + + spec_path="$1" + tag="$2" + + spec_read_tag "$spec_path" "$tag" | grep -oP "^[^%]+" +} + +spec_read_epoch() { + parsed_spec_read_tag "$1" "Epoch" +} + +spec_read_version() { + parsed_spec_read_tag "$1" "Version" +} + +spec_set_tag() { + local spec_path + local tag + local value + + spec_path="$1" + tag="$2" + value="$3" + + sed -i -E "s/($tag:\s+).*/\1$value/" "$spec_path" +} + +spec_set_release_number() { + local spec_path + local value + + spec_path="$1" + value="$2" + + spec_set_tag "$spec_path" "Release" "$value%{?dist}" +} + +spec_set_version() { + local spec_path + local value + + spec_path="$1" + value="$2" + + spec_set_tag "$spec_path" "Version" "$value" +} + +init diff --git a/toolkit/scripts/update_spec.sh b/toolkit/scripts/update_spec.sh index 8026fe3bdb..f86ef89784 100755 --- a/toolkit/scripts/update_spec.sh +++ b/toolkit/scripts/update_spec.sh @@ -8,8 +8,10 @@ # $1 - Changelog message. # ${@:2} - Paths to spec files to update. -# shellcheck source=../../toolkit/scripts/rpmops.sh -source "$(git rev-parse --show-toplevel)"/toolkit/scripts/rpmops.sh +REPO_ROOT="$(git rev-parse --show-toplevel)" + +# shellcheck source=../../toolkit/scripts/specs/specs_tools.sh +source "$REPO_ROOT"/toolkit/scripts/specs/specs_tools.sh changelog_message="$1" @@ -19,21 +21,6 @@ then exit 1 fi -user_email="$(git config user.email)" -user_name="$(git config user.name)" - -if [[ -z $user_email ]] -then - echo "ERROR: must set git user e-mail. Try running 'git config --local user.email [user_email]'." >&2 - exit 1 -fi - -if [[ -z $user_name ]] -then - echo "ERROR: must set git user name. Try running 'git config --local user.name [user_name]'." >&2 - exit 1 -fi - for spec_path in "${@:2}" do if [[ ! -f "$spec_path" ]] @@ -44,18 +31,5 @@ do echo "Updating '$spec_path'." - release=$(grep -oP "^Release:\s*\d+" "$spec_path" | grep -oP "\d+$") - release=$((release+1)) - version=$(mariner_rpmspec --srpm -q "$spec_path" --qf "%{VERSION}\n" 2>/dev/null) - - epoch="$(mariner_rpmspec --srpm -q "$spec_path" --qf "%{EPOCH}\n" 2>/dev/null):" - if [[ "$epoch" == "(none):" ]] - then - epoch="" - fi - - sed -i -E "s/^(Release:\s*).*/\1$release%{?dist}/" "$spec_path" - changelog_header=$(date "+%a %b %d %Y $user_name <$user_email> - $epoch$version-$release") - changelog_indents=$(grep -m 1 -P "^\*.*@.*>" "$spec_path" | sed -E "s/^\*(\s+).*/\1/") - sed -i -E "/\s*^%changelog.*/a *$changelog_indents$changelog_header\n-$changelog_indents$changelog_message\n" "$spec_path" + add_changelog_entry "$spec_path" "$changelog_message" done