From 9aad362cf3fe3aa94793f1ff1fe7289171526c0d Mon Sep 17 00:00:00 2001 From: Ramakrishna Gali Date: Wed, 11 Sep 2024 00:53:31 +0000 Subject: [PATCH] Added Coconut-SVSM support to invoke AMD-ES/AMD-SEV for setting up the host, launching the guest (directly setting up snpguest in the guest VM), and performing attestation & measurement verification (using igvmmeasure vs snpguest) --- tools/snp.sh | 368 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 231 insertions(+), 137 deletions(-) diff --git a/tools/snp.sh b/tools/snp.sh index 95fa637..2503ed6 100755 --- a/tools/snp.sh +++ b/tools/snp.sh @@ -24,6 +24,15 @@ # 5. ./snp.sh attest-guest # 6. ssh -p 10022 -i snp-guest-key amd@localhost + +# AMDSEV - svsm-latest: +# 1. Enable host SNP options in BIOS +# 2. ./snp.sh -svsm setup-host +# 3. sudo reboot +# 4. ./snp.sh -svsm launch-guest +# 5. ./snp.sh -svsm attest-guest +# 6. ssh -p 10022 -i snp-guest-key amd@localhost + # BYOI Example: # Image must have the GUEST_USER already added. # Image must have the ssh key already injected for the specified user. @@ -90,12 +99,14 @@ QEMU_CMDLINE_FILE="${QEMU_CMDLINE:-${LAUNCH_WORKING_DIR}/qemu.cmdline}" IMAGE="${IMAGE:-${LAUNCH_WORKING_DIR}/${GUEST_NAME}.img}" GENERATED_INITRD_BIN="${SETUP_WORKING_DIR}/initrd.img" -# URLs and repos +# URLs and repois AMDSEV_URL="https://github.com/ryansavino/AMDSEV.git" AMDSEV_DEFAULT_BRANCH="snp-latest-fixes" AMDSEV_NON_UPM_BRANCH="snp-non-upm" +AMDSEV_SVSM_URL="https://github.com/ramagali24/AMDSEV-SVSM.git" +AMDSEV_SVSM_BRANCH="svsm-latest" SNPGUEST_URL="https://github.com/virtee/snpguest.git" -SNPGUEST_BRANCH="tags/v0.7.1" +SNPGUEST_BRANCH="tags/v0.3.2" NASM_SOURCE_TAR_URL="https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/nasm-2.16.01.tar.gz" CLOUD_INIT_IMAGE_URL="https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img" DRACUT_TARBALL_URL="https://github.com/dracutdevs/dracut/archive/refs/tags/059.tar.gz" @@ -116,6 +127,7 @@ usage() { >&2 echo " stop-guests Stop all SNP guests started by this script" >&2 echo " where OPTIONS are:" >&2 echo " -n|--non-upm Build AMDSEV non UPM kernel (sev-snp-devel)" + >&2 echo " -s|--svsm Build coconut-svsm components, launch guest and verity attestaion & measurement" >&2 echo " -i|--image Path to existing image file" >&2 echo " -h|--help Usage information" @@ -268,12 +280,6 @@ install_dependencies() { # pip needed for sev-snp-measure sudo apt install -y python3-pip - - # Needed to find information about CPU - sudo apt install -y cpuid - - # Needed to build 6.11.0-rc3 SNP kernel on the host - pip install tomli echo "true" > "${dependencies_installed_file}" } @@ -515,19 +521,32 @@ get_guest_kernel_version() { } save_binary_paths() { + local guest_kernel_version=$(get_guest_kernel_version) GENERATED_INITRD_BIN="${SETUP_WORKING_DIR}/initrd.img-${guest_kernel_version}" + echo " Latest bug ${guest_kernel_version}" + # Guest kernel file path points to standard kernel file path across different linux distribution local guest_kernel=$(echo $(realpath "${SETUP_WORKING_DIR}/AMDSEV/linux/guest/vmlinuz-${guest_kernel_version}")) # Save binary paths in source file -cat > "${SETUP_WORKING_DIR}/source-bins" < "${SETUP_WORKING_DIR}/source-bins" < "${SETUP_WORKING_DIR}/source-bins" < "${LAUNCH_WORKING_DIR}/source-bins" < "${LAUNCH_WORKING_DIR}/source-bins" </dev/null } -build_and_install_amdsev() { +build_and_install_amdsev() { + local amdsev_branch="${1:-${AMDSEV_DEFAULT_BRANCH}}" - + + if [ "$SVSM" = true ]; then + local amdsev_branch="${1:-${AMDSEV_DEFAULT_BRANCH}}" + + else + AMDSEV_URL=${AMDSEV_SVSM_URL} + amdsev_branch="svsm-latest" + # Create directory + + fi + # Create directory - mkdir -p "${SETUP_WORKING_DIR}" + mkdir -p "${SETUP_WORKING_DIR}" + # Clone and switch branch pushd "${SETUP_WORKING_DIR}" >/dev/null @@ -664,10 +711,12 @@ build_and_install_amdsev() { git remote set-url current "${AMDSEV_URL}" git fetch current "${amdsev_branch}" git checkout "current/${amdsev_branch}" - + + if [ "$SVSM" = true ]; then # Based on latest AMDSEV documentation # Delete the ovmf/ directory prior to the build step for ovmf re-initialization [ ! -d "ovmf" ] || rm -rf "ovmf" + fi # Build and copy files ./build.sh --package @@ -675,6 +724,8 @@ build_and_install_amdsev() { # Get guest kernel version from the guest config file local guest_kernel_version=$(get_guest_kernel_version) + + # To standardize guest kernel file location across different linux distributions local bzImage_file=$(find ${SETUP_WORKING_DIR}/AMDSEV/linux/guest -name "bzImage" | head -1) @@ -684,11 +735,13 @@ build_and_install_amdsev() { # because AMDSEV does not copy guest kernel file inside guest directory during SNP package build process in RH, fedora # so, copying bzImage file into guest kernel binary file if vmlinuz is absent inside the guest directory [ -f ${guest_kernel_bin} ] || cp -v ${bzImage_file} ${guest_kernel_bin} - + # Install latest snp-release cd $(ls -dt */ | grep snp-release- | head -1) sudo ./install.sh + + popd >/dev/null # Removing this from here for now, as the device gets recreated at various times, @@ -786,14 +839,19 @@ setup_and_launch_guest() { #add_qemu_cmdline_opts "-machine pc-q35-7.1" # snp object and kernel-hashes on - add_qemu_cmdline_opts "-object sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=1,kernel-hashes=on" + if [ "$SVSM" = true ]; then + add_qemu_cmdline_opts "-object sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=1,kernel-hashes=on" + else + add_qemu_cmdline_opts "-object memory-backend-memfd,size=2G,id=mem0,share=true,prealloc=false,reserve=off" + add_qemu_cmdline_opts "-object sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=1,init-flags=5,igvm-file="$IGVM_FILE"" + fi # ovmf, initrd, kernel and append options add_qemu_cmdline_opts "-bios ${OVMF_BIN}" add_qemu_cmdline_opts "-initrd ${INITRD_BIN}" add_qemu_cmdline_opts "-kernel ${KERNEL_BIN}" add_qemu_cmdline_opts "-append \"${GUEST_KERNEL_APPEND}\"" - + # Launch qemu cmdline "${QEMU_CMDLINE_FILE}" } @@ -929,99 +987,22 @@ setup_guest_attestation() { echo "true" > "${guest_setup_file}" } -# Pass a function and a register to collect its value -get_cpuid() { - local function=$1 - local register=$2 - local result - - result=$(cpuid -1 -r -l "${function}" | grep -oE "${register}=[[:alnum:]]+ " | cut -c5- | tr -d '[:space:]') - - if [ -z "${result}" ]; then - echo "Failed to find register ${register} for function ${function}" - else - echo "${result}" - fi -} - -# Get the socket type from cpuid -get_socket_type() { - local ebx - ebx=$(get_cpuid 0x80000001 ebx) - - local bin_value - bin_value=$(echo "obase=2; ibase=16; ${ebx^^}" | bc | rev) - - # Bits 29:31 gives us socket type - local socket_bin - socket_bin=$(echo "${bin_value}" | cut -c29-32 | rev) - - echo $((2#${socket_bin})) -} - -# Get the processor model name from the cpuid. get_cpu_code_name() { - # Read eax register from function 0x80000001 - local eax - eax=$(get_cpuid 0x80000001 eax) - - local bin_value - bin_value=$(echo "obase=2; ibase=16; ${eax^^}" | bc | rev) - - # Base family bits [11:8] - local base_family - base_family=$(echo "ibase=2; $(echo "${bin_value}" | cut -c9-12 | rev)" | bc) - - # Extended family bits [27:20] - local extended_family - extended_family=$(echo "ibase=2;$(echo "${bin_value}" | cut -c21-28 | rev)" | bc) - - # Base model bits [7:4] - local base_model - base_model=$(echo "${bin_value}" | cut -c5-8 | rev) - - # Extended model bits [19:16] - local extended_model - extended_model=$(echo "${bin_value}" | cut -c17-20 | rev) - - # Family = base family + extended family - local family - family=$((base_family + extended_family)) - - # Model = extended_model:base model - local model - model=$(bc <<< "ibase=2;${extended_model}${base_model}") - - case "${family}" in - 23) - if [ "${model}" -ge 0 ] && [ "$model" -le 15 ]; then - echo "naples" - elif [ "${model}" -ge 48 ] && [ "$model" -le 63 ]; then - echo "rome" - fi - ;; - 25) - if [ "${model}" -ge 0 ] && [ "${model}" -le 15 ]; then - echo "milan" - elif [ "${model}" -ge 16 ] && [ "${model}" -le 31 ]; then - echo "genoa" - elif [ "${model}" -ge 160 ] && [ "${model}" -le 175 ]; then - local socket - socket=$(get_socket_type) - case "${socket}" in - 4) echo "bergamo" ;; - 8) echo "siena" ;; - *) echo "Invalid CPU" ;; - esac - fi + local cpu_model=$(cat /proc/cpuinfo | grep "model name" | head -1 | cut -d ' ' -f5) + local cpu_code_name="milan" + + case "${cpu_model}" in + 7*) + cpu_code_name="milan" + echo $cpu_code_name ;; - 26) - if [ "${model}" -ge 0 ] && [ "${model}" -le 17 ]; then - echo "turin" - fi + 8*|9*) + cpu_code_name="genoa" + echo $cpu_code_name ;; *) - echo "Invalid CPU" + >&2 echo -e "Unknown CPU Model: ${cpu_model}" + return 1 ;; esac } @@ -1081,42 +1062,134 @@ attest_guest() { ssh_guest_command "sudo insmod /lib/modules/*/kernel/drivers/virt/coco/sev-guest/sev-guest.ko >/dev/null 2>&1 || true" # Request and display the snp attestation report with random data - ssh_guest_command "sudo ./snpguest report attestation-report.bin request-data.txt --random" - ssh_guest_command "./snpguest display report attestation-report.bin" + if [ "$SVSM" = true ]; then + ssh_guest_command "sudo ./snpguest report attestation-report.bin request-data.txt --random" + else + ssh_guest_command "sudo ./$Home/snpguest/target/release/snpguest report attestation-report.bin request-data.txt --random --vmpl 3" + fi + ssh_guest_command "./$Home/snpguest/target/release/snpguest display report attestation-report.bin" # Retrieve ark, ask, vcek (saved in ./certs) - ssh_guest_command "./snpguest fetch ca pem ${cpu_code_name} . --endorser vcek" - ssh_guest_command "./snpguest fetch vcek pem ${cpu_code_name} . attestation-report.bin" + ssh_guest_command "./$Home/snpguest/target/release/snpguest fetch ca pem ${cpu_code_name} ." + ssh_guest_command "./$Home/snpguest/target/release/snpguest fetch vcek pem ${cpu_code_name} . attestation-report.bin" # Verifies that ARK, ASK and VCEK are all properly signed - ssh_guest_command "./snpguest verify certs ." + ssh_guest_command "./$Home/snpguest/target/release/snpguest verify certs ." # Verifies the attestation-report trusted compute base matches vcek - ssh_guest_command "./snpguest verify attestation . attestation-report.bin" + ssh_guest_command "./$Home/snpguest/target/release/snpguest verify attestation . attestation-report.bin" + + if [ "$SVSM" = true ]; then + + # Use sev-snp-measure utility to calculate the expected measurement + local expected_measurement=$(generate_snp_expected_measurement) + echo -e "\nExpected Measurement (sev-snp-measure): ${expected_measurement}" + + # Parse the measurement out of the snp report + local snpguest_report_measurement=$(ssh_guest_command \ + "./snpguest display report attestation-report.bin \ + | tr '\n' ' ' \ + | sed \"s|.*Measurement:\(.*\)Host Data.*|\1\n|g\" \ + | sed \"s| ||g\"") + + # Remove any special characters and print the value + snpguest_report_measurement=$(echo ${snpguest_report_measurement} | sed $'s/[^[:print:]\t]//g') + echo -e "Measurement from SNP Attestation Report: ${snpguest_report_measurement}\n" + + # Compare the expected measurement to the guest report measurement + [[ "${expected_measurement}" == "${snpguest_report_measurement}" ]] \ + && echo -e "The expected measurement matches the snp guest report measurement!" \ + || { >&2 echo -e "FAIL: measurements do not match"; return 1; } +else + Verify_measurement +fi +} +setup_svsm_guest_attestation() { + + ssh_guest_command " + + # Update and install necessary packages + echo 'Updating package list...' + sudo apt-get update + + echo 'Installing necessary packages...' + sudo apt-get install -y git build-essential libtss2-dev tpm2-tools + + echo 'Installing Rust...' + source ""${HOME}"/.cargo/env" 2>/dev/null || true + + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -sSf | sh -s -- -y + source ""${HOME}"/.cargo/env" 2>/dev/null + " + + # Check if 'snpguest' directory exists and remove it if it does + ssh_guest_command " + if [ -d \"snpguest\" ]; then + echo 'Directory snpguest exists. Removing it...' + rm -rf snpguest + else + echo 'Directory snpguest does not exist.' + fi +" + + # Clone the repository and build the project + ssh_guest_command "git clone https://github.com/virtee/snpguest.git" + ssh_guest_command "cd snpguest && source \$HOME/.cargo/env && cargo build --release" +} - # Use sev-snp-measure utility to calculate the expected measurement - local expected_measurement=$(generate_snp_expected_measurement) - echo -e "\nExpected Measurement (sev-snp-measure): ${expected_measurement}" +Verify_measurement(){ + + # Define the working directory and the output file + OUTPUT_FILE="$SETUP_WORKING_DIR/make_output.txt" + # Define the firmware file path + FW_FILE="$SETUP_WORKING_DIR/AMDSEV/ovmf/Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fd" + + cd "$SETUP_WORKING_DIR"/AMDSEV/svsm + + # Run the make command and save the output to a file + FW_FILE="$FW_FILE" make &> "$OUTPUT_FILE" + + # Initialize the variable for Launch Digest + launch_digest="" + + # Read the output file line by line to find the Launch Digest + while IFS= read -r line; do + if [[ "$line" == *"Launch Digest:"* ]]; then + launch_digest=$(echo "$line" | awk '{print $3}') + break + fi + done < "$OUTPUT_FILE" + + # Output the Launch Digest to verify + echo "Launch Digest: $launch_digest" + + # Use igvmmeasure utility to calculate the expected measurement + local expected_measurement=$launch_digest + echo -e "\nExpected Measurement (igvmmeasure): ${expected_measurement}" # Parse the measurement out of the snp report - local snpguest_report_measurement=$(ssh_guest_command \ - "./snpguest display report attestation-report.bin \ - | tr '\n' ' ' \ - | sed \"s|.*Measurement:\(.*\)Host Data.*|\1\n|g\" \ - | sed \"s| ||g\"") - - # Remove any special characters and print the value - snpguest_report_measurement=$(echo ${snpguest_report_measurement} | sed $'s/[^[:print:]\t]//g') + scp_guest_command "${GUEST_USER}@localhost:$HOME/attestation-report.bin" "$SETUP_WORKING_DIR/AMDSEV/svsm" + + cd "$SETUP_WORKING_DIR"/AMDSEV/svsm + # Assign the output of the hexdump command to a variable + snpguest_report_measurement=$(hexdump -s 0x90 -n 48 -v -e '48/1 "%02X"' attestation-report.bin) + + # Print the variable to verify its content + echo "$snpguest_report_measurement" echo -e "Measurement from SNP Attestation Report: ${snpguest_report_measurement}\n" # Compare the expected measurement to the guest report measurement - [[ "${expected_measurement}" == "${snpguest_report_measurement}" ]] \ - && echo -e "The expected measurement matches the snp guest report measurement!" \ - || { >&2 echo -e "FAIL: measurements do not match"; return 1; } + if [[ "${expected_measurement}" == "${snpguest_report_measurement}" ]]; then + echo -e "The expected measurement matches the SNP guest report measurement!" + else + >&2 echo -e "FAIL: Measurements do not match" + return 1 + fi } + ############################################################################### # Main @@ -1147,6 +1220,10 @@ main() { IMAGE="${2}" SKIP_IMAGE_CREATE=true shift; shift + ;; + -svsm) + SVSM=false + shift ;; setup-host) @@ -1187,6 +1264,11 @@ main() { if ! $UPM; then SETUP_WORKING_DIR="${SETUP_WORKING_DIR}/non-upm" fi + + # Set SETUP_WORKING_DIR for non-upm + #if [ "$SVSM" = true ]; then + # SVSM_SETUP_WORKING_DIR="${SETUP_WORKING_DIR}/svsm" + #fi # Execute command case "${COMMAND}" in @@ -1196,6 +1278,7 @@ main() { ;; setup-host) + install_dependencies if $UPM; then @@ -1205,6 +1288,7 @@ main() { fi source "${SETUP_WORKING_DIR}/source-bins" + set_grub_default_snp echo -e "\nThe host must be rebooted for changes to take effect" ;; @@ -1223,8 +1307,11 @@ main() { # TEMPORARY until sev-snp-measure is updated to pass in TCB kernel modifier flags # Changes in AMDESE/linux set debug_swap on by default and affect the measurement - sudo modprobe -r kvm_amd - sudo modprobe kvm_amd debug_swap=0 + if [ "$SVSM" = true ]; then + sudo modprobe -r kvm_amd + sudo modprobe kvm_amd debug_swap=0 + fi + setup_and_launch_guest wait_and_retry_command verify_snp_guest @@ -1235,12 +1322,19 @@ main() { ;; attest-guest) + if [ "$SVSM" = true ]; then install_rust install_sev_snp_measure install_dependencies wait_and_retry_command verify_snp_guest - setup_guest_attestation - attest_guest + setup_guest_attestation + attest_guest + else + wait_and_retry_command verify_snp_guest + setup_svsm_guest_attestation + attest_guest + fi + attest_guest ;; stop-guests)