Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tdx-compliance: kprobe #320

Merged
merged 2 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions BM/tdx-compliance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 ([email protected])

142 changes: 123 additions & 19 deletions BM/tdx-compliance/tdx-compliance.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <linux/slab.h>
#include <linux/list.h>

#include <linux/kprobes.h>

#include "asm/trapnr.h"

#include "tdx-compliance.h"
Expand Down Expand Up @@ -34,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);

Expand All @@ -58,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

Expand All @@ -78,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;
Expand All @@ -90,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";
Expand All @@ -103,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");
Expand Down Expand Up @@ -223,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));
Expand Down Expand Up @@ -257,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);
Expand Down Expand Up @@ -293,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);

Expand All @@ -311,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;
}

Expand Down Expand Up @@ -373,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));
Expand Down Expand Up @@ -404,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)
Expand All @@ -417,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)
Expand All @@ -432,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;

Expand All @@ -458,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;
Expand Down Expand Up @@ -512,6 +615,7 @@ static void __exit tdx_tests_exit(void)
}
kfree(buf_ret);
debugfs_remove_recursive(d_tdx);
pr_info("The tdx_compliance module has been removed.\n");
}

module_init(tdx_tests_init);
Expand Down
4 changes: 4 additions & 0 deletions BM/tdx-compliance/tdx-compliance.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions BM/tdx-compliance/tests-trigger-cpuid.sh
Original file line number Diff line number Diff line change
@@ -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
Loading