├── .github ├── .jira_sync_config.yaml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── support.md └── workflows │ └── create-snap-action.yml ├── .gitignore ├── LICENSE ├── LICENSE.apache2 ├── README.md ├── attestation ├── check-production.sh ├── check-registration.sh ├── scripts │ ├── dump-pckcache.sh │ ├── dump-tcb-info │ ├── jwt-decode.sh │ └── read-seamldr-svn.sh ├── setup-attestation-guest.sh └── setup-attestation-host.sh ├── docs └── images │ └── td-h100.gif ├── gpu-cc └── h100 │ ├── setup-gpus.sh │ └── vfio-passthrough.rules ├── guest-tools ├── direct-boot │ ├── README.md │ ├── boot_direct.sh │ └── boot_uki.sh ├── image │ ├── cloud-init-data │ │ ├── meta-data │ │ ├── meta-data.template │ │ ├── user-data │ │ └── user-data.template │ ├── create-td-image.sh │ ├── create-td-uki.sh │ ├── create-uki.sh │ └── setup.sh ├── regular_vm.xml.template ├── run_td ├── tdvirsh ├── trust_domain-sb.xml.template └── trust_domain.xml.template ├── setup-tdx-common ├── setup-tdx-config ├── setup-tdx-guest.sh ├── setup-tdx-host.sh ├── system-report.sh └── tests ├── README.md ├── bin └── setup-env-and-run ├── checkbox ├── bin │ ├── checkbox-cli-wrapper │ ├── checkbox-cli-wrapper-image │ ├── checkbox-cli-wrapper-local │ ├── configure │ ├── install-full-deps │ ├── shell-wrapper │ ├── test-runner │ ├── test-runner-automated │ ├── test-runner-automated-boot │ ├── test-runner-automated-guest │ ├── test-runner-automated-host │ ├── test-runner-automated-perf │ ├── test-runner-automated-quote │ ├── test-runner-automated-stress │ └── wrapper_local ├── checkbox-provider-tdx │ ├── manage.py │ └── units │ │ ├── category.pxu │ │ ├── jobs.pxu │ │ ├── test-plan.pxu │ │ └── tests │ │ ├── category.pxu │ │ └── jobs.pxu └── config │ └── config_vars ├── lib ├── Qemu.py ├── common.py ├── guest │ └── test_tdreport.py ├── pts │ ├── benchmark.sh │ └── phoronix-custom-suites │ │ ├── tdx_memory │ │ └── suite-definition.xml │ │ └── tdx_outliers │ │ └── suite-definition.xml ├── setup_guest.sh ├── tdx-tools │ ├── LICENSE │ ├── pyproject.toml │ └── src │ │ └── tdxtools │ │ ├── __init__.py │ │ ├── actor.py │ │ ├── binaryblob.py │ │ ├── ccel.py │ │ ├── host.py │ │ ├── rtmr.py │ │ ├── tdeventlog.py │ │ ├── tdquote.py │ │ ├── tdreport.py │ │ ├── tdrtmrcheck.py │ │ ├── util.py │ │ └── utility.py └── util.py ├── pytest.ini ├── snap ├── hooks │ ├── configure │ ├── install │ └── remove └── snapcraft.yaml ├── tdtest ├── tests ├── boot │ ├── test_boot_basic.py │ ├── test_boot_coexist.py │ ├── test_boot_multiple.py │ └── test_boot_td_creation.py ├── conftest.py ├── eventlog │ ├── test_guest_eventlog.py │ └── test_guest_measurement.py ├── guest │ ├── test_guest_cpu_off.py │ ├── test_guest_fail.py │ ├── test_guest_memory.py │ ├── test_guest_tsc_config.py │ └── test_nmi_debug_off.py ├── host │ ├── test_host_kdump.sh │ ├── test_host_kexec.sh │ ├── test_host_tdx_hardware.py │ └── test_host_tdx_software.py ├── perf │ ├── test_perf_benchmark.py │ └── test_perf_boot_time.py ├── quote │ ├── test_guest_ita.py │ ├── test_guest_tdxattest.py │ └── test_quote_configfs_tsm.py ├── report │ └── test_guest_report.py ├── stress │ ├── test_stress_boot.py │ ├── test_stress_quote.py │ └── test_stress_resources.py └── vsock │ └── test_vsock_vm.py ├── tox.ini └── tox └── setup-env-tox.sh /.github/.jira_sync_config.yaml: -------------------------------------------------------------------------------- 1 | settings: 2 | # Jira project key to create the issue in 3 | jira_project_key: "PEK" 4 | 5 | # Dictionary mapping GitHub issue status to Jira issue status 6 | status_mapping: 7 | opened: Untriaged 8 | closed: done 9 | 10 | # (Optional) Jira project components that should be attached to the created issue 11 | # Component names are case-sensitive 12 | # components: 13 | # - IoT 14 | # - DACH TT 15 | 16 | # (Optional) GitHub labels. Only issues with one of those labels will be synchronized. 17 | # If not specified, all issues will be synchronized 18 | #labels: 19 | # - bug 20 | # - custom 21 | 22 | # (Optional) (Default: false) Add a new comment in GitHub with a link to Jira created issue 23 | add_gh_comment: true 24 | 25 | # (Optional) (Default: true) Synchronize issue description from GitHub to Jira 26 | sync_description: true 27 | 28 | # (Optional) (Default: true) Synchronize comments from GitHub to Jira 29 | sync_comments: true 30 | 31 | # (Optional) (Default: None) Parent Epic key to link the issue to 32 | #epic_key: "PEK-356" 33 | 34 | # (Optional) Dictionary mapping GitHub issue labels to Jira issue types. 35 | # If label on the issue is not in specified list, this issue will be created as a Bug 36 | label_mapping: 37 | enhancement: Story 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **System report** 20 | Please run the `system-report.sh` script (located in the root directory of this repo) on your host system and copy the output below. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an improvement or enhancement for the project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I always have to edit this manually to [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | 19 | **System report** 20 | If you think it would be helpful, please run the `system-report.sh` script (located in the root directory of this repo) on your host system and copy the output below. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support Request 3 | about: Support request or question relating to TDX on Ubuntu 4 | 5 | --- 6 | 7 | 8 | 9 | **Describe the support request** 10 | A clear and concise description of what you are looking support for. 11 | 12 | **System report** 13 | Please run the `system-report.sh` script (located in the root directory of this repo) on your host system and copy the output below. 14 | -------------------------------------------------------------------------------- /.github/workflows/create-snap-action.yml: -------------------------------------------------------------------------------- 1 | name: Do a snap build of TDX checkbox provider 2 | 3 | run-name: ${{ github.actor }} is creating testing snap 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | paths: 10 | - 'tests/**' 11 | pull_request: 12 | branches: 13 | - main 14 | paths: 15 | - 'tests/**' 16 | workflow_dispatch: # Allow manual trigger 17 | 18 | jobs: 19 | 20 | check-secret: 21 | runs-on: ubuntu-24.04 22 | outputs: 23 | snap-key: ${{ steps.snap-key.outputs.defined }} 24 | steps: 25 | - id: snap-key 26 | name: Set defined=true if SNAPCRAFT7_CREDS is defined 27 | if: "${{ env.SNAPSTORE_KEY != '' }}" 28 | run: echo "defined=true" >> $GITHUB_OUTPUT 29 | env: 30 | SNAPSTORE_KEY: ${{ secrets.SNAPCRAFT7_CREDS }} 31 | 32 | build: 33 | runs-on: ubuntu-24.04 34 | needs: [check-secret] 35 | steps: 36 | - uses: actions/checkout@v4 37 | name: Checkout 38 | 39 | - uses: snapcore/action-build@v1 40 | name: Build snap 41 | id: build 42 | with: 43 | path: tests 44 | snapcraft-channel: latest/stable 45 | - uses: actions/upload-artifact@v4 46 | with: 47 | name: checkbox-tdx-provider 48 | path: ${{ steps.build.outputs.snap }} 49 | 50 | - name: Verify snap 51 | run: | 52 | sudo snap install checkbox24 53 | sudo snap install --dangerous --classic ${{ steps.build.outputs.snap }} 54 | 55 | - if: needs.check-secret.outputs.snap-key == 'true' && (github.ref_name == 'main') 56 | name: Publish snap to edge channel 57 | uses: snapcore/action-publish@v1.2.0 58 | env: 59 | SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT7_CREDS }} 60 | with: 61 | snap: ${{ steps.build.outputs.snap }} 62 | release: edge 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.qcow2 2 | *.img 3 | initrd* 4 | uki.efi* 5 | vmlinuz* 6 | -------------------------------------------------------------------------------- /attestation/check-production.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is part of Canonical's TDX repository which includes tools 4 | # to setup and configure a confidential computing environment 5 | # based on Intel TDX technology. 6 | # See the LICENSE file in the repository for the license text. 7 | 8 | # Copyright 2024 Canonical Ltd. 9 | # SPDX-License-Identifier: GPL-3.0-only 10 | 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License version 3, 13 | # as published by the Free Software Foundation. 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranties 16 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the GNU General Public License for more details. 18 | 19 | if [ "$EUID" -ne 0 ] 20 | then echo "Please run as root" 21 | exit 22 | fi 23 | 24 | apt install -y msr-tools &> /dev/null 25 | 26 | set -e 27 | 28 | modprobe msr 29 | PROD=$(rdmsr 0xce -f 27:27) 30 | SGX_DEBUG_MODE=$(rdmsr 0x503) 31 | 32 | CPU_MODEL=$(cat /proc/cpuinfo | awk 'match($0,/model.+: ([0-9]+)/,m){ print m[1]; exit}') 33 | 34 | CPU_GEN="unknown generation" 35 | # ref : https://github.com/qemu/qemu/blob/master/target/i386/cpu.c 36 | if [ "$CPU_MODEL" = "143" ]; then 37 | CPU_GEN="4th Gen Intel® Xeon® Scalable Processor (codename: Sapphire Rapids)" 38 | fi 39 | if [ "$CPU_MODEL" = "173" ]; then 40 | CPU_GEN="Intel® Xeon® 6 with P-cores (codename: Granite Rapids)" 41 | fi 42 | if [ "$CPU_MODEL" = "175" ]; then 43 | CPU_GEN="Intel® Xeon® 6 with E-cores (codename: Sierra Forest)" 44 | fi 45 | if [ "$CPU_MODEL" = "207" ]; then 46 | CPU_GEN="5th Gen Intel® Xeon® Scalable Processor (codename: Emerald Rapids)" 47 | fi 48 | 49 | echo "CPU: ${CPU_GEN}" 50 | if [ "${PROD}" = "0" ]; then 51 | echo "Production" 52 | else 53 | echo "Pre-production" 54 | fi 55 | printf "SGX_DEBUG_MODE=0x%s\n" ${SGX_DEBUG_MODE} 56 | -------------------------------------------------------------------------------- /attestation/check-registration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is part of Canonical's TDX repository which includes tools 4 | # to setup and configure a confidential computing environment 5 | # based on Intel TDX technology. 6 | # See the LICENSE file in the repository for the license text. 7 | 8 | # Copyright 2024 Canonical Ltd. 9 | # SPDX-License-Identifier: GPL-3.0-only 10 | 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License version 3, 13 | # as published by the Free Software Foundation. 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranties 16 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the GNU General Public License for more details. 18 | 19 | if [ "$EUID" -ne 0 ] 20 | then echo "Please run as root" 21 | exit 22 | fi 23 | 24 | # check platform registration 25 | 26 | set -e 27 | 28 | REGISTRATION_SUCCESS=1 29 | 30 | check_mpa_status() { 31 | # Use mpa_manage -get_registration_status to detect the status of registration 32 | # Do not use mpa_manage -get_last_registration_error_code because it outputs 33 | # code 0 even when the registration is in progress 34 | if mpa_manage -get_registration_status | grep "completed successfully" 2>&1 > /dev/null 35 | then 36 | REGISTRATION_SUCCESS=0 37 | echo "mpa_manage: registration status OK." 38 | else 39 | echo "mpa_manage: registration status NOK: platform not registered" 40 | fi 41 | } 42 | 43 | # check if MPA is used for platform registration 44 | if systemctl is-enabled mpa_registration_tool.service 2>&1 > /dev/null 45 | then 46 | if command -v mpa_manage 2>&1 > /dev/null 47 | then 48 | check_mpa_status 49 | else 50 | echo "mpa_manage is not available, assuming platform not registered." 51 | fi 52 | fi 53 | 54 | exit $REGISTRATION_SUCCESS 55 | -------------------------------------------------------------------------------- /attestation/scripts/dump-pckcache.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$EUID" -ne 0 ] 4 | then echo "Please run as root" 5 | exit 6 | fi 7 | 8 | if ! which sqlite3 &> /dev/null; then 9 | echo "install sqlite3" 10 | apt install sqlite3 11 | fi 12 | 13 | CACHE_DB="/opt/intel/sgx-dcap-pccs/pckcache.db" 14 | TABLES="appraisal_policies pck_certchain platforms crl_cache pck_crl platforms_registered 15 | enclave_identities pcs_certificates umzug fmspc_tcbs pcs_version pck_cert platform_tcbs" 16 | 17 | for table in $TABLES; do 18 | echo "Dump table $table" 19 | echo "===" 20 | sqlite3 -header -csv $CACHE_DB "select * from $table;" 21 | done 22 | 23 | -------------------------------------------------------------------------------- /attestation/scripts/dump-tcb-info: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sqlite3 4 | import json 5 | 6 | conn = sqlite3.connect('/opt/intel/sgx-dcap-pccs/pckcache.db') 7 | 8 | try: 9 | cursor = conn.cursor() 10 | data=cursor.execute('''select tcbinfo from fmspc_tcbs''') 11 | for row in data: 12 | tcbinfo = row[0].decode('utf-8') 13 | tcbinfo_dict = json.loads(tcbinfo) 14 | print(tcbinfo_dict['tcbInfo']['id']) 15 | print('-----') 16 | print(json.dumps(tcbinfo_dict, indent=2)) 17 | except: 18 | pass 19 | 20 | conn.close() 21 | -------------------------------------------------------------------------------- /attestation/scripts/jwt-decode.sh: -------------------------------------------------------------------------------- 1 | function jwt-decode() { 2 | sed 's/\./\n/g' <<< $(cut -d. -f1,2 <<< $1) | base64 --decode | jq 3 | } 4 | 5 | # This script will allow to dump the attestation token we receive from the intel trust authority 6 | # the attestation is a JWT 7 | 8 | # Usage 9 | # echo "xxxx" | jwt-decode.sh 10 | 11 | read JWT 12 | 13 | jwt-decode $JWT 14 | -------------------------------------------------------------------------------- /attestation/scripts/read-seamldr-svn.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is part of Canonical's TDX repository which includes tools 4 | # to setup and configure a confidential computing environment 5 | # based on Intel TDX technology. 6 | # See the LICENSE file in the repository for the license text. 7 | 8 | # Copyright 2024 Canonical Ltd. 9 | # SPDX-License-Identifier: GPL-3.0-only 10 | 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License version 3, 13 | # as published by the Free Software Foundation. 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranties 16 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the GNU General Public License for more details. 18 | 19 | if [ "$EUID" -ne 0 ] 20 | then echo "Please run as root" 21 | exit 22 | fi 23 | 24 | apt install -y msr-tools &> /dev/null 25 | 26 | set -e 27 | 28 | modprobe msr 29 | 30 | # According to Intel® Trust Domain Extensions - SEAM Loader (SEAMLDR) 31 | # Interface Specification. The SVN of the SEAMLDR ACM is reported in the 32 | # IA32_SGX_SVN_STATUS MSR bits 63:56 33 | SEAMLDR_SVN=$(rdmsr 0x500 -f 63:56) 34 | 35 | echo "SEAMLDR SVN: ${SEAMLDR_SVN}" 36 | -------------------------------------------------------------------------------- /attestation/setup-attestation-guest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is part of Canonical's TDX repository which includes tools 4 | # to setup and configure a confidential computing environment 5 | # based on Intel TDX technology. 6 | # See the LICENSE file in the repository for the license text. 7 | 8 | # Copyright 2024 Canonical Ltd. 9 | # SPDX-License-Identifier: GPL-3.0-only 10 | 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License version 3, 13 | # as published by the Free Software Foundation. 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranties 16 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the GNU General Public License for more details. 18 | 19 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 20 | 21 | if [ "$EUID" -ne 0 ] 22 | then echo "Please run as root" 23 | exit 24 | fi 25 | 26 | source ${SCRIPT_DIR}/../setup-tdx-config 27 | source ${SCRIPT_DIR}/../setup-tdx-common 28 | 29 | apt install --yes software-properties-common 30 | add_kobuk_ppa ${TDX_PPA_ATTESTATION:-tdx-attestation-release} 31 | 32 | apt update 33 | apt install --yes --allow-downgrades libtdx-attest-dev trustauthority-cli 34 | 35 | # compile tdx-attest source 36 | apt install --yes build-essential 37 | (cd /usr/share/doc/libtdx-attest-dev/examples/ && make) 38 | 39 | # run : /usr/share/doc/libtdx-attest-dev/examples/test_tdx_attest 40 | -------------------------------------------------------------------------------- /attestation/setup-attestation-host.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is part of Canonical's TDX repository which includes tools 4 | # to setup and configure a confidential computing environment 5 | # based on Intel TDX technology. 6 | # See the LICENSE file in the repository for the license text. 7 | 8 | # Copyright 2024 Canonical Ltd. 9 | # SPDX-License-Identifier: GPL-3.0-only 10 | 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License version 3, 13 | # as published by the Free Software Foundation. 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranties 16 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the GNU General Public License for more details. 18 | 19 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 20 | 21 | if [ "$EUID" -ne 0 ] 22 | then echo "Please run as root" 23 | exit 24 | fi 25 | 26 | source ${SCRIPT_DIR}/../setup-tdx-config 27 | source ${SCRIPT_DIR}/../setup-tdx-common 28 | 29 | apt install --yes software-properties-common 30 | add_kobuk_ppa ${TDX_PPA_ATTESTATION:-tdx-attestation-release} 31 | 32 | apt update 33 | 34 | apt install --yes --allow-downgrades sgx-dcap-pccs tdx-qgs 35 | 36 | # install the Intel Quote Provider library implementation 37 | # this package can be skipped if user can provider its own 38 | # provider 39 | apt install --yes --allow-downgrades libsgx-dcap-default-qpl 40 | 41 | # using RA registration (direct registration method) 42 | apt install --yes --allow-downgrades sgx-ra-service 43 | 44 | # using indirect registration method 45 | apt install --yes --allow-downgrades sgx-pck-id-retrieval-tool 46 | 47 | -------------------------------------------------------------------------------- /docs/images/td-h100.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/tdx/325b23387bd56f923228731dc5331f057eff2586/docs/images/td-h100.gif -------------------------------------------------------------------------------- /gpu-cc/h100/setup-gpus.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is part of Canonical's TDX repository which includes tools 4 | # to setup and configure a confidential computing environment 5 | # based on Intel TDX technology. 6 | # See the LICENSE file in the repository for the license text. 7 | 8 | # Copyright 2025 Canonical Ltd. 9 | # SPDX-License-Identifier: GPL-3.0-only 10 | 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License version 3, 13 | # as published by the Free Software Foundation. 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranties 16 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the GNU General Public License for more details. 18 | 19 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 20 | 21 | PROJECT_DIR=$SCRIPT_DIR/../.. 22 | 23 | # source config file 24 | if [ -f ${PROJECT_DIR}/setup-tdx-config ]; then 25 | source ${PROJECT_DIR}/setup-tdx-config 26 | fi 27 | 28 | on_exit() { 29 | rc=$? 30 | if [ ${rc} -ne 0 ]; then 31 | echo "=====================================" 32 | echo "ERROR : The script failed..." 33 | echo "=====================================" 34 | fi 35 | return ${rc} 36 | } 37 | 38 | _error() { 39 | echo "Error : $1" 40 | exit 1 41 | } 42 | 43 | trap "on_exit" EXIT 44 | 45 | source ${PROJECT_DIR}/setup-tdx-common 46 | 47 | if [ "$EUID" -ne 0 ] 48 | then echo "Please run as root" 49 | exit 50 | fi 51 | 52 | if [ ! -d "nvtrust" ]; then 53 | rm -rf nvtrust 54 | git clone -b 2025.4.11.001 --recursive https://github.com/NVIDIA/nvtrust.git 55 | fi 56 | 57 | nvidia_h100_bdfs() { 58 | while read -r line 59 | do 60 | # extract BDF 61 | bdf=$(echo $line | cut -d " " -f1) 62 | echo ${bdf} 63 | done < <(lspci -nn | grep "${NVIDIA_VENDOR_ID}" | grep "H100") 64 | } 65 | 66 | enable_cc_mode() { 67 | GPU_BDF=$1 68 | ./nvtrust/host_tools/python/nvidia_gpu_tools.py --set-ppcie-mode=off --reset-after-ppcie-mode-switch --gpu-bdf=${GPU_BDF} 69 | ./nvtrust/host_tools/python/nvidia_gpu_tools.py --set-cc-mode=on --reset-after-cc-mode-switch --gpu-bdf=${GPU_BDF} 70 | } 71 | 72 | setup_udev() { 73 | cp ${SCRIPT_DIR}/vfio-passthrough.rules /etc/udev/rules.d/ 74 | udevadm trigger 75 | } 76 | 77 | gpus_bdfs() { 78 | GPUS_BDFS=$(./nvtrust/host_tools/python/nvidia_gpu_tools.py --query-cc-mode 2>&1 | gawk 'match($0, /[ ]+[0-9]+ GPU ([0-0]{4}:[a-z0-9]{2,4}:[a-z0-9]{2}.[0-9]+)/, a) {print a[1]}') 79 | echo ${GPUS_BDFS} 80 | } 81 | 82 | GPUS=$(gpus_bdfs) 83 | 84 | if [ ! -z "$1" ]; then 85 | if [ "$1" != "*" ]; then 86 | GPUS=${1//,/ } 87 | fi 88 | 89 | for gpu_bdf in ${GPUS} 90 | do 91 | echo "======= Prepare ${gpu_bdf}" 92 | enable_cc_mode ${gpu_bdf} 93 | # virsh expect input format : pci_0000_b8_00_0 94 | virsh_gpu_bdf=$(echo "${gpu_bdf}" | tr :. _) 95 | # TMP: detach vfio first if already attached to vfio 96 | # this will avoid to have the error "Invalid argument" when 97 | # qemu tries to bind to the iommufd object just after the previous instance 98 | # has been stopped 99 | virsh nodedev-reattach pci_${virsh_gpu_bdf} || true 100 | virsh nodedev-detach pci_${virsh_gpu_bdf} 101 | done 102 | 103 | setup_udev 104 | else 105 | echo "================================" 106 | echo "List of NVidia GPUs (PCI BDFs):" 107 | echo ${GPUS} 108 | echo "================================" 109 | fi 110 | -------------------------------------------------------------------------------- /gpu-cc/h100/vfio-passthrough.rules: -------------------------------------------------------------------------------- 1 | # /dev/vfio/** 2 | SUBSYSTEM=="vfio", OWNER="root", GROUP="kvm" 3 | 4 | # /dev/iommu 5 | SUBSYSTEM=="misc", KERNEL=="iommu", OWNER="root", GROUP="kvm" 6 | 7 | # /dev/vfio/devices/vfio** 8 | SUBSYSTEM=="vfio-dev", OWNER="root", GROUP="kvm" 9 | 10 | -------------------------------------------------------------------------------- /guest-tools/direct-boot/README.md: -------------------------------------------------------------------------------- 1 | # TD Boot Methods 2 | 3 | This folder contains scripts and instructions to boot a TD using the [direct 4 | boot](https://qemu-project.gitlab.io/qemu/system/linuxboot.html) method. 5 | 6 | The direct boot method is an alternative to the boot method that is being used by `tdvirsh` 7 | to run TDs. 8 | We will refer to the `tdvirsh` boot method as the `indirect boot` method. 9 | With the `indirect boot` method, the boot chain involves following components: 10 | - TDVF (virtual UEFI firmware) 11 | - SHIM 12 | - Grub 13 | - Kernel + Initrd 14 | 15 | The `direct boot` will skip `SHIM` and `Grub` in the boot chain by providing the `Kernel` 16 | and `Initrd` directly to `qemu`. Per consequence, the boot chain involves these components: 17 | - TDVF (virtual UEFI firmware) 18 | - Kernel + Initrd 19 | 20 | The boot chain structure is important for remote attestation since it impacts the size of 21 | the event log journal. Indeed, each component in the boot chain generates a set of entries of the event 22 | log journal. The more components we have in the boot chain, the more event logs we will have and the harder 23 | is the verification of the correctness of the measurement values. 24 | 25 | For `direct boot`, we would like to investigate 2 ways of passing `kernel` and `initrd` to `qemu`: 26 | - Separately using `-kernel` and `-initrd` arguments 27 | - Bundled together as part of an [Unified Kernel Image](https://uapi-group.org/specifications/specs/unified_kernel_image/) 28 | 29 | ### Prerequisites 30 | 31 | To later perform direct boot with the two direct boot variants, we need to generate following files: 32 | 33 | 1. TD Guest Image 34 | 35 | The boot scripts need a guest image as the final rootfs to boot into. 36 | 37 | To generate this guest image, please refer to the section [Create TD Image](../../README.md#create-td-image). 38 | 39 | NOTE: The credentials necessary for login into the TD guest can also be found in this section. 40 | 41 | 2. Kernel, initrd and UKI 42 | 43 | NOTE: the provided instructions are for `24.04` guest. 44 | Please replace `24.04` with `25.04` if you want to work with `plucky` TD guest. 45 | 46 | 47 | ``` 48 | $ cd guest-tools/image 49 | $ ./create-td-uki.sh tdx-guest-ubuntu-24.04-generic.qcow2 50 | ``` 51 | 52 | This script will generate 3 files: 53 | - `vmlinuz-24.04` : the kernel of the guest image 54 | - `initrd.img-24.04` : the initrd of the guest image 55 | - `uki.efi-24.04` : the Unified Kernel Image that bundles together the kernel, the kernel's commandline and the initrd 56 | 57 | ### Direct boot 58 | 59 | ``` 60 | $ cd guest-tools/direct-boot 61 | $ ./boot_direct.sh 24.04 62 | ``` 63 | 64 | Once you are in the guest console, you can see the event log journal by: 65 | 66 | ``` 67 | $ tdeventlog 68 | ``` 69 | 70 | Example output: 71 | 72 | ``` 73 | root@tdx-guest:~# tdeventlog 74 | ==== TDX Event Log Entry - 0 [0x7FBEF000] ==== 75 | RTMR : 0 76 | Type : 3 (EV_NO_ACTION) 77 | Length : 65 78 | Algorithms Number : 1 79 | Algorithms[0xC] Size: 384 80 | RAW DATA: ---------------------------------------------- 81 | 7FBEF000 01 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 ................ 82 | 7FBEF010 00 00 00 00 00 00 00 00 00 00 00 00 21 00 00 00 ............!... 83 | 7FBEF020 53 70 65 63 20 49 44 20 45 76 65 6E 74 30 33 00 Spec ID Event03. 84 | 7FBEF030 00 00 00 00 00 02 00 02 01 00 00 00 0C 00 30 00 ..............0. 85 | 7FBEF040 00 . 86 | RAW DATA: ---------------------------------------------- 87 | 88 | ... 89 | ... 90 | 91 | ==== TDX Event Log Entry - 19 [0x7FBEF77F] ==== 92 | RTMR : 1 93 | Type : 0x80000007 (EV_EFI_ACTION) 94 | Length : 95 95 | Algorithms ID : 12 (TPM_ALG_SHA384) 96 | Digest[0] : 214b0bef1379756011344877743fdc2a5382bac6e70362d624ccf3f654407c1b4badf7d8f9295dd3dabdef65b27677e0 97 | RAW DATA: ---------------------------------------------- 98 | 7FBEF77F 02 00 00 00 07 00 00 80 01 00 00 00 0C 00 21 4B ..............!K 99 | 7FBEF78F 0B EF 13 79 75 60 11 34 48 77 74 3F DC 2A 53 82 ...yu`.4Hwt?.*S. 100 | 7FBEF79F BA C6 E7 03 62 D6 24 CC F3 F6 54 40 7C 1B 4B AD ....b.$...T@|.K. 101 | 7FBEF7AF F7 D8 F9 29 5D D3 DA BD EF 65 B2 76 77 E0 1D 00 ...)]....e.vw... 102 | 7FBEF7BF 00 00 45 78 69 74 20 42 6F 6F 74 20 53 65 72 76 ..Exit Boot Serv 103 | 7FBEF7CF 69 63 65 73 20 49 6E 76 6F 63 61 74 69 6F 6E ices Invocation 104 | RAW DATA: ---------------------------------------------- 105 | 106 | ==== TDX Event Log Entry - 20 [0x7FBEF7DE] ==== 107 | RTMR : 1 108 | Type : 0x80000007 (EV_EFI_ACTION) 109 | Length : 106 110 | Algorithms ID : 12 (TPM_ALG_SHA384) 111 | Digest[0] : 0a2e01c85deae718a530ad8c6d20a84009babe6c8989269e950d8cf440c6e997695e64d455c4174a652cd080f6230b74 112 | RAW DATA: ---------------------------------------------- 113 | 7FBEF7DE 02 00 00 00 07 00 00 80 01 00 00 00 0C 00 0A 2E ................ 114 | 7FBEF7EE 01 C8 5D EA E7 18 A5 30 AD 8C 6D 20 A8 40 09 BA ..]....0..m .@.. 115 | 7FBEF7FE BE 6C 89 89 26 9E 95 0D 8C F4 40 C6 E9 97 69 5E .l..&.....@...i^ 116 | 7FBEF80E 64 D4 55 C4 17 4A 65 2C D0 80 F6 23 0B 74 28 00 d.U..Je,...#.t(. 117 | 7FBEF81E 00 00 45 78 69 74 20 42 6F 6F 74 20 53 65 72 76 ..Exit Boot Serv 118 | 7FBEF82E 69 63 65 73 20 52 65 74 75 72 6E 65 64 20 77 69 ices Returned wi 119 | 7FBEF83E 74 68 20 53 75 63 63 65 73 73 th Success 120 | RAW DATA: ---------------------------------------------- 121 | 122 | 123 | ==== Replayed RTMR values from event log ==== 124 | rtmr_0 : a40d9875d5a7477e2d14201a27fc2aef21d9e6243ffe483262d2212cf518fd249fb0956d5d3ba30e6dca6d839c8e6212 125 | rtmr_1 : 8584f2ccb76201a023e8dc30ed918e40650fa96fc5c5802d78cda055a1ef8d65a0845d1ced5bb9601ed0060a5bcf8802 126 | rtmr_2 : 82abbec34b50b6784bd9edc785fdbfd2e49d05acbb0f1ae58c011f057b64bb6532cac5b9146bdb245992118d55d90013 127 | rtmr_3 : 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 128 | ``` 129 | 130 | ### Direct boot with UKI 131 | 132 | Another way to do direct boot is to use the [Unified Kernel Image](https://uapi-group.org/specifications/specs/unified_kernel_image/). 133 | UKI leads to better UEFI Secure Boot support, better supporting TPM measurements and confidential computing, and a more robust boot process. 134 | 135 | ``` 136 | $ cd guest-tools/direct-boot 137 | $ ./boot_uki.sh 24.04 138 | ``` 139 | 140 | Once you are in the guest console, you can see the event log journal by using `tdeventlog` as explained in the previous section. 141 | 142 | -------------------------------------------------------------------------------- /guest-tools/direct-boot/boot_direct.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is part of Canonical's TDX repository which includes tools 4 | # to setup and configure a confidential computing environment 5 | # based on Intel TDX technology. 6 | # See the LICENSE file in the repository for the license text. 7 | 8 | # Copyright 2024 Canonical Ltd. 9 | # SPDX-License-Identifier: GPL-3.0-only 10 | 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License version 3, 13 | # as published by the Free Software Foundation. 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranties 16 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the GNU General Public License for more details. 18 | 19 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 20 | 21 | UBUNTU_VERSION=$1 22 | if [[ -z "${UBUNTU_VERSION}" ]]; then 23 | echo "Usage: $0 <24.04|25.04>" 24 | exit 1 25 | fi 26 | 27 | PROCESS_NAME=td 28 | TDVF_FIRMWARE=/usr/share/ovmf/OVMF.fd 29 | 30 | KERNEL_FILE=$(realpath ${SCRIPT_DIR}/../image/vmlinuz-${UBUNTU_VERSION}) 31 | INITRD_FILE=$(realpath ${SCRIPT_DIR}/../image/initrd.img-${UBUNTU_VERSION}) 32 | TD_IMG=$(realpath ${SCRIPT_DIR}/../image/tdx-guest-ubuntu-${UBUNTU_VERSION}-generic.qcow2) 33 | 34 | if [[ ! -f "${KERNEL_FILE}" ]]; then 35 | echo "Missing kernel file: ${KERNEL_FILE} 36 | You can use guest-tools/image/create-td-uki.sh to generate it" 37 | exit 1 38 | fi 39 | 40 | if [[ ! -f "${INITRD_FILE}" ]]; then 41 | echo "Missing initrd file: ${INITRD_FILE} 42 | You can use guest-tools/image/create-td-uki.sh to generate it" 43 | exit 1 44 | fi 45 | 46 | if [[ ! -f "${TD_IMG}" ]]; then 47 | echo "Missing guest image file: ${TD_IMG} 48 | You can use guest-tools/image/create-td-image.sh to generate it" 49 | exit 1 50 | fi 51 | 52 | set -e 53 | 54 | qemu-system-x86_64 -accel kvm \ 55 | -m 2G -smp 16 \ 56 | -name ${PROCESS_NAME},process=${PROCESS_NAME},debug-threads=on \ 57 | -cpu host \ 58 | -object '{"qom-type":"tdx-guest","id":"tdx","quote-generation-socket":{"type": "vsock", "cid":"2","port":"4050"}}' \ 59 | -machine q35,kernel_irqchip=split,confidential-guest-support=tdx,hpet=off \ 60 | -bios ${TDVF_FIRMWARE} \ 61 | -nographic \ 62 | -nodefaults \ 63 | -kernel ${KERNEL_FILE} \ 64 | -initrd ${INITRD_FILE} \ 65 | -append "root=/dev/sda1 console=ttyS0" \ 66 | -hda ${TD_IMG} \ 67 | -serial stdio \ 68 | -pidfile /tmp/tdx-demo-td-pid.pid 69 | -------------------------------------------------------------------------------- /guest-tools/direct-boot/boot_uki.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is part of Canonical's TDX repository which includes tools 4 | # to setup and configure a confidential computing environment 5 | # based on Intel TDX technology. 6 | # See the LICENSE file in the repository for the license text. 7 | 8 | # Copyright 2024 Canonical Ltd. 9 | # SPDX-License-Identifier: GPL-3.0-only 10 | 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License version 3, 13 | # as published by the Free Software Foundation. 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranties 16 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the GNU General Public License for more details. 18 | 19 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 20 | 21 | UBUNTU_VERSION=$1 22 | 23 | if [[ -z "${UBUNTU_VERSION}" ]]; then 24 | echo "Usage: $0 <24.04|25.04>" 25 | exit 1 26 | fi 27 | 28 | UKI_FILE=$(realpath ${SCRIPT_DIR}/../image/uki.efi-${UBUNTU_VERSION}) 29 | TD_IMG=$(realpath ${SCRIPT_DIR}/../image/tdx-guest-ubuntu-${UBUNTU_VERSION}-generic.qcow2) 30 | 31 | usage() { 32 | cat </dev/null && pwd) 22 | 23 | TMP_GUEST_IMG="${SCRIPT_DIR}/tdx-guest-tmp.qcow2" 24 | TMP_GUEST_FOLDER="${SCRIPT_DIR}/tdx-guest-tmp/" 25 | 26 | TD_GUEST_IMG=$1 27 | if [[ -z "${TD_GUEST_IMG}" ]]; then 28 | echo "Usage : $0 " 29 | exit 1 30 | fi 31 | 32 | if [ "$EUID" -eq 0 ] 33 | then echo "Please do not run as root" 34 | exit 35 | fi 36 | 37 | cleanup() { 38 | echo "cleanup ..." 39 | umount ${TMP_GUEST_FOLDER} &> /dev/null 40 | rm -rf ${TMP_GUEST_FOLDER} 41 | rm -f ${TMP_GUEST_IMG} 42 | } 43 | 44 | trap "cleanup" EXIT 45 | 46 | cleanup 47 | 48 | set -e 49 | 50 | # Ubuntu make the kernels (vmlinuz) readable ONLY for root user 51 | # this makes libguestfs fails when it is run as normal user 52 | # For more details, see the LP bug on that topic: 53 | # https://bugs.launchpad.net/ubuntu/+source/linux/+bug/759725 54 | # To work-around this issue, we allow normal users to read the kernel 55 | # files 56 | sudo chmod 0644 /boot/vmlinuz-* 57 | 58 | # create an overlay image with guest image as a backing image 59 | qemu-img create -f qcow2 -b ${TD_GUEST_IMG} -F qcow2 ${TMP_GUEST_IMG} 60 | 61 | # virt-customize does in-place customization and use host kernel 62 | # we have to give the create-uki script the guest image kernel 63 | virt-customize -a ${TMP_GUEST_IMG} \ 64 | --mkdir /tmp/tdx/ \ 65 | --copy-in ${SCRIPT_DIR}/create-uki.sh:/tmp/tdx/ \ 66 | --run-command "/tmp/tdx/create-uki.sh" 67 | 68 | # retrieve files 69 | mkdir -p ${TMP_GUEST_FOLDER} && guestmount -a ${TMP_GUEST_IMG} -i --ro ${TMP_GUEST_FOLDER} 70 | 71 | cp ${TMP_GUEST_FOLDER}/uki.efi* ./ 72 | cp ${TMP_GUEST_FOLDER}/vmlinuz* ./ 73 | cp ${TMP_GUEST_FOLDER}/initrd* ./ 74 | -------------------------------------------------------------------------------- /guest-tools/image/create-uki.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is part of Canonical's TDX repository which includes tools 4 | # to setup and configure a confidential computing environment 5 | # based on Intel TDX technology. 6 | # See the LICENSE file in the repository for the license text. 7 | 8 | # Copyright 2024 Canonical Ltd. 9 | # SPDX-License-Identifier: GPL-3.0-only 10 | 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License version 3, 13 | # as published by the Free Software Foundation. 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranties 16 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the GNU General Public License for more details. 18 | 19 | # This script will create a UKI (Unified Kernel Image) using 20 | # systemd-ukify, update-initramfs 21 | 22 | # if this script is executed with virt-customize, $(uname -r) 23 | # will give the kernel version on host, so this information 24 | # we have to look for it from /boot/ 25 | KERNEL_VER=$(find /boot/vmlinuz-*-generic 2>&1 | \ 26 | /usr/lib/grub/grub-sort-version -r 2>&1 | \ 27 | gawk 'match($0 , /^\/boot\/vmlinuz-(.*)/, a) {print a[1];exit}') 28 | 29 | UBUNTU_VERSION=$(lsb_release -rs) 30 | 31 | if [[ -z "${KERNEL_VER}" ]]; then 32 | echo "Cannot detect kernel version" 33 | exit 1 34 | fi 35 | 36 | echo "Creating the UKI with kernel ${KERNEL_VER}" 37 | 38 | # the kernel commandline to put in the UKI 39 | # the boot is specified using label, on Ubuntu cloud 40 | # image, the rootfs partition is labelled as cloudimg-rootfs 41 | # lrwxrwxrwx 1 11 UEFI -> ../../sda15 42 | # lrwxrwxrwx 1 11 BOOT -> ../../sda13 43 | # lrwxrwxrwx 1 10 cloudimg-rootfs -> ../../sda1 44 | KERNEL_CMDLINE="console=tty1 console=ttyS0 root=LABEL=cloudimg-rootfs" 45 | 46 | # use systemd-ukify to generate UKI 47 | sudo apt install -y systemd-ukify systemd-boot-efi 48 | 49 | # use update-initramfs 50 | # add kernel moules: 51 | # - tdx_guest 52 | echo tdx_guest | sudo tee -a /etc/initramfs-tools/modules 53 | sudo update-initramfs -c -k ${KERNEL_VER} 54 | 55 | # copy the initrd 56 | sudo cp /boot/initrd.img-${KERNEL_VER} initrd.img-${UBUNTU_VERSION} 57 | 58 | # copy the kernel 59 | sudo cp /boot/vmlinuz-${KERNEL_VER} ./vmlinuz-${UBUNTU_VERSION} 60 | sudo chmod a+rw vmlinuz-${KERNEL_VER} 61 | 62 | ukify build --linux=./vmlinuz-${UBUNTU_VERSION} \ 63 | --cmdline "${KERNEL_CMDLINE}" \ 64 | --initrd=initrd.img-${UBUNTU_VERSION} \ 65 | --output uki.efi-${UBUNTU_VERSION} \ 66 | --os-release '@/etc/os-release' \ 67 | --uname ${KERNEL_VER} 68 | -------------------------------------------------------------------------------- /guest-tools/image/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (C) 2024 Canonical Ltd. 4 | # 5 | # This file is part of tdx repo. See LICENSE file for license information. 6 | 7 | _on_error() { 8 | trap '' ERR 9 | line_path=$(caller) 10 | line=${line_path% *} 11 | path=${line_path#* } 12 | 13 | echo "" 14 | echo "ERR $path:$line $BASH_COMMAND exited with $1" 15 | exit 1 16 | } 17 | trap '_on_error $?' ERR 18 | 19 | set -eE 20 | 21 | apt update 22 | 23 | # Utilities packages for automated testing 24 | # linux-tools-common for perf, please make sure that linux-tools is also installed 25 | apt install -y cpuid linux-tools-common msr-tools python3 python3-pip 26 | 27 | # setup ssh 28 | # allow password auth + root login 29 | sed -i 's|[#]*PasswordAuthentication .*|PasswordAuthentication yes|g' /etc/ssh/sshd_config 30 | sed -i 's|[#]*PermitRootLogin .*|PermitRootLogin yes|g' /etc/ssh/sshd_config 31 | sed -i 's|[#]*KbdInteractiveAuthentication .*|KbdInteractiveAuthentication yes|g' /etc/ssh/sshd_config 32 | # livecd-rootfs adds 60-cloudimg-settings.conf file to set PasswordAuthentication to no 33 | # if the file exists, remove it 34 | rm -f /etc/ssh/sshd_config.d/60-cloudimg-settings.conf 35 | 36 | # Enable TDX 37 | /tmp/tdx/setup-tdx-guest.sh 38 | 39 | # Install tools 40 | cd /tmp/tdx/tdx-tools/ 41 | python3 -m pip install --break-system-packages ./ 42 | 43 | rm -rf /tmp/tdx || true 44 | -------------------------------------------------------------------------------- /guest-tools/regular_vm.xml.template: -------------------------------------------------------------------------------- 1 | 2 | DOMAIN 3 | 2 4 | 16 5 | 6 | hvm 7 | /usr/share/qemu/OVMF.fd 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | destroy 19 | restart 20 | destroy 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | /usr/bin/qemu-system-x86_64 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /guest-tools/run_td: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This file is part of Canonical's TDX repository which includes tools 4 | # to setup and configure a confidential computing environment 5 | # based on Intel TDX technology. 6 | # See the LICENSE file in the repository for the license text. 7 | 8 | # Copyright 2025 Canonical Ltd. 9 | # SPDX-License-Identifier: GPL-3.0-only 10 | 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License version 3, 13 | # as published by the Free Software Foundation. 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranties 16 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the GNU General Public License for more details. 18 | 19 | import argparse 20 | import os 21 | import platform 22 | import signal 23 | import subprocess 24 | import time 25 | 26 | file_path = os.path.realpath(os.path.dirname(__file__)) 27 | 28 | pidfile='/tmp/tdx-demo-td-pid.pid' 29 | process_name='td' 30 | ssh_port=10022 31 | logfile='/tmp/tdx-guest-td.log' 32 | 33 | if os.environ.get('TD_IMG'): 34 | td_img=os.environ.get('TD_IMG') 35 | else: 36 | ubuntu_version=platform.freedesktop_os_release().get('VERSION_ID') 37 | td_img=f'{file_path}/image/tdx-guest-ubuntu-{ubuntu_version}-generic.qcow2' 38 | 39 | tdvf_params='/usr/share/ovmf/OVMF.fd' 40 | 41 | def do_print(): 42 | try: 43 | with open(pidfile) as pid_file: 44 | pid=int(pid_file.read()) 45 | print(f'TD started by QEMU with PID: {pid}.') 46 | print(f'To log in with the non-root user (default: tdx / password: 123456), as specified in setup-tdx-config, use:') 47 | print(f' $ ssh -p {ssh_port} @localhost') 48 | print('To log in as root (default password: 123456), use:') 49 | print(f' $ ssh -p {ssh_port} root@localhost') 50 | except: 51 | pass 52 | 53 | def do_clean(): 54 | print('Clean VM') 55 | with open(pidfile) as pid_file: 56 | pid=int(pid_file.read()) 57 | os.kill(pid, signal.SIGTERM) 58 | # wait for process to exit 59 | time.sleep(3) 60 | os.remove(pidfile) 61 | 62 | def add_vsock(cmd): 63 | cmd.extend(['-device', 'vhost-vsock-pci,guest-cid=3']) 64 | 65 | def prepare_gpus(gpus): 66 | gpu_args = ','.join(gpus) 67 | setup_cmd = f'sudo {file_path}/../gpu-cc/h100/setup-gpus.sh {gpu_args}' 68 | print(setup_cmd) 69 | subprocess.check_call(setup_cmd, shell=True, stderr=subprocess.STDOUT) 70 | 71 | def add_gpus(cmd, gpus): 72 | if len(gpus) <= 0: 73 | return 74 | prepare_gpus(gpus) 75 | index=0 76 | for gpu in gpus: 77 | gpu_cmd = ['-object', f'iommufd,id=iommufd{index}', 78 | '-device', f'pcie-root-port,id=pci.{index},bus=pcie.{index}', 79 | '-device', f'vfio-pci,host={gpu},bus=pci.{index},iommufd=iommufd{index}'] 80 | cmd.extend(gpu_cmd) 81 | index = index + 1 82 | 83 | def do_run(img_path, gpus): 84 | print('Run VM') 85 | print(f' Image: {img_path}') 86 | if len(gpus): 87 | print(f' Passthrough GPUs: {gpus}') 88 | 89 | qemu_cmds = ['qemu-system-x86_64', 90 | '-accel', 'kvm', 91 | '-m', '100G', '-smp', '32', 92 | '-name', f'{process_name},process={process_name},debug-threads=on', 93 | '-cpu', 'host,-avx10', 94 | '-object', '{"qom-type":"tdx-guest","id":"tdx","quote-generation-socket":{"type": "vsock", "cid":"2","port":"4050"}}', 95 | '-object', 'memory-backend-ram,id=mem0,size=100G', 96 | '-machine', 'q35,kernel_irqchip=split,confidential-guest-support=tdx,memory-backend=mem0', 97 | '-bios', tdvf_params, 98 | '-nographic', '-daemonize', 99 | '-nodefaults', '-vga', 'none', 100 | '-device', 'virtio-net-pci,netdev=nic0_td', '-netdev', f'user,id=nic0_td,hostfwd=tcp::{ssh_port}-:22', 101 | '-drive', f'file={img_path},if=none,id=virtio-disk0', 102 | '-device', 'virtio-blk-pci,drive=virtio-disk0', 103 | '-pidfile', pidfile 104 | ] 105 | 106 | add_vsock(qemu_cmds) 107 | add_gpus(qemu_cmds, gpus) 108 | 109 | subprocess.run(qemu_cmds, stderr=subprocess.STDOUT) 110 | 111 | do_print() 112 | 113 | def run_td(args): 114 | global td_img 115 | try: 116 | do_clean() 117 | except: 118 | pass 119 | if args.clean: 120 | return 121 | if args.image: 122 | td_img=args.image 123 | do_run(td_img, args.gpus) 124 | 125 | if __name__ == '__main__': 126 | parser = argparse.ArgumentParser() 127 | parser.add_argument("--image", type=str, help="Guest image") 128 | parser.add_argument("--gpus", nargs='*', default=[], help="GPUs to pass-through") 129 | parser.add_argument("--clean", action='store_true', help="Clean the current VM") 130 | args = parser.parse_args() 131 | run_td(args) 132 | -------------------------------------------------------------------------------- /guest-tools/trust_domain-sb.xml.template: -------------------------------------------------------------------------------- 1 | 2 | DOMAIN 3 | 2 4 | 5 | 6 | 7 | 8 | 16 9 | 10 | hvm 11 | /usr/share/ovmf/OVMF.tdx.fd 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | destroy 23 | restart 24 | destroy 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | /usr/bin/qemu-system-x86_64 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
58 | 59 | 60 | 61 | 62 | 0x10000000 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /guest-tools/trust_domain.xml.template: -------------------------------------------------------------------------------- 1 | 2 | DOMAIN 3 | 16 4 | 5 | 6 | 7 | 8 | 32 9 | 10 | hvm 11 | /usr/share/qemu/OVMF.fd 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | destroy 23 | restart 24 | destroy 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | /usr/bin/qemu-system-x86_64 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
58 | 59 | HOSTDEV_DEVICES 60 | 61 | 62 | 63 | 0x10000000 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /setup-tdx-common: -------------------------------------------------------------------------------- 1 | # This file is part of Canonical's TDX repository which includes tools 2 | # to setup and configure a confidential computing environment 3 | # based on Intel TDX technology. 4 | # See the LICENSE file in the repository for the license text. 5 | 6 | # Copyright 2024 Canonical Ltd. 7 | # SPDX-License-Identifier: GPL-3.0-only 8 | 9 | # This program is free software: you can redistribute it and/or modify it 10 | # under the terms of the GNU General Public License version 3, 11 | # as published by the Free Software Foundation. 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranties 14 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 15 | # See the GNU General Public License for more details. 16 | 17 | # If not set, detect Ubuntu code name and version 18 | if [[ -z "${UBUNTU_CODENAME}" ]] || [[ -z "${UBUNTU_VERSION}" ]]; then 19 | UBUNTU_CODENAME=$(lsb_release -cs) 20 | UBUNTU_VERSION=$(lsb_release -rs) 21 | fi 22 | 23 | # Pinning the packages of specific PPA 24 | # 25 | # unattended-upgrade: 26 | # unattended-upgrade will temporarily set APT pinning for the archives 27 | # it will ignore the pinning configuration for PPAs that are not listed 28 | # in the Allowed-Origins list 29 | # we have to add the PPA into this list to prevent unattended from 30 | # setting its pinning priority to -32768 31 | # we also need to tell unattended-upgrade to allow the downgrade 32 | # in case the packages in the PPA have older versions than the ones 33 | # from Ubuntu archive 34 | add_kobuk_ppa() { 35 | ppa_id=$1 36 | team=kobuk-team 37 | # try to detect if the PPA is specified as fullname 38 | # example : ppa:canonical-kernel-team/bootstrap 39 | if [[ ${ppa_id} =~ ppa:([^/]+)/([^/]+)$ ]]; then 40 | team=${BASH_REMATCH[1]} 41 | ppa_id=${BASH_REMATCH[2]} 42 | fi 43 | distro_id=LP-PPA-${team}-${ppa_id} 44 | 45 | add-apt-repository -y ppa:${team}/${ppa_id} 46 | 47 | cat < 71 | # examples : linux-image-intel, linux-image-generic, linux-image-aws, ... 72 | get_kernel_version() { 73 | local kernel_flavour=$1 74 | local kernel_version 75 | kernel_version=$(apt show ${kernel_flavour} 2>&1 | gawk 'match($0, /Depends:.* linux-image-([^, ]+)/, a) {print a[1]}') 76 | echo $kernel_version 77 | } 78 | 79 | # grub: switch to kernel version 80 | grub_switch_kernel() { 81 | KERNELVER=$1 82 | MID=$(awk '/Advanced options for Ubuntu/{print $(NF-1)}' /boot/grub/grub.cfg | cut -d\' -f2) 83 | KID=$(awk "/with Linux $KERNELVER/"'{print $(NF-1)}' /boot/grub/grub.cfg | cut -d\' -f2 | head -n1) 84 | cat > /etc/default/grub.d/99-tdx-kernel.cfg <&1 | \ 99 | /usr/lib/grub/grub-sort-version -r 2>&1 | \ 100 | gawk 'match($0 , /^\/boot\/vmlinuz-(.*)/, a) {print a[1];exit}') 101 | fi 102 | if [ -z "${KERNEL_RELEASE}" ]; then 103 | echo "ERROR : unable to determine kernel release" 104 | exit 1 105 | fi 106 | grub_switch_kernel "${KERNEL_RELEASE}" 107 | } 108 | 109 | # Enable non-root user to run TDs 110 | # Try to detect the normal user when the script is run with sudo 111 | # If the script is run as root, just skip this step 112 | # 113 | # This script modifies the configuration file /etc/libvirt/qemu.conf: 114 | # user = 115 | # group = 116 | # dynamic_ownership = 0 117 | enable_normal_user_libvirt() { 118 | if [[ -z "${SUDO_USER}" ]]; then 119 | return 120 | fi 121 | LIBVIRT_USER=${SUDO_USER} 122 | LIBVIRT_GROUP=$(id -g ${SUDO_USER}) 123 | 124 | echo "Enable non-root user ${LIBVIRT_USER} to run TDs" 125 | sed -E -i "s/\#user[ ]*=[ ]*\".+\"/user = \"${LIBVIRT_USER}\"/g" /etc/libvirt/qemu.conf 126 | sed -E -i "s/\#group[ ]*=[ ]*\".+\"/group = \"${LIBVIRT_GROUP}\"/g" /etc/libvirt/qemu.conf 127 | sed -E -i "s/\#dynamic_ownership[ ]*=[ ]*.+/dynamic_ownership = 0/g" /etc/libvirt/qemu.conf 128 | 129 | # restart libvirtd 130 | systemctl restart libvirtd || true 131 | } -------------------------------------------------------------------------------- /setup-tdx-config: -------------------------------------------------------------------------------- 1 | # This file is part of Canonical's TDX repository which includes tools 2 | # to setup and configure a confidential computing environment 3 | # based on Intel TDX technology. 4 | # See the LICENSE file in the repository for the license text. 5 | 6 | # Copyright 2024 Canonical Ltd. 7 | # SPDX-License-Identifier: GPL-3.0-only 8 | 9 | # This program is free software: you can redistribute it and/or modify it 10 | # under the terms of the GNU General Public License version 3, 11 | # as published by the Free Software Foundation. 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranties 14 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 15 | # See the GNU General Public License for more details. 16 | 17 | ################################################################ 18 | # GENERAL # 19 | ################################################################ 20 | 21 | ################################################################ 22 | # The TDX PPAs to use 23 | # By default, there is only the release PPA : tdx-release 24 | # but for development purpose, users can change this list to 25 | # contain an arbitrary number of PPAs 26 | # The PPA can be specified in short for full name format: 27 | # - short : tdx (in this case, the team will be kobuk-team) 28 | # - full : ppa:canonical-kernel-team/bootstrap 29 | ################################################################ 30 | TDX_PPA="tdx-release" 31 | 32 | ################################################################ 33 | # The TDX PPA for attestation 34 | ################################################################ 35 | TDX_PPA_ATTESTATION=tdx-attestation-release 36 | 37 | ################################################################ 38 | # Enable the installation of DCAP packages from Canonical's 39 | # repository. This flag is considered during host OS and guest 40 | # OS setup. 41 | # Set to 1 to enable 42 | # By default, the attestation components are not installed 43 | ################################################################ 44 | TDX_SETUP_ATTESTATION=0 45 | 46 | ################################################################ 47 | # Enable the NVIDIA H100 48 | # 49 | # Set to 1 to setup necessary components to enable H100 GPU 50 | # pass-through to TD VM 51 | ################################################################ 52 | TDX_SETUP_NVIDIA_H100=0 53 | 54 | ################################################################ 55 | # HOST # 56 | ################################################################ 57 | 58 | 59 | ################################################################ 60 | # GUEST # 61 | ################################################################ 62 | 63 | ################################################################ 64 | # Enable the intel optimized kernel for the guest 65 | # Set to 1 to enable 66 | # By default, the generic kernel is used 67 | ################################################################ 68 | TDX_SETUP_INTEL_KERNEL=0 69 | 70 | ################################################################ 71 | # Configure the guest credentials 72 | ################################################################ 73 | GUEST_USER="tdx" 74 | GUEST_PASSWORD="123456" 75 | GUEST_HOSTNAME="tdx-guest" 76 | 77 | ################################################################ 78 | # APPS 79 | ################################################################ 80 | 81 | ################################################################ 82 | # Enable AI Apps in the guest 83 | ################################################################ 84 | TDX_SETUP_APPS_OLLAMA=0 85 | -------------------------------------------------------------------------------- /setup-tdx-guest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is part of Canonical's TDX repository which includes tools 4 | # to setup and configure a confidential computing environment 5 | # based on Intel TDX technology. 6 | # See the LICENSE file in the repository for the license text. 7 | 8 | # Copyright 2024 Canonical Ltd. 9 | # SPDX-License-Identifier: GPL-3.0-only 10 | 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License version 3, 13 | # as published by the Free Software Foundation. 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranties 16 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the GNU General Public License for more details. 18 | 19 | _on_error() { 20 | trap '' ERR 21 | line_path=$(caller) 22 | line=${line_path% *} 23 | path=${line_path#* } 24 | 25 | echo "" 26 | echo "ERR $path:$line $BASH_COMMAND exited with $1" 27 | exit 1 28 | } 29 | trap '_on_error $?' ERR 30 | 31 | parse_params() { 32 | while :; do 33 | case "${1-}" in 34 | -h | --help) 35 | usage 36 | exit 0 37 | ;; 38 | upgrade) 39 | install_kobuk 40 | exit 0 41 | ;; 42 | "") 43 | break 44 | esac 45 | shift 46 | done 47 | } 48 | 49 | parse_params "$@" 50 | 51 | if [ "$EUID" -ne 0 ] 52 | then echo "Please run as root" 53 | exit 54 | fi 55 | 56 | set -eE 57 | 58 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 59 | 60 | # source config file 61 | if [ -f ${SCRIPT_DIR}/setup-tdx-config ]; then 62 | source ${SCRIPT_DIR}/setup-tdx-config 63 | fi 64 | 65 | # the kernel flavour/type we want to use 66 | KERNEL_TYPE=linux-image-generic 67 | 68 | # use can use -intel kernel by setting TDX_SETUP_INTEL_KERNEL 69 | if [[ "${TDX_SETUP_INTEL_KERNEL}" == "1" ]]; then 70 | KERNEL_TYPE=linux-image-intel 71 | fi 72 | 73 | source ${SCRIPT_DIR}/setup-tdx-common 74 | 75 | install_kobuk() { 76 | # install TDX feature 77 | # install modules-extra to have tdx_guest module 78 | apt install --yes --allow-downgrades \ 79 | ${KERNEL_TYPE} \ 80 | shim-signed \ 81 | grub-efi-amd64-signed \ 82 | grub-efi-amd64-bin 83 | 84 | # if a specific kernel has to be used instead of generic 85 | # TODO : install linux-modules-extra 86 | if [ -n "${KERNEL_RELEASE}" ]; then 87 | apt install --yes --allow-downgrades \ 88 | "linux-image-unsigned-${KERNEL_RELEASE}" 89 | fi 90 | 91 | KERNEL_RELEASE=$(get_kernel_version "$KERNEL_TYPE") 92 | # select the right kernel for next boot 93 | grub_set_kernel 94 | 95 | # some kernels (for example -intel) might not be installed with the modules-extra 96 | # but we need it to support a wider range of hardware (network cards, ...) 97 | # just force the installation of modules-extra to make sure we have it 98 | apt install --yes --allow-downgrades linux-modules-extra-${KERNEL_RELEASE} 99 | } 100 | 101 | apt update 102 | apt install --yes software-properties-common gawk &> /dev/null 103 | 104 | # cleanup 105 | # NB: '*' before kobuk to keep backward compatiblity to make sure 106 | # we clean up all conf files that have been deployed in the 107 | # previous releases 108 | rm -f /etc/apt/preferences.d/*kobuk*tdx-* 109 | rm -f /etc/apt/apt.conf.d/99unattended-upgrades-kobuk 110 | 111 | # We want wordsplitting if there is multiple entries 112 | # shellcheck disable=SC2086 113 | add_kobuk_ppas ${TDX_PPA:-tdx-release} 114 | 115 | # upgrade the system to have the latest components (mostly generic kernel) 116 | apt upgrade --yes 117 | 118 | install_kobuk 119 | 120 | # setup attestation 121 | if [[ "${TDX_SETUP_ATTESTATION}" == "1" ]]; then 122 | "${SCRIPT_DIR}"/attestation/setup-attestation-guest.sh 123 | else 124 | echo "Skip installing attestation components..." 125 | fi 126 | 127 | # We only support Nvidia drivers in Ubuntu LTS 128 | # So restrict the driver installation only on Noble 24.04 129 | if [[ "${TDX_SETUP_NVIDIA_H100}" == "1" ]] && [[ "$(lsb_release -sr)" == "24.04" ]]; then 130 | echo "Setup components for NVIDIA H100..." 131 | echo "Setup components for NVIDIA H100... Enable LKCA" 132 | # Enable LKCA 133 | cat <<-EOF > /etc/modprobe.d/nvidia-lkca.conf 134 | install nvidia /sbin/modprobe ecdsa_generic; /sbin/modprobe ecdh; /sbin/modprobe --ignore-install nvidia 135 | EOF 136 | update-initramfs -u 137 | 138 | # https://documentation.ubuntu.com/server/how-to/graphics/install-nvidia-drivers/index.html 139 | # Cannot use ubuntu-drivers because we do not have GPUs passed-through in the guest image build 140 | # VM 141 | #apt install --yes ubuntu-drivers-common 142 | #ubuntu-drivers install --gpgpu nvidia:570-server 143 | #apt install --yes nvidia-utils-570-server 144 | 145 | echo "Setup components for NVIDIA H100... Install CUDA driver and toolkit" 146 | wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/cuda-keyring_1.1-1_all.deb 147 | dpkg -i cuda-keyring_1.1-1_all.deb 148 | apt update 149 | apt install --yes cuda-toolkit-12-8 150 | wget https://us.download.nvidia.com/tesla/570.86.15/nvidia-driver-local-repo-ubuntu2404-570.86.15_1.0-1_amd64.deb 151 | dpkg -i ./nvidia-driver-local-repo-ubuntu2404-570.86.15_1.0-1_amd64.deb 152 | cp /var/nvidia-driver-local-repo-ubuntu2404-570.86.15/nvidia-driver-local-41F54E74-keyring.gpg /usr/share/keyrings/ 153 | apt install --yes nvidia-driver-570-open 154 | 155 | # install nvtop 156 | echo "Setup components for NVIDIA H100... Install utilities" 157 | apt install --yes nvtop 158 | 159 | # enable and setup persistance mode 160 | echo "Setup components for NVIDIA H100... Enable persistence mode" 161 | systemctl enable nvidia-persistenced.service 162 | mkdir -p /etc/systemd/system/nvidia-persistenced.service.d/ 163 | cat <<-EOF > /etc/systemd/system/nvidia-persistenced.service.d/override.conf 164 | [Service] 165 | ExecStart= 166 | ExecStart=/usr/bin/nvidia-persistenced --uvm-persistence-mode --verbose 167 | EOF 168 | 169 | # needs to put NVIDIA GPU to ready state before any USE 170 | # add init script to do it at VM boot 171 | echo "Setup components for NVIDIA H100... Add set ready script" 172 | cat <<-EOF > /lib/systemd/system/nvidia-tdx.service 173 | [Unit] 174 | Description=TDX H100 setup 175 | After=nvidia-persistenced.service 176 | 177 | [Service] 178 | ExecStart=nvidia-smi conf-compute -srs 1 179 | 180 | [Install] 181 | WantedBy=multi-user.target 182 | EOF 183 | systemctl enable nvidia-tdx 184 | fi 185 | 186 | # install ollama 187 | if [[ "${TDX_SETUP_APPS_OLLAMA}" == "1" ]]; then 188 | echo "Install OLLAMA" 189 | curl \-fsSL https://ollama.com/install.sh | sh 190 | mkdir -p /etc/systemd/system/ollama.service.d/ 191 | 192 | if [[ "${TDX_SETUP_NVIDIA_H100}" == "1" ]]; then 193 | cat <<-EOF > /etc/systemd/system/ollama.service.d/override.conf 194 | [Unit] 195 | After=nvidia-tdx.service 196 | EOF 197 | fi 198 | 199 | systemctl enable ollama.service 200 | sync 201 | fi 202 | -------------------------------------------------------------------------------- /setup-tdx-host.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is part of Canonical's TDX repository which includes tools 4 | # to setup and configure a confidential computing environment 5 | # based on Intel TDX technology. 6 | # See the LICENSE file in the repository for the license text. 7 | 8 | # Copyright 2024 Canonical Ltd. 9 | # SPDX-License-Identifier: GPL-3.0-only 10 | 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License version 3, 13 | # as published by the Free Software Foundation. 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranties 16 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the GNU General Public License for more details. 18 | 19 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 20 | 21 | # source config file 22 | if [ -f ${SCRIPT_DIR}/setup-tdx-config ]; then 23 | source ${SCRIPT_DIR}/setup-tdx-config 24 | fi 25 | 26 | on_exit() { 27 | rc=$? 28 | if [ ${rc} -ne 0 ]; then 29 | echo "=====================================" 30 | echo "ERROR : The script failed..." 31 | echo "=====================================" 32 | fi 33 | return ${rc} 34 | } 35 | 36 | _error() { 37 | echo "Error : $1" 38 | exit 1 39 | } 40 | 41 | trap "on_exit" EXIT 42 | 43 | source ${SCRIPT_DIR}/setup-tdx-common 44 | 45 | # the kernel flavour/type we want to use 46 | KERNEL_TYPE=linux-image-intel 47 | 48 | 49 | usage() { 50 | cat < /dev/null 151 | 152 | # cleanup 153 | # NB: '*' before kobuk to keep backward compatiblity to make sure 154 | # we clean up all conf files that have been deployed in the 155 | # previous releases 156 | rm -f /etc/apt/preferences.d/*kobuk*tdx-* 157 | rm -f /etc/apt/apt.conf.d/99unattended-upgrades-kobuk 158 | 159 | # stop at error 160 | set -e 161 | 162 | # We want wordsplitting if there is multiple entries 163 | # shellcheck disable=SC2086 164 | add_kobuk_ppas ${TDX_PPA:-tdx-release} 165 | 166 | install_kobuk 167 | 168 | # in recent -intel kernel, tdx is enabled by default in kvm_intel 169 | # so this is not necessary anymore 170 | #grub_cmdline_kvm || true 171 | 172 | grub_cmdline_nohibernate || true 173 | add_user_to_kvm || true 174 | 175 | # setup attestation 176 | if [[ "${TDX_SETUP_ATTESTATION}" == "1" ]]; then 177 | "${SCRIPT_DIR}"/attestation/setup-attestation-host.sh 178 | else 179 | echo "Skip installing attestation components..." 180 | fi 181 | 182 | # configure non-root user for libvirt 183 | enable_normal_user_libvirt 184 | 185 | echo "========================================================================" 186 | echo "The host OS setup has been done successfully. Now, please enable Intel TDX in the BIOS." 187 | echo "========================================================================" 188 | -------------------------------------------------------------------------------- /system-report.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This file is part of Canonical's TDX repository which includes tools 4 | # to setup and configure a confidential computing environment 5 | # based on Intel TDX technology. 6 | # See the LICENSE file in the repository for the license text. 7 | 8 | # Copyright 2024 Canonical Ltd. 9 | # SPDX-License-Identifier: GPL-3.0-only 10 | 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License version 3, 13 | # as published by the Free Software Foundation. 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranties 16 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the GNU General Public License for more details. 18 | 19 | # 20 | # This script outputs relevant system information for 21 | # reporting TDX issues at: 22 | # 23 | # https://github.com/canonical/tdx/issues 24 | # 25 | 26 | print_section() { 27 | if [ $# -ne 2 ]; then 28 | >&2 echo "$0
" 29 | exit 1 30 | fi 31 | header=$1 32 | cmdout=$2 33 | printf "### ${header}\n\n" 34 | printf "\`\`\`\n" 35 | printf "${cmdout}" 36 | printf "\n\`\`\`\n\n" 37 | } 38 | 39 | set_pkg_result_string() { 40 | if [ $# -ne 1 ]; then 41 | >&2 echo "$0 " 42 | exit 1 43 | fi 44 | package=$1 45 | result=$( \ 46 | if dpkg -s ${package} &> /dev/null; then \ 47 | echo "Status: Installed"; \ 48 | else echo "Status: Not Installed"; \ 49 | fi) 50 | result="$result\n$(apt info ${package} 2>/dev/null | grep -E 'Package|Version|APT-Sources')" 51 | } 52 | 53 | # load msr kernel module and install msr-tools if needed 54 | install_msr() { 55 | if ! which rdmsr &> /dev/null; then 56 | sudo apt install -y msr-tools &> /dev/null 57 | fi 58 | sudo modprobe msr &> /dev/null 59 | } 60 | 61 | set_msr_result_string() { 62 | install_msr 63 | MK_TME_ENABLED=$(sudo rdmsr 0x982 -f 1:1) 64 | result="MK_TME_ENABLED bit: ${MK_TME_ENABLED} (expected value: 1)" 65 | SEAM_RR=$(sudo rdmsr 0x1401 -f 11:11) 66 | result="$result\nSEAM_RR bit: $SEAM_RR (expected value: 1)" 67 | NUM_TDX_PRIV_KEYS=$(sudo rdmsr 0x87 -f 63:32) 68 | result="$result\nNUM_TDX_PRIV_KEYS: $NUM_TDX_PRIV_KEYS" 69 | SGX_AND_MCHECK_STATUS=$(sudo rdmsr 0xa0) 70 | result="$result\nSGX_AND_MCHECK_STATUS: $SGX_AND_MCHECK_STATUS (expected value: 0)" 71 | PROD=$(sudo rdmsr 0xce -f 27:27) 72 | PRODUCTION="Pre-production" 73 | if [[ "${PROD}" = "0" ]]; then 74 | PRODUCTION="Production" 75 | fi 76 | result="$result\nProduction platform: ${PRODUCTION} (expected value: Production)" 77 | } 78 | 79 | printf "If you are running this for reporting an issue on GitHub,\n" 80 | printf "copy all output between the markers below.\n\n" 81 | 82 | printf "<======== COPY BELOW HERE ========>\n\n" 83 | 84 | git_ref=$(git rev-parse HEAD 2> /dev/null) 85 | print_section "Git ref" "${git_ref}" 86 | 87 | result=$(lsb_release -a) 88 | print_section "Operating system details" "${result}" 89 | 90 | result=$(uname -rvpio) # show everything but hostname 91 | print_section "Kernel version" "${result}" 92 | 93 | dmesg_head=$(sudo dmesg | grep -i tdx | head -n 10) 94 | dmesg_tail=$(sudo dmesg | grep -i tdx | tail -n 20) 95 | result="${dmesg_head}\n...\n${dmesg_tail}" 96 | print_section "TDX kernel logs" "${result}" 97 | 98 | result=$( \ 99 | if grep -q tdx /proc/cpuinfo; then \ 100 | echo "CPU supports TDX according to /proc/cpuinfo"; \ 101 | else echo "No TDX support in CPU according to /proc/cpuinfo"; \ 102 | fi) 103 | print_section "TDX CPU instruction support" "${result}" 104 | 105 | set_msr_result_string 106 | print_section "Model specific registers (MSRs)" "${result}" 107 | 108 | result=$(grep -m1 "model name" /proc/cpuinfo | cut -f2 -d":") 109 | print_section "CPU details" "${result}" 110 | 111 | set_pkg_result_string "qemu-system-x86" 112 | print_section "QEMU package details" "${result}" 113 | 114 | set_pkg_result_string "libvirt-clients" 115 | print_section "Libvirt package details" "${result}" 116 | 117 | set_pkg_result_string "ovmf" 118 | print_section "OVMF package details" "${result}" 119 | 120 | set_pkg_result_string "sgx-dcap-pccs" 121 | print_section "sgx-dcap-pccs package details" "${result}" 122 | 123 | set_pkg_result_string "tdx-qgs" 124 | print_section "tdx-qgs package details" "${result}" 125 | 126 | set_pkg_result_string "sgx-ra-service" 127 | print_section "sgx-ra-service package details" "${result}" 128 | 129 | set_pkg_result_string "sgx-pck-id-retrieval-tool" 130 | print_section "sgx-pck-id-retrieval-tool package details" "${result}" 131 | 132 | result=$(systemctl status qgsd 2>&1) 133 | print_section "QGSD service status" "${result}" 134 | 135 | result=$(systemctl status pccs 2>&1) 136 | print_section "PCCS service status" "${result}" 137 | 138 | result=$(tail -n 30 /var/log/mpa_registration.log 2>/dev/null) 139 | print_section "MPA registration logs (last 30 lines)" "${result}" 140 | 141 | printf "<======== COPY ABOVE HERE ========>\n" 142 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | ## Intel TDX Tests 2 | 3 | This folder contains Intel TDX tests. 4 | 5 | ### Pre-requisites 6 | 7 | - The tests must be executed on a host that has been set up properly for Intel TDX. 8 | 9 | ### Run tests 10 | 11 | The script `tdtest` is the test runner, it is a wrapper around `pytest`. 12 | Please run `tdtest -h` for more details about its usage. 13 | 14 | The tests are organized in different categories and this organization is 15 | reflected in the structure of the `tests`. 16 | 17 | You can choose to run a category of tests by specifying the appropriate sudirectory under `tests`. For example, to run the boot tests: 18 | 19 | ``` 20 | $ sudo ./tdtest tests/boot 21 | ``` 22 | 23 | You can run all tests except the performance: 24 | 25 | ``` 26 | $ sudo ./tdtest -k 'not test_perf' 27 | ``` 28 | 29 | Since `tdtest` is a wrapper of pytest, it exposes all the features of `pytest` 30 | that you can use to run, manage and inspect the tests. 31 | 32 | ### Run tests with checkbox: 33 | 34 | Go to the `tests` folder. 35 | 36 | ``` 37 | $ snapcraft 38 | $ sudo snap install ./checkbox-tdx_1.0.0_amd64.snap --dangerous --classic 39 | ``` 40 | 41 | - Run sanity tests to check the host setup: 42 | 43 | ``` 44 | $ checkbox-tdx.test-runner-automated-host 45 | ``` 46 | 47 | - Run guest tests: 48 | 49 | ``` 50 | $ checkbox-tdx.test-runner-automated-guest 51 | ``` 52 | 53 | - Run boot tests: 54 | 55 | ``` 56 | $ checkbox-tdx.test-runner-automated-boot 57 | ``` 58 | 59 | - Run perf tests: 60 | 61 | ``` 62 | $ checkbox-tdx.test-runner-automated-perf 63 | ``` 64 | 65 | - Run quote tests: 66 | 67 | ``` 68 | $ checkbox-tdx.test-runner-automated-quote 69 | ``` 70 | 71 | - Run stress tests: 72 | 73 | ``` 74 | $ checkbox-tdx.test-runner-automated-stress 75 | ``` 76 | 77 | - Run all tests: 78 | 79 | Please note that the performance tests can take a long time to run. 80 | 81 | ``` 82 | $ checkbox-tdx.test-runner-automated 83 | ``` 84 | 85 | 86 | ### Intel TDX Tests specification 87 | 88 | For sanity and functionality test cases of Intel TDX, please see this [wiki](https://github.com/intel/tdx/wiki/Tests). 89 | -------------------------------------------------------------------------------- /tests/bin/setup-env-and-run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is part of Canonical's TDX repository which includes tools 4 | # to setup and configure a confidential computing environment 5 | # based on Intel TDX technology. 6 | # See the LICENSE file in the repository for the license text. 7 | 8 | # Copyright 2024 Canonical Ltd. 9 | # SPDX-License-Identifier: GPL-3.0-only 10 | 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License version 3, 13 | # as published by the Free Software Foundation. 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranties 16 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the GNU General Public License for more details. 18 | 19 | # this is the wrapper script to run a test file with pytest 20 | 21 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 22 | PROVIDER_FOLDER=${SNAP}/providers/checkbox-provider-tdx 23 | 24 | # for provider python simple utilities 25 | export PYTHONPATH=${PYTHONPATH}:${PROVIDER_FOLDER}/lib 26 | # for tdxtools package 27 | export PYTHONPATH=${PYTHONPATH}:${SNAP}/lib/python3.12/site-packages/ 28 | 29 | # use a cache for pytest 30 | PYTEST_CACHE=${PLAINBOX_SESSION_SHARE}/pytest/.pytest_cache 31 | mkdir -p ${PYTEST_CACHE} 32 | 33 | # Go to the pytest folder, it is important for pytest to load some 34 | # conf files (conftest.py) 35 | 36 | # default pytest arguments 37 | # -s : do not capture logs 38 | # -v : increase verbosity 39 | cd ${PROVIDER_FOLDER}/tests && pytest -o cache_dir=${PYTEST_CACHE} -s -v "$@" 40 | -------------------------------------------------------------------------------- /tests/checkbox/bin/checkbox-cli-wrapper: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # wrapper around the checkbox-cli 4 | # can't use /snap/bin/checkbox.checkbox-cli as it will run 5 | # within the context of the wrong snap and the tdx tests 6 | # won't get discovered via PROVIDERPATH 7 | # checkbox-cli resolves to /snap/checkbox24/current/bin/checkbox-cli 8 | exec checkbox-cli "$@" 9 | -------------------------------------------------------------------------------- /tests/checkbox/bin/checkbox-cli-wrapper-image: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "${TDXTEST_GUEST_IMG}" ]; then 4 | echo "TDXTEST_GUEST_IMG must be specified!" 5 | echo "e.g. export TDXTEST_GUEST_IMG=/tmp/tmp.qcow2" 6 | echo "(Use sudo -E to pass environment to sudo)" 7 | exit 1 8 | fi 9 | 10 | if ! test -f $TDXTEST_GUEST_IMG; then 11 | echo "\$TDXTEST_GUEST_IMG specified, but does not exist!" 12 | echo " Can't find $TDXTEST_GUEST_IMG" 13 | exit 1 14 | fi 15 | 16 | # wrapper around the checkbox-cli 17 | # can't use /snap/bin/checkbox.checkbox-cli as it will run 18 | # within the context of the wrong snap and the tdx tests 19 | # won't get discovered via PROVIDERPATH 20 | # checkbox-cli resolves to /snap/checkbox24/current/bin/checkbox-cli 21 | exec checkbox-cli "$@" 22 | -------------------------------------------------------------------------------- /tests/checkbox/bin/checkbox-cli-wrapper-local: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Remove temporary folder and rebuild in setup-env-and-run 4 | TMP_FOLDER=/tmp/snap/checkbox-tdx 5 | rm -rf ${TMP_FOLDER} 6 | 7 | # wrapper around the checkbox-cli 8 | # must use /snap/bin/checkbox.checkbox-cli outside the snap 9 | TDX_LOCAL_TESTING=1 exec /snap/bin/checkbox.checkbox-cli "$@" 10 | -------------------------------------------------------------------------------- /tests/checkbox/bin/configure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2018-2022 Canonical Ltd. 3 | # All rights reserved. 4 | # 5 | # Written by: 6 | # Maciej Kisielewski 7 | # Sylvain Pineau 8 | import os 9 | import re 10 | import sys 11 | 12 | sys.path.append(os.path.expandvars("$SNAP/usr/lib/python3/dist-packages")) 13 | sitepkgpath = "$SNAP/lib/python3.12/site-packages" 14 | sys.path.append(os.path.expandvars(sitepkgpath)) 15 | 16 | sys.path.append(os.path.expandvars( 17 | "/snap/checkbox24/current/usr/lib/python3/dist-packages")) 18 | runtimepath = "/snap/checkbox24/current/lib/python3.12/site-packages" 19 | sys.path.append(os.path.expandvars(runtimepath)) 20 | 21 | try: 22 | from checkbox_support.snap_utils.config import update_configuration 23 | from checkbox_support.snap_utils.config import print_checkbox_conf 24 | except ImportError: 25 | msg = """ 26 | checkbox-support not found! 27 | You need to install the checkbox24 snap: 28 | 29 | snap install checkbox24 30 | """ 31 | print(os.path.expandvars(msg), file=sys.stderr) 32 | sys.exit(1) 33 | 34 | 35 | def main(): 36 | # we need run as root to be able to write to /var/snap/... 37 | if os.geteuid() != 0: 38 | print('You have to run this command with sudo') 39 | return 40 | 41 | if len(sys.argv) > 1 and sys.argv[1] == '-l': 42 | print_checkbox_conf() 43 | return 44 | 45 | key_re = re.compile(r"^(?:[A-Z0-9]+_?)*[A-Z](?:_?[A-Z0-9])*$") 46 | vars_to_set = dict() 47 | for pair in sys.argv[1:]: 48 | k, _, v = pair.partition('=') 49 | if not key_re.match(k) or not v: 50 | raise SystemExit("'%s' is not a valid configuration entry. " 51 | "Should be KEY=val" % pair) 52 | k = k.replace('_', '-').lower() 53 | vars_to_set[k] = v 54 | update_configuration(vars_to_set) 55 | 56 | 57 | if __name__ == '__main__': 58 | main() 59 | -------------------------------------------------------------------------------- /tests/checkbox/bin/install-full-deps: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # setup the host system before the tests 4 | -------------------------------------------------------------------------------- /tests/checkbox/bin/shell-wrapper: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "$SNAP_NAME runtime shell, type 'exit' to quit the session" 4 | exec bash 5 | -------------------------------------------------------------------------------- /tests/checkbox/bin/test-runner: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S checkbox-cli-wrapper 2 | [launcher] 3 | app_id = com.canonical.certification:checkbox 4 | launcher_version = 1 5 | stock_reports = text, submission_files 6 | 7 | [test plan] 8 | unit = com.canonical.certification::intel-tdx 9 | -------------------------------------------------------------------------------- /tests/checkbox/bin/test-runner-automated: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S checkbox-cli-wrapper-image 2 | [launcher] 3 | app_id = com.canonical.certification:checkbox 4 | launcher_version = 1 5 | stock_reports = text, submission_files 6 | 7 | [test plan] 8 | unit = com.canonical.certification::intel-tdx 9 | forced = yes 10 | 11 | [test selection] 12 | forced = yes 13 | 14 | [ui] 15 | type = silent 16 | auto_retry = no 17 | 18 | -------------------------------------------------------------------------------- /tests/checkbox/bin/test-runner-automated-boot: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S checkbox-cli-wrapper-image 2 | [launcher] 3 | app_id = com.canonical.certification:checkbox 4 | launcher_version = 1 5 | stock_reports = text, submission_files 6 | 7 | [test plan] 8 | unit = com.canonical.certification::intel-tdx-boot 9 | forced = yes 10 | 11 | [test selection] 12 | forced = yes 13 | 14 | [ui] 15 | type = silent 16 | auto_retry = no 17 | 18 | -------------------------------------------------------------------------------- /tests/checkbox/bin/test-runner-automated-guest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S checkbox-cli-wrapper-image 2 | [launcher] 3 | app_id = com.canonical.certification:checkbox 4 | launcher_version = 1 5 | stock_reports = text, submission_files 6 | 7 | [test plan] 8 | unit = com.canonical.certification::intel-tdx-guest 9 | forced = yes 10 | 11 | [test selection] 12 | forced = yes 13 | 14 | [ui] 15 | type = silent 16 | auto_retry = no 17 | 18 | -------------------------------------------------------------------------------- /tests/checkbox/bin/test-runner-automated-host: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S checkbox-cli-wrapper 2 | [launcher] 3 | app_id = com.canonical.certification:checkbox 4 | launcher_version = 1 5 | stock_reports = text, submission_files 6 | 7 | [test plan] 8 | unit = com.canonical.certification::intel-tdx-host 9 | forced = yes 10 | 11 | [test selection] 12 | forced = yes 13 | 14 | [ui] 15 | type = silent 16 | auto_retry = no 17 | 18 | -------------------------------------------------------------------------------- /tests/checkbox/bin/test-runner-automated-perf: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S checkbox-cli-wrapper-image 2 | [launcher] 3 | app_id = com.canonical.certification:checkbox 4 | launcher_version = 1 5 | stock_reports = text, submission_files 6 | 7 | [test plan] 8 | unit = com.canonical.certification::intel-tdx-perf 9 | forced = yes 10 | 11 | [test selection] 12 | forced = yes 13 | 14 | [ui] 15 | type = silent 16 | auto_retry = no 17 | 18 | -------------------------------------------------------------------------------- /tests/checkbox/bin/test-runner-automated-quote: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S checkbox-cli-wrapper-image 2 | [launcher] 3 | app_id = com.canonical.certification:checkbox 4 | launcher_version = 1 5 | stock_reports = text, submission_files 6 | 7 | [test plan] 8 | unit = com.canonical.certification::intel-tdx-quote 9 | forced = yes 10 | 11 | [test selection] 12 | forced = yes 13 | 14 | [ui] 15 | type = silent 16 | auto_retry = no 17 | 18 | -------------------------------------------------------------------------------- /tests/checkbox/bin/test-runner-automated-stress: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S checkbox-cli-wrapper-image 2 | [launcher] 3 | app_id = com.canonical.certification:checkbox 4 | launcher_version = 1 5 | stock_reports = text, submission_files 6 | 7 | [test plan] 8 | unit = com.canonical.certification::intel-tdx-stress 9 | forced = yes 10 | 11 | [test selection] 12 | forced = yes 13 | 14 | [ui] 15 | type = silent 16 | auto_retry = no 17 | 18 | -------------------------------------------------------------------------------- /tests/checkbox/bin/wrapper_local: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | case "$SNAP_ARCH" in 4 | "amd64") ARCH='x86_64-linux-gnu' 5 | ;; 6 | *) 7 | echo "Unsupported architecture: $SNAP_ARCH" 8 | ;; 9 | esac 10 | 11 | ################################################ 12 | # Launcher common exports for any checkbox app # 13 | ################################################ 14 | 15 | RUNTIME=/snap/checkbox24/current 16 | if [ ! -d "$RUNTIME" ]; then 17 | echo "You need to install the checkbox24 snap." 18 | echo "" 19 | echo "You can do this with this command:" 20 | echo "snap install checkbox24" 21 | exit 1 22 | fi 23 | 24 | export LC_ALL=C.UTF-8 25 | PERL_VERSION=$(perl -e '$^V=~/^v(\d+\.\d+)/;print $1') 26 | export PERL5LIB="$PERL5LIB:$SNAP/usr/lib/$ARCH/perl/$PERL_VERSION:$SNAP/usr/lib/$ARCH/perl5/$PERL_VERSION:$SNAP/usr/share/perl/$PERL_VERSION:$SNAP/usr/share/perl5" 27 | export GI_TYPELIB_PATH=$SNAP/usr/lib/girepository-1.0:$SNAP/usr/lib/$ARCH/girepository-1.0 28 | export PATH="$SNAP/usr/sbin:$SNAP/sbin:$SNAP/usr/bin:$SNAP/bin:/snap/bin:$PATH" 29 | export ALSA_CONFIG_PATH=$SNAP/usr/share/alsa/alsa.conf:$SNAP/usr/share/alsa/pcm/default.conf 30 | export PYTHONPATH="$SNAP/lib:$SNAP/usr/lib/python3/dist-packages:$PYTHONPATH" 31 | export PATH="$SNAP/providers/checkbox-provider-tdx/bin/:$PATH" 32 | 33 | if [ -e $RUNTIME/wrapper_common_classic ]; then 34 | . $RUNTIME/wrapper_common_classic 35 | else 36 | echo "ERROR: no $RUNTIME/wrapper_common_classic found" 37 | exit 0 38 | fi 39 | 40 | exec "$@" 41 | -------------------------------------------------------------------------------- /tests/checkbox/checkbox-provider-tdx/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from plainbox.provider_manager import setup, N_ 3 | 4 | # You can inject other stuff here but please don't go overboard. 5 | # 6 | # In particular, if you need comprehensive compilation support to get 7 | # your bin/ populated then please try to discuss that with us in the 8 | # upstream project IRC channel #checkbox on irc.freenode.net. 9 | 10 | # NOTE: one thing that you could do here, that makes a lot of sense, 11 | # is to compute version somehow. This may vary depending on the 12 | # context of your provider. Future version of PlainBox will offer git, 13 | # bzr and mercurial integration using the versiontools library 14 | # (optional) 15 | 16 | setup( 17 | name='com.canonical.certification:intel-tdx', 18 | version="1.0", 19 | description=N_("The com.canonical.certification:intel-tdx provider"), 20 | gettext_domain="com_canonical_certification_intel-tdx", 21 | ) 22 | -------------------------------------------------------------------------------- /tests/checkbox/checkbox-provider-tdx/units/category.pxu: -------------------------------------------------------------------------------- 1 | unit: category 2 | id: tdx 3 | _name: Intel TDX basic tests 4 | 5 | -------------------------------------------------------------------------------- /tests/checkbox/checkbox-provider-tdx/units/jobs.pxu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/tdx/325b23387bd56f923228731dc5331f057eff2586/tests/checkbox/checkbox-provider-tdx/units/jobs.pxu -------------------------------------------------------------------------------- /tests/checkbox/checkbox-provider-tdx/units/test-plan.pxu: -------------------------------------------------------------------------------- 1 | id: intel-tdx 2 | unit: test plan 3 | _name: Intel Trusted Domain eXtension (TDX) full tests 4 | include: 5 | tdx-host/.* 6 | tdx-guest/.* 7 | tdx-boot/.* 8 | tdx-quote/.* 9 | tdx-stress/.* 10 | tdx-eventlog/.* 11 | mandatory_include: 12 | com.canonical.certification::miscellanea/submission-resources 13 | bootstrap_include: 14 | com.canonical.certification::executable 15 | com.canonical.certification::snap 16 | 17 | id: intel-tdx-host 18 | unit: test plan 19 | _name: Intel Trusted Domain eXtension (TDX) host tests 20 | include: 21 | tdx-host/.* 22 | mandatory_include: 23 | com.canonical.certification::miscellanea/submission-resources 24 | bootstrap_include: 25 | com.canonical.certification::executable 26 | com.canonical.certification::snap 27 | 28 | id: intel-tdx-guest 29 | unit: test plan 30 | _name: Intel Trusted Domain eXtension (TDX) guest tests 31 | include: 32 | tdx-guest/.* 33 | mandatory_include: 34 | com.canonical.certification::miscellanea/submission-resources 35 | bootstrap_include: 36 | com.canonical.certification::executable 37 | com.canonical.certification::snap 38 | 39 | id: intel-tdx-boot 40 | unit: test plan 41 | _name: Intel Trusted Domain eXtension (TDX) guest boot tests 42 | include: 43 | tdx-boot/.* 44 | mandatory_include: 45 | com.canonical.certification::miscellanea/submission-resources 46 | bootstrap_include: 47 | com.canonical.certification::executable 48 | com.canonical.certification::snap 49 | 50 | id: intel-tdx-perf 51 | unit: test plan 52 | _name: Intel Trusted Domain eXtension (TDX) perf tests 53 | include: 54 | tdx-perf/.* 55 | mandatory_include: 56 | com.canonical.certification::miscellanea/submission-resources 57 | bootstrap_include: 58 | com.canonical.certification::executable 59 | com.canonical.certification::snap 60 | 61 | id: intel-tdx-quote 62 | unit: test plan 63 | _name: Intel Trusted Domain eXtension (TDX) attestation tests 64 | include: 65 | tdx-quote/.* 66 | mandatory_include: 67 | com.canonical.certification::miscellanea/submission-resources 68 | bootstrap_include: 69 | com.canonical.certification::executable 70 | com.canonical.certification::snap 71 | 72 | id: intel-tdx-eventlog 73 | unit: test plan 74 | _name: Intel Trusted Domain eXtension (TDX) eventlog tests 75 | include: 76 | tdx-eventlog/.* 77 | mandatory_include: 78 | com.canonical.certification::miscellanea/submission-resources 79 | bootstrap_include: 80 | com.canonical.certification::executable 81 | com.canonical.certification::snap 82 | 83 | id: intel-tdx-stress 84 | unit: test plan 85 | _name: Intel Trusted Domain eXtension (TDX) stress tests 86 | include: 87 | tdx-stress/.* 88 | mandatory_include: 89 | com.canonical.certification::miscellanea/submission-resources 90 | bootstrap_include: 91 | com.canonical.certification::executable 92 | com.canonical.certification::snap 93 | -------------------------------------------------------------------------------- /tests/checkbox/checkbox-provider-tdx/units/tests/category.pxu: -------------------------------------------------------------------------------- 1 | unit: category 2 | id: tdx-host 3 | _name: Intel TDX host tests 4 | 5 | unit: category 6 | id: tdx-guest 7 | _name: Intel TDX guest tests 8 | 9 | unit: category 10 | id: tdx-boot 11 | _name: Intel TDX boot tests 12 | 13 | unit: category 14 | id: tdx-perf 15 | _name: Intel TDX perf tests 16 | 17 | unit: category 18 | id: tdx-quote 19 | _name: Intel TDX quote tests 20 | 21 | unit: category 22 | id: tdx-stress 23 | _name: Intel TDX stress tests 24 | -------------------------------------------------------------------------------- /tests/checkbox/checkbox-provider-tdx/units/tests/jobs.pxu: -------------------------------------------------------------------------------- 1 | id: tdx-host/test_host_tdx_hardware_enabled 2 | category_id: tdx-host 3 | flags: simple 4 | user: root 5 | _summary: Check if platform is TDX capable from hardware standpoint 6 | depends: 7 | after: 8 | requires: 9 | command: 10 | setup-env-and-run host/test_host_tdx_hardware.py -k 'test_host_tdx_hardware_enabled' 11 | 12 | id: tdx-host/test_host_tdx_cpu 13 | category_id: tdx-host 14 | flags: simple 15 | user: root 16 | _summary: Check if the CPU is TDX capable 17 | depends: 18 | tdx-host/test_host_tdx_hardware_enabled 19 | after: 20 | requires: 21 | command: 22 | setup-env-and-run host/test_host_tdx_software.py -k 'test_host_tdx_cpu' 23 | 24 | id: tdx-host/test_host_tdx_software 25 | category_id: tdx-host 26 | flags: simple 27 | user: root 28 | _summary: Check if the software stack is TDX capable 29 | depends: 30 | tdx-host/test_host_tdx_hardware_enabled 31 | after: 32 | requires: 33 | command: 34 | setup-env-and-run host/test_host_tdx_software.py -k 'test_host_tdx_software' 35 | 36 | id: tdx-host/test_host_tdx_module_load 37 | category_id: tdx-host 38 | flags: simple 39 | user: root 40 | _summary: Check if the TDX module is loaded 41 | depends: 42 | tdx-host/test_host_tdx_hardware_enabled 43 | after: 44 | requires: 45 | command: 46 | setup-env-and-run host/test_host_tdx_software.py -k 'test_host_tdx_module_load' 47 | 48 | id: tdx-boot/td-boot-guest 49 | category_id: tdx-boot 50 | flags: simple 51 | _summary: Test TD boot 52 | depends: 53 | after: 54 | requires: 55 | executable.name == 'qemu-system-x86_64' 56 | command: 57 | setup-env-and-run boot/test_boot_basic.py -k 'test_guest_boot' 58 | 59 | id: tdx-boot/td-boot-guest-printk 60 | category_id: tdx-boot 61 | flags: simple 62 | _summary: Test TD boot earlyprintk 63 | depends: 64 | after: 65 | requires: 66 | executable.name == 'qemu-system-x86_64' 67 | command: 68 | setup-env-and-run boot/test_boot_basic.py -k 'test_guest_early_printk' 69 | 70 | id: tdx-boot/td-coexist 71 | category_id: tdx-boot 72 | flags: simple 73 | _summary: Test coexistence of TD and VM 74 | depends: 75 | after: 76 | requires: 77 | executable.name == 'qemu-system-x86_64' 78 | command: 79 | setup-env-and-run boot/test_boot_coexist.py -k 'test_coexist_boot' 80 | 81 | id: tdx-boot/td-creation-without-ovmf 82 | category_id: tdx-boot 83 | flags: simple 84 | _summary: Test TD creation 85 | depends: 86 | after: 87 | requires: 88 | executable.name == 'qemu-system-x86_64' 89 | command: 90 | setup-env-and-run boot/test_boot_td_creation.py -k 'test_create_td_without_ovmf' 91 | 92 | id: tdx-boot/test_4vcpus_1socket_10times 93 | category_id: tdx-boot 94 | flags: simple 95 | _summary: Test multiple TD creation 96 | depends: 97 | after: 98 | requires: 99 | executable.name == 'qemu-system-x86_64' 100 | command: 101 | setup-env-and-run boot/test_boot_multiple.py -k 'test_4vcpus_1socket_10times' 102 | 103 | id: tdx-boot/test_4vcpus_2sockets_5times 104 | category_id: tdx-boot 105 | flags: simple 106 | _summary: Test multiple TD creation 107 | depends: 108 | after: 109 | requires: 110 | executable.name == 'qemu-system-x86_64' 111 | command: 112 | setup-env-and-run boot/test_boot_multiple.py -k 'test_4vcpus_2sockets_5times' 113 | 114 | id: tdx-guest/test_guest_cpu_off 115 | category_id: tdx-guest 116 | flags: simple 117 | _summary: Test TD with cpu on/off 118 | depends: 119 | after: 120 | requires: 121 | executable.name == 'qemu-system-x86_64' 122 | command: 123 | setup-env-and-run boot/test_guest_cpu_off.py -k 'test_guest_cpu_off' 124 | 125 | id: tdx-guest/test_guest_cpu_pinned_off 126 | category_id: tdx-guest 127 | flags: simple 128 | _summary: Test TD with cpu pinned 129 | depends: 130 | after: 131 | requires: 132 | executable.name == 'qemu-system-x86_64' 133 | command: 134 | setup-env-and-run boot/test_guest_cpu_off.py -k 'test_guest_cpu_pinned_off' 135 | 136 | id: tdx-guest/test_nmi_debug_off 137 | category_id: tdx-guest 138 | flags: simple 139 | _summary: Test TD nmi after boot 140 | depends: 141 | after: 142 | requires: 143 | executable.name == 'qemu-system-x86_64' 144 | command: 145 | setup-env-and-run test_nmi_debug_off.py -k 'test_nmi_debug_off' 146 | 147 | id: tdx-stress/test_stress_boot 148 | category_id: tdx-stress 149 | flags: simple 150 | _summary: Test TD boot loop 151 | depends: 152 | after: 153 | requires: 154 | executable.name == 'qemu-system-x86_64' 155 | command: 156 | setup-env-and-run stress/test_stress_boot.py -k 'test_stress_boot' 157 | 158 | id: tdx-eventlog/test_guest_measurement_check_rtmr 159 | category_id: tdx-guest 160 | flags: simple 161 | _summary: Test guest measurements 162 | depends: 163 | after: 164 | requires: 165 | executable.name == 'qemu-system-x86_64' 166 | command: 167 | setup-env-and-run eventlog/test_guest_measurement.py -k 'test_guest_measurement_check_rtmr' 168 | 169 | id: tdx-quote/test_quote_check_configfs_tsm 170 | category_id: tdx-quote 171 | flags: simple 172 | _summary: Test guest configfs tsm check 173 | depends: 174 | after: 175 | requires: 176 | executable.name == 'qemu-system-x86_64' 177 | command: 178 | setup-env-and-run quote/test_quote_configfs_tsm.py -k 'test_quote_check_configfs_tsm' 179 | -------------------------------------------------------------------------------- /tests/checkbox/config/config_vars: -------------------------------------------------------------------------------- 1 | # environment variables that will set inside the snap 2 | -------------------------------------------------------------------------------- /tests/lib/common.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # Authors: 3 | # - Hector Cao 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | import os 19 | import Qemu 20 | 21 | script_path=os.path.dirname(os.path.realpath(__file__)) + '/' 22 | # put in /var/tmp instead of /tmp to be persistent across reboots 23 | guest_workdir='/var/tmp/tdxtest' 24 | 25 | def deploy_and_setup(m : Qemu.QemuSSH): 26 | m.rsync_file(f'{script_path}/../', f'{guest_workdir}') 27 | m.check_exec(f'cd {guest_workdir} && ./lib/setup_guest.sh') 28 | -------------------------------------------------------------------------------- /tests/lib/guest/test_tdreport.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # Authors: 3 | # - Hector Cao 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | import os 19 | 20 | from tdxtools.tdreport import TdReport 21 | 22 | def test_report_check_fields(): 23 | """ 24 | Boot measurements check 25 | """ 26 | report = TdReport.get_td_report() 27 | assert report['report_mac_struct']['report_type']['type'] == 0x81 28 | 29 | if __name__ == '__main__': 30 | test_report_check_fields() 31 | -------------------------------------------------------------------------------- /tests/lib/pts/benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) 4 | TEST_PROFILE="default" 5 | 6 | # test results 7 | # if root : /var/lib/phoronix-test-suite/test-results 8 | # normal user : ~/.phoronix-test-suite/test-results 9 | PTS_FOLDER=$HOME/.phoronix-test-suite/ 10 | if [ "$EUID" -eq 0 ]; then 11 | PTS_FOLDER=/var/lib/phoronix-test-suite/ 12 | fi 13 | 14 | cleanup() { 15 | rm -rf $PTS_FOLDER/test-results/ 16 | } 17 | 18 | setup() { 19 | # installation 20 | sudo DEBIAN_FRONTEND=noninteractive apt install -y php-cli php-xml build-essential unzip 21 | 22 | if [ ! -f ${SCRIPT_DIR}/phoronix-test-suite_10.8.4_all.deb ]; then 23 | wget https://phoronix-test-suite.com/releases/repo/pts.debian/files/phoronix-test-suite_10.8.4_all.deb 24 | fi 25 | sudo DEBIAN_FRONTEND=noninteractive apt install -y ./phoronix-test-suite_10.8.4_all.deb 26 | 27 | # phoronix configuration 28 | phoronix-test-suite user-config-set \ 29 | UploadResults=FALSE \ 30 | PromptForTestIdentifier=FALSE \ 31 | PromptForTestDescription=FALSE \ 32 | PromptSaveName=FALSE \ 33 | RunAllTestCombinations=TRUE \ 34 | Configured=TRUE \ 35 | DropNoisyResults=TRUE 36 | 37 | rm -rf $PTS_FOLDER/test-suites/local/* 38 | cp -r ${SCRIPT_DIR}/phoronix-custom-suites/* $PTS_FOLDER/test-suites/local/ 39 | } 40 | 41 | echo " start setup $(date +%s)" 42 | 43 | setup &> /dev/null 44 | 45 | echo " end start setup $(date +%s)" 46 | 47 | cleanup 48 | 49 | export TEST_RESULTS_IDENTIFIER=tdx-memory-benchmark-id 50 | export TEST_RESULTS_NAME=memory-benchmark 51 | export TEST_RESULTS_DESCRIPTION='PTS memory benchmarking for TDX' 52 | 53 | if [ ! -z "$1" ]; then 54 | TEST_PROFILE=$1 55 | fi 56 | 57 | # run 58 | 59 | #phoronix-test-suite batch-install stream 60 | #phoronix-test-suite batch-run stream 61 | 62 | PTS_SILENT_MODE=1 phoronix-test-suite batch-benchmark $TEST_PROFILE 63 | 64 | # OUTPUT_FILE only affects result-file-to-* and not result-file-raw-to-csv 65 | # so we cannot use it here, we wil have to copy the file ourself to benchmark.csv 66 | phoronix-test-suite result-file-raw-to-csv $TEST_RESULTS_NAME 67 | 68 | cp ${HOME}/memory-benchmark-raw.csv ${SCRIPT_DIR}/benchmark.csv 69 | 70 | echo " end test $(date +%s)" 71 | -------------------------------------------------------------------------------- /tests/lib/pts/phoronix-custom-suites/tdx_memory/suite-definition.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tdx_memory 6 | 1.0.0 7 | Other 8 | A suite for explore performance penalty for TDX 9 | Hector Cao 10 | 11 | 12 | memory 13 | Type: Memory benchmark 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/lib/pts/phoronix-custom-suites/tdx_outliers/suite-definition.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | tdx_outliers 7 | 1.0.0 8 | Other 9 | A suite for explore performance penalty for TDX 10 | Hector Cao 11 | 12 | 13 | pts/mbw 14 | -t2 1024 15 | Test: Memory Copy, Fixed Block Size - Array Size: 1024 MiB 16 | 17 | 18 | 19 | pts/stream-1.3.4 20 | Copy 21 | Type: Copy 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/lib/setup_guest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # this script is supposed to be executed under root 4 | 5 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 6 | 7 | DEBIAN_FRONTEND=noninteractive apt install -y python3 python3-pip 8 | 9 | cd ${SCRIPT_DIR}/tdx-tools/ 10 | python3 -m pip install --break-system-packages ./ 11 | sudo apt remove iperf3 -y 12 | sudo add-apt-repository ppa:kobuk-team/tdx-testing -y 13 | sudo apt update 14 | sudo apt install iperf-vsock -y 15 | -------------------------------------------------------------------------------- /tests/lib/tdx-tools/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "tdxtools" 3 | version = "1.0.0" 4 | description = "Intel Trust eXtension tools" 5 | readme = "README.rst" 6 | license = {file = "LICENSE.rst"} 7 | maintainers = [{name = "Hector Cao", email = "hector.cao@canonical.com"}] 8 | classifiers = [ 9 | "Development Status :: 5 - Production/Stable", 10 | "Environment :: System Tools", 11 | "Intended Audience :: Developers", 12 | "License :: OSI Approved :: GPLv3", 13 | "Operating System :: OS Independent", 14 | "Programming Language :: Python", 15 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content", 16 | "Topic :: Internet :: WWW/HTTP :: WSGI", 17 | "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", 18 | "Topic :: Software Development :: Libraries :: Application Frameworks", 19 | ] 20 | requires-python = ">=3.8" 21 | dependencies = [ 22 | "py-cpuinfo>=9.0.0", 23 | ] 24 | 25 | [project.urls] 26 | "Source Code" = "https://github.com/canonical/tdx/" 27 | "Issue Tracker" = "https://github.com/canonical/tdx/issues/" 28 | 29 | [project.scripts] 30 | tdreport = "tdxtools.tdreport:main" 31 | tdeventlog = "tdxtools.tdeventlog:print_eventlog" 32 | tdrtmrcheck = "tdxtools.tdrtmrcheck:verify_rtmr" 33 | tdtsmcheck = "tdxtools.tdquote:verify_tsm" 34 | tdeventlog_check_initrd = "tdxtools.tdeventlog:check_initrd" -------------------------------------------------------------------------------- /tests/lib/tdx-tools/src/tdxtools/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tdxtools is a collection of scripts for Intel TDX 3 | """ 4 | 5 | from .ccel import * 6 | from .tdreport import * 7 | from .tdeventlog import * 8 | from .rtmr import * 9 | from .util import * 10 | from .host import * 11 | -------------------------------------------------------------------------------- /tests/lib/tdx-tools/src/tdxtools/actor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Actors package, the bussiness logic layer. 3 | """ 4 | 5 | import os 6 | import logging 7 | from typing import Dict, List 8 | from hashlib import sha384 9 | 10 | from .rtmr import RTMR 11 | from .tdreport import TdReport 12 | from .tdeventlog import TDEventLogEntry, TDEventLogType, TDEventLogSpecIdHeader 13 | from .ccel import CCEL 14 | from .binaryblob import BinaryBlob 15 | 16 | __author__ = "cpio" 17 | 18 | LOG = logging.getLogger(__name__) 19 | 20 | 21 | # pylint: disable=too-few-public-methods 22 | class VerifyActor: 23 | """ 24 | Actor to verify the RTMR 25 | """ 26 | 27 | def _verify_single_rtmr(self, rtmr_index: int, rtmr_value_1: RTMR, 28 | rtmr_value_2: RTMR) -> None: 29 | 30 | if rtmr_value_1 == rtmr_value_2: 31 | LOG.info("RTMR[%d] passed the verification.", rtmr_index) 32 | else: 33 | LOG.error("RTMR[%d] did not pass the verification", rtmr_index) 34 | 35 | def verify_rtmr(self) -> None: 36 | """ 37 | Get TD report and RTMR replayed by event log to do verification. 38 | """ 39 | # 1. Read CCEL from ACPI table at /sys/firmware/acpi/tables/CCEL 40 | ccelobj = CCEL.create_from_acpi_file() 41 | if ccelobj is None: 42 | return 43 | 44 | # 2. Get the start address and length for event log area 45 | td_event_log_actor = TDEventLogActor( 46 | ccelobj.log_area_start_address, 47 | ccelobj.log_area_minimum_length) 48 | 49 | # 3. Collect event log and replay the RTMR value according to event log 50 | td_event_log_actor.replay() 51 | 52 | # 4. Read TD REPORT via TDCALL.GET_TDREPORT 53 | td_report = TdReport.get_td_report() 54 | 55 | # 5. Verify individual RTMR value from TDREPORT and recalculated from 56 | # event log 57 | self._verify_single_rtmr( 58 | 0, 59 | td_event_log_actor.get_rtmr_by_index(0), 60 | RTMR(bytearray(td_report.td_info.rtmr_0))) 61 | 62 | self._verify_single_rtmr( 63 | 1, 64 | td_event_log_actor.get_rtmr_by_index(1), 65 | RTMR(bytearray(td_report.td_info.rtmr_1))) 66 | 67 | self._verify_single_rtmr( 68 | 2, 69 | td_event_log_actor.get_rtmr_by_index(2), 70 | RTMR(bytearray(td_report.td_info.rtmr_2))) 71 | 72 | self._verify_single_rtmr( 73 | 3, 74 | td_event_log_actor.get_rtmr_by_index(3), 75 | RTMR(bytearray(td_report.td_info.rtmr_3))) 76 | 77 | 78 | -------------------------------------------------------------------------------- /tests/lib/tdx-tools/src/tdxtools/binaryblob.py: -------------------------------------------------------------------------------- 1 | """ 2 | Manage the binary blob 3 | """ 4 | import logging 5 | import string 6 | import struct 7 | import binascii 8 | 9 | LOG = logging.getLogger(__name__) 10 | 11 | __author__ = "cpio" 12 | 13 | 14 | class BinaryBlob: 15 | """ 16 | Manage the binary blob. 17 | """ 18 | 19 | def __init__(self, data, base=0): 20 | self._data = data 21 | self._base_address = base 22 | 23 | @property 24 | def length(self): 25 | """ 26 | Length of binary in bytes 27 | """ 28 | return len(self._data) 29 | 30 | @property 31 | def data(self): 32 | """ 33 | Raw data of binary blob 34 | """ 35 | return self._data 36 | 37 | def to_hex_string(self): 38 | """ 39 | To hex string 40 | """ 41 | return "".join(f"{b:02x}" % b for b in self._data) 42 | 43 | def get_uint16(self, pos): 44 | """ 45 | Get UINT16 integer 46 | """ 47 | assert pos + 2 <= self.length 48 | return (struct.unpack(" # pylint: disable=line-too-long 4 | 5 | """ 6 | 7 | import os 8 | import logging 9 | 10 | from .binaryblob import BinaryBlob 11 | 12 | __author__ = "cpio" 13 | 14 | LOG = logging.getLogger(__name__) 15 | 16 | class CCEL(BinaryBlob, dict): 17 | """ 18 | The Confidential Computing Event Log (CCEL) table provides the address 19 | and length of the CCEL records area in UEFI reserved memory. To access 20 | these records, userspace can use /dev/mem to retrieve them. But 21 | '/dev/mem' is not enabled on many systems for security reasons. 22 | 23 | So to allow user space access these event log records without the 24 | /dev/mem interface, add support to access it via sysfs interface. The 25 | ACPI driver has provided read only access to BERT records area via 26 | '/sys/firmware/acpi/tables/data/BERT' in sysfs. So follow the same way, 27 | and add support for /sys/firmware/acpi/tables/data/CCEL to enable 28 | read-only access to the CCEL records area. 29 | 30 | More details about the CCEL table can be found in ACPI specification 31 | r6.5, sec titled "CC Event Log ACPI Table". 32 | """ 33 | 34 | def __init__(self, data): 35 | super().__init__(data) 36 | self.parse() 37 | 38 | def parse(self): 39 | self.__setitem__('length', self.length) 40 | self.__setitem__('checksum', self.checksum) 41 | self.__setitem__('revision', self.revision) 42 | self.__setitem__('checksum', self.checksum) 43 | self.__setitem__('oem_id', self.oem_id) 44 | self.__setitem__('cc_type', self.cc_type) 45 | self.__setitem__('cc_subtype', self.cc_subtype) 46 | # UEFI memory region start that contains the event log table 47 | self.__setitem__('log_start_addr', hex(self.log_area_start_address)) 48 | self.__setitem__('log_min_len', self.log_area_minimum_length) 49 | 50 | @property 51 | def revision(self): 52 | """ 53 | Revision value in integer 54 | """ 55 | revision, _ = self.get_uint8(8) 56 | return revision 57 | 58 | @property 59 | def checksum(self): 60 | """ 61 | Checksum value in integer 62 | """ 63 | checksum, _ = self.get_uint8(9) 64 | return checksum 65 | 66 | @property 67 | def oem_id(self): 68 | """ 69 | OEM ID value in byte array 70 | """ 71 | oem_id, _ = self.get_bytes(10, 6) 72 | return oem_id 73 | 74 | @property 75 | def cc_type(self): 76 | """ 77 | Confidential Computing type in integer 78 | """ 79 | cc_type, _ = self.get_uint8(36) 80 | return cc_type 81 | 82 | @property 83 | def cc_subtype(self): 84 | """ 85 | Confidential Computing specific sub-type in integer 86 | """ 87 | cc_subtype, _ = self.get_uint8(37) 88 | return cc_subtype 89 | 90 | @property 91 | def log_area_minimum_length(self): 92 | """ 93 | LAML value in integer 94 | """ 95 | laml, _ = self.get_uint64(40) 96 | return laml 97 | 98 | @property 99 | def log_area_start_address(self): 100 | """ 101 | LASA value in integer 102 | """ 103 | lasa, _ = self.get_uint64(48) 104 | return lasa 105 | 106 | def dump(self): 107 | """ 108 | Dump the full information 109 | """ 110 | super().dump() 111 | 112 | if not self.is_valid(): 113 | LOG.error("CCEL is not valid") 114 | return 115 | 116 | LOG.info("Revision: %d", self.revision) 117 | LOG.info("Length: %d", self.length) 118 | LOG.info("Checksum: %02X", self.checksum) 119 | LOG.info("OEM ID: %s", self.oem_id) 120 | LOG.info("CC Type: %s", self.cc_type) 121 | LOG.info("CC Sub-type: %s", self.cc_subtype) 122 | LOG.info("Log Lenght: 0x%08X", self.log_area_minimum_length) 123 | LOG.info("Log Address: 0x%08X", self.log_area_start_address) 124 | 125 | def is_valid(self): 126 | """ 127 | Judge whether the CCEL data is valid. 128 | - Check the signature 129 | - Check the length 130 | """ 131 | return self.length > 0 and \ 132 | self.data[0:4] == b'CCEL' and \ 133 | self.length == self.data[4] 134 | 135 | @staticmethod 136 | def create_from_acpi_file(acpi_file="/sys/firmware/acpi/tables/CCEL"): 137 | """ 138 | Read the CCEL table from the /sys/firmware/acpi/tables/CCEL 139 | """ 140 | if not os.path.exists(acpi_file): 141 | LOG.error("Could not find the ACPI file %s", acpi_file) 142 | return None 143 | 144 | try: 145 | with open(acpi_file, "rb") as fobj: 146 | data = fobj.read() 147 | assert len(data) > 0 and data[0:4] == b'CCEL', \ 148 | "Invalid CCEL table" 149 | return CCEL(data) 150 | except (PermissionError, OSError): 151 | LOG.error("Need root permission to open file %s", acpi_file) 152 | return None 153 | -------------------------------------------------------------------------------- /tests/lib/tdx-tools/src/tdxtools/host.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # Authors: 5 | # - Hector Cao 6 | # 7 | # This program is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 3, as published 9 | # by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 13 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # 19 | 20 | import os 21 | import cpuinfo 22 | import struct 23 | 24 | def support_tdx(): 25 | """ 26 | Check whether support TDX in CPU info 27 | """ 28 | return 'tdx_host_platform' in cpuinfo.get_cpu_info()['flags'] 29 | 30 | 31 | def support_sgx(): 32 | """ 33 | Check whether support TDX in CPU info 34 | """ 35 | return 'sgx' in cpuinfo.get_cpu_info()['flags'] 36 | 37 | """ 38 | MSR(Model Specific Register) Class 39 | 40 | /dev/cpu//msr provides an interface to read and write the 41 | model-specific registers (MSRs) of an x86 CPU. CPUNUM is the 42 | number of the CPU to access as listed in /proc/cpuinfo. 43 | 44 | The register access is done by opening the file and seeking to 45 | the MSR number as offset in the file, and then reading or writing 46 | in chunks of 8 bytes. An I/O transfer of more than 8 bytes means 47 | multiple reads or writes of the same register. 48 | 49 | This file is protected so that it can be read and written only by 50 | the user root, or members of the group root. 51 | 52 | For more information about the MSR, please read https://man7.org/linux/man-pages/man4/msr.4.html 53 | """ 54 | class MSR: 55 | SGX_MCU_ERRORCODE = 0xa0 56 | SGX_DEBUG = 0x503 57 | IA32_FEATURE_CONTROL = 0x3a 58 | IA32_MKTME_PARTITIONING = 0x87 59 | IA32_TME_CAPABILITY = 0x981 60 | IA32_TME_ACTIVATE = 0x982 61 | 62 | def _check_kmod(): 63 | """ 64 | Check whether the MSR is loaded, modprobe if not. 65 | """ 66 | if not os.path.exists("/dev/cpu/0/msr"): 67 | os.system("modprobe msr") 68 | 69 | def readmsr(msr, highbit=63, lowbit=0, cpu=0): 70 | """ 71 | Read MSR register 72 | """ 73 | assert abs(msr) < 0xffffffff 74 | assert os.geteuid() == 0, "need root priviledge" 75 | val = None 76 | fdobj = os.open(f'/dev/cpu/{cpu}/msr', os.O_RDONLY) 77 | os.lseek(fdobj, msr, os.SEEK_SET) 78 | val = struct.unpack('Q', os.read(fdobj, 8))[0] 79 | bits = highbit - lowbit + 1 80 | if bits < 64: 81 | val >>= lowbit 82 | val &= (1 << bits) - 1 83 | return val 84 | 85 | def writemsr(msr, val): 86 | """ 87 | Write MSR register 88 | """ 89 | assert abs(msr) < 0xffffffff 90 | assert os.geteuid() == 0, "need root priviledge" 91 | 92 | items = glob.glob('/dev/cpu/[0-9]*/msr') 93 | for cpu in items: 94 | try: 95 | fdobj = os.open(cpu, os.O_WRONLY) 96 | except (IOError, OSError) as err: 97 | LOG.error("Fail to open MSR device file: %d", err.errno) 98 | return False 99 | 100 | os.lseek(fdobj, msr, os.SEEK_SET) 101 | 102 | try: 103 | os.write(fdobj, struct.pack('Q', val)) 104 | except (IOError, OSError) as err: 105 | LOG.error("Fail to write MSR device file: %d", err.errno) 106 | os.close(fdobj) 107 | return False 108 | os.close(fdobj) 109 | return True 110 | 111 | def changebit(msr, bit, val): 112 | n = glob.glob('/dev/cpu/[0-9]*/msr') 113 | for c in n: 114 | f = os.open(c, os.O_RDWR) 115 | os.lseek(f, msr, os.SEEK_SET) 116 | v = struct.unpack('Q', os.read(f, 8))[0] 117 | if val: 118 | v = v | (1 << bit) 119 | else: 120 | v = v & ~(1 << bit) 121 | os.lseek(f, msr, os.SEEK_SET) 122 | os.write(f, struct.pack('Q', v)) 123 | os.close(f) 124 | if not n: 125 | raise OSError("msr module not loaded (run modprobe msr)") 126 | -------------------------------------------------------------------------------- /tests/lib/tdx-tools/src/tdxtools/rtmr.py: -------------------------------------------------------------------------------- 1 | """ 2 | RTMR data structures 3 | """ 4 | 5 | from .binaryblob import BinaryBlob 6 | 7 | 8 | class RTMR(BinaryBlob): 9 | """ 10 | Data structure for RTMR registers. 11 | A RTMR register manages a 48-bytes (384-bits) hash value. 12 | """ 13 | RTMR_COUNT = 4 14 | RTMR_LENGTH_BY_BYTES = 48 15 | 16 | def __init__(self, data: bytearray = bytearray(RTMR_LENGTH_BY_BYTES), 17 | base_addr=0): 18 | super().__init__(data, base_addr) 19 | 20 | def __eq__(self, other): 21 | bytearray_1, _ = self.get_bytes(0, RTMR.RTMR_LENGTH_BY_BYTES) 22 | bytearray_2, _ = other.get_bytes(0, RTMR.RTMR_LENGTH_BY_BYTES) 23 | 24 | return bytearray(bytearray_1) == bytearray(bytearray_2) 25 | -------------------------------------------------------------------------------- /tests/lib/tdx-tools/src/tdxtools/tdquote.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # Authors: 5 | # - Hector Cao 6 | # 7 | # This program is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 3, as published 9 | # by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 13 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # 19 | 20 | import os 21 | import logging 22 | from pathlib import Path 23 | 24 | LOG = logging.getLogger(__name__) 25 | 26 | class QuoteConfigTsm: 27 | """ 28 | Quote generation through ConfigFs-Tsm 29 | 30 | report=/sys/kernel/config/tsm/report/report0 31 | mkdir $report 32 | dd if=/dev/urandom of=userdata bs=64B count=1 33 | dd if=userdata > $report/inblob 34 | hexdump $report/outblob 35 | 36 | userdata_nonce : 37 | Up to 64 bytes of user specified binary data. 38 | For replay protection this should include a nonce, 39 | but the kernel does not place any restrictions on the content. 40 | """ 41 | 42 | def __init__(self): 43 | self._read() 44 | 45 | def _read(self, tsm_dir : Path = Path('/sys/kernel/config/tsm/report/')): 46 | assert os.path.exists(tsm_dir), f"Could not find the TSM dir {tsm_dir}" 47 | report_dir='report0' 48 | if not os.path.exists(tsm_dir / report_dir): 49 | os.makedirs(tsm_dir / report_dir) 50 | with open(tsm_dir / report_dir / 'provider') as pf: 51 | self.provider = pf.readline().strip('\n') 52 | 53 | def verify_tsm(): 54 | logging.basicConfig(level=logging.DEBUG, format='%(message)s') 55 | 56 | tsm = QuoteConfigTsm() 57 | assert tsm.provider == 'tdx_guest' 58 | -------------------------------------------------------------------------------- /tests/lib/tdx-tools/src/tdxtools/tdrtmrcheck.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # Authors: 5 | # - Hector Cao 6 | # 7 | # This program is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 3, as published 9 | # by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 13 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # 19 | 20 | from .ccel import CCEL 21 | from .rtmr import RTMR 22 | from .tdeventlog import * 23 | from .tdreport import * 24 | 25 | LOG = logging.getLogger(__name__) 26 | 27 | class VerifyActor: 28 | """ 29 | Actor to verify the RTMR 30 | """ 31 | 32 | def _verify_single_rtmr(self, rtmr_index: int, rtmr_value_1: RTMR, 33 | rtmr_value_2: RTMR) -> None: 34 | 35 | if rtmr_value_1 == rtmr_value_2: 36 | LOG.info("RTMR[%d] passed the verification.", rtmr_index) 37 | else: 38 | LOG.error("RTMR[%d] did not pass the verification", rtmr_index) 39 | 40 | def verify_rtmr(self) -> None: 41 | """ 42 | Get TD report and RTMR replayed by event log to do verification. 43 | """ 44 | # 1. Read CCEL from ACPI table at /sys/firmware/acpi/tables/CCEL 45 | ccelobj = CCEL.create_from_acpi_file() 46 | if ccelobj is None: 47 | return 48 | 49 | # 2. Get the start address and length for event log area 50 | td_event_log_actor = TDEventLogActor( 51 | ccelobj.log_area_start_address, 52 | ccelobj.log_area_minimum_length) 53 | 54 | #td_event_log_actor.dump_td_event_logs() 55 | 56 | # 3. Collect event log and replay the RTMR value according to event log 57 | td_event_log_actor.replay() 58 | td_event_log_actor.dump_rtmrs() 59 | 60 | # 4. Read TD REPORT via TDCALL.GET_TDREPORT 61 | td_report = TdReport.get_td_report() 62 | LOG.info("Report rtmrs: %s", td_report.get_rtmrs()) 63 | 64 | # 5. Verify individual RTMR value from TDREPORT and recalculated from 65 | # event log 66 | self._verify_single_rtmr( 67 | 0, 68 | td_event_log_actor.get_rtmr_by_index(0), 69 | RTMR(bytearray.fromhex(td_report['td_info']['rtmr_0']))) 70 | 71 | self._verify_single_rtmr( 72 | 1, 73 | td_event_log_actor.get_rtmr_by_index(1), 74 | RTMR(bytearray.fromhex(td_report['td_info']['rtmr_1']))) 75 | 76 | self._verify_single_rtmr( 77 | 2, 78 | td_event_log_actor.get_rtmr_by_index(2), 79 | RTMR(bytearray.fromhex(td_report['td_info']['rtmr_2']))) 80 | 81 | self._verify_single_rtmr( 82 | 3, 83 | td_event_log_actor.get_rtmr_by_index(3), 84 | RTMR(bytearray.fromhex(td_report['td_info']['rtmr_3']))) 85 | 86 | def verify_rtmr(): 87 | logging.basicConfig(level=logging.DEBUG, format='%(message)s') 88 | VerifyActor().verify_rtmr() 89 | -------------------------------------------------------------------------------- /tests/lib/tdx-tools/src/tdxtools/util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # Authors: 5 | # - Hector Cao 6 | # 7 | # This program is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 3, as published 9 | # by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 13 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # 19 | 20 | import socket 21 | import time 22 | from functools import wraps 23 | 24 | def timeit(func): 25 | @wraps(func) 26 | def timeit_wrapper(*args, **kwargs): 27 | start_time = time.perf_counter() 28 | result = func(*args, **kwargs) 29 | end_time = time.perf_counter() 30 | total_time = end_time - start_time 31 | # first item in the args, ie `args[0]` is `self` 32 | print(f'Function {func.__name__}{args} {kwargs} Took {total_time:.4f} seconds') 33 | return result 34 | return timeit_wrapper 35 | -------------------------------------------------------------------------------- /tests/lib/util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # Authors: 5 | # - Hector Cao 6 | # 7 | # This program is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 3, as published 9 | # by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 13 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # 19 | 20 | import socket 21 | import time 22 | from functools import wraps 23 | import subprocess 24 | import multiprocessing 25 | import random 26 | import os 27 | 28 | def timeit(func): 29 | @wraps(func) 30 | def timeit_wrapper(*args, **kwargs): 31 | start_time = time.perf_counter() 32 | result = func(*args, **kwargs) 33 | end_time = time.perf_counter() 34 | total_time = end_time - start_time 35 | # first item in the args, ie `args[0]` is `self` 36 | print(f'Function {func.__name__}{args} {kwargs} Took {total_time:.4f} seconds') 37 | return result 38 | return timeit_wrapper 39 | 40 | def tcp_port_available(): 41 | sock = socket.socket() 42 | sock.bind(('', 0)) 43 | port = sock.getsockname()[1] 44 | sock.close() 45 | return port 46 | 47 | def get_max_td_vms(): 48 | """ 49 | MKTME encryption engine is used both for legacy MKTME operation and TDX operation 50 | The key space is partitionned in 3 ranges: 51 | - first key 52 | - shared keys 53 | - TDX keys 54 | So if we have 128 keys and we decide to split this range into 2 equal sets (in BIOS) 55 | TDX key space will only have 63 keys instead of 64. 56 | The nb of TDX key space can be read from the IA32_MKTME_KEYID_PARTITIONING MSR (0x87) 57 | """ 58 | cmd = ['rdmsr', '0x87'] 59 | rc = subprocess.run(cmd, capture_output=True) 60 | assert rc.returncode == 0, "Failed getting max td vms" 61 | data = rc.stdout.decode().split() 62 | assert len(data) > 0, "Failed getting max td vms" 63 | rdmsr = int(data[0], 16) 64 | max_td_vms = (rdmsr >> 32) - 1 65 | return max_td_vms 66 | 67 | def get_memory_free_gb(): 68 | cmd = ['free', '-hg'] 69 | rc = subprocess.run(cmd, capture_output=True) 70 | assert rc.returncode == 0, "Failed getting free memory" 71 | lines = rc.stdout.decode().split('\n') 72 | assert len(lines) >= 2, "Invalid response to free command" 73 | assert "Mem" in lines[1], "Invalid response to free command" 74 | assert len(lines[1].split()) > 3, "Invalid response to free command" 75 | free_mem = lines[1].split()[3] 76 | assert 'Gi' in free_mem, "Invalid response to free command" 77 | return float(free_mem.split('Gi')[0]) 78 | 79 | def get_memory_available_gb(): 80 | cmd = ['free', '-hg'] 81 | rc = subprocess.run(cmd, capture_output=True) 82 | assert rc.returncode == 0, "Failed getting available memory" 83 | lines = rc.stdout.decode().split('\n') 84 | assert len(lines) >= 2, "Invalid response to free command" 85 | assert "Mem" in lines[1], "Invalid response to free command" 86 | assert len(lines[1].split()) > 6, "Invalid response to free command" 87 | free_mem = lines[1].split()[6] 88 | assert 'Gi' in free_mem, "Invalid response to free command" 89 | return float(free_mem.split('Gi')[0]) 90 | 91 | def get_current_td_vms(): 92 | current_td_vms = 0 93 | cmd = ['ps', 'wwaux'] 94 | rc = subprocess.run(cmd, capture_output=True) 95 | assert rc.returncode == 0, "Failed getting max td vms" 96 | lines = rc.stdout.decode().split('\n') 97 | for l in lines: 98 | if "qemu-system" in l and "tdx" in l: 99 | current_td_vms += 1 100 | return current_td_vms 101 | 102 | def pin_process_on_cpu(pid, cpu): 103 | cs = subprocess.run(['sudo', 'taskset', '-pc', f'{cpu}', f'{pid}'], capture_output=True) 104 | assert cs.returncode == 0, f'Failed pinning qemu pid {pid} to cpu {cpu} : {cs.returncode}' 105 | 106 | def cpu_off_random(): 107 | cpu = cpu_select() 108 | cpu_on_off(f'/sys/devices/system/cpu/cpu{cpu}/online', 1) 109 | cpu_on_off(f'/sys/devices/system/cpu/cpu{cpu}/online', 0) 110 | return cpu 111 | 112 | def cpu_select(): 113 | """ 114 | Select cpu core to set online/offline 115 | NB: On some system, cpu0 cannot be set offline because the IRQ is wired on that core 116 | so try several times to avoid selecting core that does not support "on/off" feature 117 | """ 118 | cpu_count = multiprocessing.cpu_count() 119 | for _ in range(5): 120 | cpu = random.randint(0, cpu_count-1) 121 | if os.path.exists(f'/sys/devices/system/cpu/cpu{cpu}/online'): 122 | return cpu 123 | return None 124 | 125 | # Helper function for turning cpu on/off 126 | def cpu_on_off(file_str, val): 127 | dev_f = open(file_str, 'w') 128 | cs = subprocess.run(['echo', f'{val}'], check=True, stdout=dev_f) 129 | assert cs.returncode == 0, 'Failed turning cpu off' 130 | dev_f.close() 131 | 132 | class CpuOnOff: 133 | """ 134 | CPU core on/off 135 | """ 136 | def __init__(self, cpu): 137 | self.initial_state = 1 138 | self.cpu = cpu 139 | 140 | def set_state(self, state): 141 | if state != self.state: 142 | cpu_on_off(f'/sys/devices/system/cpu/cpu{self.cpu}/online', state) 143 | 144 | @property 145 | def state(self): 146 | with open(f'/sys/devices/system/cpu/cpu{self.cpu}/online') as f: 147 | return int(f.read()) 148 | return None 149 | 150 | def __enter__(self): 151 | self.initial_state = self.state 152 | return self 153 | 154 | def __exit__(self, exc_type, exc_value, exc_tb): 155 | self.set_state(self.initial_state) 156 | -------------------------------------------------------------------------------- /tests/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | quote_generation: marks quote generation tests (deselect with '-m "not quote_generation"') 4 | -------------------------------------------------------------------------------- /tests/snap/hooks/configure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2017-2022 Canonical Ltd. 3 | # All rights reserved. 4 | 5 | """ 6 | This hook reads `config_vars` file from the root of a snap and reenters this 7 | configuration using snapd's config facilities and rewrites the file to a 8 | checkbox config format in `$SNAP_DATA/checkbox.conf`. 9 | 10 | config_vars should list all the configuration variables in a `key=value` 11 | syntax. The line can list variable name only, if the variable should not have a 12 | default value. All keys should comprise of CAPS, numbers and undescores (_). 13 | E.g.: 14 | 15 | OPEN_N_SSID 16 | OPEN_BG_SSID 17 | STRESS_S3_WAIT_DELAY=60 18 | 19 | To change those values use the configure launcher. 20 | E.g. 21 | $ checkbox-tdx.configure OPEN_N_SSID=my-wifi 22 | 23 | If you __really__ need to change those values using `snap set` command, 24 | you need to change CAPS to lowercase and underscores to dashes (-). 25 | 26 | E.g. 27 | $ snap set checkbox-tdx open-n-ssid=my-wifi 28 | """ 29 | 30 | import os 31 | import sys 32 | 33 | sys.path.append(os.path.expandvars("$SNAP/usr/lib/python3/dist-packages")) 34 | sitepkgpath = "$SNAP/lib/python3.12/site-packages" 35 | sys.path.append(os.path.expandvars(sitepkgpath)) 36 | 37 | sys.path.append(os.path.expandvars( 38 | "/snap/checkbox24/current/usr/lib/python3/dist-packages")) 39 | runtimepath = "/snap/checkbox24/current/lib/python3.12/site-packages" 40 | sys.path.append(os.path.expandvars(runtimepath)) 41 | 42 | try: 43 | from checkbox_support.snap_utils.config import refresh_configuration 44 | except ImportError: 45 | msg = """ 46 | checkbox-support not found! 47 | You need to install the checkbox24 snap: 48 | 49 | snap install checkbox24 50 | """ 51 | print(os.path.expandvars(msg), file=sys.stderr) 52 | sys.exit(1) 53 | refresh_configuration() 54 | -------------------------------------------------------------------------------- /tests/snap/hooks/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | snapctl set slave=enabled 5 | 6 | echo "$(id -un 1000 2>/dev/null || id -un 1001 2>/dev/null || echo ubuntu) ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/checkbox 7 | 8 | mkdir -p /etc/polkit-1/localauthority/50-local.d 9 | cat < /etc/polkit-1/localauthority/50-local.d/com.canonical.certification.checkbox.pkla 10 | [Checkbox] 11 | Identity=unix-group:sudo 12 | Action=org.freedesktop.NetworkManager.* 13 | ResultAny=yes 14 | ResultInactive=yes 15 | ResultActive=yes 16 | EOF 17 | -------------------------------------------------------------------------------- /tests/snap/hooks/remove: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | rm -f /etc/sudoers.d/checkbox 5 | rm -f /etc/polkit-1/localauthority/50-local.d/com.canonical.certification.checkbox.pkla || /bin/true 6 | -------------------------------------------------------------------------------- /tests/snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: checkbox-tdx 2 | summary: Checkbox tests for TDX 3 | description: | 4 | Collection of tests for Intel TDX (Trusted Domain eXtension) 5 | version: '1.0.0' 6 | confinement: classic 7 | grade: stable 8 | 9 | base: core24 10 | 11 | # Here are the available applications of the TDX checkbox provider snap 12 | # To run : snap run checkbox-tdx. 13 | # 14 | # checkbox-cli: 15 | # - checkbox client, can be used to talk to the checkbox daemon 16 | # configure: 17 | # - inject environment variable into checkbox snap environnement 18 | # the variable initial list can be found in config/config_vars 19 | # use configure -l to get the list of the current variables 20 | # remote-slave: 21 | # - checkbox slave daemon that will the responsible for running the test sesssion 22 | # in the remote fashion (through checkbox-cli) 23 | # test-runner / test-runner-automated-xxx: 24 | # - execute all provider tests inside the snap environment 25 | # the test execution is standalone and does not depend on the remote-slave daemon 26 | # shell: 27 | # - give shell access to the provider snap 28 | # install-full-deps: 29 | # - install all depedencies needed for provider jobs 30 | apps: 31 | checkbox-cli: 32 | command-chain: [bin/wrapper_local] 33 | command: bin/checkbox-cli-wrapper-image 34 | configure: 35 | command-chain: [bin/wrapper_local] 36 | command: bin/configure 37 | remote-slave: 38 | command-chain: [bin/wrapper_local] 39 | command: bin/checkbox-cli-wrapper slave 40 | daemon: simple 41 | restart-condition: always 42 | shell: 43 | command-chain: [bin/wrapper_local] 44 | command: bin/shell-wrapper 45 | test-runner: 46 | command-chain: [bin/wrapper_local] 47 | command: bin/test-runner 48 | test-runner-automated: 49 | command-chain: [bin/wrapper_local] 50 | command: bin/test-runner-automated 51 | test-runner-automated-host: 52 | command-chain: [bin/wrapper_local] 53 | command: bin/test-runner-automated-host 54 | test-runner-automated-guest: 55 | command-chain: [bin/wrapper_local] 56 | command: bin/test-runner-automated-guest 57 | test-runner-automated-boot: 58 | command-chain: [bin/wrapper_local] 59 | command: bin/test-runner-automated-boot 60 | test-runner-automated-perf: 61 | command-chain: [bin/wrapper_local] 62 | command: bin/test-runner-automated-perf 63 | test-runner-automated-quote: 64 | command-chain: [bin/wrapper_local] 65 | command: bin/test-runner-automated-quote 66 | test-runner-automated-stress: 67 | command-chain: [bin/wrapper_local] 68 | command: bin/test-runner-automated-stress 69 | install-full-deps: 70 | command: bin/install-full-deps 71 | 72 | passthrough: 73 | hooks: 74 | configure: 75 | command-chain: [bin/wrapper_local] 76 | 77 | package-repositories: 78 | - type: apt 79 | ppa: kobuk-team/tdx-testing 80 | 81 | parts: 82 | checkbox-provider-tdx: 83 | plugin: dump 84 | source: ./checkbox/checkbox-provider-tdx 85 | source-type: local 86 | build-snaps: 87 | - checkbox-provider-tools 88 | - checkbox24 89 | stage-packages: 90 | - python3-parameterized 91 | - python3-paramiko 92 | - python3-pytest 93 | - sshpass 94 | - cpuid 95 | - iperf-vsock 96 | override-build: | 97 | # the validation tool needs to access the to all checkbox providers 98 | # we have to set the PROVIDERPATH to point to them 99 | for path in $(find "/snap/checkbox24/current/providers/" -mindepth 1 -maxdepth 1 -type d); do export PROVIDERPATH=$path${PROVIDERPATH:+:$PROVIDERPATH}; done 100 | checkbox-provider-tools validate 101 | checkbox-provider-tools build 102 | checkbox-provider-tools install --layout=relocatable --prefix=/providers/checkbox-provider-tdx --root="$SNAPCRAFT_PART_INSTALL" 103 | override-prime: | 104 | snapcraftctl prime 105 | # python3-pytest does not install the /usr/bin/pytest but only /usr/bin/pytest-3 106 | # we have to make the symlink ourself 107 | ln -sf py.test-3 usr/bin/pytest 108 | 109 | tdx-tools: 110 | plugin: python 111 | source: lib/tdx-tools/ 112 | source-type: local 113 | stage-packages: 114 | - python3.12-minimal 115 | 116 | provider-bin: 117 | plugin: dump 118 | source: bin 119 | organize: 120 | '*': providers/checkbox-provider-tdx/bin/ 121 | 122 | provider-tests: 123 | plugin: dump 124 | source: tests 125 | organize: 126 | '*': providers/checkbox-provider-tdx/tests/ 127 | 128 | provider-lib: 129 | plugin: dump 130 | source: lib 131 | organize: 132 | '*': providers/checkbox-provider-tdx/lib/ 133 | bin: 134 | plugin: dump 135 | source: checkbox/bin/ 136 | organize: 137 | '*': bin/ 138 | config-variables: 139 | plugin: dump 140 | source: checkbox/config/ 141 | 142 | # ignore rpath and interpreter link warnings 143 | lint: 144 | ignore: 145 | - classic 146 | - library: 147 | - usr/bin/cpuid 148 | -------------------------------------------------------------------------------- /tests/tdtest: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 4 | 5 | usage() { 6 | cat < Ubuntu guest image 21 | -h|--help Show this help 22 | 23 | Examples: 24 | To run tests in boot folder: 25 | $ sudo $(basename "$0") tests/boot 26 | To list tests in boot folder: 27 | $ sudo $(basename "$0") --co tests/boot 28 | To run all the tests: 29 | $ sudo $(basename "$0") 30 | 31 | --- 32 | EOM 33 | } 34 | 35 | parse_params() { 36 | while :; do 37 | case "${1-}" in 38 | -h | --help) 39 | usage 40 | exit 0 41 | ;; 42 | -i | --td-image) 43 | TDXTEST_GUEST_IMG=$(realpath "${2-}") 44 | shift 45 | ;; 46 | "") 47 | break 48 | ;; 49 | *) 50 | # other arguments will be passed to pytest 51 | break 52 | ;; 53 | esac 54 | shift 55 | done 56 | } 57 | 58 | parse_params "$@" 59 | 60 | if [ "$EUID" -ne 0 ] 61 | then echo "Please run as root" 62 | exit 63 | fi 64 | 65 | if [[ -z "${UBUNTU_VER}" ]]; then 66 | UBUNTU_VER=$(lsb_release -rs) 67 | fi 68 | 69 | if [[ ! -f "${TDXTEST_GUEST_IMG}" ]]; then 70 | TDXTEST_GUEST_IMG=$PWD/../guest-tools/image/tdx-guest-ubuntu-${UBUNTU_VER}-generic.qcow2 71 | fi 72 | 73 | apt install -y python3 tox 74 | 75 | export TDXTEST_GUEST_IMG 76 | 77 | echo "Run tests with TD image: ${TDXTEST_GUEST_IMG}" 78 | echo " pytest args: ${pytest_args}" 79 | tox -- --ignore=lib/ "$@" 80 | -------------------------------------------------------------------------------- /tests/tests/boot/test_boot_basic.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # Authors: 3 | # - Hector Cao 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | import os 19 | import Qemu 20 | from common import * 21 | 22 | def test_guest_boot(qm): 23 | """ 24 | Boot TD 25 | """ 26 | qm.run() 27 | 28 | m = Qemu.QemuSSH(qm) 29 | 30 | deploy_and_setup(m) 31 | 32 | # tdx guest device driver 33 | m.check_exec('ls -la /dev/tdx_guest') 34 | # CCEL table (event log) 35 | m.check_exec('ls -la /sys/firmware/acpi/tables/CCEL') 36 | 37 | qm.stop() 38 | 39 | def test_guest_early_printk(qm): 40 | """ 41 | Test Early Printk with Debug Off (Intel Case ID 018) 42 | """ 43 | 44 | qm.run() 45 | 46 | m = Qemu.QemuSSH(qm) 47 | add_earlyprintk_cmd = r''' 48 | sed -i -E "s/GRUB_CMDLINE_LINUX=\"(.*)\"/GRUB_CMDLINE_LINUX=\"\1 earlyprintk=ttyS0,115200\"/g" /etc/default/grub 49 | update-grub 50 | grub-install --no-nvram 51 | ''' 52 | m.check_exec(add_earlyprintk_cmd) 53 | 54 | qm.reboot() 55 | 56 | m = Qemu.QemuSSH(qm) 57 | m.check_exec('grep earlyprintk=ttyS0,115200 /proc/cmdline') 58 | 59 | qm.stop() 60 | -------------------------------------------------------------------------------- /tests/tests/boot/test_boot_coexist.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # Authors: 3 | # - Hector Cao 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | import os 19 | 20 | from Qemu import QemuEfiMachine, QemuEfiFlashSize, QemuMachineService 21 | import Qemu 22 | 23 | import util 24 | 25 | def test_coexist_boot(): 26 | """ 27 | Boot check 28 | """ 29 | qm = Qemu.QemuMachine('td', 30 | QemuEfiMachine.OVMF_Q35_TDX, 31 | service_blacklist = [QemuMachineService.QEMU_MACHINE_PORT_FWD]) 32 | qm_normal = Qemu.QemuMachine('normal', 33 | QemuEfiMachine.OVMF_Q35, 34 | service_blacklist = [QemuMachineService.QEMU_MACHINE_PORT_FWD]) 35 | with qm, qm_normal: 36 | qm.run() 37 | qm_normal.run() 38 | 39 | m = Qemu.QemuMonitor(qm) 40 | m.wait_for_state('running') 41 | m_normal = Qemu.QemuMonitor(qm_normal) 42 | m_normal.wait_for_state('running') 43 | 44 | qm.stop() 45 | qm_normal.stop() 46 | -------------------------------------------------------------------------------- /tests/tests/boot/test_boot_multiple.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # Authors: 5 | # - Hector Cao 6 | # 7 | # This program is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 3, as published 9 | # by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 13 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # 19 | 20 | import os 21 | import Qemu 22 | 23 | def test_4vcpus_1socket_10times(qm): 24 | """ 25 | Test 4vcpus 1socket 10 times (Intel Case ID 009) 26 | """ 27 | 28 | qm.qcmd.plugins['cpu'].nb_cores = 4 29 | 30 | for i in range(0,10): 31 | qm.run() 32 | ssh = Qemu.QemuSSH(qm) 33 | qm.stop() 34 | 35 | 36 | def test_4vcpus_2sockets_5times(qm): 37 | """ 38 | Test 4vcpus 2sockets 5 times (Intel Case ID 010) 39 | """ 40 | 41 | qm.qcmd.plugins['cpu'].nb_cores = 4 42 | qm.qcmd.plugins['cpu'].nb_sockets = 2 43 | 44 | for i in range(0,5): 45 | qm.run() 46 | ssh = Qemu.QemuSSH(qm) 47 | qm.stop() 48 | -------------------------------------------------------------------------------- /tests/tests/boot/test_boot_td_creation.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # Authors: 3 | # - Hector Cao 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | import os 19 | 20 | from Qemu import QemuEfiMachine, QemuEfiFlashSize, QemuMachineService 21 | import Qemu 22 | 23 | def test_create_td_without_ovmf(qm): 24 | """ 25 | TD creation should be impossible without OVMF 26 | qemu-system-x86_64 should output the errors: 27 | Cannot find TDX_METADATA_OFFSET_GUID 28 | failed to parse TDVF for TDX VM 29 | """ 30 | # remove ovmf 31 | qm.qcmd.plugins.pop('ovmf') 32 | qm.run() 33 | 34 | # expect qemu quit immediately 35 | _, err = qm.communicate() 36 | assert "failed to parse TDVF for TDX VM" in err.decode() 37 | 38 | qm.stop() 39 | -------------------------------------------------------------------------------- /tests/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | import distro 4 | import subprocess 5 | 6 | import Qemu 7 | import util 8 | 9 | script_path=os.path.dirname(os.path.realpath(__file__)) 10 | 11 | # Is platform registered for quote generation 12 | def is_platform_registered(): 13 | try: 14 | subprocess.check_call([f'{script_path}/../../attestation/check-registration.sh']) 15 | except: 16 | return False 17 | return True 18 | 19 | def pytest_runtest_setup(item): 20 | """ 21 | Test setup function 22 | """ 23 | # skip the test if needed 24 | for mark in item.iter_markers(name='quote_generation'): 25 | if not is_platform_registered(): 26 | pytest.skip('Platform not registered, skip quote generation test') 27 | 28 | @pytest.fixture(autouse=True) 29 | def run_before_and_after_tests(tmpdir): 30 | """ 31 | Fixture to execute before and after a test is run 32 | Even in case of test failure 33 | """ 34 | # Setup: fill with any logic you want 35 | # enable the debug flag if TDXTEST_DEBUG is set 36 | debug = os.environ.get('TDXTEST_DEBUG', False) 37 | if debug: 38 | Qemu.QemuMachine.set_debug(debug) 39 | 40 | yield # this is where the testing happens 41 | 42 | # Teardown : fill with any logic you want 43 | 44 | def pytest_exception_interact(node, call, report): 45 | """ 46 | Called at test failure 47 | """ 48 | if report.failed: 49 | # enable debug flag to avoid cleanup to happen 50 | Qemu.QemuMachine.set_debug(True) 51 | 52 | @pytest.fixture() 53 | def release_kvm_use(): 54 | """ 55 | Clean all running instances of qemu to release the kvm_intel driver use 56 | """ 57 | Qemu.QemuMachine.stop_all_running_qemus() 58 | 59 | @pytest.fixture() 60 | def qm(): 61 | """ 62 | Fixture to create a QEMU machine as context manager 63 | """ 64 | with Qemu.QemuMachine() as qm: 65 | yield qm 66 | 67 | @pytest.fixture() 68 | def cpu_core(): 69 | """ 70 | Fixture to create CPU core manager 71 | """ 72 | cpu = util.cpu_select() 73 | with util.CpuOnOff(cpu) as cpu: 74 | yield cpu 75 | 76 | @pytest.fixture() 77 | def tdx_version(): 78 | """ 79 | This fixure gives the generation of TDX releases we deliver 80 | For now, we devide our releases into 2 major versions: 81 | - 1 (TDX1.0) : for releases with kernel < 6.14 and QEMU < 9.2.1 82 | this version has been released Ubuntu 24.04 and 24.10 83 | - 2 (TDX2.0) : for kernel >= 6.14 and qemu >= 9.2.1 84 | this version is released for Ubuntu 25.04 85 | Default version is TDX2.0 if the fixture cannot determine the version. 86 | """ 87 | version=2 88 | # try to detect version 1 89 | lri = distro.lsb_release_info() 90 | if (lri.get('codename') == 'noble') or (lri.get('codename') == 'oracular'): 91 | version = 1 92 | yield version 93 | -------------------------------------------------------------------------------- /tests/tests/eventlog/test_guest_eventlog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # Authors: 5 | # - Hector Cao 6 | # 7 | # This program is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 3, as published 9 | # by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 13 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # 19 | 20 | import os 21 | import time 22 | 23 | import Qemu 24 | import util 25 | from common import * 26 | 27 | def test_guest_eventlog(qm): 28 | """ 29 | Dump event log 30 | """ 31 | qm.run() 32 | 33 | m = Qemu.QemuSSH(qm) 34 | 35 | deploy_and_setup(m) 36 | 37 | stdout, stderr = m.check_exec('tdeventlog') 38 | for l in stderr.readlines(): 39 | print(l.rstrip()) 40 | 41 | qm.stop() 42 | 43 | def test_guest_eventlog_initrd(qm): 44 | """ 45 | Check presence of event log for initrd measurement 46 | """ 47 | qm.run() 48 | 49 | m = Qemu.QemuSSH(qm) 50 | 51 | deploy_and_setup(m) 52 | 53 | stdout, stderr = m.check_exec('tdeventlog_check_initrd') 54 | for l in stderr.readlines(): 55 | print(l.rstrip()) 56 | 57 | qm.stop() 58 | -------------------------------------------------------------------------------- /tests/tests/eventlog/test_guest_measurement.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | import os 19 | import time 20 | import json 21 | import subprocess 22 | 23 | import Qemu 24 | from common import * 25 | 26 | def test_guest_measurement_check_rtmr(qm): 27 | """ 28 | Boot measurements check 29 | """ 30 | with Qemu.QemuMachine() as qm: 31 | qm.run() 32 | 33 | m = Qemu.QemuSSH(qm) 34 | 35 | deploy_and_setup(m) 36 | 37 | m.check_exec('tdrtmrcheck') 38 | 39 | qm.stop() 40 | -------------------------------------------------------------------------------- /tests/tests/guest/test_guest_cpu_off.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # Authors: 5 | # - Hector Cao 6 | # 7 | # This program is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 3, as published 9 | # by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 13 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # 19 | 20 | import os 21 | import subprocess 22 | import time 23 | import re 24 | 25 | import Qemu 26 | import util 27 | 28 | script_path=os.path.dirname(os.path.realpath(__file__)) 29 | 30 | def test_guest_cpu_off(qm, cpu_core): 31 | """ 32 | tdx_VMP_cpu_onoff test case (See https://github.com/intel/tdx/wiki/Tests) 33 | """ 34 | 35 | # Startup Qemu and connect via SSH 36 | qm.run() 37 | m = Qemu.QemuSSH(qm) 38 | 39 | cpu_core.set_state(0) 40 | 41 | # make sure the VM still does things 42 | m.check_exec('ls /tmp') 43 | 44 | qm.stop() 45 | 46 | def test_guest_cpu_pinned_off(): 47 | """ 48 | tdx_cpuoff_pinedVMdown test case (See https://github.com/intel/tdx/wiki/Tests) 49 | """ 50 | 51 | # do 20 iterations of starting up a VM, pinning the VM pid, turning off 52 | # the pinned cpu and making sure host still works 53 | for i in range(1,20): 54 | print(f'Iteration: {i}') 55 | with Qemu.QemuMachine() as qm: 56 | qm.run_and_wait() 57 | 58 | cpu = util.cpu_select() 59 | 60 | with util.CpuOnOff(cpu) as cpu_manager: 61 | # bring the cpu online if necessary 62 | cpu_manager.set_state(1) 63 | util.pin_process_on_cpu(qm.pid, cpu) 64 | 65 | # bring the cpu core offline 66 | cpu_manager.set_state(0) 67 | m = Qemu.QemuSSH(qm) 68 | m.check_exec('sudo init 0 &') 69 | 70 | qm.stop() 71 | -------------------------------------------------------------------------------- /tests/tests/guest/test_guest_fail.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # Authors: 5 | # - Hector Cao 6 | # 7 | # This program is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 3, as published 9 | # by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 13 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # 19 | 20 | import os 21 | import subprocess 22 | import time 23 | import re 24 | 25 | import Qemu 26 | import util 27 | 28 | script_path=os.path.dirname(os.path.realpath(__file__)) 29 | 30 | def test_guest_noept_fail(qm, release_kvm_use, tdx_version): 31 | """ 32 | tdx_NOEPT test case (See https://github.com/intel/tdx/wiki/Tests) 33 | """ 34 | 35 | # Get initial dmesg contents for comparison later 36 | cs = subprocess.run(['sudo', 'dmesg'], check=True, capture_output=True) 37 | assert cs.returncode == 0, 'Failed getting dmesg' 38 | dmesg_start = str(cs.stdout) 39 | 40 | if tdx_version == 1: 41 | kvm_args='pt_mode=1 ept=0' 42 | else: 43 | kvm_args='ept=0' 44 | 45 | with KvmIntelModuleReloader(kvm_args) as module: 46 | # Get after modprobe dmesg contents 47 | cs = subprocess.run(['sudo', 'dmesg'], check=True, capture_output=True) 48 | assert cs.returncode == 0, 'Failed getting dmesg' 49 | dmesg_end = str(cs.stdout) 50 | 51 | def check_dmesg(msg): 52 | dmesg_start_count = dmesg_start.count(msg) 53 | dmesg_end_count = dmesg_end.count(msg) 54 | assert dmesg_end_count == dmesg_start_count+1, "dmesg missing proper message" 55 | 56 | if tdx_version == 1: 57 | # Verify "kvm_intel: EPT is required for TDX" in dmesg (but only one more time) 58 | check_dmesg("TDX requires TDP MMU. Please enable TDP MMU for TDX") 59 | else: 60 | check_dmesg("kvm_intel: EPT is required for TDX") 61 | 62 | # Run Qemu and verify failure 63 | qm.run() 64 | 65 | # expect qemu quit immediately with specific error message 66 | _, err = qm.communicate() 67 | if tdx_version == 1: 68 | assert "-accel kvm: vm-type tdx not supported by KVM" in err.decode() 69 | else: 70 | assert "-accel kvm: vm-type TDX not supported by KVM" in err.decode() 71 | 72 | def test_guest_disable_tdx_fail(qm, release_kvm_use, tdx_version): 73 | """ 74 | tdx_disabled test case (See https://github.com/intel/tdx/wiki/Tests) 75 | """ 76 | 77 | with KvmIntelModuleReloader('tdx=0') as module: 78 | # Run Qemu and verify failure 79 | qm.run() 80 | 81 | # expect qemu quit immediately with specific error message 82 | _, err = qm.communicate() 83 | if tdx_version == 1: 84 | assert "-accel kvm: vm-type tdx not supported by KVM" in err.decode() 85 | else: 86 | assert "-accel kvm: vm-type TDX not supported by KVM" in err.decode() 87 | 88 | class KvmIntelModuleReloader: 89 | """ 90 | kvm_intel module reloader (context manager) 91 | Allow to reload kvm_intel module with custom arguments 92 | """ 93 | def __init__(self, module_args=''): 94 | self.args = module_args 95 | def __enter__(self): 96 | subprocess.check_call('sudo rmmod kvm_intel', shell=True) 97 | subprocess.check_call(f'sudo modprobe kvm_intel {self.args}', shell=True) 98 | return self 99 | def __exit__(self, exc_type, exc_value, exc_tb): 100 | # reload the kvm_intel 101 | subprocess.check_call('sudo rmmod kvm_intel', shell=True) 102 | subprocess.check_call('sudo modprobe kvm_intel', shell=True) 103 | -------------------------------------------------------------------------------- /tests/tests/guest/test_guest_memory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | import os 19 | import time 20 | import json 21 | import subprocess 22 | 23 | import Qemu 24 | from common import * 25 | 26 | MEMORY_FILE='memory.bin' 27 | CHECK_EXEC='export INCLEAR=6150079244' 28 | 29 | def check_for_string(filename, needle): 30 | cs = subprocess.run(['sudo', 'grep', needle, filename], capture_output=True) 31 | return cs.returncode 32 | 33 | def remove_file(filename): 34 | cs = subprocess.run(['sudo', 'rm', '-f', filename], capture_output=True) 35 | return cs.returncode 36 | 37 | def test_guest_memory_confidentiality_no_tdx(qm): 38 | """ 39 | Test guest memory confidentiality without TDX 40 | """ 41 | with Qemu.QemuMachine(memory='1G', machine=Qemu.QemuEfiMachine.OVMF_Q35) as qm: 42 | qm.run() 43 | 44 | qmon = Qemu.QemuMonitor(qm) 45 | qmon.wait_for_state('running') 46 | 47 | qssh = Qemu.QemuSSH(qm) 48 | 49 | qssh.check_exec(CHECK_EXEC) 50 | 51 | qmon.send_command('dump-guest-memory %s' % (MEMORY_FILE)) 52 | 53 | rc = check_for_string(MEMORY_FILE, CHECK_EXEC) 54 | assert rc == 0, 'Failed finding %s in %s' % (CHECK_EXEC, MEMORY_FILE) 55 | 56 | remove_file(MEMORY_FILE) 57 | 58 | qm.stop() 59 | 60 | def test_guest_memory_confidentiality_tdx(qm): 61 | """ 62 | Test guest memory confidentiality with TDX 63 | """ 64 | with Qemu.QemuMachine(memory='1G') as qm: 65 | qm.run() 66 | 67 | qmon = Qemu.QemuMonitor(qm) 68 | qmon.wait_for_state('running') 69 | 70 | qssh = Qemu.QemuSSH(qm) 71 | 72 | qssh.check_exec(CHECK_EXEC) 73 | 74 | qmon.send_command('dump-guest-memory %s' % (MEMORY_FILE)) 75 | 76 | rc = check_for_string(MEMORY_FILE, CHECK_EXEC) 77 | assert rc == 1, 'Failed by finding %s in %s' % (CHECK_EXEC, MEMORY_FILE) 78 | 79 | remove_file(MEMORY_FILE) 80 | 81 | qm.stop() 82 | -------------------------------------------------------------------------------- /tests/tests/guest/test_guest_tsc_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # Authors: 5 | # - Hector Cao 6 | # 7 | # This program is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 3, as published 9 | # by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 13 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # 19 | 20 | import os 21 | import subprocess 22 | import time 23 | import re 24 | 25 | import Qemu 26 | import util 27 | 28 | script_path=os.path.dirname(os.path.realpath(__file__)) 29 | 30 | def test_guest_tsc_config(qm): 31 | """ 32 | tdx_tsc_config test case (See https://github.com/intel/tdx/wiki/Tests) 33 | """ 34 | 35 | # Get and parse cpuid value from host 36 | cs = subprocess.run(['cpuid', '-rl', '0x15', '-1'], check=True, capture_output=True) 37 | assert cs.returncode == 0, 'Failed getting cpuid' 38 | out_str = str(cs.stdout.strip()) 39 | eax, ebx, ecx, edx = parse_cpuid_0x15_values(out_str) 40 | assert edx == 0, "CPUID values incorrect" 41 | 42 | # calculate tsc value 43 | tsc_host = ecx * ebx / eax 44 | 45 | qm.run() 46 | 47 | # Get cpuid value from guest and parse it 48 | m = Qemu.QemuSSH(qm) 49 | out_str = '' 50 | [outlines, err] = m.check_exec('cpuid -rl 0x15 -1') 51 | for l in outlines.readlines(): 52 | out_str += l 53 | eax, ebx, ecx, edx = parse_cpuid_0x15_values(out_str) 54 | 55 | # calculate tsc value on guest and make sure same as host 56 | tsc_guest = ecx * ebx / eax 57 | assert tsc_guest == tsc_host, "TSC host and guest don't match" 58 | 59 | # Verify tsc detected in guest dmesg logs 60 | stdout, _ = m.check_exec('dmesg') 61 | output = stdout.read().decode('utf-8') 62 | assert 'tsc: Detected' in output 63 | 64 | qm.stop() 65 | 66 | 67 | def test_guest_set_tsc_frequency(qm): 68 | """ 69 | tdx_tsc_config test case (See https://github.com/intel/tdx/wiki/Tests) 70 | """ 71 | 72 | # Set guest tsc frequency 73 | tsc_frequency = 3000000000 74 | qm.qcmd.plugins['cpu'].cpu_flags += f',tsc-freq={tsc_frequency}' 75 | qm.run() 76 | 77 | # Get cpuid value from guest and parse it 78 | m = Qemu.QemuSSH(qm) 79 | out_str = '' 80 | [outlines, err] = m.check_exec('cpuid -rl 0x15 -1') 81 | for l in outlines.readlines(): 82 | out_str += l 83 | eax, ebx, ecx, edx = parse_cpuid_0x15_values(out_str) 84 | 85 | # calculate tsc on guest and make sure its equal to value set 86 | tsc_guest = ecx * ebx / eax 87 | assert tsc_guest == tsc_frequency, "TSC frequency not set correctly" 88 | 89 | def test_guest_tsc_deadline_enable(qm): 90 | """ 91 | tdx_tsc_deadline_enable test case (See https://github.com/intel/tdx/wiki/Tests) 92 | """ 93 | qm.run() 94 | 95 | m = Qemu.QemuSSH(qm) 96 | 97 | stdout, _ = m.check_exec('lscpu') 98 | output = stdout.read().decode('utf-8') 99 | assert 'Flags' in output 100 | assert 'tsc_deadline_timer' in output 101 | 102 | qm.stop() 103 | 104 | def test_guest_tsc_deadline_disable(qm): 105 | """ 106 | tdx_tsc_deadline_disable test case (See https://github.com/intel/tdx/wiki/Tests) 107 | """ 108 | qm.qcmd.plugins['cpu'].cpu_flags += f',-tsc-deadline' 109 | qm.run() 110 | 111 | # NB : on 24.10, the VM takes a long time to boot > 75s (on 24.04, only 15sec) 112 | # for now, extend the ssh connexion timeout but should be fixed in the future 113 | m = Qemu.QemuSSH(qm, timeout=100) 114 | 115 | stdout, _ = m.check_exec('lscpu') 116 | output = stdout.read().decode('utf-8') 117 | assert 'Flags' in output 118 | assert 'tsc_deadline_timer' not in output 119 | 120 | qm.stop() 121 | 122 | # helper function for parsing cpuid value into registers 123 | def parse_cpuid_0x15_values(val_str): 124 | # parse register values 125 | try: 126 | eax = int(re.findall(r'eax=0x([0-9a-f]+)', val_str)[0], 16) 127 | ebx = int(re.findall(r'ebx=0x([0-9a-f]+)', val_str)[0], 16) 128 | ecx = int(re.findall(r'ecx=0x([0-9a-f]+)', val_str)[0], 16) 129 | edx = int(re.findall(r'edx=0x([0-9a-f]+)', val_str)[0], 16) 130 | except Exception as e: 131 | assert False, print(f'Failed parsing cpuid registers {e}') 132 | return eax, ebx, ecx, edx 133 | -------------------------------------------------------------------------------- /tests/tests/guest/test_nmi_debug_off.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # 3 | # This program is free software: you can redistribute it and/or modify it 4 | # under the terms of the GNU General Public License version 3, as published 5 | # by the Free Software Foundation. 6 | # 7 | # This program is distributed in the hope that it will be useful, but WITHOUT 8 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 9 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | # 12 | # You should have received a copy of the GNU General Public License along with 13 | # this program. If not, see . 14 | # 15 | 16 | import os 17 | 18 | import Qemu 19 | 20 | # Tests 21 | 22 | def test_nmi_debug_off(qm): 23 | """ 24 | Boot TDX VM and make sure nmi runs without issue in monitor (Intel Case ID: 006) 25 | """ 26 | qm.run() 27 | 28 | mon = Qemu.QemuMonitor(qm) 29 | mon.wait_for_state('running') 30 | 31 | # tdx guest run nmi 32 | msgs = mon.send_command('nmi') 33 | assert len(msgs) > 0, "Invalid response from nmi command" 34 | for msg in msgs: 35 | assert "unknown" not in msg 36 | 37 | # make sure system is still running 38 | running = False 39 | msgs = mon.send_command('info status') 40 | for msg in msgs: 41 | running |= "running" in msg 42 | assert running, "Invalid state after running nmi command" 43 | mon.send_command('quit') 44 | 45 | qm.stop() 46 | -------------------------------------------------------------------------------- /tests/tests/host/test_host_kdump.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | SSH_OPTIONS=" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" 19 | 20 | if [ ! -v DEVICE_IP ]; then 21 | echo "Must define DEVICE_IP" 22 | exit 1 23 | fi 24 | 25 | echo "Installing linux-crashdump and rebooting" 26 | set -e 27 | ssh $SSH_OPTIONS ubuntu@$DEVICE_IP sudo apt install linux-crashdump 28 | ssh $SSH_OPTIONS ubuntu@$DEVICE_IP sudo systemctl reboot 29 | 30 | echo "Waiting for system to go down" 31 | cnt=0 32 | until ! ssh -o ConnectTimeout=2 $SSH_OPTIONS ubuntu@$DEVICE_IP ls &> /dev/null; do 33 | sleep 1; 34 | cnt=$(expr $cnt + 1); 35 | if [ $cnt -gt 30 ]; then 36 | echo "$DEVICE_IP did not reboot" 37 | exit 1 38 | fi 39 | done 40 | 41 | echo "Waiting for system to come back up" 42 | cnt=0 43 | until ssh -o ConnectTimeout=2 $SSH_OPTIONS ubuntu@$DEVICE_IP ls &> /dev/null; do 44 | sleep 1; 45 | cnt=$(expr $cnt + 1); 46 | if [ $cnt -gt 120 ]; then 47 | echo "Timed out waiting for $DEVICE_IP to come back up ($cnt)" 48 | exit 1 49 | fi 50 | done 51 | 52 | echo "System came back up, printing out useful debug info" 53 | 54 | ssh $SSH_OPTIONS ubuntu@$DEVICE_IP sudo kdump-config show 55 | ssh $SSH_OPTIONS ubuntu@$DEVICE_IP cat /proc/cmdline 56 | 57 | # Get crash directory before to compare later 58 | CRASH_DIR_BEFORE="$(ssh ubuntu@$DEVICE_IP ls /var/crash)" 59 | 60 | echo "Crashing system" 61 | ssh $SSH_OPTIONS ubuntu@$DEVICE_IP "echo c | sudo tee /proc/sysrq-trigger" > /dev/null & 62 | 63 | echo "Waiting for system to go down" 64 | cnt=0 65 | until ! ssh -o ConnectTimeout=2 $SSH_OPTIONS ubuntu@$DEVICE_IP ls &> /dev/null; do 66 | sleep 1; 67 | cnt=$(expr $cnt + 1); 68 | if [ $cnt -gt 30 ]; then 69 | echo "$DEVICE_IP did not reboot" 70 | exit 1 71 | fi 72 | done 73 | 74 | echo "Waiting for system to come back up" 75 | cnt=0 76 | until ssh -o ConnectTimeout=2 $SSH_OPTIONS ubuntu@$DEVICE_IP ls &> /dev/null; do 77 | sleep 1; 78 | cnt=$(expr $cnt + 1); 79 | if [ $cnt -gt 120 ]; then 80 | echo "Timed out waiting for $DEVICE_IP to come back up ($cnt)" 81 | exit 1 82 | fi 83 | done 84 | 85 | echo "System came back up" 86 | CRASH_DIR_AFTER="$(ssh $SSH_OPTIONS ubuntu@$DEVICE_IP ls /var/crash)" 87 | echo Before crash directory listing: $CRASH_DIR_BEFORE 88 | echo After crash directory listing: $CRASH_DIR_AFTER 89 | 90 | # Verifying crash directory are not the same (extra entry should be in place) 91 | if [ "$CRASH_DIR_BEFORE" == "$CRASH_DIR_AFTER" ]; then 92 | echo "Crash directories are the same" 93 | exit 1 94 | fi 95 | 96 | echo "Kdump Successfully Performed" 97 | -------------------------------------------------------------------------------- /tests/tests/host/test_host_kexec.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | SSH_OPTIONS=" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" 19 | 20 | if [ ! -v DEVICE_IP ]; then 21 | echo "Must define DEVICE_IP" 22 | exit 1 23 | fi 24 | 25 | set -e 26 | 27 | echo "Getting information for kexec call" 28 | UNAME_RELEASE="$(ssh ubuntu@$DEVICE_IP uname -r)" 29 | KERNEL="/boot/vmlinuz-$UNAME_RELEASE" 30 | INITRD="/boot/initrd.img-$UNAME_RELEASE" 31 | ssh $SSH_OPTIONS ubuntu@$DEVICE_IP ls $KERNEL > /dev/null 32 | ssh $SSH_OPTIONS ubuntu@$DEVICE_IP ls $INITRD > /dev/null 33 | CMDLINE="$(ssh $SSH_OPTIONS ubuntu@$DEVICE_IP cat /proc/cmdline)" 34 | 35 | echo "Calling kexec: ssh $SSH_OPTIONS ubuntu@$DEVICE_IP sudo kexec -l $KERNEL --initrd=$INITRD --command-line=\"$CMDLINE\"" 36 | ssh $SSH_OPTIONS ubuntu@$DEVICE_IP sudo kexec -l $KERNEL --initrd=$INITRD --command-line=\"$CMDLINE\" 37 | 38 | echo "Running kexec -e in background" 39 | ssh $SSH_OPTIONS ubuntu@$DEVICE_IP sudo kexec -e & 40 | 41 | echo "Waiting for system to go down" 42 | cnt=0 43 | until ! ssh -o ConnectTimeout=2 $SSH_OPTIONS ubuntu@$DEVICE_IP ls &> /dev/null; do 44 | sleep 1; 45 | cnt=$(expr $cnt + 1); 46 | if [ $cnt -gt 30 ]; then 47 | echo "$DEVICE_IP did not reboot" 48 | exit 1 49 | fi 50 | done 51 | 52 | echo "Waiting for system to come back up" 53 | cnt=0 54 | until ssh -o ConnectTimeout=2 $SSH_OPTIONS ubuntu@$DEVICE_IP ls &> /dev/null; do 55 | sleep 1; 56 | cnt=$(expr $cnt + 1); 57 | if [ $cnt -gt 120 ]; then 58 | echo "Timed out waiting for $DEVICE_IP to come back up ($cnt)" 59 | exit 1 60 | fi 61 | done 62 | 63 | echo "Kexec Successfully Performed" 64 | -------------------------------------------------------------------------------- /tests/tests/host/test_host_tdx_hardware.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # Authors: 3 | # - Hector Cao 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | import os 19 | import subprocess 20 | import tdxtools 21 | 22 | def test_host_tdx_hardware_enabled(): 23 | """ 24 | Check if host is TDX capabled 25 | """ 26 | 27 | # 28 | # Check the bit 1 of MSR 0x982. 1 means MK-TME is enabled in BIOS. 29 | # SDM: 30 | # Vol. 4 Model Specific Registers (MSRs) 31 | # Table 2-2. IA-32 Architectural MSRs (Contd.) 32 | # Register Address: 982H 33 | # Architectural MSR Name: IA32_TME_ACTIVATE 34 | # Bit Fields: 1 35 | # Bit Description: Hardware Encryption Enable. This bit also enables TME-MK. 36 | # 37 | assert tdxtools.host.readmsr(0x982, 1, 1) == 1 38 | 39 | # Intel® Trust Domain CPU Architectural Extensions 40 | # IA32_SEAMRR_PHYS_BASE MSR 41 | # 11:11 : Enable bit for SEAMRR (SEAM Range Registers) 42 | assert tdxtools.host.readmsr(0x1401, 11, 11) == 1 43 | 44 | # Intel® Trust Domain CPU Architectural Extensions 45 | # IA32_TME_CAPABILITY MSR 46 | # 63:32 : NUM_TDX_PRIV_KEYS 47 | assert tdxtools.host.readmsr(0x87, 63, 32) > 16 48 | 49 | if __name__ == '__main__': 50 | test_host_tdx_hardware_enabled() 51 | -------------------------------------------------------------------------------- /tests/tests/host/test_host_tdx_software.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # Authors: 5 | # - Hector Cao 6 | # 7 | # This program is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 3, as published 9 | # by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 13 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # 19 | 20 | import re 21 | import subprocess 22 | import tdxtools 23 | 24 | def test_host_tdx_cpu(): 25 | """ 26 | Check that the CPU has TDX support enabled 27 | (the flag tdx_host_platform is present) 28 | """ 29 | assert tdxtools.host.support_tdx() 30 | 31 | def test_host_tdx_software(): 32 | 33 | # when TDX is not properly loaded or initialized 34 | # this value should by 'N' 35 | # otherwise, the value 'Y' means tdx has been successfully initialized 36 | subprocess.check_call('grep Y /sys/module/kvm_intel/parameters/tdx', shell=True) 37 | 38 | subprocess.check_call('grep Y /sys/module/kvm_intel/parameters/sgx', shell=True) 39 | 40 | def test_host_tdx_module_load(tdx_version): 41 | """ 42 | Check the tdx module has been loaded successfuly on the host 43 | Check a log in dmesg with appropriate versioning information 44 | 45 | tdx_uefi test case (See https://github.com/intel/tdx/wiki/Tests) 46 | """ 47 | 48 | # Get dmesg and make sure it has the tdx module load message 49 | cs = subprocess.run(['sudo', 'dmesg'], check=True, capture_output=True) 50 | assert cs.returncode == 0, 'Failed getting dmesg' 51 | dmesg_str = cs.stdout.decode('utf-8') 52 | 53 | if (tdx_version == 1): 54 | items=re.findall(r'tdx: TDX module: attributes 0x[0-9]+, vendor_id 0x8086, major_version [0-9]+, minor_version [0-9]+, build_date [0-9]+, build_num [0-9]+', dmesg_str) 55 | else: 56 | items=re.findall(r'virt/tdx: TDX module [0-9\.]+, build number [0-9]+, build date [0-9a-z]+', dmesg_str) 57 | assert len(items) > 0 58 | 59 | if __name__ == '__main__': 60 | test_host_tdx_software() 61 | -------------------------------------------------------------------------------- /tests/tests/perf/test_perf_benchmark.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # Authors: 3 | # - Hector Cao 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | import os 19 | 20 | from parameterized import parameterized 21 | 22 | from Qemu import QemuEfiMachine, QemuEfiFlashSize 23 | import Qemu 24 | 25 | import util 26 | 27 | @parameterized.expand([ 28 | ['normal', QemuEfiMachine.OVMF_Q35], 29 | ['td', QemuEfiMachine.OVMF_Q35_TDX] 30 | ]) 31 | def test_run_perf(name, machine): 32 | """ 33 | Run benchmark.sh script on TD VM and VM 34 | """ 35 | qm = Qemu.QemuMachine(name, 36 | machine, 37 | memory='32G') 38 | qm.run() 39 | try: 40 | test_profile='tdx_memory' 41 | m = Qemu.QemuSSH(qm) 42 | script_path=os.path.dirname(os.path.realpath(__file__)) 43 | qm.rsync_file(f'{script_path}/../lib/pts', '/') 44 | m.ssh_conn.exec_command('chmod a+x /pts/benchmark.sh') 45 | _, stdout, _ = m.ssh_conn.exec_command(f'/pts/benchmark.sh {test_profile} &> /pts/benchmark-{name}.txt') 46 | assert (0 == stdout.channel.recv_exit_status()), 'benchmark run failed !' 47 | m.get(f'/pts/benchmark-{name}.txt', f'{script_path}/benchmark-{name}.txt') 48 | m.get(f'/pts/benchmark.csv', f'{script_path}/benchmark-{name}.csv') 49 | m.poweroff() 50 | except Exception as e: 51 | pytest.fail('Error : %s' % (e)) 52 | -------------------------------------------------------------------------------- /tests/tests/perf/test_perf_boot_time.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # Authors: 3 | # - Hector Cao 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | from parameterized import parameterized 19 | 20 | from Qemu import QemuEfiMachine, QemuEfiFlashSize 21 | import Qemu 22 | 23 | import util 24 | 25 | @parameterized.expand([ 26 | ['normal', QemuEfiMachine.OVMF_Q35,'2G'], 27 | ['td', QemuEfiMachine.OVMF_Q35_TDX,'2G'], 28 | ['normal_16G', QemuEfiMachine.OVMF_Q35,'16G'], 29 | ['td_16G', QemuEfiMachine.OVMF_Q35_TDX,'16G'], 30 | ['normal_64G', QemuEfiMachine.OVMF_Q35,'64G'], 31 | ['td_64G', QemuEfiMachine.OVMF_Q35_TDX,'64G'], 32 | ]) 33 | def test_boot_time(name, machine, memory): 34 | """ 35 | Boot time statistics for Normal VM and TD VM 36 | """ 37 | qm = Qemu.QemuMachine(name, 38 | machine, 39 | memory=memory) 40 | def boot_vm_and_wait_ssh(): 41 | qm.run() 42 | m = Qemu.QemuSSH(qm, timeout=200) 43 | 44 | with qm: 45 | util.timeit(boot_vm_and_wait_ssh)() 46 | 47 | qm.stop() 48 | -------------------------------------------------------------------------------- /tests/tests/quote/test_guest_ita.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | import os 19 | import time 20 | import json 21 | import subprocess 22 | import pytest 23 | 24 | import Qemu 25 | import util 26 | 27 | ubuntu_codename = None 28 | 29 | @pytest.mark.quote_generation 30 | def test_guest_measurement_trust_authority_success(): 31 | """ 32 | Trust Authority CLI quote generation success 33 | """ 34 | change_qgsd_state('start') 35 | quote_str = run_trust_authority() 36 | check_ita_output(quote_str, for_success = True) 37 | 38 | @pytest.mark.quote_generation 39 | def test_guest_measurement_trust_authority_failure(): 40 | """ 41 | Trust Authority CLI quote generation failure 42 | """ 43 | change_qgsd_state('stop') 44 | quote_str = run_trust_authority() 45 | change_qgsd_state('start') 46 | check_ita_output(quote_str, for_success = False) 47 | 48 | def change_qgsd_state(state): 49 | cmd = ['systemctl', state, 'qgsd'] 50 | subprocess.run(cmd) 51 | rc = subprocess.run(cmd, stderr=subprocess.STDOUT, timeout=30) 52 | assert 0 == rc.returncode, 'Failed change state of qgsd' 53 | 54 | def check_ita_output(quote_str : str, for_success : bool = True): 55 | """ 56 | Check the validity of ITA quote output 57 | Depending on the version of the ITA client, the output 58 | may vary: 59 | - Ubuntu 24.04 (ITA 1.5.0) 60 | On success: [4 0 2 0 129 0 0 ... 0 0 0 0 0 ] 61 | On failure: [] 62 | - Ubuntu 24.10 (ITA 1.6.1) 63 | On success: 64 | Quote: 65 | runtime_data: base64_encoded_runtime_data <- Optional 66 | user_data: base64_encoded_user_data <- Optional 67 | On failure: 68 | Quote: 69 | - Ubuntu 25.04 70 | In 25.04, the ITA version is 1.9.0 and there is a major 71 | client API shift with the version 1.6.1. The CLI quote 72 | command is replaced by evidence command and it requires a configuration 73 | file as input. There is also a major change in the output of the evidence 74 | command. 75 | On success: 76 | Json file 77 | """ 78 | # regex to check the output of ITA quote command, the regex depends on ITA version 79 | # for the moment, we extract the ITA version from the ubuntu release 80 | # {10,0}: check for at least 10 characters to declare the quote valid 81 | ita_output_regexp = { 82 | 'noble' : '\\[[0-9 ]{10,}\\]', 83 | 'oracular' : 'Quote: [-A-Za-z0-9+/]{10,}', 84 | 'plucky' : 'TODO' 85 | } 86 | import re 87 | pattern = re.compile(ita_output_regexp[ubuntu_codename]) 88 | assert (bool(pattern.match(quote_str)) == for_success), f'Error for code name : {ubuntu_codename}' 89 | 90 | def run_trust_authority(): 91 | global ubuntu_codename 92 | 93 | quote_str = "" 94 | with Qemu.QemuMachine() as qm: 95 | machine = qm.qcmd.plugins['machine'] 96 | machine.enable_qgs_addr() 97 | 98 | qm.run() 99 | 100 | ssh = Qemu.QemuSSH(qm) 101 | 102 | stdout, _ = ssh.check_exec('lsb_release -cs') 103 | ubuntu_codename = stdout.read().decode().strip() 104 | 105 | stdout, stderr = ssh.check_exec('trustauthority-cli quote') 106 | quote_str = stdout.read().decode() 107 | return quote_str 108 | -------------------------------------------------------------------------------- /tests/tests/quote/test_guest_tdxattest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | import random 19 | import string 20 | import pytest 21 | 22 | import Qemu 23 | 24 | # This file contains tests for the tdxattest lib in the guest 25 | # The tdxattest library allows application to request a quote from the 26 | # host system 27 | # This request can be done through 2 channels: 28 | # - vsock : the application connects directly to the QGSD service in the host 29 | # - configfs tsm : the application use configsf tsm to ask the guest kernel 30 | # to contact the QGSD service for the quote generation 31 | 32 | @pytest.mark.quote_generation 33 | def test_guest_tdxattest_tsm(): 34 | """ 35 | TDX attest library 36 | Success when only TSM is available 37 | vsock support is disabled by removing the configuration file and not enabling 38 | the vsock support in QEMU command line 39 | """ 40 | with Qemu.QemuMachine() as qm: 41 | machine = qm.qcmd.plugins['machine'] 42 | machine.enable_qgs_addr() 43 | 44 | qm.run() 45 | ssh = Qemu.QemuSSH(qm) 46 | 47 | ssh.check_exec('rm -f /etc/tdx-attest.conf') 48 | stdout, _ = ssh.check_exec('/usr/share/doc/libtdx-attest-dev/examples/test_tdx_attest') 49 | 50 | assert 'Successfully get the TD Quote' in stdout.read().decode() 51 | 52 | @pytest.mark.quote_generation 53 | def test_guest_tdxattest_tsm_failure(): 54 | """ 55 | TDX attest library 56 | Failure if we force the lib to use TSM but QEMU does not have the 57 | quote generation socket specified. 58 | """ 59 | with Qemu.QemuMachine() as qm: 60 | qm.run() 61 | ssh = Qemu.QemuSSH(qm) 62 | 63 | ssh.check_exec('rm -f /etc/tdx-attest.conf') 64 | 65 | ret, stdout, stderr = ssh.exec_command('/usr/share/doc/libtdx-attest-dev/examples/test_tdx_attest') 66 | assert (ret != 0) and ('Failed to get the quote' in stderr.read().decode()) 67 | 68 | @pytest.mark.quote_generation 69 | def test_guest_tdxattest_vsock(): 70 | """ 71 | TDX attest library 72 | Success when only vsock is available 73 | ConfigFs TSM is disabled 74 | """ 75 | with Qemu.QemuMachine() as qm: 76 | qm.qcmd.add_vsock(10) 77 | 78 | qm.run() 79 | ssh = Qemu.QemuSSH(qm) 80 | 81 | disable_tsm(ssh) 82 | 83 | stdout, _ = ssh.check_exec('/usr/share/doc/libtdx-attest-dev/examples/test_tdx_attest') 84 | 85 | assert 'Successfully get the TD Quote' in stdout.read().decode() 86 | 87 | @pytest.mark.quote_generation 88 | def test_guest_tdxattest_vsock_wrong_qgs_addr(qm): 89 | """ 90 | Success even when QGS address is not properly configured 91 | Test setup: 92 | - the qgs addr is not properly configured by using CID=3 instead of 2 93 | (the configfs tsm method should fail however) 94 | - vsock is enabled for the guest 95 | Expected behavior: 96 | The quote generation request should succeed because 97 | vsock is enabled and tdxattest should fallback to use vsock 98 | """ 99 | qm.qcmd.add_vsock(10) 100 | 101 | machine = qm.qcmd.plugins['machine'] 102 | machine.enable_qgs_addr(addr = {'type': 'vsock', 'cid':'3','port':'4050'}) 103 | 104 | qm.run() 105 | ssh = Qemu.QemuSSH(qm) 106 | 107 | stdout, _ = ssh.check_exec('/usr/share/doc/libtdx-attest-dev/examples/test_tdx_attest') 108 | 109 | assert 'Successfully get the TD Quote' in stdout.read().decode() 110 | 111 | @pytest.mark.quote_generation 112 | def test_guest_tdxattest_vsock_failure(): 113 | """ 114 | TDX attest library 115 | Failure if we force the lib to use vsock and QEMU does not have the 116 | vsock arguments specified 117 | """ 118 | with Qemu.QemuMachine() as qm: 119 | qm.run() 120 | ssh = Qemu.QemuSSH(qm) 121 | 122 | disable_tsm(ssh) 123 | 124 | ret, stdout, stderr = ssh.exec_command('/usr/share/doc/libtdx-attest-dev/examples/test_tdx_attest') 125 | assert (ret != 0) and ('Failed to get the quote' in stderr.read().decode()) 126 | 127 | @pytest.mark.quote_generation 128 | def test_guest_tdxattest_failure(): 129 | """ 130 | TDX attest library 131 | Fail when vsock and TSM are both disabled 132 | """ 133 | with Qemu.QemuMachine() as qm: 134 | qm.run() 135 | ssh = Qemu.QemuSSH(qm) 136 | 137 | disable_tsm(ssh) 138 | ssh.check_exec('rm -f /etc/tdx-attest.conf') 139 | 140 | ret, stdout, stderr = ssh.exec_command('/usr/share/doc/libtdx-attest-dev/examples/test_tdx_attest') 141 | 142 | assert (ret != 0) and ('Failed to get the quote' in stderr.read().decode()) 143 | 144 | @pytest.mark.quote_generation 145 | def test_guest_tdxattest_failure_1(qm): 146 | """ 147 | Failure when vsock disabled and QGS addr is not properly configured 148 | Test setup: 149 | - the qgs addr is not properly configured by using CID=3 instead of 2 150 | (the configfs tsm method should fail however) 151 | - vsock is not enabled for the guest 152 | Expected behavior: 153 | The quote generation request should fail 154 | """ 155 | machine = qm.qcmd.plugins['machine'] 156 | machine.enable_qgs_addr(addr = {'type': 'vsock', 'cid':'3','port':'4050'}) 157 | 158 | qm.run() 159 | ssh = Qemu.QemuSSH(qm) 160 | 161 | ret, stdout, stderr = ssh.exec_command('/usr/share/doc/libtdx-attest-dev/examples/test_tdx_attest') 162 | 163 | assert (ret != 0) and ('Failed to get the quote' in stderr.read().decode()) 164 | 165 | def disable_tsm(ssh): 166 | """ 167 | Disable the configfs tsm 168 | There is no official way to disable the configfs tsm functionality 169 | but we can simulate configfs tsm errors by bind mounting an empty folder 170 | on top of the tsm folder. 171 | """ 172 | tmp_folder_name=''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(4)) 173 | ssh.check_exec(f'mkdir -p /tmp/{tmp_folder_name}') 174 | ssh.check_exec(f'mount --bind /tmp/{tmp_folder_name} /sys/kernel/config/tsm/report/') 175 | -------------------------------------------------------------------------------- /tests/tests/quote/test_quote_configfs_tsm.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # Authors: 3 | # - Hector Cao 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | import os 19 | import time 20 | 21 | import Qemu 22 | from common import deploy_and_setup 23 | 24 | script_path=os.path.dirname(os.path.realpath(__file__)) 25 | 26 | def test_quote_check_configfs_tsm(qm): 27 | """ 28 | Check that the configfs tsm for quote generation is available 29 | """ 30 | qm.run() 31 | 32 | m = Qemu.QemuSSH(qm) 33 | 34 | deploy_and_setup(m) 35 | 36 | m.check_exec('tdtsmcheck') 37 | 38 | qm.stop() 39 | 40 | def test_qgs_socket(qm): 41 | """ 42 | Test QGS socket (No Intel Case ID) 43 | """ 44 | machine = qm.qcmd.plugins['machine'] 45 | machine.enable_qgs_addr() 46 | 47 | qm.run() 48 | 49 | # do basic tsm_config test on guest 50 | ssh = Qemu.QemuSSH(qm) 51 | deploy_and_setup(ssh) 52 | ssh.check_exec('tdtsmcheck') 53 | 54 | qm.stop() 55 | -------------------------------------------------------------------------------- /tests/tests/report/test_guest_report.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # Authors: 5 | # - Hector Cao 6 | # 7 | # This program is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 3, as published 9 | # by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 13 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # this program. If not, see . 18 | # 19 | 20 | import os 21 | import time 22 | 23 | import Qemu 24 | import util 25 | from common import * 26 | 27 | def test_guest_report(qm): 28 | """ 29 | Boot measurements check 30 | """ 31 | qm.run() 32 | 33 | m = Qemu.QemuSSH(qm) 34 | 35 | deploy_and_setup(m) 36 | 37 | m.check_exec(f'python3 {guest_workdir}/lib/guest/test_tdreport.py') 38 | 39 | qm.stop() 40 | -------------------------------------------------------------------------------- /tests/tests/stress/test_stress_boot.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # Authors: 3 | # - Hector Cao 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | import os 19 | 20 | from Qemu import QemuEfiMachine, QemuEfiFlashSize 21 | import Qemu 22 | 23 | import util 24 | 25 | def test_stress_boot(): 26 | """ 27 | Boot in loop 28 | """ 29 | for i in range(0,100): 30 | print(f'\nBooting TD nb={i}') 31 | with Qemu.QemuMachine() as qm: 32 | qm.run() 33 | m = Qemu.QemuSSH(qm) 34 | qm.stop() 35 | -------------------------------------------------------------------------------- /tests/tests/stress/test_stress_quote.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | import pytest 19 | 20 | import Qemu 21 | 22 | @pytest.mark.quote_generation 23 | def test_stress_tdxattest_tsm(): 24 | """ 25 | Stress test on quote generation 26 | Have a loop to generate 200 quotes 27 | """ 28 | with Qemu.QemuMachine() as qm: 29 | machine = qm.qcmd.plugins['machine'] 30 | machine.enable_qgs_addr() 31 | 32 | qm.run() 33 | ssh = Qemu.QemuSSH(qm) 34 | 35 | ssh.check_exec('rm -f /etc/tdx-attest.conf') 36 | nb_iterations = 200 37 | stdout, _ = ssh.check_exec(f''' 38 | count={nb_iterations} 39 | for i in $(seq $count); do 40 | /usr/share/doc/libtdx-attest-dev/examples/test_tdx_attest | grep "Successfully get the TD Quote" 41 | done 42 | ''') 43 | assert stdout.read().decode().count('Successfully get the TD Quote') == nb_iterations 44 | -------------------------------------------------------------------------------- /tests/tests/stress/test_stress_resources.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # 3 | # This program is free software: you can redistribute it and/or modify it 4 | # under the terms of the GNU General Public License version 3, as published 5 | # by the Free Software Foundation. 6 | # 7 | # This program is distributed in the hope that it will be useful, but WITHOUT 8 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 9 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | # 12 | # You should have received a copy of the GNU General Public License along with 13 | # this program. If not, see . 14 | # 15 | 16 | import os 17 | import subprocess 18 | import time 19 | import multiprocessing 20 | import pytest 21 | 22 | import Qemu 23 | import util 24 | 25 | def test_stress_huge_resource_vm(qm): 26 | """ 27 | Test huge resources (Intel Case ID 007) 28 | """ 29 | 30 | # choose half available memory per Intel case spec 31 | huge_mem_gb = int(util.get_memory_free_gb() / 2) 32 | num_cpus = int(multiprocessing.cpu_count() / 2) 33 | 34 | qm.qcmd.plugins['cpu'].nb_cores = num_cpus 35 | qm.qcmd.plugins['memory'].memory = '%dG' % (huge_mem_gb) 36 | qm.run() 37 | 38 | # huge guest memory -> increase the timeout to give more time to guest to boot 39 | ssh = Qemu.QemuSSH(qm, timeout=100) 40 | 41 | qm.stop() 42 | 43 | def test_stress_memory_limit_resource_vm(qm): 44 | """ 45 | Test memory limit resource (No Intel Case) 46 | """ 47 | 48 | # choose half available memory per Intel case spec 49 | huge_mem_gb = int(util.get_memory_available_gb()) 50 | 51 | qm.qcmd.plugins['memory'].memory = '%dG' % (huge_mem_gb) 52 | qm.run() 53 | 54 | ssh = Qemu.QemuSSH(qm) 55 | 56 | qm.stop() 57 | 58 | 59 | def test_stress_max_vcpus(qm): 60 | """ 61 | Test max vcpus (No Intel Case ID) 62 | """ 63 | num_cpus = multiprocessing.cpu_count() 64 | if num_cpus > 255: 65 | num_cpus = 255 # max possible right now 66 | 67 | qm.qcmd.plugins['cpu'].nb_cores = num_cpus 68 | qm.run() 69 | 70 | ssh = Qemu.QemuSSH(qm, timeout=100) 71 | 72 | qm.stop() 73 | 74 | def check_qemu_fail_to_start(qm, error_msg=None): 75 | try: 76 | _, err = qm.communicate(timeout=5) 77 | except: 78 | # if timeout, that means the QEMU is running fine 79 | # try to connect with ssh to make sure the TD is running fine 80 | try: 81 | ssh = Qemu.QemuSSH(qm) 82 | except: 83 | # the qemu is running but we cannot connect to SSH 84 | # we consider that the check is OK 85 | qm.stop() 86 | return 87 | pytest.fail('The TD is running !') 88 | if error_msg: 89 | assert error_msg in err.decode() 90 | 91 | def test_stress_max_guests(): 92 | """ 93 | Test max guests (No Intel Case ID) 94 | 95 | There is a limit on the number of TDs that can be run in parralel. 96 | This limit can be due to several factors, but the most prevalent factor 97 | is the number of keys the CPU can allocate to TDs. 98 | In fact, TDX takes advantage of an existing CPU feature called MK-TME 99 | (Multi-key Total Memory Encryption) to encrypt the VM memory. It enables 100 | the CPU to encrypt each TD’s memory with a unique Advanced Encryption Standard (AES) key. 101 | MK-TME offers a number of keys and this key space is partionned into 2 sets: 102 | Shared (VMM) and Private (TDX). The number of key in the Private space defines the 103 | maximum number of TDs we can run in parralel. 104 | 105 | This test verifies that we can run TDs up to this limit and any new TD creation 106 | is refused by qemu in a nice way. 107 | """ 108 | 109 | # get max number of TD VMs we can create (max - current) 110 | max_td_vms = util.get_max_td_vms() - util.get_current_td_vms() 111 | assert max_td_vms > 0, "No available space for TD VMs" 112 | 113 | print(f'The limit number of TDs is : {max_td_vms}') 114 | 115 | qm = [None] * max_td_vms 116 | 117 | # initialize machines 118 | for i in range(max_td_vms): 119 | qm[i] = Qemu.QemuMachine() 120 | 121 | # start machines 122 | for i in range(max_td_vms): 123 | print("Starting machine %d" % (i)) 124 | qm[i].run() 125 | 126 | # wait for all machines running 127 | for i in range(max_td_vms): 128 | print("Waiting for machine %d" % (i)) 129 | ssh = Qemu.QemuSSH(qm[i], timeout=100) 130 | 131 | # try to run a new TD 132 | # expect qemu quit immediately with a specific error message 133 | with Qemu.QemuMachine() as one_more: 134 | one_more.run() 135 | # The qemu error message may vary depending on the TDX QEMU release. 136 | # On QEMU older than 9.2.1: 137 | # - KVM_TDX_INIT_VM failed: No space left on device 138 | # On more recent QEMU releases: 139 | # - TDX ioctl KVM_TDX_INIT_VM failed, hw_errors: 0x0: No space left on device 140 | check_qemu_fail_to_start(one_more, error_msg="No space left on device") 141 | 142 | # stop all machines 143 | for i in range(max_td_vms): 144 | print("Stopping machine %d" % (i)) 145 | qm[i].shutdown() 146 | 147 | # wait for all machines to exit 148 | for i in range(max_td_vms): 149 | print("Stopping machine %d" % (i)) 150 | try: 151 | qm[i].communicate() 152 | except: 153 | pass 154 | -------------------------------------------------------------------------------- /tests/tests/vsock/test_vsock_vm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2024 Canonical Ltd. 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License version 3, as published 7 | # by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # this program. If not, see . 16 | # 17 | 18 | import os 19 | import subprocess 20 | import threading 21 | import time 22 | 23 | import Qemu 24 | from common import deploy_and_setup 25 | 26 | # Global Variables 27 | script_path=os.path.dirname(os.path.realpath(__file__)) 28 | guest_cid=25 29 | 30 | # Helper Functions 31 | 32 | def run_iperf_server_on_host(): 33 | cmd = ['iperf3', '--vsock', '-s', '-1'] 34 | subprocess.run(cmd, stderr=subprocess.STDOUT, timeout=30) 35 | 36 | 37 | def run_iperf_server_on_guest(ssh): 38 | cmd = 'iperf3 --vsock -s -1' 39 | stdout, stderr = ssh.check_exec(cmd) 40 | 41 | 42 | # Tests 43 | 44 | def test_vsock_vm_client(qm): 45 | """ 46 | vsock vm guest client and host as server (Intel Case ID: 028) 47 | """ 48 | qm.qcmd.add_vsock(guest_cid) 49 | qm.run() 50 | 51 | ssh = Qemu.QemuSSH(qm) 52 | deploy_and_setup(ssh) 53 | 54 | t = threading.Thread(target=run_iperf_server_on_host) 55 | t.start() 56 | 57 | cmd = 'iperf3 --vsock -c 2' 58 | stdout, stderr = ssh.check_exec(cmd) 59 | assert 0 == stdout.channel.recv_exit_status(), 'Failed iperf server on client test' 60 | 61 | t.join(timeout=30.0) 62 | qm.stop() 63 | 64 | 65 | def test_vsock_vm_server(qm): 66 | """ 67 | vsock vm guest server and host as client (Intel Case ID: 027) 68 | """ 69 | qm.qcmd.add_vsock(guest_cid) 70 | qm.run() 71 | 72 | ssh = Qemu.QemuSSH(qm) 73 | deploy_and_setup(ssh) 74 | 75 | t = threading.Thread(target=run_iperf_server_on_guest, args=(ssh,)) 76 | t.start() 77 | 78 | # Give time to iperf server to start 79 | time.sleep(1) 80 | 81 | cmd = ['iperf3', '--vsock', '-c', '%d' % (guest_cid)] 82 | rc = subprocess.run(cmd, stderr=subprocess.STDOUT, timeout=30) 83 | assert 0 == rc.returncode, 'Failed iperf server on guest test' 84 | 85 | t.join(timeout=30.0) 86 | qm.stop() 87 | -------------------------------------------------------------------------------- /tests/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | tdx 4 | isolated_build = True 5 | skip_missing_interpreters = True 6 | 7 | [testenv] 8 | allowlist_externals = 9 | bash 10 | {toxinidir}/tox/setup-env-tox.sh 11 | pass_env = TDXTEST_GUEST_IMG, TDXTEST_DEBUG 12 | envdir = {toxworkdir}/.venv 13 | deps = 14 | paramiko==3.3.1 15 | pytest==8.2.1 16 | parameterized==0.9.0 17 | py-cpuinfo==9.0.0 18 | distro 19 | lib/tdx-tools 20 | setenv = 21 | PYTHONPATH={env:PYTHONPATH}:{toxinidir}/lib 22 | commands_pre = 23 | bash -c 'if [ `id -u` -ne 0 ]; then echo "Must run all tests with sudo" 1>&2; (exit -1); fi' 24 | {toxinidir}/tox/setup-env-tox.sh 25 | 26 | [testenv:tdx] 27 | commands = 28 | python3 -m pytest -s -v {posargs} 29 | -------------------------------------------------------------------------------- /tests/tox/setup-env-tox.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is part of Canonical's TDX repository which includes tools 4 | # to setup and configure a confidential computing environment 5 | # based on Intel TDX technology. 6 | # See the LICENSE file in the repository for the license text. 7 | 8 | # Copyright 2024 Canonical Ltd. 9 | # SPDX-License-Identifier: GPL-3.0-only 10 | 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License version 3, 13 | # as published by the Free Software Foundation. 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranties 16 | # of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the GNU General Public License for more details. 18 | 19 | SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) 20 | LOCAL_IMG="${SCRIPT_DIR}/../tdx-guest.qcow2" 21 | 22 | _error() { 23 | echo "Error : $1" 24 | exit 1 25 | } 26 | 27 | cleanup() { 28 | for f in /tmp/tdxtest-*-*; do 29 | if [ -f "$f/qemu.pid" ]; then 30 | pkill -TERM -F "$f/qemu.pid" 31 | fi 32 | rm -rf $f 33 | done 34 | rm -rf /tmp/tdxtest-* 35 | rm -rf /run/user/$UID/tdxtest-* 36 | } 37 | 38 | install_deps() { 39 | sudo apt install -y sshpass cpuid 40 | sudo apt remove iperf3 -y 41 | sudo add-apt-repository ppa:kobuk-team/tdx-testing -y 42 | sudo apt update || true 43 | sudo apt install iperf-vsock -y 44 | } 45 | 46 | if [ -z "${TDXTEST_GUEST_IMG}" ]; then 47 | if ! test -f $LOCAL_IMG; then 48 | echo "TDXTEST_GUEST_IMG must be specified!" 49 | echo "e.g. export TDXTEST_GUEST_IMG=/tmp/tmp.qcow2" 50 | echo "(Use sudo -E to pass environment to sudo)" 51 | exit 1 52 | fi 53 | else 54 | if ! test -f $TDXTEST_GUEST_IMG; then 55 | echo "\$TDXTEST_GUEST_IMG specified, but does not exist!" 56 | echo " Can't find $TDXTEST_GUEST_IMG" 57 | exit 1 58 | fi 59 | fi 60 | 61 | cleanup 62 | 63 | set -e 64 | 65 | install_deps 66 | --------------------------------------------------------------------------------