diff --git a/guest-test/guest.test_executor.sh b/guest-test/guest.test_executor.sh index 5627ec2d..af685b94 100755 --- a/guest-test/guest.test_executor.sh +++ b/guest-test/guest.test_executor.sh @@ -31,19 +31,34 @@ EOF # function based on sshpass to scp $1 source_code_dir and compile $2 test_binary in Guest VM guest_test_source_code() { - sshpass -e scp -P "$PORT" -o StrictHostKeyChecking=no -r "$1" root@localhost:"$GUEST_TEST_DIR" + sshpass -e ssh -p "$PORT" -o StrictHostKeyChecking=no root@localhost << EOF + mkdir -p $GUEST_TEST_DIR/$1 +EOF + sshpass -e scp -P "$PORT" -o StrictHostKeyChecking=no -r "$1"/* root@localhost:"$GUEST_TEST_DIR/$1" sshpass -e ssh -p "$PORT" -o StrictHostKeyChecking=no root@localhost << EOF source $GUEST_TEST_DIR/common.sh - cd $GUEST_TEST_DIR/$1 - make || die "Failed to compile source code $1" + cd $GUEST_TEST_DIR + cd $1 + dnf list installed gcc || dnf install -y gcc || \ + { die "Failed to install gcc in guest os"; return 1; } + dnf list installed glibc-static || dnf install -y glibc-static || \ + { die "Failed to install glibc-static in guest os"; return 1; } + make || { die "Failed to compile source code $1"; return 1; } if [ -f $2 ]; then chmod a+x $2 cp $2 $GUEST_TEST_DIR else die "Can't find test binary $2" + return 1 fi EOF +ERR_NUM=$? +if [ $ERR_NUM -eq 0 ]; then test_print_trc "Guest VM test source code and binary prepare complete" + return 0 +else + return 1 +fi } # function based on sshpass to execute $1 test_script.sh and potential $2 script params in Guest VM @@ -73,6 +88,18 @@ EOF test_print_trc "Guest VM closed properly after test" } +guest_attest_test() { + selftest_item=$1 + guest_test_prepare tdx/tdx_attest_check.sh + guest_test_source_code tdx/tdx_attest_test_suite tdx_guest_test || \ + { die "Failed to prepare guest test source code for $selftest_item"; return 1; } + guest_test_entry tdx_attest_check.sh "-t $selftest_item" || \ + { die "Failed on $TESTCASE tdx_attest_check.sh -t $selftest_item"; return 1; } + if [[ $GCOV == "off" ]]; then + guest_test_close + fi +} + ###################### Do Works ###################### cd "$(dirname "$0")" 2>/dev/null || exit 1 source ../.env @@ -95,11 +122,27 @@ case "$TESTCASE" in guest_test_prepare guest_test.sh guest_test_source_code test_source_code_dir_example test_binary_example guest_test_entry guest_test.sh "-t $TESTCASE" || \ - die "Failed on GUEST_TESTCASE_EXAMPLE guest_test.sh -t $TESTCASE" + die "Failed on $TESTCASE guest_test.sh -t $TESTCASE" if [[ $GCOV == "off" ]]; then guest_test_close fi ;; + TD_ATTEST_VERIFY_REPORT) + guest_attest_test "global.verify_report" || \ + die "Failed on $TESTCASE" + ;; + TD_ATTEST_VERITY_REPORTMAC) + guest_attest_test "global.verify_reportmac" || \ + die "Failed on $TESTCASE" + ;; + TD_ATTEST_VERIFY_RTMR_EXTEND) + guest_attest_test "global.verify_rtmr_extend" || \ + die "Failed on $TESTCASE" + ;; + TD_ATTEST_VERIFY_QUOTE) + guest_attest_test "global.verify_quote" || \ + die "Failed on $TESTCASE" + ;; :) test_print_err "Must specify the test scenario option by [-t]" usage && exit 1 diff --git a/guest-test/guest.test_launcher.sh b/guest-test/guest.test_launcher.sh index 4fffe61c..075a6027 100755 --- a/guest-test/guest.test_launcher.sh +++ b/guest-test/guest.test_launcher.sh @@ -75,7 +75,7 @@ EOF } guest_kernel_reboot() { - sshpass -e ssh -p "$PORT" -o StrictHostKeyChecking=no root@localhost << EOF + sshpass -e ssh -p "$PORT" -o StrictHostKeyChecking=no root@localhost << EOF systemctl reboot --reboot-argument=now EOF } @@ -86,11 +86,11 @@ source ../.env ## PART 0: prepare test prerequisites ## if [ ! "$(which sshpass)" ]; then - dnf install -y sshpass > /dev/null - apt install -y sshpass > /dev/null + dnf install -y sshpass > /dev/null + apt install -y sshpass > /dev/null else - test_print_trc "sshpass prerequisites is ready for use" - test_print_trc "VM test is starting now..." + test_print_trc "sshpass prerequisites is ready for use" + test_print_trc "VM test is starting now..." fi ## PART 1: get params from qemu.cfg.json and script args ## @@ -142,7 +142,7 @@ while getopts :v:s:m:d:t:x:c:p:g:h arg; do GCOV=$OPTARG echo GCOV="\"$GCOV\"" >> "$SCRIPT_DIR"/test_params.py ;; - h) + h) usage && exit 0 ;; :) @@ -205,13 +205,13 @@ export GCOV cd "$SCRIPT_DIR" || die "fail to switch to $SCRIPT_DIR" rm -rf /root/.ssh/known_hosts while read -r line; do - echo "[${VM_TYPE}_vm]: $line" + echo "[${VM_TYPE}_vm]: $line" # within $TIMEOUT but bypass the very first 2 seconds to avoid unexpected $BOOT_PATTERN match (from parameter handling logic) - if [[ $SECONDS -lt $TIMEOUT ]] && [[ $SECONDS -ge 2 ]]; then - if [[ $line == $BOOT_PATTERN ]]; then - test_print_trc "VM_TYPE: $VM_TYPE, VCPU: $VCPU, SOCKETS: $SOCKETS, MEM: $MEM, DEBUG: $DEBUG, PMU: $PMU, CMDLINE: $CMDLINE, TESTCASE: $TESTCASE, SECONDS: $SECONDS" + if [[ $SECONDS -lt $TIMEOUT ]] && [[ $SECONDS -ge 2 ]]; then + if [[ $line == $BOOT_PATTERN ]] && [[ $EXEC_FLAG -ne 0 ]]; then + test_print_trc "VM_TYPE: $VM_TYPE, VCPU: $VCPU, SOCKETS: $SOCKETS, MEM: $MEM, DEBUG: $DEBUG, PMU: $PMU, CMDLINE: $CMDLINE, TESTCASE: $TESTCASE, SECONDS: $SECONDS" EXEC_FLAG=0 - if ! ./guest.test_executor.sh; then EXEC_FLAG=1 && break; fi # break while read loop in case of TD VM test failure + if ! ./guest.test_executor.sh; then EXEC_FLAG=1 && break; fi # break while read loop in case of TD VM test failure # err_handlers string matching elif [[ $line == $ERR_STR1 ]]; then test_print_err "There is $ERR_STR1, test is not fully PASS" @@ -228,23 +228,16 @@ while read -r line; do elif [[ $line == $ERR_STR5 ]]; then test_print_wrg "There is $ERR_STR5, please check" ERR_FLAG5=1 - fi + fi # end of err_handlers string matching - elif [[ $SECONDS -ge $TIMEOUT ]]; then # break while read loop in case of TD VM boot timeout (no $BOOT_PATTERN found) - break - fi -done < <(if [ "$GCOV" == "off" ]; then timeout "$TIMEOUT" ./guest.qemu_runner.sh; else ./guest.qemu_runner.sh; fi) + elif [[ $SECONDS -ge $TIMEOUT ]]; then # break while read loop in case of TD VM boot timeout (no $BOOT_PATTERN found) + break + fi +done < <(if [ "$GCOV" == "off" ]; then timeout "$TIMEOUT" ./guest.qemu_runner.sh; \ + else test_print_trc "${VM_TYPE}vm_$PORT keep alive for gcov data collection" && ./guest.qemu_runner.sh; fi) ## PART 3: err_handlers error management # unexpected error/bug/warning/call trace handling -if [ $ERR_FLAG1 -ne 0 ]; then - die "$VM_TYPE VM test failed with $ERR_STR1, please check |ERROR| in test log for more info" -fi - -if [ $ERR_FLAG2 -ne 0 ]; then - die "$VM_TYPE VM test failed with $ERR_STR2, please check |ERROR| in test log for more info" -fi - if [ $ERR_FLAG3 -ne 0 ]; then test_print_wrg "$VM_TYPE VM test hit $ERR_STR3, please check |WARNING| in test log for more info" fi @@ -256,6 +249,15 @@ fi if [ $ERR_FLAG5 -ne 0 ]; then test_print_wrg "$VM_TYPE VM test hit $ERR_STR5, please check |WARNING| in test log for more info" fi + +# handle error/bug in the end to avoid missing above warning/call trace info +if [ $ERR_FLAG1 -ne 0 ]; then + die "$VM_TYPE VM test failed with $ERR_STR1, please check |ERROR| in test log for more info" +fi + +if [ $ERR_FLAG2 -ne 0 ]; then + die "$VM_TYPE VM test failed with $ERR_STR2, please check |ERROR| in test log for more info" +fi # end of err_handlers error management ## PART 4: timeout control in case of tdvm boot up failure/test failure ## @@ -276,27 +278,28 @@ sleep 3 # time count less or qual than 3 is case b # - handling: nothing to do, die for TDVM boot early failure, likely qemu config issue if ! guest_kernel_check; then - if [ "$SECONDS" -gt 3 ] && [ "$SECONDS" -lt "$TIMEOUT" ] && [ "$EXEC_FLAG" -eq 0 ]; then - test_print_trc "$VM_TYPE VM test complete..." - elif [ "$SECONDS" -ge "$TIMEOUT" ] && [ "$GCOV" == "on" ]; then - pkill "${VM_TYPE}vm_$PORT" - die "TEST TIMEOUT!!!!!!!!!!!!" - elif [ "$GCOV" == "off" ] && [ "$EXEC_FLAG" -eq 1 ]; then - die "$VM_TYPE VM test seems fail at beginning, please check test log" - fi + if [ "$SECONDS" -gt 3 ] && [ "$SECONDS" -lt "$TIMEOUT" ] && [ "$EXEC_FLAG" -eq 0 ]; then + test_print_trc "$VM_TYPE VM test complete..." + elif [ "$SECONDS" -ge "$TIMEOUT" ] && [ "$GCOV" == "on" ]; then + pkill "${VM_TYPE}vm_$PORT" + die "TEST TIMEOUT!!!!!!!!!!!!" + elif [ "$GCOV" == "off" ] && [ "$EXEC_FLAG" -eq 1 ]; then + pkill "${VM_TYPE}vm_$PORT" + die "$VM_TYPE VM test seems fail at beginning, please check test log" + fi # guest_kernel_kernel function zero return value shows TDVM is still accessible handling # handling: no matter why it's still accessible, close it by guest_kernel_reboot function elif [ "$GCOV" == "off" ]; then - if ! guest_kernel_reboot; then - test_print_trc "$VM_TYPE VM is still up" - test_print_trc "time: $SECONDS" - test_print_trc "SSHPASS: $SSHPASS" - test_print_trc "PORT: $PORT" - test_print_trc "$VM_TYPE VM closed" + if ! guest_kernel_reboot; then + test_print_trc "$VM_TYPE VM is still up" + test_print_trc "time: $SECONDS" + test_print_trc "SSHPASS: $SSHPASS" + test_print_trc "PORT: $PORT" + test_print_trc "$VM_TYPE VM closed" # must die here since TDVM should be closed and not accessible if test complete all correctly # else it's due to test die before reaching final close point td_test_close function - die "$VM_TYPE VM test fail, please check test log" - fi + die "$VM_TYPE VM test fail, please check test log" + fi else # [ $GCOV == "on" ] || [ guest_kernel_check return 0 ] test_print_trc "${VM_TYPE}vm_$PORT keep alive for gcov data collection" test_print_trc "'ssh -p $PORT root@localhost' with PASSWORD '$SSHPASS' to login and get data" @@ -320,5 +323,6 @@ else # [ $GCOV == "on" ] else test_print_err "$VM_TYPE VM test fail, please check test log" test_print_trc "Please shutdown $VM_TYPE VM after gcov collect or debug completed" + die "$VM_TYPE VM test fail, please check test log" fi fi \ No newline at end of file diff --git a/guest-test/qemu.config.json b/guest-test/qemu.config.json index 49243370..5fd58edf 100644 --- a/guest-test/qemu.config.json +++ b/guest-test/qemu.config.json @@ -1,49 +1,49 @@ { - "common": { - "kernel_img": "/boot/vmlinuz-xxx-yyy", - "initrd_img": "/boot/initramfs-xxx-yyy", - "bios_img": "/path/to/EDKII/OVMF.fd or other virtual BIOS", - "qemu_img": "/path/to/qemu-kvm with proper capabilty of VM test", - "guest_img": "/path/to/prepared/guest_os_image, in qcow2 or raw image format", - "guest_img_format": "raw", - "boot_pattern": "*Kernel*on*x86_64*", - "guest_root_passwd": "123456", - "vm_type": "tdx", - "pmu": "off", - "cpus": "4", - "sockets": "1", - "mem": "16", - "cmdline": "accept_memory=lazy", - "debug": "on" - }, + "common": { + "kernel_img": "/boot/vmlinuz-xxx-yyy", + "initrd_img": "/boot/initramfs-xxx-yyy", + "bios_img": "/path/to/EDKII/OVMF.fd or other virtual BIOS", + "qemu_img": "/path/to/qemu-kvm with proper capabilty of VM test", + "guest_img": "/path/to/prepared/guest_os_image, in qcow2 or raw image format", + "guest_img_format": "raw", + "boot_pattern": "*Kernel*on*an*x86_64*", + "guest_root_passwd": "123456", + "vm_type": "tdx", + "pmu": "off", + "cpus": "4", + "sockets": "1", + "mem": "16", + "cmdline": "accept_memory=lazy", + "debug": "on" + }, - "vm": { - "cfg_1": "-accel kvm -no-reboot -nographic -vga none -device virtio-net-pci,netdev=mynet0,mac=DE:AD:BE:EF:AB:CD,romfile= ", - "cfg_2": "-chardev stdio,id=mux,mux=on,signal=off -device virtio-serial,romfile= -device virtconsole,chardev=mux ", - "cfg_3": "-serial chardev:mux -monitor chardev:mux -monitor pty -no-hpet -nodefaults ", - "cfg_var_1": "-name process=$VM_TYPEVM_$PORT,debug-threads=on ", - "cfg_var_2": "-cpu host,host-phys-bits,pmu=$PMU ", - "cfg_var_3": "-smp cpus=$VCPU,sockets=$SOCKETS ", - "cfg_var_4": "-m $MEMG ", - "cfg_var_5": "-kernel $KERNEL_IMG ", - "cfg_var_6": "-initrd $INITRD_IMG", - "cfg_var_7": "-netdev user,id=mynet0,hostfwd=tcp::$PORT-:22 ", - "cfg_var_8": "-drive file=$GUEST_IMG,if=virtio,format=$IMG_FORMAT ", - "cfg_var_9": "-append \"root=/dev/vda3 ro console=hvc0 earlyprintk=ttyS0 ignore_loglevel debug earlyprintk l1tf=off initcall_debug log_buf_len=200M nokaslr tsc=reliable efi=debug mce=off efi=debug $CMDLINE\" ", - "cfg_var_10": "-bios $BIOS_IMG " - }, + "vm": { + "cfg_1": "-accel kvm -no-reboot -nographic -vga none -device virtio-net-pci,netdev=mynet0,mac=DE:AD:BE:EF:AB:CD,romfile= ", + "cfg_2": "-chardev stdio,id=mux,mux=on,signal=off -device virtio-serial,romfile= -device virtconsole,chardev=mux ", + "cfg_3": "-serial chardev:mux -monitor chardev:mux -monitor pty -no-hpet -nodefaults ", + "cfg_var_1": "-name process=$VM_TYPEVM_$PORT,debug-threads=on ", + "cfg_var_2": "-cpu host,host-phys-bits,pmu=$PMU ", + "cfg_var_3": "-smp cpus=$VCPU,sockets=$SOCKETS ", + "cfg_var_4": "-m $MEMG ", + "cfg_var_5": "-kernel $KERNEL_IMG ", + "cfg_var_6": "-initrd $INITRD_IMG", + "cfg_var_7": "-netdev user,id=mynet0,hostfwd=tcp::$PORT-:22 ", + "cfg_var_8": "-drive file=$GUEST_IMG,if=virtio,format=$IMG_FORMAT ", + "cfg_var_9": "-append \"root=/dev/vda3 ro console=hvc0 earlyprintk=ttyS0 ignore_loglevel debug earlyprintk l1tf=off initcall_debug log_buf_len=200M nokaslr tsc=reliable efi=debug mce=off efi=debug $CMDLINE\" ", + "cfg_var_10": "-bios $BIOS_IMG " + }, - "tdx": { - "cfg_1": "-machine q35,kernel_irqchip=split,confidential-guest-support=tdx,memory-backend=ram1 ", - "cfg_var_1": "-object tdx-guest,id=tdx,debug=$DEBUG,sept-ve-disable=on,quote-generation-service=vsock:2:4050 ", - "cfg_var_2": "-object memory-backend-memfd-private,id=ram1,size=$MEMG " - }, + "tdx": { + "cfg_1": "-machine q35,kernel_irqchip=split,confidential-guest-support=tdx,memory-backend=ram1 ", + "cfg_var_1": "-object tdx-guest,id=tdx,debug=$DEBUG,sept-ve-disable=on,quote-generation-service=vsock:2:4050 ", + "cfg_var_2": "-object memory-backend-memfd-private,id=ram1,size=$MEMG " + }, - "tdxio": { - "cfg_1": "-object iommufd,id=iommufd0 ", - "cfg_2": "-device vfio-pci,host=tee_bdf1,id=hostdev2,addr=0x3,x-secure-mode=on ", - "cfg_3": "-device vfio-pci,host=tee_bdf2,id=hostdev3,addr=0x4,x-secure-mode=on ", - "cfg_4": "-device vfio-pci,host=tee_bdf3,id=hostdev4,addr=0x5,x-secure-mode=on ", - "cfg_5": "-device vfio-pci,host=tee_bdf4,id=hostdev5,addr=0x6,x-secure-mode=on " - } + "tdxio": { + "cfg_1": "-object iommufd,id=iommufd0 ", + "cfg_2": "-device vfio-pci,host=tee_bdf1,id=hostdev2,addr=0x3,x-secure-mode=on ", + "cfg_3": "-device vfio-pci,host=tee_bdf2,id=hostdev3,addr=0x4,x-secure-mode=on ", + "cfg_4": "-device vfio-pci,host=tee_bdf3,id=hostdev4,addr=0x5,x-secure-mode=on ", + "cfg_5": "-device vfio-pci,host=tee_bdf4,id=hostdev5,addr=0x6,x-secure-mode=on " + } } \ No newline at end of file diff --git a/guest-test/qemu_get_config.py b/guest-test/qemu_get_config.py index d9e906d0..d21cc47c 100755 --- a/guest-test/qemu_get_config.py +++ b/guest-test/qemu_get_config.py @@ -25,9 +25,9 @@ # read from qemu.config.json format for all raw qemu vm config cwd = Path(os.getcwd()) if cwd.stem == "guest-test": - raw_config = Path(f"{os.getcwd()}/qemu.config.json").read_text() + raw_config = Path(f"{os.getcwd()}/qemu.config.json").read_text() else: - exit(1) + exit(1) qemu_config = json.loads(raw_config) @@ -54,40 +54,40 @@ # O-list variables default value from qemu.config.json vm_type = qemu_config["common"]["vm_type"] if 'PMU' in dir(): - pmu = PMU + pmu = PMU else: - pmu = qemu_config["common"]["pmu"] + pmu = qemu_config["common"]["pmu"] if 'VCPU' in dir(): - cpus = VCPU + cpus = VCPU else: - cpus = qemu_config["common"]["cpus"] + cpus = qemu_config["common"]["cpus"] if 'SOCKETS' in dir(): - sockets = SOCKETS + sockets = SOCKETS else: - sockets = qemu_config["common"]["sockets"] + sockets = qemu_config["common"]["sockets"] if 'MEM' in dir(): - mem = MEM + mem = MEM else: - mem = qemu_config["common"]["mem"] + mem = qemu_config["common"]["mem"] if 'CMDLINE' in dir(): - cmdline = CMDLINE + cmdline = CMDLINE else: - cmdline = qemu_config["common"]["cmdline"] + cmdline = qemu_config["common"]["cmdline"] if 'DEBUG' in dir(): - debug = DEBUG + debug = DEBUG else: - debug = qemu_config["common"]["debug"] + debug = qemu_config["common"]["debug"] if 'TESTCASE' in dir(): - testcase = TESTCASE + testcase = TESTCASE else: - print("No TESTCASE info found, can't run any test!") - exit(1) + print("No TESTCASE info found, can't run any test!") + exit(1) # O-list variables override value handling with args passed options, not used in framework, keep it for customization params_o_list = argparse.ArgumentParser() @@ -111,21 +111,21 @@ # NOTICE!! O-list veriables' value will be override if passed through above args option if args.vmtype is not None: - vm_type = args.vmtype + vm_type = args.vmtype if args.pmu is not None: - pmu = args.pmu + pmu = args.pmu if args.cpus is not None: - cpus = args.cpus + cpus = args.cpus if args.sockets is not None: - sockets = args.sockets + sockets = args.sockets if args.mem is not None: - mem = args.mem + mem = args.mem if args.cmdline is not None: - cmdline = args.cmdline + cmdline = args.cmdline if args.debug is not None: - debug = args.debug + debug = args.debug if args.testcase is not None: - testcase = args.testcase + testcase = args.testcase # end of O-list variables handling @@ -138,18 +138,18 @@ qemu_config["vm"]["cfg_var_5"] = qemu_config["vm"]["cfg_var_5"].replace("$KERNEL_IMG", kernel_img) # bypass -initrd config option in case it's not provided if os.path.isfile(initrd_img): - qemu_config["vm"]["cfg_var_6"] = qemu_config["vm"]["cfg_var_6"].replace("$INITRD_IMG", initrd_img) + qemu_config["vm"]["cfg_var_6"] = qemu_config["vm"]["cfg_var_6"].replace("$INITRD_IMG", initrd_img) else: - qemu_config["vm"]["cfg_var_6"] = "" + qemu_config["vm"]["cfg_var_6"] = "" qemu_config["vm"]["cfg_var_7"] = qemu_config["vm"]["cfg_var_7"].replace("$PORT", str(port)) qemu_config["vm"]["cfg_var_8"] = qemu_config["vm"]["cfg_var_8"].replace("$GUEST_IMG", guest_img).replace("$IMG_FORMAT", guest_img_format) qemu_config["vm"]["cfg_var_9"] = qemu_config["vm"]["cfg_var_9"].replace("$CMDLINE", cmdline) # bypass -bios config option in case it's not provided, default seabios to use if os.path.isfile(bios_img): - qemu_config["vm"]["cfg_var_10"] = qemu_config["vm"]["cfg_var_10"].replace("$BIOS_IMG", bios_img) + qemu_config["vm"]["cfg_var_10"] = qemu_config["vm"]["cfg_var_10"].replace("$BIOS_IMG", bios_img) else: - qemu_config["vm"]["cfg_var_10"] = "" + qemu_config["vm"]["cfg_var_10"] = "" qemu_config["tdx"]["cfg_var_1"] = qemu_config["tdx"]["cfg_var_1"].replace("$DEBUG", debug) qemu_config["tdx"]["cfg_var_2"] = qemu_config["tdx"]["cfg_var_2"].replace("$MEM", str(mem)) @@ -158,31 +158,31 @@ ###################### Functions ###################### def get_sub_keys(d, key): - """ - Recursively get all 2nd-level keys in a dictionary. - """ - if isinstance(d, dict): - for k, v in d.items(): - if isinstance(v, dict): - if k == key: - for k2 in v.keys(): - yield k2 + """ + Recursively get all 2nd-level keys in a dictionary. + """ + if isinstance(d, dict): + for k, v in d.items(): + if isinstance(v, dict): + if k == key: + for k2 in v.keys(): + yield k2 def print_sub_keys(l, key): - """ - Recursively get each 2nd-level key. - """ - print("Key %s has sub-keys:" %(key)) - for i in l: - print(i) + """ + Recursively get each 2nd-level key. + """ + print("Key %s has sub-keys:" %(key)) + for i in l: + print(i) def get_sub_cfgs(l, key, result=""): - """ - Recursively collect all 2nd-level key cfg string. - """ - for i in l: - result += qemu_config[key][i] - return result + """ + Recursively collect all 2nd-level key cfg string. + """ + for i in l: + result += qemu_config[key][i] + return result ###################### Do Works ###################### #common_keys = list(get_sub_keys(qemu_config, "common")) @@ -192,30 +192,30 @@ def get_sub_cfgs(l, key, result=""): #print_sub_keys(vm_keys, "vm") if vm_type == "legacy": - vm_cfg = get_sub_cfgs(vm_keys, "vm") - print("HERE're all the vm configs to launch legacy vm:") - print("#### qemu config option, part 1 ####") - print(vm_cfg) + vm_cfg = get_sub_cfgs(vm_keys, "vm") + print("HERE're all the vm configs to launch legacy vm:") + print("#### qemu config option, part 1 ####") + print(vm_cfg) #print_sub_keys(tdx_keys, "tdx") if vm_type == "tdx": - vm_cfg = get_sub_cfgs(vm_keys, "vm") - tdx_cfg = get_sub_cfgs(tdx_keys, "tdx") - print("HERE're all the tdx configs to launch tdx vm:") - print("#### qemu config option, part 1 ####") - print(vm_cfg) - print("#### qemu config option, part 2 ####") - print(tdx_cfg) + vm_cfg = get_sub_cfgs(vm_keys, "vm") + tdx_cfg = get_sub_cfgs(tdx_keys, "tdx") + print("HERE're all the tdx configs to launch tdx vm:") + print("#### qemu config option, part 1 ####") + print(vm_cfg) + print("#### qemu config option, part 2 ####") + print(tdx_cfg) #print_sub_keys(tdxio_keys, "tdxio") if vm_type == "tdxio": - vm_cfg = get_sub_cfgs(vm_keys, "vm") - tdx_cfg = get_sub_cfgs(tdx_keys, "tdx") - tdxio_cfg = get_sub_cfgs(tdxio_keys, "tdxio") - print("HERE're all the tdx configs to launch tdxio vm:") - print("#### qemu config option, part 1 ####") - print(vm_cfg) - print("#### qemu config option, part 2 ####") - print(tdx_cfg) - print("#### qemu config option, part 3 ####") - print(tdxio_cfg) \ No newline at end of file + vm_cfg = get_sub_cfgs(vm_keys, "vm") + tdx_cfg = get_sub_cfgs(tdx_keys, "tdx") + tdxio_cfg = get_sub_cfgs(tdxio_keys, "tdxio") + print("HERE're all the tdx configs to launch tdxio vm:") + print("#### qemu config option, part 1 ####") + print(vm_cfg) + print("#### qemu config option, part 2 ####") + print(tdx_cfg) + print("#### qemu config option, part 3 ####") + print(tdxio_cfg) \ No newline at end of file diff --git a/guest-test/qemu_runner.py b/guest-test/qemu_runner.py index 6164352e..cb5ada75 100644 --- a/guest-test/qemu_runner.py +++ b/guest-test/qemu_runner.py @@ -27,15 +27,15 @@ ###################### Do Works ###################### # launch legacy common vm based on vm_type config if vm_type == "legacy": - command = '{} {}'.format(qemu_img, vm_cfg) - sp.run(command, shell=True) + command = '{} {}'.format(qemu_img, vm_cfg) + sp.run(command, shell=True) # launch tdx vm based on vm_type config if vm_type == "tdx": - command = '{} {} {}'.format(qemu_img, vm_cfg, tdx_cfg) - sp.run(command, shell=True) + command = '{} {} {}'.format(qemu_img, vm_cfg, tdx_cfg) + sp.run(command, shell=True) # launch tdxio vm based on vm_type config if vm_type == "tdxio": - command = '{} {} {} {}'.format(qemu_img, vm_cfg, tdx_cfg, tdxio_cfg) - sp.run(command, shell=True) + command = '{} {} {} {}'.format(qemu_img, vm_cfg, tdx_cfg, tdxio_cfg) + sp.run(command, shell=True) \ No newline at end of file diff --git a/guest-test/tdx/tdx_attest_check.sh b/guest-test/tdx/tdx_attest_check.sh new file mode 100755 index 00000000..ccde1059 --- /dev/null +++ b/guest-test/tdx/tdx_attest_check.sh @@ -0,0 +1,84 @@ +#!/usr/bin/bash +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (c) 2023 Intel Corporation + +# Author: Hongyu Ning +# +# History: 16, Oct., 2023 - Hongyu Ning - creation + + +# @desc This script do basic TD attestation check in TDX Guest VM +# test binary is based on kselftest linux/tools/testing/selftests/tdx implementation + +###################### Variables ###################### +SCRIPT_DIR="$( cd "$( dirname "$0" )" && pwd )" +echo "$SCRIPT_DIR" +source common.sh + +while getopts :t: arg; do + case $arg in + t) + ATTEST_CASE=$OPTARG + ;; + *) + test_print_err "Must supply an argument to -$OPTARG." + exit 1 + ;; + esac +done + +###################### Functions ###################### +attest_result() { + selftest_item=$1 + case "$selftest_item" in + global.verify_report) + selftest_num=1 + ;; + global.verify_reportmac) + selftest_num=2 + ;; + global.verify_rtmr_extend) + selftest_num=3 + ;; + global.verify_quote) + selftest_num=4 + ;; + esac + test_print_trc "TD attestation - $selftest_item start." + if [ -f "attest.log" ]; then + rm -rf attest.log + fi + ./tdx_guest_test | tee attest.log + results=$(grep "not ok $selftest_num $selftest_item" attest.log) + if [ -z "$results" ]; then + test_print_trc "TD attestation - $selftest_item PASS." + else + die "TD attestation - $selftest_item FAIL." + return 1 + fi +} + +###################### Do Works ###################### + +case "$ATTEST_CASE" in + global.verify_report) + attest_result "$ATTEST_CASE" + ;; + global.verify_reportmac) + attest_result "$ATTEST_CASE" + ;; + global.verify_rtmr_extend) + attest_result "$ATTEST_CASE" + ;; + global.verify_quote) + attest_result "$ATTEST_CASE" + ;; + :) + test_print_err "Must specify the attest case option by [-t]" + exit 1 + ;; + \?) + test_print_err "Input test case option $ATTEST_CASE is not supported" + exit 1 + ;; +esac \ No newline at end of file diff --git a/guest-test/tdx/tdx_attest_test_suite/.gitignore b/guest-test/tdx/tdx_attest_test_suite/.gitignore new file mode 100644 index 00000000..5db4d15c --- /dev/null +++ b/guest-test/tdx/tdx_attest_test_suite/.gitignore @@ -0,0 +1 @@ +tdx_guest_test diff --git a/guest-test/tdx/tdx_attest_test_suite/Makefile b/guest-test/tdx/tdx_attest_test_suite/Makefile new file mode 100644 index 00000000..32295177 --- /dev/null +++ b/guest-test/tdx/tdx_attest_test_suite/Makefile @@ -0,0 +1,22 @@ +# MakeFile function :: MakeFile for tdx_attest_test_suite + +CC := gcc + +VAR_CFLAGS := $(shell pkg-config --cflags libtracefs 2>/dev/null) +VAR_LDLIBS := $(shell pkg-config --libs libtracefs 2>/dev/null) + +CFLAGS += -static -Wall -Wextra -g -O2 $(VAR_CFLAGS) +LDFLAGS += -lpthread $(VAR_LDLIBS) +INCLUDES = -I include + +#List of source files- Update this on adding a new C file +SOURCES := \ + tdx-attest-test.c \ + +MAKE_TARGETS := tdx_guest_test + +tdx_guest_test: + $(CC) $(CFLAGS) $(LDFLAGS) -o ${MAKE_TARGETS} ${INCLUDES} ${SOURCES} + +clean: + rm -rf ${MAKE_TARGETS} diff --git a/guest-test/tdx/tdx_attest_test_suite/include/linux/kselftest.h b/guest-test/tdx/tdx_attest_test_suite/include/linux/kselftest.h new file mode 100644 index 00000000..33a0dbd2 --- /dev/null +++ b/guest-test/tdx/tdx_attest_test_suite/include/linux/kselftest.h @@ -0,0 +1,319 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * kselftest.h: low-level kselftest framework to include from + * selftest programs. When possible, please use + * kselftest_harness.h instead. + * + * Copyright (c) 2014 Shuah Khan + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * + * Using this API consists of first counting how many tests your code + * has to run, and then starting up the reporting: + * + * ksft_print_header(); + * ksft_set_plan(total_number_of_tests); + * + * For each test, report any progress, debugging, etc with: + * + * ksft_print_msg(fmt, ...); + * + * and finally report the pass/fail/skip/xfail state of the test with one of: + * + * ksft_test_result(condition, fmt, ...); + * ksft_test_result_pass(fmt, ...); + * ksft_test_result_fail(fmt, ...); + * ksft_test_result_skip(fmt, ...); + * ksft_test_result_xfail(fmt, ...); + * ksft_test_result_error(fmt, ...); + * + * When all tests are finished, clean up and exit the program with one of: + * + * ksft_finished(); + * ksft_exit(condition); + * ksft_exit_pass(); + * ksft_exit_fail(); + * + * If the program wants to report details on why the entire program has + * failed, it can instead exit with a message (this is usually done when + * the program is aborting before finishing all tests): + * + * ksft_exit_fail_msg(fmt, ...); + * + */ +#ifndef __KSELFTEST_H +#define __KSELFTEST_H + +#include +#include +#include +#include +#include + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#endif + +/* + * gcc cpuid.h provides __cpuid_count() since v4.4. + * Clang/LLVM cpuid.h provides __cpuid_count() since v3.4.0. + * + * Provide local define for tests needing __cpuid_count() because + * selftests need to work in older environments that do not yet + * have __cpuid_count(). + */ +#ifndef __cpuid_count +#define __cpuid_count(level, count, a, b, c, d) \ + __asm__ __volatile__ ("cpuid\n\t" \ + : "=a" (a), "=b" (b), "=c" (c), "=d" (d) \ + : "0" (level), "2" (count)) +#endif + +/* define kselftest exit codes */ +#define KSFT_PASS 0 +#define KSFT_FAIL 1 +#define KSFT_XFAIL 2 +#define KSFT_XPASS 3 +#define KSFT_SKIP 4 + +/* counters */ +struct ksft_count { + unsigned int ksft_pass; + unsigned int ksft_fail; + unsigned int ksft_xfail; + unsigned int ksft_xpass; + unsigned int ksft_xskip; + unsigned int ksft_error; +}; + +static struct ksft_count ksft_cnt; +static unsigned int ksft_plan; + +static inline unsigned int ksft_test_num(void) +{ + return ksft_cnt.ksft_pass + ksft_cnt.ksft_fail + + ksft_cnt.ksft_xfail + ksft_cnt.ksft_xpass + + ksft_cnt.ksft_xskip + ksft_cnt.ksft_error; +} + +static inline void ksft_inc_pass_cnt(void) { ksft_cnt.ksft_pass++; } +static inline void ksft_inc_fail_cnt(void) { ksft_cnt.ksft_fail++; } +static inline void ksft_inc_xfail_cnt(void) { ksft_cnt.ksft_xfail++; } +static inline void ksft_inc_xpass_cnt(void) { ksft_cnt.ksft_xpass++; } +static inline void ksft_inc_xskip_cnt(void) { ksft_cnt.ksft_xskip++; } +static inline void ksft_inc_error_cnt(void) { ksft_cnt.ksft_error++; } + +static inline int ksft_get_pass_cnt(void) { return ksft_cnt.ksft_pass; } +static inline int ksft_get_fail_cnt(void) { return ksft_cnt.ksft_fail; } +static inline int ksft_get_xfail_cnt(void) { return ksft_cnt.ksft_xfail; } +static inline int ksft_get_xpass_cnt(void) { return ksft_cnt.ksft_xpass; } +static inline int ksft_get_xskip_cnt(void) { return ksft_cnt.ksft_xskip; } +static inline int ksft_get_error_cnt(void) { return ksft_cnt.ksft_error; } + +static inline void ksft_print_header(void) +{ + if (!(getenv("KSFT_TAP_LEVEL"))) + printf("TAP version 13\n"); +} + +static inline void ksft_set_plan(unsigned int plan) +{ + ksft_plan = plan; + printf("1..%d\n", ksft_plan); +} + +static inline void ksft_print_cnts(void) +{ + if (ksft_plan != ksft_test_num()) + printf("# Planned tests != run tests (%u != %u)\n", + ksft_plan, ksft_test_num()); + printf("# Totals: pass:%d fail:%d xfail:%d xpass:%d skip:%d error:%d\n", + ksft_cnt.ksft_pass, ksft_cnt.ksft_fail, + ksft_cnt.ksft_xfail, ksft_cnt.ksft_xpass, + ksft_cnt.ksft_xskip, ksft_cnt.ksft_error); +} + +static inline void ksft_print_msg(const char *msg, ...) +{ + int saved_errno = errno; + va_list args; + + va_start(args, msg); + printf("# "); + errno = saved_errno; + vprintf(msg, args); + va_end(args); +} + +static inline void ksft_test_result_pass(const char *msg, ...) +{ + int saved_errno = errno; + va_list args; + + ksft_cnt.ksft_pass++; + + va_start(args, msg); + printf("ok %d ", ksft_test_num()); + errno = saved_errno; + vprintf(msg, args); + va_end(args); +} + +static inline void ksft_test_result_fail(const char *msg, ...) +{ + int saved_errno = errno; + va_list args; + + ksft_cnt.ksft_fail++; + + va_start(args, msg); + printf("not ok %d ", ksft_test_num()); + errno = saved_errno; + vprintf(msg, args); + va_end(args); +} + +/** + * ksft_test_result() - Report test success based on truth of condition + * + * @condition: if true, report test success, otherwise failure. + */ +#define ksft_test_result(condition, fmt, ...) do { \ + if (!!(condition)) \ + ksft_test_result_pass(fmt, ##__VA_ARGS__);\ + else \ + ksft_test_result_fail(fmt, ##__VA_ARGS__);\ + } while (0) + +static inline void ksft_test_result_xfail(const char *msg, ...) +{ + int saved_errno = errno; + va_list args; + + ksft_cnt.ksft_xfail++; + + va_start(args, msg); + printf("ok %d # XFAIL ", ksft_test_num()); + errno = saved_errno; + vprintf(msg, args); + va_end(args); +} + +static inline void ksft_test_result_skip(const char *msg, ...) +{ + int saved_errno = errno; + va_list args; + + ksft_cnt.ksft_xskip++; + + va_start(args, msg); + printf("ok %d # SKIP ", ksft_test_num()); + errno = saved_errno; + vprintf(msg, args); + va_end(args); +} + +/* TODO: how does "error" differ from "fail" or "skip"? */ +static inline void ksft_test_result_error(const char *msg, ...) +{ + int saved_errno = errno; + va_list args; + + ksft_cnt.ksft_error++; + + va_start(args, msg); + printf("not ok %d # error ", ksft_test_num()); + errno = saved_errno; + vprintf(msg, args); + va_end(args); +} + +static inline int ksft_exit_pass(void) +{ + ksft_print_cnts(); + exit(KSFT_PASS); +} + +static inline int ksft_exit_fail(void) +{ + ksft_print_cnts(); + exit(KSFT_FAIL); +} + +/** + * ksft_exit() - Exit selftest based on truth of condition + * + * @condition: if true, exit self test with success, otherwise fail. + */ +#define ksft_exit(condition) do { \ + if (!!(condition)) \ + ksft_exit_pass(); \ + else \ + ksft_exit_fail(); \ + } while (0) + +/** + * ksft_finished() - Exit selftest with success if all tests passed + */ +#define ksft_finished() \ + ksft_exit(ksft_plan == \ + ksft_cnt.ksft_pass + \ + ksft_cnt.ksft_xfail + \ + ksft_cnt.ksft_xskip) + +static inline int ksft_exit_fail_msg(const char *msg, ...) +{ + int saved_errno = errno; + va_list args; + + va_start(args, msg); + printf("Bail out! "); + errno = saved_errno; + vprintf(msg, args); + va_end(args); + + ksft_print_cnts(); + exit(KSFT_FAIL); +} + +static inline int ksft_exit_xfail(void) +{ + ksft_print_cnts(); + exit(KSFT_XFAIL); +} + +static inline int ksft_exit_xpass(void) +{ + ksft_print_cnts(); + exit(KSFT_XPASS); +} + +static inline int ksft_exit_skip(const char *msg, ...) +{ + int saved_errno = errno; + va_list args; + + va_start(args, msg); + + /* + * FIXME: several tests misuse ksft_exit_skip so produce + * something sensible if some tests have already been run + * or a plan has been printed. Those tests should use + * ksft_test_result_skip or ksft_exit_fail_msg instead. + */ + if (ksft_plan || ksft_test_num()) { + ksft_cnt.ksft_xskip++; + printf("ok %d # SKIP ", 1 + ksft_test_num()); + } else { + printf("1..0 # SKIP "); + } + if (msg) { + errno = saved_errno; + vprintf(msg, args); + va_end(args); + } + if (ksft_test_num()) + ksft_print_cnts(); + exit(KSFT_SKIP); +} + +#endif /* __KSELFTEST_H */ diff --git a/guest-test/tdx/tdx_attest_test_suite/include/linux/kselftest_harness.h b/guest-test/tdx/tdx_attest_test_suite/include/linux/kselftest_harness.h new file mode 100644 index 00000000..25f4d540 --- /dev/null +++ b/guest-test/tdx/tdx_attest_test_suite/include/linux/kselftest_harness.h @@ -0,0 +1,1093 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * + * kselftest_harness.h: simple C unit test helper. + * + * See documentation in Documentation/dev-tools/kselftest.rst + * + * API inspired by code.google.com/p/googletest + */ + +/** + * DOC: example + * + * .. code-block:: c + * + * #include "../kselftest_harness.h" + * + * TEST(standalone_test) { + * do_some_stuff; + * EXPECT_GT(10, stuff) { + * stuff_state_t state; + * enumerate_stuff_state(&state); + * TH_LOG("expectation failed with state: %s", state.msg); + * } + * more_stuff; + * ASSERT_NE(some_stuff, NULL) TH_LOG("how did it happen?!"); + * last_stuff; + * EXPECT_EQ(0, last_stuff); + * } + * + * FIXTURE(my_fixture) { + * mytype_t *data; + * int awesomeness_level; + * }; + * FIXTURE_SETUP(my_fixture) { + * self->data = mytype_new(); + * ASSERT_NE(NULL, self->data); + * } + * FIXTURE_TEARDOWN(my_fixture) { + * mytype_free(self->data); + * } + * TEST_F(my_fixture, data_is_good) { + * EXPECT_EQ(1, is_my_data_good(self->data)); + * } + * + * TEST_HARNESS_MAIN + */ + +#ifndef __KSELFTEST_HARNESS_H +#define __KSELFTEST_HARNESS_H + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kselftest.h" + +#define TEST_TIMEOUT_DEFAULT 30 + +/* Utilities exposed to the test definitions */ +#ifndef TH_LOG_STREAM +# define TH_LOG_STREAM stderr +#endif + +#ifndef TH_LOG_ENABLED +# define TH_LOG_ENABLED 1 +#endif + +/** + * TH_LOG() + * + * @fmt: format string + * @...: optional arguments + * + * .. code-block:: c + * + * TH_LOG(format, ...) + * + * Optional debug logging function available for use in tests. + * Logging may be enabled or disabled by defining TH_LOG_ENABLED. + * E.g., #define TH_LOG_ENABLED 1 + * + * If no definition is provided, logging is enabled by default. + * + * If there is no way to print an error message for the process running the + * test (e.g. not allowed to write to stderr), it is still possible to get the + * ASSERT_* number for which the test failed. This behavior can be enabled by + * writing `_metadata->no_print = true;` before the check sequence that is + * unable to print. When an error occur, instead of printing an error message + * and calling `abort(3)`, the test process call `_exit(2)` with the assert + * number as argument, which is then printed by the parent process. + */ +#define TH_LOG(fmt, ...) do { \ + if (TH_LOG_ENABLED) \ + __TH_LOG(fmt, ##__VA_ARGS__); \ +} while (0) + +/* Unconditional logger for internal use. */ +#define __TH_LOG(fmt, ...) \ + fprintf(TH_LOG_STREAM, "# %s:%d:%s:" fmt "\n", \ + __FILE__, __LINE__, _metadata->name, ##__VA_ARGS__) + +/** + * SKIP() + * + * @statement: statement to run after reporting SKIP + * @fmt: format string + * @...: optional arguments + * + * .. code-block:: c + * + * SKIP(statement, fmt, ...); + * + * This forces a "pass" after reporting why something is being skipped + * and runs "statement", which is usually "return" or "goto skip". + */ +#define SKIP(statement, fmt, ...) do { \ + snprintf(_metadata->results->reason, \ + sizeof(_metadata->results->reason), fmt, ##__VA_ARGS__); \ + if (TH_LOG_ENABLED) { \ + fprintf(TH_LOG_STREAM, "# SKIP %s\n", \ + _metadata->results->reason); \ + } \ + _metadata->passed = 1; \ + _metadata->skip = 1; \ + _metadata->trigger = 0; \ + statement; \ +} while (0) + +/** + * TEST() - Defines the test function and creates the registration + * stub + * + * @test_name: test name + * + * .. code-block:: c + * + * TEST(name) { implementation } + * + * Defines a test by name. + * Names must be unique and tests must not be run in parallel. The + * implementation containing block is a function and scoping should be treated + * as such. Returning early may be performed with a bare "return;" statement. + * + * EXPECT_* and ASSERT_* are valid in a TEST() { } context. + */ +#define TEST(test_name) __TEST_IMPL(test_name, -1) + +/** + * TEST_SIGNAL() + * + * @test_name: test name + * @signal: signal number + * + * .. code-block:: c + * + * TEST_SIGNAL(name, signal) { implementation } + * + * Defines a test by name and the expected term signal. + * Names must be unique and tests must not be run in parallel. The + * implementation containing block is a function and scoping should be treated + * as such. Returning early may be performed with a bare "return;" statement. + * + * EXPECT_* and ASSERT_* are valid in a TEST() { } context. + */ +#define TEST_SIGNAL(test_name, signal) __TEST_IMPL(test_name, signal) + +#define __TEST_IMPL(test_name, _signal) \ + static void test_name(struct __test_metadata *_metadata); \ + static inline void wrapper_##test_name( \ + struct __test_metadata *_metadata, \ + struct __fixture_variant_metadata *variant) \ + { \ + _metadata->setup_completed = true; \ + if (setjmp(_metadata->env) == 0) \ + test_name(_metadata); \ + __test_check_assert(_metadata); \ + } \ + static struct __test_metadata _##test_name##_object = \ + { .name = #test_name, \ + .fn = &wrapper_##test_name, \ + .fixture = &_fixture_global, \ + .termsig = _signal, \ + .timeout = TEST_TIMEOUT_DEFAULT, }; \ + static void __attribute__((constructor)) _register_##test_name(void) \ + { \ + __register_test(&_##test_name##_object); \ + } \ + static void test_name( \ + struct __test_metadata __attribute__((unused)) *_metadata) + +/** + * FIXTURE_DATA() - Wraps the struct name so we have one less + * argument to pass around + * + * @datatype_name: datatype name + * + * .. code-block:: c + * + * FIXTURE_DATA(datatype_name) + * + * Almost always, you want just FIXTURE() instead (see below). + * This call may be used when the type of the fixture data + * is needed. In general, this should not be needed unless + * the *self* is being passed to a helper directly. + */ +#define FIXTURE_DATA(datatype_name) struct _test_data_##datatype_name + +/** + * FIXTURE() - Called once per fixture to setup the data and + * register + * + * @fixture_name: fixture name + * + * .. code-block:: c + * + * FIXTURE(fixture_name) { + * type property1; + * ... + * }; + * + * Defines the data provided to TEST_F()-defined tests as *self*. It should be + * populated and cleaned up using FIXTURE_SETUP() and FIXTURE_TEARDOWN(). + */ +#define FIXTURE(fixture_name) \ + FIXTURE_VARIANT(fixture_name); \ + static struct __fixture_metadata _##fixture_name##_fixture_object = \ + { .name = #fixture_name, }; \ + static void __attribute__((constructor)) \ + _register_##fixture_name##_data(void) \ + { \ + __register_fixture(&_##fixture_name##_fixture_object); \ + } \ + FIXTURE_DATA(fixture_name) + +/** + * FIXTURE_SETUP() - Prepares the setup function for the fixture. + * *_metadata* is included so that EXPECT_* and ASSERT_* work correctly. + * + * @fixture_name: fixture name + * + * .. code-block:: c + * + * FIXTURE_SETUP(fixture_name) { implementation } + * + * Populates the required "setup" function for a fixture. An instance of the + * datatype defined with FIXTURE_DATA() will be exposed as *self* for the + * implementation. + * + * ASSERT_* are valid for use in this context and will prempt the execution + * of any dependent fixture tests. + * + * A bare "return;" statement may be used to return early. + */ +#define FIXTURE_SETUP(fixture_name) \ + void fixture_name##_setup( \ + struct __test_metadata __attribute__((unused)) *_metadata, \ + FIXTURE_DATA(fixture_name) __attribute__((unused)) *self, \ + const FIXTURE_VARIANT(fixture_name) \ + __attribute__((unused)) *variant) + +/** + * FIXTURE_TEARDOWN() + * *_metadata* is included so that EXPECT_* and ASSERT_* work correctly. + * + * @fixture_name: fixture name + * + * .. code-block:: c + * + * FIXTURE_TEARDOWN(fixture_name) { implementation } + * + * Populates the required "teardown" function for a fixture. An instance of the + * datatype defined with FIXTURE_DATA() will be exposed as *self* for the + * implementation to clean up. + * + * A bare "return;" statement may be used to return early. + */ +#define FIXTURE_TEARDOWN(fixture_name) \ + void fixture_name##_teardown( \ + struct __test_metadata __attribute__((unused)) *_metadata, \ + FIXTURE_DATA(fixture_name) __attribute__((unused)) *self, \ + const FIXTURE_VARIANT(fixture_name) \ + __attribute__((unused)) *variant) + +/** + * FIXTURE_VARIANT() - Optionally called once per fixture + * to declare fixture variant + * + * @fixture_name: fixture name + * + * .. code-block:: c + * + * FIXTURE_VARIANT(fixture_name) { + * type property1; + * ... + * }; + * + * Defines type of constant parameters provided to FIXTURE_SETUP(), TEST_F() and + * FIXTURE_TEARDOWN as *variant*. Variants allow the same tests to be run with + * different arguments. + */ +#define FIXTURE_VARIANT(fixture_name) struct _fixture_variant_##fixture_name + +/** + * FIXTURE_VARIANT_ADD() - Called once per fixture + * variant to setup and register the data + * + * @fixture_name: fixture name + * @variant_name: name of the parameter set + * + * .. code-block:: c + * + * FIXTURE_VARIANT_ADD(fixture_name, variant_name) { + * .property1 = val1, + * ... + * }; + * + * Defines a variant of the test fixture, provided to FIXTURE_SETUP() and + * TEST_F() as *variant*. Tests of each fixture will be run once for each + * variant. + */ +#define FIXTURE_VARIANT_ADD(fixture_name, variant_name) \ + extern FIXTURE_VARIANT(fixture_name) \ + _##fixture_name##_##variant_name##_variant; \ + static struct __fixture_variant_metadata \ + _##fixture_name##_##variant_name##_object = \ + { .name = #variant_name, \ + .data = &_##fixture_name##_##variant_name##_variant}; \ + static void __attribute__((constructor)) \ + _register_##fixture_name##_##variant_name(void) \ + { \ + __register_fixture_variant(&_##fixture_name##_fixture_object, \ + &_##fixture_name##_##variant_name##_object); \ + } \ + FIXTURE_VARIANT(fixture_name) \ + _##fixture_name##_##variant_name##_variant = + +/** + * TEST_F() - Emits test registration and helpers for + * fixture-based test cases + * + * @fixture_name: fixture name + * @test_name: test name + * + * .. code-block:: c + * + * TEST_F(fixture, name) { implementation } + * + * Defines a test that depends on a fixture (e.g., is part of a test case). + * Very similar to TEST() except that *self* is the setup instance of fixture's + * datatype exposed for use by the implementation. + */ +#define TEST_F(fixture_name, test_name) \ + __TEST_F_IMPL(fixture_name, test_name, -1, TEST_TIMEOUT_DEFAULT) + +#define TEST_F_SIGNAL(fixture_name, test_name, signal) \ + __TEST_F_IMPL(fixture_name, test_name, signal, TEST_TIMEOUT_DEFAULT) + +#define TEST_F_TIMEOUT(fixture_name, test_name, timeout) \ + __TEST_F_IMPL(fixture_name, test_name, -1, timeout) + +#define __TEST_F_IMPL(fixture_name, test_name, signal, tmout) \ + static void fixture_name##_##test_name( \ + struct __test_metadata *_metadata, \ + FIXTURE_DATA(fixture_name) *self, \ + const FIXTURE_VARIANT(fixture_name) *variant); \ + static inline void wrapper_##fixture_name##_##test_name( \ + struct __test_metadata *_metadata, \ + struct __fixture_variant_metadata *variant) \ + { \ + /* fixture data is alloced, setup, and torn down per call. */ \ + FIXTURE_DATA(fixture_name) self; \ + memset(&self, 0, sizeof(FIXTURE_DATA(fixture_name))); \ + if (setjmp(_metadata->env) == 0) { \ + fixture_name##_setup(_metadata, &self, variant->data); \ + /* Let setup failure terminate early. */ \ + if (!_metadata->passed) \ + return; \ + _metadata->setup_completed = true; \ + fixture_name##_##test_name(_metadata, &self, variant->data); \ + } \ + if (_metadata->setup_completed) \ + fixture_name##_teardown(_metadata, &self, variant->data); \ + __test_check_assert(_metadata); \ + } \ + static struct __test_metadata \ + _##fixture_name##_##test_name##_object = { \ + .name = #test_name, \ + .fn = &wrapper_##fixture_name##_##test_name, \ + .fixture = &_##fixture_name##_fixture_object, \ + .termsig = signal, \ + .timeout = tmout, \ + }; \ + static void __attribute__((constructor)) \ + _register_##fixture_name##_##test_name(void) \ + { \ + __register_test(&_##fixture_name##_##test_name##_object); \ + } \ + static void fixture_name##_##test_name( \ + struct __test_metadata __attribute__((unused)) *_metadata, \ + FIXTURE_DATA(fixture_name) __attribute__((unused)) *self, \ + const FIXTURE_VARIANT(fixture_name) \ + __attribute__((unused)) *variant) + +/** + * TEST_HARNESS_MAIN - Simple wrapper to run the test harness + * + * .. code-block:: c + * + * TEST_HARNESS_MAIN + * + * Use once to append a main() to the test file. + */ +#define TEST_HARNESS_MAIN \ + static void __attribute__((constructor)) \ + __constructor_order_last(void) \ + { \ + if (!__constructor_order) \ + __constructor_order = _CONSTRUCTOR_ORDER_BACKWARD; \ + } \ + int main(int argc, char **argv) { \ + return test_harness_run(argc, argv); \ + } + +/** + * DOC: operators + * + * Operators for use in TEST() and TEST_F(). + * ASSERT_* calls will stop test execution immediately. + * EXPECT_* calls will emit a failure warning, note it, and continue. + */ + +/** + * ASSERT_EQ() + * + * @expected: expected value + * @seen: measured value + * + * ASSERT_EQ(expected, measured): expected == measured + */ +#define ASSERT_EQ(expected, seen) \ + __EXPECT(expected, #expected, seen, #seen, ==, 1) + +/** + * ASSERT_NE() + * + * @expected: expected value + * @seen: measured value + * + * ASSERT_NE(expected, measured): expected != measured + */ +#define ASSERT_NE(expected, seen) \ + __EXPECT(expected, #expected, seen, #seen, !=, 1) + +/** + * ASSERT_LT() + * + * @expected: expected value + * @seen: measured value + * + * ASSERT_LT(expected, measured): expected < measured + */ +#define ASSERT_LT(expected, seen) \ + __EXPECT(expected, #expected, seen, #seen, <, 1) + +/** + * ASSERT_LE() + * + * @expected: expected value + * @seen: measured value + * + * ASSERT_LE(expected, measured): expected <= measured + */ +#define ASSERT_LE(expected, seen) \ + __EXPECT(expected, #expected, seen, #seen, <=, 1) + +/** + * ASSERT_GT() + * + * @expected: expected value + * @seen: measured value + * + * ASSERT_GT(expected, measured): expected > measured + */ +#define ASSERT_GT(expected, seen) \ + __EXPECT(expected, #expected, seen, #seen, >, 1) + +/** + * ASSERT_GE() + * + * @expected: expected value + * @seen: measured value + * + * ASSERT_GE(expected, measured): expected >= measured + */ +#define ASSERT_GE(expected, seen) \ + __EXPECT(expected, #expected, seen, #seen, >=, 1) + +/** + * ASSERT_NULL() + * + * @seen: measured value + * + * ASSERT_NULL(measured): NULL == measured + */ +#define ASSERT_NULL(seen) \ + __EXPECT(NULL, "NULL", seen, #seen, ==, 1) + +/** + * ASSERT_TRUE() + * + * @seen: measured value + * + * ASSERT_TRUE(measured): measured != 0 + */ +#define ASSERT_TRUE(seen) \ + __EXPECT(0, "0", seen, #seen, !=, 1) + +/** + * ASSERT_FALSE() + * + * @seen: measured value + * + * ASSERT_FALSE(measured): measured == 0 + */ +#define ASSERT_FALSE(seen) \ + __EXPECT(0, "0", seen, #seen, ==, 1) + +/** + * ASSERT_STREQ() + * + * @expected: expected value + * @seen: measured value + * + * ASSERT_STREQ(expected, measured): !strcmp(expected, measured) + */ +#define ASSERT_STREQ(expected, seen) \ + __EXPECT_STR(expected, seen, ==, 1) + +/** + * ASSERT_STRNE() + * + * @expected: expected value + * @seen: measured value + * + * ASSERT_STRNE(expected, measured): strcmp(expected, measured) + */ +#define ASSERT_STRNE(expected, seen) \ + __EXPECT_STR(expected, seen, !=, 1) + +/** + * EXPECT_EQ() + * + * @expected: expected value + * @seen: measured value + * + * EXPECT_EQ(expected, measured): expected == measured + */ +#define EXPECT_EQ(expected, seen) \ + __EXPECT(expected, #expected, seen, #seen, ==, 0) + +/** + * EXPECT_NE() + * + * @expected: expected value + * @seen: measured value + * + * EXPECT_NE(expected, measured): expected != measured + */ +#define EXPECT_NE(expected, seen) \ + __EXPECT(expected, #expected, seen, #seen, !=, 0) + +/** + * EXPECT_LT() + * + * @expected: expected value + * @seen: measured value + * + * EXPECT_LT(expected, measured): expected < measured + */ +#define EXPECT_LT(expected, seen) \ + __EXPECT(expected, #expected, seen, #seen, <, 0) + +/** + * EXPECT_LE() + * + * @expected: expected value + * @seen: measured value + * + * EXPECT_LE(expected, measured): expected <= measured + */ +#define EXPECT_LE(expected, seen) \ + __EXPECT(expected, #expected, seen, #seen, <=, 0) + +/** + * EXPECT_GT() + * + * @expected: expected value + * @seen: measured value + * + * EXPECT_GT(expected, measured): expected > measured + */ +#define EXPECT_GT(expected, seen) \ + __EXPECT(expected, #expected, seen, #seen, >, 0) + +/** + * EXPECT_GE() + * + * @expected: expected value + * @seen: measured value + * + * EXPECT_GE(expected, measured): expected >= measured + */ +#define EXPECT_GE(expected, seen) \ + __EXPECT(expected, #expected, seen, #seen, >=, 0) + +/** + * EXPECT_NULL() + * + * @seen: measured value + * + * EXPECT_NULL(measured): NULL == measured + */ +#define EXPECT_NULL(seen) \ + __EXPECT(NULL, "NULL", seen, #seen, ==, 0) + +/** + * EXPECT_TRUE() + * + * @seen: measured value + * + * EXPECT_TRUE(measured): 0 != measured + */ +#define EXPECT_TRUE(seen) \ + __EXPECT(0, "0", seen, #seen, !=, 0) + +/** + * EXPECT_FALSE() + * + * @seen: measured value + * + * EXPECT_FALSE(measured): 0 == measured + */ +#define EXPECT_FALSE(seen) \ + __EXPECT(0, "0", seen, #seen, ==, 0) + +/** + * EXPECT_STREQ() + * + * @expected: expected value + * @seen: measured value + * + * EXPECT_STREQ(expected, measured): !strcmp(expected, measured) + */ +#define EXPECT_STREQ(expected, seen) \ + __EXPECT_STR(expected, seen, ==, 0) + +/** + * EXPECT_STRNE() + * + * @expected: expected value + * @seen: measured value + * + * EXPECT_STRNE(expected, measured): strcmp(expected, measured) + */ +#define EXPECT_STRNE(expected, seen) \ + __EXPECT_STR(expected, seen, !=, 0) + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) +#endif + +/* Support an optional handler after and ASSERT_* or EXPECT_*. The approach is + * not thread-safe, but it should be fine in most sane test scenarios. + * + * Using __bail(), which optionally abort()s, is the easiest way to early + * return while still providing an optional block to the API consumer. + */ +#define OPTIONAL_HANDLER(_assert) \ + for (; _metadata->trigger; _metadata->trigger = \ + __bail(_assert, _metadata)) + +#define __INC_STEP(_metadata) \ + /* Keep "step" below 255 (which is used for "SKIP" reporting). */ \ + if (_metadata->passed && _metadata->step < 253) \ + _metadata->step++; + +#define is_signed_type(var) (!!(((__typeof__(var))(-1)) < (__typeof__(var))1)) + +#define __EXPECT(_expected, _expected_str, _seen, _seen_str, _t, _assert) do { \ + /* Avoid multiple evaluation of the cases */ \ + __typeof__(_expected) __exp = (_expected); \ + __typeof__(_seen) __seen = (_seen); \ + if (_assert) __INC_STEP(_metadata); \ + if (!(__exp _t __seen)) { \ + /* Report with actual signedness to avoid weird output. */ \ + switch (is_signed_type(__exp) * 2 + is_signed_type(__seen)) { \ + case 0: { \ + unsigned long long __exp_print = (uintptr_t)__exp; \ + unsigned long long __seen_print = (uintptr_t)__seen; \ + __TH_LOG("Expected %s (%llu) %s %s (%llu)", \ + _expected_str, __exp_print, #_t, \ + _seen_str, __seen_print); \ + break; \ + } \ + case 1: { \ + unsigned long long __exp_print = (uintptr_t)__exp; \ + long long __seen_print = (intptr_t)__seen; \ + __TH_LOG("Expected %s (%llu) %s %s (%lld)", \ + _expected_str, __exp_print, #_t, \ + _seen_str, __seen_print); \ + break; \ + } \ + case 2: { \ + long long __exp_print = (intptr_t)__exp; \ + unsigned long long __seen_print = (uintptr_t)__seen; \ + __TH_LOG("Expected %s (%lld) %s %s (%llu)", \ + _expected_str, __exp_print, #_t, \ + _seen_str, __seen_print); \ + break; \ + } \ + case 3: { \ + long long __exp_print = (intptr_t)__exp; \ + long long __seen_print = (intptr_t)__seen; \ + __TH_LOG("Expected %s (%lld) %s %s (%lld)", \ + _expected_str, __exp_print, #_t, \ + _seen_str, __seen_print); \ + break; \ + } \ + } \ + _metadata->passed = 0; \ + /* Ensure the optional handler is triggered */ \ + _metadata->trigger = 1; \ + } \ +} while (0); OPTIONAL_HANDLER(_assert) + +#define __EXPECT_STR(_expected, _seen, _t, _assert) do { \ + const char *__exp = (_expected); \ + const char *__seen = (_seen); \ + if (_assert) __INC_STEP(_metadata); \ + if (!(strcmp(__exp, __seen) _t 0)) { \ + __TH_LOG("Expected '%s' %s '%s'.", __exp, #_t, __seen); \ + _metadata->passed = 0; \ + _metadata->trigger = 1; \ + } \ +} while (0); OPTIONAL_HANDLER(_assert) + +/* List helpers */ +#define __LIST_APPEND(head, item) \ +{ \ + /* Circular linked list where only prev is circular. */ \ + if (head == NULL) { \ + head = item; \ + item->next = NULL; \ + item->prev = item; \ + return; \ + } \ + if (__constructor_order == _CONSTRUCTOR_ORDER_FORWARD) { \ + item->next = NULL; \ + item->prev = head->prev; \ + item->prev->next = item; \ + head->prev = item; \ + } else { \ + item->next = head; \ + item->next->prev = item; \ + item->prev = item; \ + head = item; \ + } \ +} + +struct __test_results { + char reason[1024]; /* Reason for test result */ +}; + +struct __test_metadata; +struct __fixture_variant_metadata; + +/* Contains all the information about a fixture. */ +struct __fixture_metadata { + const char *name; + struct __test_metadata *tests; + struct __fixture_variant_metadata *variant; + struct __fixture_metadata *prev, *next; +} _fixture_global __attribute__((unused)) = { + .name = "global", + .prev = &_fixture_global, +}; + +static struct __fixture_metadata *__fixture_list = &_fixture_global; +static int __constructor_order; + +#define _CONSTRUCTOR_ORDER_FORWARD 1 +#define _CONSTRUCTOR_ORDER_BACKWARD -1 + +static inline void __register_fixture(struct __fixture_metadata *f) +{ + __LIST_APPEND(__fixture_list, f); +} + +struct __fixture_variant_metadata { + const char *name; + const void *data; + struct __fixture_variant_metadata *prev, *next; +}; + +static inline void +__register_fixture_variant(struct __fixture_metadata *f, + struct __fixture_variant_metadata *variant) +{ + __LIST_APPEND(f->variant, variant); +} + +/* Contains all the information for test execution and status checking. */ +struct __test_metadata { + const char *name; + void (*fn)(struct __test_metadata *, + struct __fixture_variant_metadata *); + pid_t pid; /* pid of test when being run */ + struct __fixture_metadata *fixture; + int termsig; + int passed; + int skip; /* did SKIP get used? */ + int trigger; /* extra handler after the evaluation */ + int timeout; /* seconds to wait for test timeout */ + bool timed_out; /* did this test timeout instead of exiting? */ + __u8 step; + bool no_print; /* manual trigger when TH_LOG_STREAM is not available */ + bool aborted; /* stopped test due to failed ASSERT */ + bool setup_completed; /* did setup finish? */ + jmp_buf env; /* for exiting out of test early */ + struct __test_results *results; + struct __test_metadata *prev, *next; +}; + +/* + * Since constructors are called in reverse order, reverse the test + * list so tests are run in source declaration order. + * https://gcc.gnu.org/onlinedocs/gccint/Initialization.html + * However, it seems not all toolchains do this correctly, so use + * __constructor_order to detect which direction is called first + * and adjust list building logic to get things running in the right + * direction. + */ +static inline void __register_test(struct __test_metadata *t) +{ + __LIST_APPEND(t->fixture->tests, t); +} + +static inline int __bail(int for_realz, struct __test_metadata *t) +{ + /* if this is ASSERT, return immediately. */ + if (for_realz) { + t->aborted = true; + longjmp(t->env, 1); + } + /* otherwise, end the for loop and continue. */ + return 0; +} + +static inline void __test_check_assert(struct __test_metadata *t) +{ + if (t->aborted) { + if (t->no_print) + _exit(t->step); + abort(); + } +} + +struct __test_metadata *__active_test; +static void __timeout_handler(int sig, siginfo_t *info, void *ucontext) +{ + struct __test_metadata *t = __active_test; + + /* Sanity check handler execution environment. */ + if (!t) { + fprintf(TH_LOG_STREAM, + "# no active test in SIGALRM handler!?\n"); + abort(); + } + if (sig != SIGALRM || sig != info->si_signo) { + fprintf(TH_LOG_STREAM, + "# %s: SIGALRM handler caught signal %d!?\n", + t->name, sig != SIGALRM ? sig : info->si_signo); + abort(); + } + + t->timed_out = true; + // signal process group + kill(-(t->pid), SIGKILL); +} + +void __wait_for_test(struct __test_metadata *t) +{ + struct sigaction action = { + .sa_sigaction = __timeout_handler, + .sa_flags = SA_SIGINFO, + }; + struct sigaction saved_action; + int status; + + if (sigaction(SIGALRM, &action, &saved_action)) { + t->passed = 0; + fprintf(TH_LOG_STREAM, + "# %s: unable to install SIGALRM handler\n", + t->name); + return; + } + __active_test = t; + t->timed_out = false; + alarm(t->timeout); + waitpid(t->pid, &status, 0); + alarm(0); + if (sigaction(SIGALRM, &saved_action, NULL)) { + t->passed = 0; + fprintf(TH_LOG_STREAM, + "# %s: unable to uninstall SIGALRM handler\n", + t->name); + return; + } + __active_test = NULL; + + if (t->timed_out) { + t->passed = 0; + fprintf(TH_LOG_STREAM, + "# %s: Test terminated by timeout\n", t->name); + } else if (WIFEXITED(status)) { + if (t->termsig != -1) { + t->passed = 0; + fprintf(TH_LOG_STREAM, + "# %s: Test exited normally instead of by signal (code: %d)\n", + t->name, + WEXITSTATUS(status)); + } else { + switch (WEXITSTATUS(status)) { + /* Success */ + case 0: + t->passed = 1; + break; + /* SKIP */ + case 255: + t->passed = 1; + t->skip = 1; + break; + /* Other failure, assume step report. */ + default: + t->passed = 0; + fprintf(TH_LOG_STREAM, + "# %s: Test failed at step #%d\n", + t->name, + WEXITSTATUS(status)); + } + } + } else if (WIFSIGNALED(status)) { + t->passed = 0; + if (WTERMSIG(status) == SIGABRT) { + fprintf(TH_LOG_STREAM, + "# %s: Test terminated by assertion\n", + t->name); + } else if (WTERMSIG(status) == t->termsig) { + t->passed = 1; + } else { + fprintf(TH_LOG_STREAM, + "# %s: Test terminated unexpectedly by signal %d\n", + t->name, + WTERMSIG(status)); + } + } else { + fprintf(TH_LOG_STREAM, + "# %s: Test ended in some other way [%u]\n", + t->name, + status); + } +} + +void __run_test(struct __fixture_metadata *f, + struct __fixture_variant_metadata *variant, + struct __test_metadata *t) +{ + /* reset test struct */ + t->passed = 1; + t->skip = 0; + t->trigger = 0; + t->step = 1; + t->no_print = 0; + memset(t->results->reason, 0, sizeof(t->results->reason)); + + ksft_print_msg(" RUN %s%s%s.%s ...\n", + f->name, variant->name[0] ? "." : "", variant->name, t->name); + + /* Make sure output buffers are flushed before fork */ + fflush(stdout); + fflush(stderr); + + t->pid = fork(); + if (t->pid < 0) { + ksft_print_msg("ERROR SPAWNING TEST CHILD\n"); + t->passed = 0; + } else if (t->pid == 0) { + setpgrp(); + t->fn(t, variant); + if (t->skip) + _exit(255); + /* Pass is exit 0 */ + if (t->passed) + _exit(0); + /* Something else happened, report the step. */ + _exit(t->step); + } else { + __wait_for_test(t); + } + ksft_print_msg(" %4s %s%s%s.%s\n", t->passed ? "OK" : "FAIL", + f->name, variant->name[0] ? "." : "", variant->name, t->name); + + if (t->skip) + ksft_test_result_skip("%s\n", t->results->reason[0] ? + t->results->reason : "unknown"); + else + ksft_test_result(t->passed, "%s%s%s.%s\n", + f->name, variant->name[0] ? "." : "", variant->name, t->name); +} + +static int test_harness_run(int __attribute__((unused)) argc, + char __attribute__((unused)) **argv) +{ + struct __fixture_variant_metadata no_variant = { .name = "", }; + struct __fixture_variant_metadata *v; + struct __fixture_metadata *f; + struct __test_results *results; + struct __test_metadata *t; + int ret = 0; + unsigned int case_count = 0, test_count = 0; + unsigned int count = 0; + unsigned int pass_count = 0; + + for (f = __fixture_list; f; f = f->next) { + for (v = f->variant ?: &no_variant; v; v = v->next) { + case_count++; + for (t = f->tests; t; t = t->next) + test_count++; + } + } + + results = mmap(NULL, sizeof(*results), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + + ksft_print_header(); + ksft_set_plan(test_count); + ksft_print_msg("Starting %u tests from %u test cases.\n", + test_count, case_count); + for (f = __fixture_list; f; f = f->next) { + for (v = f->variant ?: &no_variant; v; v = v->next) { + for (t = f->tests; t; t = t->next) { + count++; + t->results = results; + __run_test(f, v, t); + t->results = NULL; + if (t->passed) + pass_count++; + else + ret = 1; + } + } + } + munmap(results, sizeof(*results)); + + ksft_print_msg("%s: %u / %u tests passed.\n", ret ? "FAILED" : "PASSED", + pass_count, count); + ksft_exit(ret == 0); + + /* unreachable */ + return KSFT_FAIL; +} + +static void __attribute__((constructor)) __constructor_order_first(void) +{ + if (!__constructor_order) + __constructor_order = _CONSTRUCTOR_ORDER_FORWARD; +} + +#endif /* __KSELFTEST_HARNESS_H */ diff --git a/guest-test/tdx/tdx_attest_test_suite/include/linux/types.h b/guest-test/tdx/tdx_attest_test_suite/include/linux/types.h new file mode 100644 index 00000000..6e14a533 --- /dev/null +++ b/guest-test/tdx/tdx_attest_test_suite/include/linux/types.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _TOOLS_LINUX_TYPES_H_ +#define _TOOLS_LINUX_TYPES_H_ + +#include +#include +#include + +#ifndef __SANE_USERSPACE_TYPES__ +#define __SANE_USERSPACE_TYPES__ /* For PPC64, to get LL64 types */ +#endif + +#include +#include + +struct page; +struct kmem_cache; + +typedef enum { + GFP_KERNEL, + GFP_ATOMIC, + __GFP_HIGHMEM, + __GFP_HIGH +} gfp_t; + +/* + * We define u64 as uint64_t for every architecture + * so that we can print it with "%"PRIx64 without getting warnings. + * + * typedef __u64 u64; + * typedef __s64 s64; + */ +typedef uint64_t u64; +typedef int64_t s64; + +typedef __u32 u32; +typedef __s32 s32; + +typedef __u16 u16; +typedef __s16 s16; + +typedef __u8 u8; +typedef __s8 s8; + +#ifdef __CHECKER__ +#define __bitwise__ __attribute__((bitwise)) +#else +#define __bitwise__ +#endif +#define __bitwise __bitwise__ + +#define __force +#define __user +#define __must_check +#define __cold + +typedef __u16 __bitwise __le16; +typedef __u16 __bitwise __be16; +typedef __u32 __bitwise __le32; +typedef __u32 __bitwise __be32; +typedef __u64 __bitwise __le64; +typedef __u64 __bitwise __be64; + +typedef __u16 __bitwise __sum16; +typedef __u32 __bitwise __wsum; + +typedef struct { + int counter; +} atomic_t; + +#ifndef __aligned_u64 +# define __aligned_u64 __u64 __attribute__((aligned(8))) +#endif + +struct list_head { + struct list_head *next, *prev; +}; + +struct hlist_head { + struct hlist_node *first; +}; + +struct hlist_node { + struct hlist_node *next, **pprev; +}; + +#endif /* _TOOLS_LINUX_TYPES_H_ */ diff --git a/guest-test/tdx/tdx_attest_test_suite/tdx-attest-test.c b/guest-test/tdx/tdx_attest_test_suite/tdx-attest-test.c new file mode 100644 index 00000000..d7d784a9 --- /dev/null +++ b/guest-test/tdx/tdx_attest_test_suite/tdx-attest-test.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tdx-attest-test.c - utility to test TDX attestation feature. + * + * Copyright (C) 2022 - 2023 Intel Corporation. All rights reserved. + * + * Author: Kuppuswamy Sathyanarayanan + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* uintmax_t */ +#include +#include + +#include +#include "include/linux/kselftest_harness.h" + +#define TDX_GUEST_DEVNAME "/dev/tdx_guest" +#define HEX_DUMP_SIZE 8 +#define DEBUG 1 +#define QUOTE_SIZE 8192 + +/** + * struct tdreport_type - Type header of TDREPORT_STRUCT. + * @type: Type of the TDREPORT (0 - SGX, 81 - TDX, rest are reserved) + * @sub_type: Subtype of the TDREPORT (Default value is 0). + * @version: TDREPORT version (Default value is 0). + * @reserved: Added for future extension. + * + * More details can be found in TDX v1.0 module specification, sec + * titled "REPORTTYPE". + */ +struct tdreport_type { + __u8 type; + __u8 sub_type; + __u8 version; + __u8 reserved; +}; + +/** + * struct reportmac - TDX guest report data, MAC and TEE hashes. + * @type: TDREPORT type header. + * @reserved1: Reserved for future extension. + * @cpu_svn: CPU security version. + * @tee_tcb_info_hash: SHA384 hash of TEE TCB INFO. + * @tee_td_info_hash: SHA384 hash of TDINFO_STRUCT. + * @reportdata: User defined unique data passed in TDG.MR.REPORT request. + * @reserved2: Reserved for future extension. + * @mac: CPU MAC ID. + * + * It is MAC-protected and contains hashes of the remainder of the + * report structure along with user provided report data. More details can + * be found in TDX v1.0 Module specification, sec titled "REPORTMACSTRUCT" + */ +struct reportmac { + struct tdreport_type type; + __u8 reserved1[12]; + __u8 cpu_svn[16]; + __u8 tee_tcb_info_hash[48]; + __u8 tee_td_info_hash[48]; + __u8 reportdata[64]; + __u8 reserved2[32]; + __u8 mac[32]; +}; + +/** + * struct td_info - TDX guest measurements and configuration. + * @attr: TDX Guest attributes (like debug, spet_disable, etc). + * @xfam: Extended features allowed mask. + * @mrtd: Build time measurement register. + * @mrconfigid: Software-defined ID for non-owner-defined configuration + * of the guest - e.g., run-time or OS configuration. + * @mrowner: Software-defined ID for the guest owner. + * @mrownerconfig: Software-defined ID for owner-defined configuration of + * the guest - e.g., specific to the workload. + * @rtmr: Run time measurement registers. + * @reserved: Added for future extension. + * + * It contains the measurements and initial configuration of the TDX guest + * that was locked at initialization and a set of measurement registers + * that are run-time extendable. More details can be found in TDX v1.0 + * Module specification, sec titled "TDINFO_STRUCT". + */ +struct td_info { + __u8 attr[8]; + __u64 xfam; + __u64 mrtd[6]; + __u64 mrconfigid[6]; + __u64 mrowner[6]; + __u64 mrownerconfig[6]; + __u64 rtmr[24]; + __u64 reserved[14]; +}; + +/* + * struct tdreport - Output of TDCALL[TDG.MR.REPORT]. + * @reportmac: Mac protected header of size 256 bytes. + * @tee_tcb_info: Additional attestable elements in the TCB are not + * reflected in the reportmac. + * @reserved: Added for future extension. + * @tdinfo: Measurements and configuration data of size 512 bytes. + * + * More details can be found in TDX v1.0 Module specification, sec + * titled "TDREPORT_STRUCT". + */ +struct tdreport { + struct reportmac reportmac; + __u8 tee_tcb_info[239]; + __u8 reserved[17]; + struct td_info tdinfo; +}; + +static void print_array_hex(const char *title, const char *prefix_str, + const void *buf, int len) +{ + int i, j, line_len, rowsize = HEX_DUMP_SIZE; + const __u8 *ptr = buf; + + printf("\t\t%s", title); + + for (j = 0; j < len; j += rowsize) { + line_len = rowsize < (len - j) ? rowsize : (len - j); + printf("%s%.8x:", prefix_str, j); + for (i = 0; i < line_len; i++) + printf(" %.2x", ptr[j + i]); + printf("\n"); + } + + printf("\n"); +} + +/* Helper function to get TDREPORT */ +long get_tdreport0(int devfd, struct tdx_report_req *req) +{ + int i; + + /* Generate sample report data */ + for (i = 0; i < TDX_REPORTDATA_LEN; i++) + req->reportdata[i] = i; + + return ioctl(devfd, TDX_CMD_GET_REPORT0, req); +} + +TEST(verify_report) +{ + struct tdx_report_req req; + struct tdreport *tdreport; + int devfd; + + devfd = open(TDX_GUEST_DEVNAME, O_RDWR | O_SYNC); + ASSERT_LT(0, devfd); + + /* Get TDREPORT */ + ASSERT_EQ(0, get_tdreport0(devfd, &req)); + + if (DEBUG) { + print_array_hex("\n\t\tTDX report data\n", "", + req.reportdata, sizeof(req.reportdata)); + + print_array_hex("\n\t\tTDX tdreport data\n", "", + req.tdreport, sizeof(req.tdreport)); + } + + /* Make sure TDREPORT data includes the REPORTDATA passed */ + tdreport = (struct tdreport *)req.tdreport; + ASSERT_EQ(0, memcmp(&tdreport->reportmac.reportdata[0], + req.reportdata, sizeof(req.reportdata))); + + ASSERT_EQ(0, close(devfd)); +} + +TEST(verify_reportmac) +{ + struct tdx_verify_report_req req = { }; + struct tdx_report_req rep_req; + struct tdreport *tdreport; + int devfd, ret; + + devfd = open(TDX_GUEST_DEVNAME, O_RDWR | O_SYNC); + + ASSERT_LT(0, devfd); + + /* Get TDREPORT */ + ASSERT_EQ(0, get_tdreport0(devfd, &rep_req)); + + /* Fill VERIFYREPORT request */ + tdreport = (struct tdreport *)rep_req.tdreport; + memcpy(req.reportmac, &tdreport->reportmac, sizeof(req.reportmac)); + + /* Verify reportmac and make sure it is valid */ + ret = ioctl(devfd, TDX_CMD_VERIFY_REPORT, &req); + if (DEBUG && ret) + printf("verify_report TDCALL failed, err:%llx\n", req.err_code); + + ASSERT_EQ(0, ret); + + ASSERT_EQ(0, close(devfd)); +} + +TEST(verify_rtmr_extend) +{ + struct tdx_extend_rtmr_req req; + int devfd, i; + + devfd = open(TDX_GUEST_DEVNAME, O_RDWR | O_SYNC); + + ASSERT_LT(0, devfd); + + /* Generate sample RTMR extend data */ + for (i = 0; i < TDX_EXTEND_RTMR_DATA_LEN; i++) + req.data[i] = i; + + req.index = 2; + + /* Verify reportmac and make sure it is valid */ + ASSERT_EQ(0, ioctl(devfd, TDX_CMD_EXTEND_RTMR, &req)); + + ASSERT_EQ(0, close(devfd)); +} + +TEST(verify_quote) +{ + struct tdx_quote_hdr *quote_hdr; + struct tdx_report_req rep_req; + struct tdx_quote_req req; + __u64 quote_buf_size; + __u8 *quote_buf; + int devfd; + + /* Open attestation device */ + devfd = open(TDX_GUEST_DEVNAME, O_RDWR | O_SYNC); + + ASSERT_LT(0, devfd); + + /* Add size for quote header */ + quote_buf_size = sizeof(*quote_hdr) + QUOTE_SIZE; + + /* Allocate quote buffer */ + quote_buf = malloc(quote_buf_size); + ASSERT_NE(NULL, quote_buf); + + quote_hdr = (struct tdx_quote_hdr *)quote_buf; + + /* Initialize GetQuote header */ + quote_hdr->version = 1; + quote_hdr->status = GET_QUOTE_SUCCESS; + quote_hdr->in_len = TDX_REPORT_LEN; + quote_hdr->out_len = 0; + + /* Get TDREPORT data */ + ASSERT_EQ(0, get_tdreport0(devfd, &rep_req)); + + /* Fill GetQuote request */ + memcpy(quote_hdr->data, rep_req.tdreport, TDX_REPORT_LEN); + req.buf = (__u64)quote_buf; + req.len = quote_buf_size; + + ASSERT_EQ(0, ioctl(devfd, TDX_CMD_GET_QUOTE, &req)); + + /* Check whether GetQuote request is successful */ + EXPECT_EQ(0, quote_hdr->status); + + free(quote_buf); + + ASSERT_EQ(0, close(devfd)); +} + +TEST_HARNESS_MAIN diff --git a/guest-test/tdx/tdx_guest_boot_check.sh b/guest-test/tdx/tdx_guest_boot_check.sh index 5830db9b..4815c9bd 100755 --- a/guest-test/tdx/tdx_guest_boot_check.sh +++ b/guest-test/tdx/tdx_guest_boot_check.sh @@ -15,21 +15,21 @@ echo "$SCRIPT_DIR" source common.sh while getopts :v:s:m: arg; do - case $arg in - v) - VCPU=$OPTARG - ;; - s) - SOCKETS=$OPTARG - ;; - m) - MEM=$OPTARG - ;; - *) - test_print_err "Must supply an argument to -$OPTARG." - exit 1 - ;; - esac + case $arg in + v) + VCPU=$OPTARG + ;; + s) + SOCKETS=$OPTARG + ;; + m) + MEM=$OPTARG + ;; + *) + test_print_err "Must supply an argument to -$OPTARG." + exit 1 + ;; + esac done ###################### Do Works ###################### @@ -40,11 +40,11 @@ test_print_trc "vcpu_td: $vcpu_td" test_print_trc "sockets_td: $sockets_td" if [[ "$vcpu_td" -ne "$VCPU" ]]; then - die "Guest TD VM boot with vcpu: $vcpu_td (expected $VCPU)" + die "Guest TD VM boot with vcpu: $vcpu_td (expected $VCPU)" fi if [[ "$sockets_td" -ne "$SOCKETS" ]]; then - die "Guest TD VM boot with sockets: $sockets_td (expected $SOCKETS)" + die "Guest TD VM boot with sockets: $sockets_td (expected $SOCKETS)" fi # check memory size @@ -53,14 +53,14 @@ test_print_trc "mem_td: $mem_td" # $MEM less than or equal to 4GB need special memory size check if [[ $MEM -le 4 ]]; then - if [[ $(( MEM / mem_td )) -lt 1 ]] || [[ $(( MEM / mem_td )) -gt 2 ]]; then - die "Guest TD VM boot with memory: $mem_td GB (expected $MEM GB)" - fi + if [[ $(( MEM / mem_td )) -lt 1 ]] || [[ $(( MEM / mem_td )) -gt 2 ]]; then + die "Guest TD VM boot with memory: $mem_td GB (expected $MEM GB)" + fi # $MEM more than 4GB use general memory size check else - if [[ $(( MEM / mem_td )) -ne 1 ]]; then - die "Guest TD VM boot with memory: $mem_td GB (expected $MEM GB)" - fi + if [[ $(( MEM / mem_td )) -ne 1 ]]; then + die "Guest TD VM boot with memory: $mem_td GB (expected $MEM GB)" + fi fi test_print_trc "Guest TD VM boot up successfully with config:" diff --git a/guest-test/tdx/tests b/guest-test/tdx/tests index 3bd40166..89b51bfa 100644 --- a/guest-test/tdx/tests +++ b/guest-test/tdx/tests @@ -7,4 +7,7 @@ guest.test_launcher.sh -v 4 -s 2 -m 96 -d on -t tdx -x TD_BOOT -c "accept_memory guest.test_launcher.sh -v 64 -s 8 -m 96 -d on -t tdx -x TD_BOOT -c "accept_memory=lazy" -p off guest.test_launcher.sh -v 288 -s 1 -m 1 -d on -t tdx -x TD_BOOT -c "accept_memory=lazy" -p off guest.test_launcher.sh -v 288 -s 8 -m 96 -d on -t tdx -x TD_BOOT -c "accept_memory=lazy" -p off -guest.test_launcher.sh -v 1 -s 1 -m 1 -d off -t tdx -x TD_BOOT -c "accept_memory=lazy" -p off +guest.test_launcher.sh -v 1 -s 1 -m 1 -d on -t tdx -x TD_ATTEST_VERIFY_REPORT -c "accept_memory=lazy" -p off +guest.test_launcher.sh -v 1 -s 1 -m 1 -d on -t tdx -x TD_ATTEST_VERITY_REPORTMAC -c "accept_memory=lazy" -p off +guest.test_launcher.sh -v 1 -s 1 -m 1 -d on -t tdx -x TD_ATTEST_VERIFY_RTMR_EXTEND -c "accept_memory=lazy" -p off +guest.test_launcher.sh -v 1 -s 1 -m 1 -d on -t tdx -x TD_ATTEST_VERIFY_QUOTE -c "accept_memory=lazy" -p off \ No newline at end of file