From 606df6b0d56eaa99f7ca1f68062544350d64e84e Mon Sep 17 00:00:00 2001 From: Haoliang Zhu Date: Wed, 10 Jul 2024 11:33:21 +0800 Subject: [PATCH] tdx-compliance: enhancement 1. Dynamic registration of kretprobe. 2. Trigger cupid in kernel space. 3. Dump the number of VE and GP triggered by cpuid in dmesg. 4. Improve the readme and fix some compiled warnings. Signed-off-by: Haoliang Zhu --- BM/tdx-compliance/README.md | 29 +++- BM/tdx-compliance/tdx-compliance.c | 188 +++++++++++++++-------- BM/tdx-compliance/tdx-compliance.h | 4 + BM/tdx-compliance/tests-trigger-cpuid.sh | 9 ++ 4 files changed, 161 insertions(+), 69 deletions(-) create mode 100644 BM/tdx-compliance/tests-trigger-cpuid.sh diff --git a/BM/tdx-compliance/README.md b/BM/tdx-compliance/README.md index 6aa894e4..d0a49c0c 100644 --- a/BM/tdx-compliance/README.md +++ b/BM/tdx-compliance/README.md @@ -26,9 +26,9 @@ To build tdx-compliance, follow these steps: ```bash git clone https://github.com/intel/lkvs.git -cd lkvs/tdx-compliance +cd lkvs/BM/tdx-compliance make -insmod tdx-compliance +insmod tdx-compliance.ko ``` ### Step 3: Run test cases To run all kinds of compliance tests, simply enter the following command: @@ -93,6 +93,31 @@ Usage: echo cr [version] > /sys/kernel/debug/tdx/tdx-tests ``` +* Trigger the cpuid and capture #VE or #GP. + +Some CPUID instructions will trigger #VE (Virtualization Exceptions), and during the process of handling #VE, the ``tdx_handle_virt_exception function`` is called. Based on the return value of this function, it can be determined whether it is #VE or #GP. The #VE triggered by the CPUID instruction will ultimately be handled by the ``handle_cpuid`` function, and the return value of this function determines the return value of the ``tdx_handle_virt_exception`` function. Therefore, it is necessary to capture the return value of the ``handle_cpuid`` function. We use the kernel-provided ``kretprobe`` mechanism to capture the return value of the handle_cpuid function, thereby determining whether it is #VE or #GP. + +Usage: +Register probe points: +``` +echo kretprobe > /sys/kernel/debug/tdx/tdx-tests +``` +Single trigger cpuid instruction, and the captured information is printed in dmesg. ``0x1f 0x0 0x0 0x0`` are the input of eax, ebx, ecx, edx.: +``` +echo trigger_cpuid 0x1f 0x0 0x0 0x0 > /sys/kernel/debug/tdx/tdx-tests +``` +Trigger all cpuid instructions in tdx-compliance.h: +``` +echo cpuid > /sys/kernel/debug/tdx/tdx-tests +``` +Unregister the kretprobe: +``` +echo unregister > /sys/kernel/debug/tdx/tdx-tests +rmmod tdx_compliance +``` +Note: +Executing the CPUID instruction in user space can lead to contamination, resulting in multiple captures of ``handle_cpuid``; therefore, the most accurate method is to trigger the CPUID instruction in kernel space. + ## Contact: Sun, Yi (yi.sun@intel.com) diff --git a/BM/tdx-compliance/tdx-compliance.c b/BM/tdx-compliance/tdx-compliance.c index 2f83190f..4194e110 100644 --- a/BM/tdx-compliance/tdx-compliance.c +++ b/BM/tdx-compliance/tdx-compliance.c @@ -36,6 +36,7 @@ int spec_version; char case_name[256]; char version_name[32]; char *buf_ret; +bool kretprobe_switch; static struct dentry *f_tdx_tests, *d_tdx; LIST_HEAD(cpuid_list); @@ -60,6 +61,9 @@ LIST_HEAD(cpuid_list); #define OPMASK_CPUID 1 #define OPMASK_CR 2 #define OPMASK_MSR 4 +#define OPMASK_KRETKPROBE 8 +#define OPMASK_UNREGISTER 16 +#define TRIGGER_CPUID 32 #define OPMASK_DUMP 0x800 #define OPMASK_SINGLE 0x8000 @@ -80,7 +84,7 @@ static char *result_str(int ret) return "UNKNOWN"; } -void parse_version(void) +void parse_version(void) { if (strstr(version_name, "1.0")) spec_version = VER1_0; @@ -92,7 +96,8 @@ void parse_version(void) spec_version = (VER1_0 | VER1_5 | VER2_0); } -static char* case_version(int ret) { +static char *case_version(int ret) +{ switch (ret) { case VER1_0: return "1.0"; @@ -105,18 +110,21 @@ static char* case_version(int ret) { return ""; } -void parse_input(char* s) +void parse_input(char *s) { memset(case_name, 0, sizeof(case_name)); memset(version_name, 0, sizeof(version_name)); char *space = strchr(s, ' '); - if (space != NULL) { + + if (space) { size_t length_case = space - s; + strncpy(case_name, s, length_case); case_name[length_case] = '\0'; - size_t length_ver = strlen(space+1); - strncpy(version_name, space+1, length_ver); + size_t length_ver = strlen(space + 1); + + strncpy(version_name, space + 1, length_ver); } else { strcpy(case_name, s); strcpy(version_name, "generic"); @@ -225,7 +233,8 @@ static int run_all_msr(void) if (operation & 0x8000 && strcmp(case_name, t->name) != 0) continue; - if (!(spec_version & t->version)) continue; + if (!(spec_version & t->version)) + continue; if (operation & 0x800) { pr_buf("%s %s\n", t->name, case_version(t->version)); @@ -259,7 +268,7 @@ static int check_results_cpuid(struct test_cpuid *t) return 1; /* - * Show the detail that resutls in the failure, + * Show the detail that results in the failure, * CPUID here focus on the fixed bit, not actual cpuid val. */ pr_buf("CPUID: %s_%s\n", t->name, version_name); @@ -295,16 +304,18 @@ static int run_all_cpuid(void) pr_tdx_tests("Testing CPUID...\n"); list_for_each_entry(t, &cpuid_list, list) { - if (operation & 0x8000 && strcmp(case_name, t->name) != 0) continue; - if (!(spec_version & t->version)) continue; + if (!(spec_version & t->version)) + continue; if (operation & 0x800) { pr_buf("%s %s\n", t->name, case_version(t->version)); continue; } + if (kretprobe_switch) + pr_info("leaf:%X subleaf:%X\n", t->leaf, t->subleaf); run_cpuid(t); @@ -313,9 +324,14 @@ static int run_all_cpuid(void) stat_pass++; else if (t->ret == -1) stat_fail++; + if (kretprobe_switch) + pr_info("CPUID output: eax:%X, ebx:%X, ecx:%X, edx:%X\n", + t->regs.eax.val, t->regs.ebx.val, t->regs.ecx.val, t->regs.edx.val); - pr_buf("%d: %s_%s:\t %s\n", ++stat_total, t->name, version_name, result_str(t->ret)); + pr_buf("%d: %s_%s:\t %s\n", + ++stat_total, t->name, version_name, result_str(t->ret)); } + pr_tdx_tests("CPUID test end!\n"); return 0; } @@ -375,7 +391,8 @@ static int run_all_cr(void) if (operation & 0x8000 && strcmp(case_name, t->name) != 0) continue; - if (!(spec_version & t->version)) continue; + if (!(spec_version & t->version)) + continue; if (operation & 0x800) { pr_buf("%s %s\n", t->name, case_version(t->version)); @@ -406,6 +423,70 @@ static int run_all_cr(void) return 0; } +unsigned int count; +static int ret_handler(struct kretprobe_instance *ri, struct pt_regs *regs) +{ + int retval = regs_return_value(regs); + + count++; + pr_info("handle_cpuid count %d\n", count); + pr_info("handle_cpuid returned %d\n", retval); + if (retval < 0) + pr_info("#GP trigger.\n"); + else + pr_info("#VE trigger.\n"); + + return 0; +} + +static struct kretprobe my_kretprobe = { + .handler = ret_handler, + /* + * Here can modify the detection functions, such as hanlde_cpuid, read_msr, + * write_msr, handle_mmio, handle_io, etc. + * It should be noted that the detected function must be exposed to the kernel, + * that is, the address corresponding to the function needs to be in /proc/kallsyms. + */ + .kp.symbol_name = "handle_cpuid", +}; + +static int run_kretprobe(void) +{ + // Register the kretprobe. + int ret; + + ret = register_kretprobe(&my_kretprobe); + if (ret < 0) { + pr_err("register_kprobe failed, returned %d\n", ret); + return ret; + } + kretprobe_switch = true; + pr_info("Detect the return value of the %s.\n", my_kretprobe.kp.symbol_name); + pr_info("Planted kprobe at %p\n", my_kretprobe.kp.addr); + + return 0; +} + +static int unregister(void) +{ + // Unregister the kretprobe. + unregister_kretprobe(&my_kretprobe); + kretprobe_switch = false; + pr_info("kprobe at %p unregistered. Please rmmod tdx_compliance. \n", my_kretprobe.kp.addr); + return 0; +} + +static int trigger_cpuid(unsigned int *A, unsigned int *B, unsigned int *C, unsigned int *D) +{ + pr_info("CPUID leaf:%X, subleaf:%X\n", *A, *C); + __asm__ volatile("cpuid" + : "=a" (*A), "=b" (*B), "=c" (*C), "=d" (*D) + : "a" (*A), "c"(*C) + :); + pr_info("CPUID output: eax:%X, ebx:%X, ecx:%X, edx:%X\n", *A, *B, *C, *D); + return 0; +} + static ssize_t tdx_tests_proc_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) @@ -419,6 +500,7 @@ tdx_tests_proc_write(struct file *file, size_t count, loff_t *f_pos) { char *str_input; + str_input = kzalloc((count + 1), GFP_KERNEL); if (!str_input) @@ -434,16 +516,22 @@ tdx_tests_proc_write(struct file *file, parse_input(str_input); - if (strstr(case_name, "cpuid")) + if (strncmp(case_name, "cpuid", 5) == 0) operation |= OPMASK_CPUID; - else if (strstr(case_name, "cr")) + else if (strncmp(case_name, "cr", 2) == 0) operation |= OPMASK_CR; - else if (strstr(case_name, "msr")) + else if (strncmp(case_name, "msr", 3) == 0) operation |= OPMASK_MSR; - else if (strstr(case_name, "all")) + else if (strncmp(case_name, "all", 3) == 0) operation |= OPMASK_CPUID | OPMASK_CR | OPMASK_MSR; - else if (strstr(case_name, "list")) + else if (strncmp(case_name, "list", 4) == 0) operation |= OPMASK_DUMP | OPMASK_CPUID | OPMASK_CR | OPMASK_MSR; + else if (strncmp(case_name, "kretprobe", 9) == 0) + operation |= OPMASK_KRETKPROBE; + else if (strncmp(case_name, "unregister", 10) == 0) + operation |= OPMASK_UNREGISTER; + else if (strncmp(case_name, "trigger_cpuid", 13) == 0) + operation |= TRIGGER_CPUID; else operation |= OPMASK_SINGLE | OPMASK_CPUID | OPMASK_CR | OPMASK_MSR; @@ -460,11 +548,24 @@ tdx_tests_proc_write(struct file *file, run_all_cr(); if (operation & OPMASK_MSR) run_all_msr(); + if (operation & OPMASK_KRETKPROBE) + run_kretprobe(); + if (operation & OPMASK_UNREGISTER) + unregister(); + if (operation & TRIGGER_CPUID) + { + unsigned int A, B, C, D; + + if (sscanf(version_name, "%x %x %x %x", &A, &B, &C, &D) == 4) + trigger_cpuid(&A, &B, &C, &D); + else + pr_info("Error parsing input string.\n"); + } if (!(operation & OPMASK_DUMP)) pr_buf("Total:%d, PASS:%d, FAIL:%d, SKIP:%d\n", - stat_total, stat_pass, stat_fail, - stat_total - stat_pass - stat_fail); + stat_total, stat_pass, stat_fail, + stat_total - stat_pass - stat_fail); kfree(str_input); operation = 0; @@ -477,31 +578,6 @@ const struct file_operations data_file_fops = { .read = tdx_tests_proc_read, }; -unsigned count = 0; -static int ret_handler(struct kretprobe_instance *ri, struct pt_regs *regs) -{ - int retval = regs_return_value(regs); - count++; - printk(KERN_INFO "handle_cpuid count %d\n", count); - printk(KERN_INFO "handle_cpuid returned %d\n", retval); - if(retval<0) - printk(KERN_INFO "#GP trigger. \n"); - else - printk(KERN_INFO "#VE trigger. \n"); - - return 0; -} - -static struct kretprobe my_kretprobe = { - .handler = ret_handler, - /* - * Here can modify the detection functions, such as hanlde_cpuid, read_msr, write_msr, handle_mmio, handle_io, etc. - * It should be noted that the detected function must be exposed to the kernel, - * that is, the address corresponding to the function needs to be in /proc/kallsyms. - */ - .kp.symbol_name = "handle_cpuid", -}; - static int __init tdx_tests_init(void) { d_tdx = debugfs_create_dir("tdx", NULL); @@ -526,25 +602,6 @@ static int __init tdx_tests_init(void) cur_cr4 = get_cr4(); pr_buf("cur_cr0: %016llx, cur_cr4: %016llx\n", cur_cr0, cur_cr4); - // Register the kretprobe. - int ret; - ret = register_kretprobe(&my_kretprobe); - if (ret < 0) { - pr_err("register_kprobe failed, returned %d\n", ret); - return ret; - } - pr_info("Planted kprobe at %p\n", my_kretprobe.kp.addr); - - // Immediately trigger cpuid 0x1f once in kernel space, which can accurately capture the trigger once #VE. - unsigned int A, B, C, D; - A = 0x1F; - __asm__ volatile( - "cpuid" - : "=a" (A), "=b" (B), "=c" (C), "=d" (D) - : "a" (A) - : - ); - return 0; } @@ -558,10 +615,7 @@ static void __exit tdx_tests_exit(void) } kfree(buf_ret); debugfs_remove_recursive(d_tdx); - - // Unregister the kretprobe. - unregister_kretprobe(&my_kretprobe); - pr_info("kprobe at %p unregistered\n", my_kretprobe.kp.addr); + pr_info("The tdx_compliance module has been removed.\n"); } module_init(tdx_tests_init); diff --git a/BM/tdx-compliance/tdx-compliance.h b/BM/tdx-compliance/tdx-compliance.h index 100b69c1..d339967e 100644 --- a/BM/tdx-compliance/tdx-compliance.h +++ b/BM/tdx-compliance/tdx-compliance.h @@ -96,6 +96,10 @@ int __no_profile _native_write_cr0(u64 val); int __no_profile _native_write_cr4(u64 val); static int write_msr_native(struct test_msr *c); static int read_msr_native(struct test_msr *c); +void initial_cpuid(void); +void parse_version(void); +void parse_input(char* s); +int check_results_cr(struct test_cr *t); u64 cur_cr4, cur_cr0; extern struct list_head cpuid_list; diff --git a/BM/tdx-compliance/tests-trigger-cpuid.sh b/BM/tdx-compliance/tests-trigger-cpuid.sh new file mode 100644 index 00000000..398ba738 --- /dev/null +++ b/BM/tdx-compliance/tests-trigger-cpuid.sh @@ -0,0 +1,9 @@ +# Some examples on how to trigger the cpuid and capture #VE. +# Register the kretprobe. +echo kretprobe > /sys/kernel/debug/tdx/tdx-tests + +# Trigger the cpuid and capture #VE. The captured information is printed in dmesg. +echo trigger_cpuid 0x1f 0x0 0x0 0x0 > /sys/kernel/debug/tdx/tdx-tests + +# Unregister the kretprobe. +echo unregister > /sys/kernel/debug/tdx/tdx-tests \ No newline at end of file