diff --git a/.buildkite/pipeline_pr.py b/.buildkite/pipeline_pr.py index 7f85f777c6b..f5818f8bd02 100755 --- a/.buildkite/pipeline_pr.py +++ b/.buildkite/pipeline_pr.py @@ -68,6 +68,15 @@ for step in kani_grp["steps"]: step["label"] = "🔍 Kani" +if any(x.parent.name == "hiding_ci" for x in changed_files): + pipeline.build_group_per_arch( + "🕵️ Build Secret Hiding Kernel", + pipeline.devtool_test( + pytest_opts="-m secret_hiding integration_tests/build/test_hiding_kernel.py", + ), + depends_on_build=False, + ) + if run_all_tests(changed_files): pipeline.build_group( "📦 Build", diff --git a/resources/hiding_ci/build_and_install_kernel.sh b/resources/hiding_ci/build_and_install_kernel.sh new file mode 100755 index 00000000000..c898a581384 --- /dev/null +++ b/resources/hiding_ci/build_and_install_kernel.sh @@ -0,0 +1,170 @@ +#!/bin/bash +# Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +# fail if we encounter an error, uninitialized variable or a pipe breaks +set -eu -o pipefail + +check_root() { + # We need sudo privileges to install the kernel + if [ "$(id -u)" -ne 0 ]; then + echo "To install, this script must be run as root or with sudo privileges" + exit 1 + fi +} + +check_ubuntu() { + # Currently this script only works on Ubuntu instances + if ! grep -qi 'ubuntu' /etc/os-release; then + echo "This script currently only works on Ubuntu." + exit 1 + fi +} + +tidy_up() { + # Some cleanup after we are done + echo "Cleaning up.." + popd + rm -rf $TMP_BUILD_DIR +} + +confirm() { + if [[ "$*" == *"--no-install"* ]]; then + echo "Not installing new kernel." + + if [[ "$*" == *"--tidy"* ]]; then + tidy_up + fi + + exit 0 + fi + + if [[ "$*" == *"--install"* ]]; then + return 0 + fi + + while true; do + read -p "Do you want to install the new kernel? (y/n) " yn + case $yn in + [Yy]*) return 0 ;; + [Nn]*) + echo "Exiting..." + exit 1 + ;; + *) echo "Please answer yes or no." ;; + esac + done +} + +apply_patch_file() { + git apply $1 +} + +apply_series_mbox() { + git am $1 --empty=drop +} + +apply_series_link() { + patch_url=$(cat $1) + echo "Fetching mbox from:" $patch_url + curl --output lore.mbox.gz "$patch_url/t.mbox.gz" + gunzip lore.mbox + apply_series_mbox lore.mbox + rm lore.mbox +} + +apply_patch_or_series() { + case "$1" in + *.patch) apply_patch_file $1 ;; + *.mbox) apply_series_mbox $1 ;; + *.lore) apply_series_link $1 ;; + *) + echo "Uknown patch file: "$1 + exit 1 + ;; + esac +} + +check_override_presence() { + while IFS= read -r line; do + if ! grep -Fq "$line" .config; then + echo "Missing config: $line" + exit 1 + fi + done <"$KERNEL_CONFIG_OVERRIDES" + + echo "All overrides correctly applied.." +} + +KERNEL_URL=$(cat kernel_url) +KERNEL_COMMIT_HASH=$(cat kernel_commit_hash) +KERNEL_PATCHES_DIR=$(pwd)/patches +KERNEL_CONFIG_OVERRIDES=$(pwd)/kernel_config_overrides + +TMP_BUILD_DIR=$(mktemp -d -t kernel-build-XXXX) + +pushd . +cd $TMP_BUILD_DIR + +echo "Cloning kernel repository into" $TMP_BUILD_DIR + +# We checkout the repository that way to make it as +# small and fast as possible +git init +git remote add origin $KERNEL_URL +git fetch --depth 1 origin $KERNEL_COMMIT_HASH +git checkout FETCH_HEAD + +# Apply our patches on top +for PATCH in $KERNEL_PATCHES_DIR/*.*; do + echo "Applying patch:" $(basename $PATCH) + apply_patch_or_series $PATCH +done + +echo "Making kernel config ready for build" +# We use olddefconfig to automatically pull in the +# config from the AMI and update to the newest +# defaults +make olddefconfig + +# Disable the ubuntu keys +scripts/config --disable SYSTEM_TRUSTED_KEYS +scripts/config --disable SYSTEM_REVOCATION_KEYS + +# We run this again to default options now changed by +# the disabling of the ubuntu keys +make olddefconfig + +# Apply our config overrides on top of the config +scripts/kconfig/merge_config.sh -m .config $KERNEL_CONFIG_OVERRIDES + +check_override_presence + +echo "Building kernel this may take a while" +make -s -j $(nproc) +echo "Building kernel modules" +make modules -s -j $(nproc) +echo "Kernel build complete!" + +KERNEL_VERSION=$(KERNELVERSION=$(make -s kernelversion) ./scripts/setlocalversion) + +echo "New kernel version:" $KERNEL_VERSION + +# Make sure a user really wants to install this kernel +confirm "$@" + +check_root +check_ubuntu + +echo "Installing kernel modules..." +make INSTALL_MOD_STRIP=1 modules_install +echo "Installing kernel..." +make INSTALL_MOD_STRIP=1 install +echo "Update initramfs" +update-initramfs -c -k $KERNEL_VERSION +echo "Updating GRUB..." +update-grub + +echo "Kernel built and installed successfully!" + +tidy_up diff --git a/resources/hiding_ci/kernel_commit_hash b/resources/hiding_ci/kernel_commit_hash new file mode 100644 index 00000000000..39d6afaaf51 --- /dev/null +++ b/resources/hiding_ci/kernel_commit_hash @@ -0,0 +1 @@ +4701f33a10702d5fc577c32434eb62adde0a1ae1 diff --git a/resources/hiding_ci/kernel_config_overrides b/resources/hiding_ci/kernel_config_overrides new file mode 100644 index 00000000000..e42464abb89 --- /dev/null +++ b/resources/hiding_ci/kernel_config_overrides @@ -0,0 +1,6 @@ +CONFIG_EXPERT=y +CONFIG_KVM=y +CONFIG_KVM_SW_PROTECTED_VM=y +CONFIG_KVM_PRIVATE_MEM=y +CONFIG_KVM_AMD_SEV=y +CONFIG_DEBUG_INFO=y diff --git a/resources/hiding_ci/kernel_url b/resources/hiding_ci/kernel_url new file mode 100644 index 00000000000..ce6e1a3e6a8 --- /dev/null +++ b/resources/hiding_ci/kernel_url @@ -0,0 +1 @@ +git://git.kernel.org/pub/scm/virt/kvm/kvm.git diff --git a/resources/hiding_ci/patches/0001.lore b/resources/hiding_ci/patches/0001.lore new file mode 100644 index 00000000000..7663841026d --- /dev/null +++ b/resources/hiding_ci/patches/0001.lore @@ -0,0 +1 @@ +https://lore.kernel.org/kvm/20250318161823.4005529-1-tabba@google.com diff --git a/tests/README.md b/tests/README.md index 1d7b7075f62..9b35c4fa4af 100644 --- a/tests/README.md +++ b/tests/README.md @@ -340,6 +340,8 @@ which tests are run in which context: in separate pipelines according to various cron schedules. - Tests marked as `no_block_pr` are run in the "optional" PR CI pipeline. This pipeline is not required to pass for merging a PR. +- Tests marked as `secret_hiding` are secret hiding specifc tests. They don't + run by default. All tests without markers are run for every pull request, and are required to pass for the PR to be merged. diff --git a/tests/integration_tests/build/test_hiding_kernel.py b/tests/integration_tests/build/test_hiding_kernel.py new file mode 100644 index 00000000000..a85a73143cb --- /dev/null +++ b/tests/integration_tests/build/test_hiding_kernel.py @@ -0,0 +1,29 @@ +# Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""A test which checks that the secret hiding enable kernel builds successfully.""" + +import pytest + +from framework import utils + + +@pytest.mark.timeout(600) +@pytest.mark.secret_hiding +def test_build_hiding_kernel(): + """ + In the test we will run our kernel build script to check it succeeds and builds the hidden kernel + """ + + # We have some extra deps for building the kernel that are not in the dev contaner + utils.check_output( + "apt install -y build-essential libncurses-dev bison flex libssl-dev libelf-dev bc dwarves libncurses5-dev kmod fakeroot" + ) + + # We have to configure git otherwise patch application fails + # the git log still credits the original author + utils.check_output('git config --global user.name "Firecracker CI"') + utils.check_output('git config --global user.email "ci@email.com"') + + utils.check_output( + "cd ../resources/hiding_ci; ./build_and_install_kernel.sh --no-install --tidy" + ) diff --git a/tests/pytest.ini b/tests/pytest.ini index 5656c8eee4d..930c4891814 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -5,12 +5,13 @@ addopts = -vv --durations=10 --showlocals - -m 'not nonci and not no_block_pr' + -m 'not nonci and not no_block_pr and not secret_hiding' --json-report --json-report-file=../test_results/test-report.json markers = no_block_pr: tests whose failure does not block PR merging. nonci: mark test as nonci. + secret_hiding: tests related to secret hiding. ; Overwrite the default norecursedirs, which includes 'build'. norecursedirs = .*