From 5db93237707716dc2916a5c39e5cf938a631d003 Mon Sep 17 00:00:00 2001 From: Pengfei Xu Date: Tue, 2 Apr 2024 12:55:24 +0800 Subject: [PATCH 1/3] shstk_cpu: add the shstk performance test code on specific cpu Signed-off-by: Pengfei Xu --- cet/Makefile | 9 +++- cet/shstk_cpu.c | 116 +++++++++++++++++++++++++++++++++++++++++ cet/shstk_cpu_legacy.c | 68 ++++++++++++++++++++++++ 3 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 cet/shstk_cpu.c create mode 100644 cet/shstk_cpu_legacy.c diff --git a/cet/Makefile b/cet/Makefile index afadbdb7..13d0e8b6 100644 --- a/cet/Makefile +++ b/cet/Makefile @@ -14,7 +14,8 @@ IS_KER_SRC = $(shell [ -d $(KER_SRC) ] && echo true) ifeq ($(GCC_GE_8),true) BIN := shstk_alloc test_shadow_stack quick_test wrss shstk_huge_page \ - shstk_unlock_test shstk_cp cet_app glibc_shstk_test + shstk_unlock_test shstk_cp cet_app glibc_shstk_test shstk_cpu \ + shstk_cpu_legacy $(info GCC major version: ${GCC_VER_MAJOR}) else @@ -57,6 +58,12 @@ cet_app: glibc_shstk_test: glibc_shstk_test.c gcc $(CETFLAGS) $^ -o $@ +shstk_cpu: shstk_cpu.c + gcc $(NOCETFLAGS) $^ -o $@ + +shstk_cpu_legacy: shstk_cpu_legacy.c + gcc $(NOCETFLAGS) $^ -o $@ + cet_ioctl: $(MAKE) -C $(KER_SRC) M=$(DRIVER_PATH) modules diff --git a/cet/shstk_cpu.c b/cet/shstk_cpu.c new file mode 100644 index 00000000..2255cd24 --- /dev/null +++ b/cet/shstk_cpu.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Verify Shadow Stack performance impact on specific CPU + * + * Author: Pengfei Xu + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +/* It's from arch/x86/include/uapi/asm/prctl.h file. */ +#define ARCH_SHSTK_ENABLE 0x5001 +#define ARCH_SHSTK_DISABLE 0x5002 +/* ARCH_SHSTK_ features bits */ +#define ARCH_SHSTK_SHSTK (1ULL << 0) +#define ARCH_SHSTK_WRSS (1ULL << 1) + +/* + * For use in inline enablement of shadow stack. + * + * The program can't return from the point where shadow stack gets enabled + * because there will be no address on the shadow stack. So it can't use + * syscall() for enablement, since it is a function. + * + * Based on code from nolibc.h. Keep a copy here because this can't pull in all + * of nolibc.h. + */ +#define ARCH_PRCTL(arg1, arg2) \ +({ \ + long _ret; \ + register long _num asm("eax") = __NR_arch_prctl; \ + register long _arg1 asm("rdi") = (long)(arg1); \ + register long _arg2 asm("rsi") = (long)(arg2); \ + \ + asm volatile ( \ + "syscall\n" \ + : "=a"(_ret) \ + : "r"(_arg1), "r"(_arg2), \ + "0"(_num) \ + : "rcx", "r11", "memory", "cc" \ + ); \ + _ret; \ +}) + +void shstk2(void) +{ + unsigned long *j; + + #ifdef __x86_64__ + asm("movq %%rbx,%0" : "=r"(j)); + #else + + asm("mov %%ebp,%0" : "=r"(j)); + #endif +} + +void shstk1(void) +{ + unsigned long *i; + + #ifdef __x86_64__ + asm("movq %%rbx,%0" : "=r"(i)); + #else + asm("mov %%ebp,%0" : "=r"(i)); + #endif + shstk2(); +} + +int main(int argc, char *argv[]) +{ + int x = 100000000, a, cpu; + clock_t tstart, tend; + cpu_set_t set; + + if (argc >= 2) { + cpu = atoi(argv[1]); + } else { + cpu = 0; + printf("There is no cpu setting, set as cpu0 by default\n"); + } + printf("testing on cpu %d\n", cpu); + CPU_ZERO(&set); + CPU_SET(cpu, &set); + if (sched_setaffinity(getpid(), sizeof(set), &set) == -1) { + printf("set affinity failed\n"); + return -1; + } + + if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) + printf("[FAIL]\tParent process could not enable SHSTK!\n"); + else + printf("[PASS]\tParent process enable SHSTK.\n"); + + tstart = clock(); + for (a = 1; a <= x; a++) { + shstk1(); + shstk2(); + } + tend = clock(); + printf("RESULTS %dloop,start:%ld,end:%ld, used CLOCK:%ld: CLOCK/SEC:%ld\n", + x, tstart, tend, (long)(tend - tstart), CLOCKS_PER_SEC); + + /* Disable SHSTK in parent process to avoid segfault issue. */ + if (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK)) + printf("[FAIL]\tParent process disable shadow stack failed.\n"); + else + printf("[PASS]\tParent process disable shadow stack successfully.\n"); + + return 0; +} diff --git a/cet/shstk_cpu_legacy.c b/cet/shstk_cpu_legacy.c new file mode 100644 index 00000000..9b4b55b9 --- /dev/null +++ b/cet/shstk_cpu_legacy.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + *Verify Shadow Stack performance impact on specific CPU + * + * Author: Pengfei Xu + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +void shstk2(void) +{ + unsigned long *j; + + #ifdef __x86_64__ + asm("movq %%rbx,%0" : "=r"(j)); + #else + + asm("mov %%ebp,%0" : "=r"(j)); + #endif +} + +void shstk1(void) +{ + unsigned long *i; + + #ifdef __x86_64__ + asm("movq %%rbx,%0" : "=r"(i)); + #else + asm("mov %%ebp,%0" : "=r"(i)); + #endif + shstk2(); +} + +int main(int argc, char *argv[]) +{ + int x = 100000000, a, cpu; + clock_t tstart, tend; + cpu_set_t set; + + if (argc >= 2) { + cpu = atoi(argv[1]); + } else { + cpu = 0; + printf("There is no cpu setting, set as cpu0 by default\n"); + } + printf("testing on cpu %d\n", cpu); + CPU_ZERO(&set); + CPU_SET(cpu, &set); + if (sched_setaffinity(getpid(), sizeof(set), &set) == -1) { + printf("set affinity failed\n"); + return -1; + } + + tstart = clock(); + for (a = 1; a <= x; a++) { + shstk1(); + shstk2(); + } + tend = clock(); + printf("RESULTS %dloop,start:%ld,end:%ld, used CLOCK:%ld: CLOCK/SEC:%ld\n", + x, tstart, tend, (long)(tend - tstart), CLOCKS_PER_SEC); +} From d286f63a42240e161c3addc14f047bfbe37941d8 Mon Sep 17 00:00:00 2001 From: Pengfei Xu Date: Tue, 2 Apr 2024 20:08:56 +0800 Subject: [PATCH 2/3] common.sh: move online all cpu function into common.sh Move online all cpu function into common.sh, and will remove the function in ifs_common.sh. Signed-off-by: Pengfei Xu --- common/common.sh | 95 +++++++++++++++++++++++++++++++++++++++++++++++ ifs/ifs_common.sh | 44 +++------------------- ifs/ifs_tests.sh | 3 ++ 3 files changed, 104 insertions(+), 38 deletions(-) diff --git a/common/common.sh b/common/common.sh index e0b0e191..b66b1db7 100755 --- a/common/common.sh +++ b/common/common.sh @@ -11,6 +11,8 @@ BIN_DMESG="" BIN_RET="" export LAST_DMESG_TIMESTAMP="" +readonly CPU_SYSFS_FOLDER="/sys/devices/system/cpu" + # Check whether current user is root, if not, exit directly root_check() { local user="" @@ -396,6 +398,99 @@ extract_case_dmesg() { fi } +# Set specific CPU online or offline +# $1: 0|1, 0 means online cpu, 1 means offline cpu +# $2: cpu_num, it should be in the range of 0 - max_cpu +set_specific_cpu_on_off() +{ + local on_off=$1 + local cpu_num=$2 + local max_cpu="" + local cpu_state="" + + # Consider the CPU offline situation for max_cpu + max_cpu=$(cut -d "-" -f 2 "${CPU_SYSFS_FOLDER}/present") + + # v6.4-rc2: e59e74dc48a309cb8: x86/topology: Remove CPU0 hotplug option + [[ "$cpu_num" -eq 0 ]] && { + test_print_trc "v6.4-rc2: e59e74dc48a309cb8: Remove CPU0 hotplug option, skip cpu0 action" + return 0 + } + + [[ "$cpu_num" -lt 0 || "$cpu_num" -gt "$max_cpu" ]] && { + block_test "Invalid cpu_num:$cpu_num, it's not in the range: 0 - $max_cpu" + } + + if [[ "$on_off" == "1" || "$on_off" == "0" ]]; then + test_print_trc "echo $on_off > ${CPU_SYSFS_FOLDER}/cpu${cpu_num}/online" + echo "$on_off" > "$CPU_SYSFS_FOLDER"/cpu"$cpu_num"/online + ret=$? + [[ "$ret" -eq 0 ]] || { + test_print_err "Failed to set cpu$cpu_num to $on_off, ret:$ret not 0" + return $ret + } + cpu_state=$(cat "$CPU_SYSFS_FOLDER"/cpu"$cpu_num"/online) + if [[ "$cpu_state" != "$on_off" ]]; then + test_print_err "Failed to set cpu$cpu_num to $on_off, cpu state:$cpu_state" + return 2 + fi + else + test_print_err "Invalid on_off:$on_off, it should be 0 or 1" + return 2 + fi + + return 0 +} + +# Set specific CPU online or offline +# $1: 0|1, 0 means online cpu, 1 means offline cpu +# $2: cpus: sample: 11,22-27,114 same format in /sys/devices/system/cpu/offline +set_cpus_on_off() +{ + local on_off=$1 + local target_cpus=$2 + local cpu="" + local cpu_start="" + local cpu_end="" + local i="" + + if [[ -n "$target_cpus" ]]; then + for cpu in $(echo "$target_cpus" | tr ',' ' '); do + if [[ "$cpu" == *"-"* ]]; then + cpu_start="" + cpu_end="" + i="" + cpu_start=$(echo "$cpu" | cut -d "-" -f 1) + cpu_end=$(echo "$cpu" | cut -d "-" -f 2) + for((i=cpu_start;i<=cpu_end;i++)); do + set_specific_cpu_on_off "$on_off" "$i" || return $? + done + else + set_specific_cpu_on_off "$on_off" "$cpu" || return $? + fi + done + fi + + return 0 +} + +online_all_cpu() +{ + local off_cpus="" + + test_print_trc "Online all CPUs:" + off_cpus=$(cat "${CPU_SYSFS_FOLDER}/offline") + set_cpus_on_off "1" "$off_cpus" || { + block_test "Online all CPUs with ret:$? not 0!" + } + off_cpus=$(cat "${CPU_SYSFS_FOLDER}/offline") + if [[ -z "$off_cpus" ]]; then + test_print_trc "All CPUs are online." + else + block_test "There is offline cpu:$off_cpus after online all cpu!" + fi +} + # Check specified pattern in dmesg # Arguments: # $1: bin name diff --git a/ifs/ifs_common.sh b/ifs/ifs_common.sh index 16c8500e..92a771a2 100755 --- a/ifs/ifs_common.sh +++ b/ifs/ifs_common.sh @@ -35,6 +35,7 @@ readonly OFFLINE_FILE="/sys/devices/system/cpu/offline" readonly ARRAY="array" export INTEL_FW="/lib/firmware/intel" +export OFFLINE_CPUS="" # New sysfsfile IFS_PATH will be updated in entry script: ifs_tests.sh # Sample: /sys/devices/virtual/misc/intel_ifs_0|1|2 folder. @@ -100,6 +101,11 @@ ifs_teardown() { echo 1 | sudo tee /sys/devices/system/cpu/cpu"$cpu"/online done } + + [[ -z "$OFFLINE_CPUS" ]] || { + set_cpus_on_off "$OFFLINE_CPUS" || test_print_err "Set offline $OFFLINE_CPUS" + } + test_print_trc "cat $OFFLINE_FILE" cat $OFFLINE_FILE @@ -111,44 +117,6 @@ ifs_teardown() { fi } -online_all_cpu() { - echo "online all cpu" - local off_cpu="" - local cpu="" - # cpu start - local cpu_s="" - # cpu end - local cpu_e="" - local i="" - - off_cpu=$(cat "$OFFLINE_FILE") - if [[ -z "$off_cpu" ]]; then - test_print_trc "No cpu offline:$off_cpu" - else - for cpu in $(echo "$off_cpu" | tr ',' ' '); do - if [[ "$cpu" == *"-"* ]]; then - cpu_s="" - cpu_e="" - i="" - cpu_s=$(echo "$cpu" | cut -d "-" -f 1) - cpu_e=$(echo "$cpu" | cut -d "-" -f 2) - for((i=cpu_s;i<=cpu_e;i++)); do - do_cmd "echo 1 | sudo tee /sys/devices/system/cpu/cpu${i}/online" - done - else - do_cmd "echo 1 | sudo tee /sys/devices/system/cpu/cpu${cpu}/online" - fi - done - off_cpu="" - off_cpu=$(cat "$OFFLINE_FILE") - if [[ -z "$off_cpu" ]]; then - test_print_trc "No offline cpu:$off_cpu after online all cpu" - else - block_test "There is offline cpu:$off_cpu after online all cpu!" - fi - fi -} - # Get the cpu model info, like spr sample: 06-8f-06 # Input: NA # Output: 0 otherwise failure or die diff --git a/ifs/ifs_tests.sh b/ifs/ifs_tests.sh index c96ac97c..49a039f8 100755 --- a/ifs/ifs_tests.sh +++ b/ifs/ifs_tests.sh @@ -244,6 +244,9 @@ test_ifs() { ;; esac + # It's for teardown: restore previous offline CPUs to offline after test + OFFLINE_CPUS=$(cat "${CPU_SYSFS_FOLDER}/offline") + export OFFLINE_CPUS list_cpus "$PROCESSOR" case $NAME in From b328e946d98d95900f689f26f5aa87c1f9a4b2df Mon Sep 17 00:00:00 2001 From: Pengfei Xu Date: Tue, 2 Apr 2024 20:11:29 +0800 Subject: [PATCH 3/3] cet: add 2 cet user space SHSTK performance test cases Signed-off-by: Pengfei Xu --- cet/cet_tests.sh | 186 ++++++++++++++++++++++++++++++++++++++++++++++- cet/tests | 4 + 2 files changed, 189 insertions(+), 1 deletion(-) diff --git a/cet/cet_tests.sh b/cet/cet_tests.sh index b5533efe..5755fd02 100755 --- a/cet/cet_tests.sh +++ b/cet/cet_tests.sh @@ -13,6 +13,7 @@ TEST_MOD="cet_ioctl" TEST_MOD_KO="${TEST_MOD}.ko" KO_FILE="./cet_driver/${TEST_MOD_KO}" +export OFFLINE_CPUS="" export teardown_handler="cet_teardown" usage() { @@ -28,6 +29,10 @@ __EOF # Reserve for taerdown, present no change for cpu test cet_teardown() { + [[ -z "$OFFLINE_CPUS" ]] || { + set_cpus_on_off "$OFFLINE_CPUS" || test_print_err "Set offline $OFFLINE_CPUS" + } + check_mod=$(lsmod | grep "$TEST_MOD") [[ -z "$check_mod" ]] || { test_print_trc "rmmod $TEST_MOD" @@ -58,6 +63,95 @@ load_cet_driver() { fi } +# Check test used time cycles in log, and print 2 test logs gap rate +# $1: 1st test log file +# $2: 2nd test log file +# $3: test log folder path +# Return: 0 for true, otherwise false or die +cet_perf_compare() { + local file1=$1 + local file2=$2 + local path=$3 + local key_word="RESULTS" + local cycle1="" + local cycle2="" + local gap="" + local gap_rate="" + local result="" + local gap_upper="0.6" + local gap_lower="-2.0" + + cycle1=$(grep "$key_word" "${path}/${file1}" | cut -d ':' -f 4) + cycle2=$(grep "$key_word" "${path}/${file2}" | cut -d ':' -f 4) + test_print_trc "$file1 used cycles $cycle1" + test_print_trc "$file2 used cycles $cycle2" + gap=$(echo "$cycle1 - $cycle2" | bc) + gap_rate=$(echo "scale=4;$gap/$cycle1" | bc) + test_print_trc "$file1 and $file2 gap rate:$gap_rate" + result=$(echo "$gap_rate > $gap_lower && $gap_rate < $gap_upper" | bc) + [[ $result -eq 1 ]] || { + test_print_wrg "gap: $gap_rate is not in the range:$gap_lower ~ $gap_upper" + return 1 + } +} + +bin_parm_test() { + local bin_name=$1 + local bin_parm=$2 + local log_path=/tmp/$3 + + [[ -n "$bin_name" ]] || die "File $bin_name does not exist" + [[ -d "$log_path" ]] || mkdir -p "$log_path" + bin=$(which "$bin_name") + [[ -e "$bin" ]] || { + die "bin:$bin does not exist" + } + + bin_parm_name=$(echo "$bin_parm" | tr ' ' '_') + if [[ "$bin_parm" == "null" ]]; then + log="${log_path}/${bin_name}_${bin_parm_name}.log" + $bin > "$log" + else + log="${log_path}/${bin_name}_${bin_parm_name}.log" + $bin "${bin_parm}" > "$log" || { + test_print_err "Failed to run $bin $bin_parm ret:$?" + return 1 + } + fi + + [[ -e "$log" ]] || die "No $log file" + + return 0 +} + +dmesg_check() { + local key=$1 + local key_parm=$2 + local dmesg_file="" + + dmesg_file=$(extract_case_dmesg -f) + verify_key=$(grep -i "$key" "$dmesg_file") + case $key_parm in + "$CONTAIN") + if [[ -z "$verify_key" ]]; then + die "No $key found in dmesg when test $BIN_NAME, fail." + else + test_print_trc "$key found in dmesg, pass." + fi + ;; + "$NULL") + if [[ -z "$verify_key" ]]; then + test_print_trc "No $key in dmesg when test $BIN_NAME, pass." + else + die "$key found in dmesg when test $BIN_NAME, fail." + fi + ;; + *) + block_test "Invalid key_parm:$key_parm" + ;; + esac +} + cet_shstk_check() { local bin_name=$1 local bin_parm=$2 @@ -144,7 +238,8 @@ cet_dmesg_check() { } cet_tests() { - bin_file="" + local bin_file="" + local legacy="legacy" # Absolute path of BIN_NAME bin_file=$(which "$BIN_NAME") @@ -167,6 +262,95 @@ cet_tests() { cet_ssp) cet_shstk_check "$bin_file" "$PARM" "$TYPE" ;; + specific_cpu_perf) + local cpus="" + local cpu_num="" + local err_num=0 + local cet_compare_path="/tmp/${TYPE}" + + cpus=$(cut -d "-" -f 2 "${CPU_SYSFS_FOLDER}/present") + if [[ "$PARM" == "random" ]]; then + cpu_num=$(shuf -i 0-"$cpus" -n 1) + elif [[ "$PARM" -ge 0 && "$PARM" -le "$cpus" ]]; then + cpu_num=$PARM + else + block_test "Invalid CPU NUM in PARM:$PARM" + fi + + # Check cpu and kernel enable user space SHSTK really first + bin_parm_test "$BIN_NAME" "0" "$TYPE" || { + ((err_num++)) + } + check_fail=$(grep "FAIL" "$cet_compare_path/${BIN_NAME}_0.log") + [[ -z "$check_fail" ]] || { + test_print_wrg "Found FAIL in $BIN_NAME 0 output:$check_fail" + block_test "CET user space SHSTK could not be enabled!" + } + [[ "$err_num" -eq 0 ]] || die "Test cpu 0 $BIN_NAME failed!" + + # CPU 0 must be 1, so do not check cpu 0 + [[ "$cpu_num" -eq 0 ]] || { + cpu_num_on_off=$(cat "${CPU_SYSFS_FOLDER}/cpu${cpu_num}/online") + [[ "$cpu_num_on_off" == "1" ]] || OFFLINE_CPUS="$cpu_num" + set_specific_cpu_on_off "1" "$cpu_num" + } + + last_dmesg_timestamp + bin_parm_test "$BIN_NAME" "$i" "$TYPE" || { + ((err_num++)) + } + bin_parm_test "${BIN_NAME}_${legacy}" "$i" "$TYPE" || { + ((err_num++)) + } + cet_perf_compare "${BIN_NAME}_${i}.log" \ + "${BIN_NAME}_${legacy}_${i}.log" "$cet_compare_path" || { + test_print_err "CPU$i met $BIN_NAME perf regression!" + ((err_num++)) + } + [[ "$err_num" -eq 0 ]] || die "All cpu cet test with err_cnt:$err_num" + dmesg_check "control protection" "$NULL" + dmesg_check "Call Trace" "$NULL" + dmesg_check "segfault" "$NULL" + ;; + all_cpu_perf) + local cet_compare_path="/tmp/${TYPE}" + local err_num=0 + local check_fail="" + + # Check cpu and kernel enable user space SHSTK really first + bin_parm_test "$BIN_NAME" "0" "$TYPE" || { + ((err_num++)) + } + check_fail=$(grep "FAIL" "$cet_compare_path/${BIN_NAME}_0.log") + [[ -z "$check_fail" ]] || { + test_print_wrg "Found FAIL in $BIN_NAME 0 output:$check_fail" + block_test "CET user space SHSTK could not be enabled!" + } + [[ "$err_num" -eq 0 ]] || die "Test cpu 0 $BIN_NAME failed!" + + last_dmesg_timestamp + OFFLINE_CPUS=$(cat "${CPU_SYSFS_FOLDER}/offline") + online_all_cpu + cpus=$(cut -d "-" -f 2 "${CPU_SYSFS_FOLDER}/present") + + for((i=0;i<=cpus;i++)); do + bin_parm_test "$BIN_NAME" "$i" "$TYPE" || { + ((err_num++)) + } + bin_parm_test "${BIN_NAME}_${legacy}" "$i" "$TYPE" || { + ((err_num++)) + } + cet_perf_compare "${BIN_NAME}_${i}.log" \ + "${BIN_NAME}_${legacy}_${i}.log" "$cet_compare_path" || { + test_print_err "CPU$i met $BIN_NAME perf regression!" + ((err_num++)) + } + done + [[ "$err_num" -eq 0 ]] || die "All cpu cet test with err_cnt:$err_num" + dmesg_check "control protection" "$NULL" + dmesg_check "Call Trace" "$NULL" + dmesg_check "segfault" "$NULL" + ;; *) usage block_test "Invalid TYPE:$TYPE" diff --git a/cet/tests b/cet/tests index c694b093..caa44b44 100755 --- a/cet/tests +++ b/cet/tests @@ -21,6 +21,10 @@ cet_tests.sh -t no_cp -n glibc_shstk_test -k "control protection" -p s1 cet_tests.sh -t no_cp -n glibc_shstk_test -k "control protection" -p s3 cet_tests.sh -t no_cp -n glibc_shstk_test -k "control protection" -p buf2 cet_tests.sh -t cet_ssp -n glibc_shstk_test -p ssp +# CET user space SHSTK enable/disable performance tests on random cpu +cet_tests.sh -t specific_cpu_perf -n shstk_cpu -p "random" +# CET user space SHSTK enable/disable performance tests on each cpu +cet_tests.sh -t all_cpu_perf -n shstk_cpu # Kernel space IBT tests cet_tests.sh -t kmod_ibt_illegal -n cet_app -p "b1" -k "Missing ENDBR" cet_tests.sh -t kmod_ibt_legal -n cet_app -p "b2" -k "Missing ENDBR"