Руководство разработчика C++ по сторонним каналам при спекулятивном выполнении команд

В этой статье содержатся рекомендации для разработчиков по выявлению и устранению уязвимостей оборудования аппаратного канала спекулятивного выполнения в программном обеспечении C++. Эти уязвимости могут раскрывать конфиденциальную информацию по границам доверия и влиять на программное обеспечение, которое работает на процессорах, поддерживающих спекулятивное выполнение инструкций вне порядка. Этот класс уязвимостей был впервые описан в январе 2018 г., а дополнительные сведения и рекомендации можно найти в рекомендациях по безопасности Майкрософт.

Рекомендации, предоставляемые этой статьей, связаны с классами уязвимостей, представленными следующими способами:

  1. CVE-2017-5753, также известный как Spectre variant 1. Этот класс уязвимостей оборудования связан с побочными каналами, которые могут возникнуть из-за спекулятивного выполнения, которое происходит в результате неправильного представления условной ветви. Компилятор Microsoft C++ в Visual Studio 2017 (начиная с версии 15.5.5) включает поддержку коммутатора, который обеспечивает устранение рисков во время компиляции для /Qspectre ограниченного набора потенциально уязвимых шаблонов программирования, связанных с CVE-2017-5753. Этот /Qspectre параметр также доступен в Visual Studio 2015 с обновлением 3 до КБ 4338871. Документация по флагу /Qspectre содержит дополнительные сведения о его влиянии и использовании.

  2. CVE-2018-3639, также известный как спекулятивный обход магазина (SSB). Этот класс уязвимостей оборудования связан с побочными каналами, которые могут возникнуть из-за спекулятивного выполнения нагрузки перед зависимым хранилищем в результате неправильного представления доступа к памяти.

Доступное введение в спекулятивное выполнение уязвимостей бокового канала можно найти в презентации под названием Case of Spectre и Meltdown одной из исследовательских групп, которые обнаружили эти проблемы.

Что такое уязвимости оборудования аппаратного канала спекулятивного выполнения?

Современные ЦП обеспечивают более высокую степень производительности, используя спекулятивное и внеупорядоченное выполнение инструкций. Например, это часто достигается путем прогнозирования целевого объекта ветвей (условного и косвенного), что позволяет ЦП начать спекулятивно выполнять инструкции в целевом объекте прогнозируемой ветви, что позволяет избежать остановки до устранения фактического целевого объекта ветви. В случае, если ЦП позже обнаруживает, что произошло неправильное определение, все состояние компьютера, вычисленное спекулятивно карта. Это гарантирует отсутствие архитектурных эффектов неправильного предположения.

Хотя спекулятивное выполнение не влияет на видимое состояние архитектуры, он может оставить остаточные трассировки в не архитектурном состоянии, например различные кэши, используемые ЦП. Это эти остаточные трассировки спекулятивного выполнения, которые могут привести к уязвимостям бокового канала. Чтобы лучше понять это, рассмотрим следующий фрагмент кода, который содержит пример CVE-2017-5753 (обход проверки границ):

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ReadByte(unsigned char *buffer, unsigned int buffer_size, unsigned int untrusted_index) {
    if (untrusted_index < buffer_size) {
        unsigned char value = buffer[untrusted_index];
        return shared_buffer[value * 4096];
    }
}

В этом примере ReadByte предоставляется буфер, размер буфера и индекс в этот буфер. Параметр индекса, указанный в untrusted_indexинструкции, предоставляется менее привилегированным контекстом, таким как не административный процесс. Если untrusted_index значение меньше buffer_size, то символ на этом индексе считывается buffer и используется для индексирования в общий регион памяти, на который shared_bufferссылается.

С точки зрения архитектуры эта последовательность кода является совершенно безопасной, так как гарантируется, что untrusted_index всегда будет меньше buffer_size. Однако при наличии спекулятивного выполнения ЦП будет неправильно использовать условную ветвь и выполнить текст инструкции if, даже если untrusted_index больше или равно buffer_size. В результате ЦП может спекулятивно считывать байт из-за границ buffer (которые могут быть секретом) и затем использовать это байтовое значение для вычисления адреса последующей загрузки.shared_buffer

Хотя ЦП в конечном итоге обнаружит это неправильное определение, остаточные побочные эффекты могут быть оставлены в кэше ЦП, который показывает сведения о значении байтов buffer, считываемых из границ. Эти побочные эффекты можно обнаружить с помощью менее привилегированного контекста, работающего в системе, проверив, как быстро осуществляется доступ к каждой строке shared_buffer кэша. Ниже приведены действия, которые можно выполнить для выполнения следующих действий.

  1. ReadByte Вызывается несколько раз с untrusted_index меньшей, чем buffer_size. Контекст атаки может привести к вызову ReadByte контекста жертвы (например, через RPC), таким образом, что прогнозатор ветви обучен не принимать как untrusted_index меньше buffer_size.

  2. Очистка всех строк кэша в shared_buffer. Контекст атаки должен сбрасывать все строки кэша в общем регионе памяти, на который ссылается shared_buffer. Так как область памяти является общим, это просто и может быть выполнено с помощью встроенных функций, таких как _mm_clflush.

  3. ReadByte Вызов с untrusted_index большим числомbuffer_size. Контекст атаки приводит к вызову ReadByte контекста жертвы таким образом, что он неправильно предсказывает, что ветвь не будет приниматься. Это приводит к тому, что процессор спекулятивно выполняет тело блока с untrusted_index большим, чем buffer_size, что приводит к внеграничному чтению buffer. Следовательно, shared_buffer индексируется с помощью потенциально секретного значения, которое было прочитано вне границ, что приводит к загрузке соответствующей строки кэша ЦП.

  4. Считывайте каждую строку кэша, shared_buffer чтобы узнать, к чему осуществляется доступ быстрее. Контекст атаки может считывать каждую строку кэша и shared_buffer обнаруживать строку кэша, которая загружается значительно быстрее, чем другие. Это строка кэша, которая, скорее всего, была доставлена на шаге 3. Так как в этом примере между значением байтов и строкой кэша существует связь 1:1, злоумышленник может определить фактическое значение байта, считываемого вне границ.

Приведенные выше действия содержат пример использования метода, известного как FLUSH+RELOAD, в сочетании с эксплойтом экземпляра CVE-2017-5753.

Какие сценарии программного обеспечения можно повлиять?

Разработка безопасного программного обеспечения с помощью такого процесса, как жизненный цикл разработки безопасности (SDL), обычно требует от разработчиков определить границы доверия, которые существуют в приложении. Граница доверия существует в тех местах, где приложение может взаимодействовать с данными, предоставляемыми менее доверенным контекстом, например другим процессом в системе или в не административном режиме пользователя в случае драйвера устройства в режиме ядра. Новый класс уязвимостей с участием спекулятивных каналов выполнения имеет отношение ко многим границам доверия в существующих моделях безопасности программного обеспечения, которые изолируют код и данные на устройстве.

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

Граница доверия Description
Граница виртуальной машины Приложения, которые изолируют рабочие нагрузки в отдельных виртуальных машинах, получающих ненадежные данные из другой виртуальной машины, могут быть подвержены риску.
Граница ядра Драйвер устройства в режиме ядра, получающий ненадежные данные из процесса, отличного от административного режима пользователя, может быть подвержен риску.
Граница процесса Приложение, которое получает ненадежные данные из другого процесса, работающего в локальной системе, например через удаленный вызов процедуры (RPC), общую память или другие механизмы взаимодействия между процессами (IPC) могут быть подвержены риску.
Граница анклава Приложение, которое выполняется в защищенном анклавах (например, Intel SGX), которое получает ненадежные данные из-за пределов анклава, может быть подвержено риску.
Граница языка Приложение, которое интерпретирует или JIT-код компилирует и выполняет ненадежный код, написанный на более высоком уровне языка, может быть подвержен риску.

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

Потенциально уязвимые шаблоны программирования

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

Как правило, спекулятивные каналы выполнения, связанные с неправильной версией условной ветви, могут возникать, когда условное выражение работает с данными, которые могут контролироваться или влиять на менее доверенный контекст. Например, это может включать условные выражения, используемые в ifоператорах , for, whileswitchили ternary. Для каждой из этих инструкций компилятор может создать условную ветвь, которую ЦП может затем прогнозировать целевой объект ветви для среды выполнения.

В каждом примере вставляется комментарий с фразой "СПЕКУЛЯЦИЯ БАРЬЕР", где разработчик может ввести барьер в качестве устранения рисков. Это подробно описано в разделе по устранению рисков.

Спекулятивная внеграничная нагрузка

Эта категория шаблонов кодирования включает в себя неправильное представление условной ветви, которая приводит к спекулятивному доступу к памяти вне границ.

Загрузка нагрузки на загрузку массива вне границ

Этот шаблон кодирования является первоначально описанным уязвимым шаблоном кодирования для CVE-2017-5753 (обход проверки границ). В фоновом разделе этой статьи подробно объясняется этот шаблон.

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ReadByte(unsigned char *buffer, unsigned int buffer_size, unsigned int untrusted_index) {
    if (untrusted_index < buffer_size) {
        // SPECULATION BARRIER
        unsigned char value = buffer[untrusted_index];
        return shared_buffer[value * 4096];
    }
}

Аналогичным образом нагрузка массива вне границ может возникать в сочетании с циклом, превышающим его завершающееся условие из-за неправильной выборки. В этом примере условная ветвь, связанная с x < buffer_size выражением, может неправильно и спекулятивно выполнять тело for цикла, если x оно больше или равно buffer_size, что приводит к спекулятивной загрузке вне границ.

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ReadBytes(unsigned char *buffer, unsigned int buffer_size) {
    for (unsigned int x = 0; x < buffer_size; x++) {
        // SPECULATION BARRIER
        unsigned char value = buffer[x];
        return shared_buffer[value * 4096];
    }
}

Загрузка непрямой ветви массива

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

В этом примере идентификатор ненадежного сообщения предоставляется в DispatchMessage с помощью untrusted_message_id параметра. Если untrusted_message_id меньше MAX_MESSAGE_ID, то он используется для индексирования в массив указателей функций и ветвей на соответствующий целевой объект ветви. Этот код является безопасным архитектурным, но если ЦП неправильно определяет условную ветвь, он может привести DispatchTable к индексации, untrusted_message_id если его значение больше или равно MAX_MESSAGE_ID, что приводит к выходу из-за пределов доступа. Это может привести к спекулятивному выполнению из целевого адреса ветви, который является производным от границ массива, что может привести к раскрытию информации в зависимости от кода, выполняемого спекулятивно.

#define MAX_MESSAGE_ID 16

typedef void (*MESSAGE_ROUTINE)(unsigned char *buffer, unsigned int buffer_size);

const MESSAGE_ROUTINE DispatchTable[MAX_MESSAGE_ID];

void DispatchMessage(unsigned int untrusted_message_id, unsigned char *buffer, unsigned int buffer_size) {
    if (untrusted_message_id < MAX_MESSAGE_ID) {
        // SPECULATION BARRIER
        DispatchTable[untrusted_message_id](buffer, buffer_size);
    }
}

Как и в случае массива, не связанного с загрузкой другой нагрузки, это условие также может возникнуть в сочетании с циклом, превышающим его завершающееся условие из-за неправильной диктовки.

Хранилище массива вне границы, питающего непрямую ветвь

Хотя в предыдущем примере показано, как спекулятивная внеграничная нагрузка может повлиять на целевой объект косвенной ветви, также можно изменить непрямую целевую ветвь, например указатель функции или возвращаемый адрес. Это может привести к спекулятивному выполнению с указанного злоумышленником адреса.

В этом примере ненадежный индекс передается через untrusted_index параметр. Если untrusted_index меньше количества элементов массива pointers (256 элементов), то указанное значение указателя записывается pointers в ptr массив. Этот код является безопасным архитектурным, но если ЦП неправильно определяет условную ветвь, это может привести к ptr спекулятивному написанию за пределами массива, выделенного pointers стека. Это может привести к спекулятивной коррупции возвращаемого адреса WriteSlot. Если злоумышленник может контролировать значение ptr, он может вызвать спекулятивное выполнение из произвольного адреса при WriteSlot возврате вдоль спекулятивного пути.

unsigned char WriteSlot(unsigned int untrusted_index, void *ptr) {
    void *pointers[256];
    if (untrusted_index < 256) {
        // SPECULATION BARRIER
        pointers[untrusted_index] = ptr;
    }
}

Аналогичным образом, если в стеке выделена локальная переменная func указателя функции, возможно, можно спекулятивно изменить адрес, который ссылается на то, func когда возникает ошибка условной ветви. Это может привести к спекулятивному выполнению из произвольного адреса при вызове указателя функции.

unsigned char WriteSlot(unsigned int untrusted_index, void *ptr) {
    void *pointers[256];
    void (*func)() = &callback;
    if (untrusted_index < 256) {
        // SPECULATION BARRIER
        pointers[untrusted_index] = ptr;
    }
    func();
}

Следует отметить, что оба этих примера включают спекулятивное изменение указателей косвенной ветви стека. Возможно, что спекулятивное изменение также может произойти для глобальных переменных, выделенной кучи памяти и даже памяти только для чтения на некоторых ЦП. Для памяти, выделенной стека, компилятор Microsoft C++ уже выполняет шаги, чтобы упростить спекулятивное изменение целевых объектов косвенной ветви стека, например путем переупорядочения локальных переменных, таких как размещение буферов рядом с файлом cookie безопасности в рамках /GS функции безопасности компилятора.

Путаница с спекулятивным типом

Эта категория относится к шаблонам кодирования, которые могут привести к путанице спекулятивного типа. Это происходит при доступе к памяти с использованием неправильного типа вдоль не архитектурного пути во время спекулятивного выполнения. Как условная ветвь, так и спекулятивный обход хранилища могут привести к путанице спекулятивного типа.

Для обхода спекулятивного хранилища это может произойти в сценариях, когда компилятор повторно использует расположение стека для переменных нескольких типов. Это связано с тем, что архитектурное хранилище переменной типа A может быть обходить, что позволяет нагрузке типа A спекулятивно выполняться перед назначением переменной. Если ранее хранимая переменная имеет другой тип, это может создать условия для смешения спекулятивного типа.

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

enum TypeName {
    Type1,
    Type2
};

class CBaseType {
public:
    CBaseType(TypeName type) : type(type) {}
    TypeName type;
};

class CType1 : public CBaseType {
public:
    CType1() : CBaseType(Type1) {}
    char field1[256];
    unsigned char field2;
};

class CType2 : public CBaseType {
public:
    CType2() : CBaseType(Type2) {}
    void (*dispatch_routine)();
    unsigned char field2;
};

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ProcessType(CBaseType *obj)
{
    if (obj->type == Type1) {
        // SPECULATION BARRIER
        CType1 *obj1 = static_cast<CType1 *>(obj);

        unsigned char value = obj1->field2;

        return shared_buffer[value * 4096];
    }
    else if (obj->type == Type2) {
        // SPECULATION BARRIER
        CType2 *obj2 = static_cast<CType2 *>(obj);

        obj2->dispatch_routine();

        return obj2->field2;
    }
}

Путаница с спекулятивным типом, приводящей к загрузке вне границ

Этот шаблон кодирования включает в себя случай, когда путаница спекулятивного типа может привести к внеграничному или запутанным полям, где загруженное значение передает последующий адрес загрузки. Это похоже на шаблон кодирования массива вне границ, но он манифестируется с помощью альтернативной последовательности кода, как показано выше. В этом примере контекст атаки может привести к тому, что контекст жертвы выполняется ProcessType несколько раз с объектом типа CType1 (type поле равно Type1). Это будет иметь эффект обучения условной ветви для первого if оператора, чтобы предсказать не принято. Затем контекст атаки может привести к выполнению ProcessType контекста жертвы с объектом типа CType2. Это может привести к путанице спекулятивного типа, если условная ветвь для первой if инструкции неправильно определяется и выполняет текст if инструкции, поэтому приведение объекта типа CType2 к CType1. Так как CType2 доступ к CType1::field2 памяти меньшеCType1, это приведет к спекулятивной загрузке данных, которые могут быть секретами. Затем это значение используется в нагрузке shared_buffer , из которой можно создавать наблюдаемые побочные эффекты, как и в примере массива вне границ, описанном ранее.

Путаница спекулятивного типа, приводящей к косвенной ветви

Этот шаблон кодирования включает в себя ситуацию, когда путаница спекулятивного типа может привести к небезопасной косвенной ветви во время спекулятивного выполнения. В этом примере контекст атаки может привести к тому, что контекст жертвы выполняется ProcessType несколько раз с объектом типа CType2 (type поле равно Type2). Это приведет к обучению условной ветви для первой if инструкции, необходимой для выполнения, и else if заявление, не принятое. Затем контекст атаки может привести к выполнению ProcessType контекста жертвы с объектом типа CType1. Это может привести к путанице спекулятивного типа, если условная ветвь для первой if инструкции прогнозирует, и else if оператор не принимается, таким образом, выполняя текст else if объекта типа и приведение объекта к типу CType1CType2. CType2::dispatch_routine Так как поле перекрывается с char массивомCType1::field1, это может привести к спекулятивной косвенной ветви целевому объекту непреднамеренных ветвей. Если контекст атаки может управлять значениями байтов в массиве CType1::field1 , они могут управлять целевым адресом ветви.

Спекулятивное неинициализированное использование

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

Спекулятивное неинициализированное использование, приводящее к загрузке вне границ

Спекулятивное неинициализированное использование может привести к загрузке вне границ с помощью управляемого злоумышленником значения. В приведенном ниже примере значение index присваивается trusted_index всем архитектурным путям и trusted_index предполагается, что значение меньше или равно buffer_size. Однако в зависимости от кода, созданного компилятором, возможно, что обход спекулятивного хранилища может произойти, что позволяет нагрузке от buffer[index] и зависимых выражений выполняться перед назначением index. Если это происходит, неинициализированное значение index будет использоваться в качестве смещения buffer , в которое злоумышленник может считывать конфиденциальную информацию вне границ и передавать это через боковой канал через зависимые нагрузки shared_buffer.

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

void InitializeIndex(unsigned int trusted_index, unsigned int *index) {
    *index = trusted_index;
}

unsigned char ReadByte(unsigned char *buffer, unsigned int buffer_size, unsigned int trusted_index) {
    unsigned int index;

    InitializeIndex(trusted_index, &index); // not inlined

    // SPECULATION BARRIER
    unsigned char value = buffer[index];
    return shared_buffer[value * 4096];
}

Спекулятивное неинициализированное использование, приводящее к косвенной ветви

Спекулятивное неинициализированное использование может привести к косвенной ветви, в которой целевой объект ветви контролируется злоумышленником. В приведенном ниже routine примере назначается DefaultMessageRoutine1 либо или DefaultMessageRoutine в зависимости от значения mode. На пути архитектуры это приведет к routine всегда инициализации перед косвенной ветвью. Однако в зависимости от кода, созданного компилятором, может произойти обход спекулятивного хранилища, который позволяет косвенно выполнять ветвь routine перед назначением routine. Если это происходит, злоумышленник может иметь возможность спекулятивно выполнять из произвольного адреса, предполагая, что злоумышленник может влиять на неинициализированное значение routineили контролировать его.

#define MAX_MESSAGE_ID 16

typedef void (*MESSAGE_ROUTINE)(unsigned char *buffer, unsigned int buffer_size);

const MESSAGE_ROUTINE DispatchTable[MAX_MESSAGE_ID];
extern unsigned int mode;

void InitializeRoutine(MESSAGE_ROUTINE *routine) {
    if (mode == 1) {
        *routine = &DefaultMessageRoutine1;
    }
    else {
        *routine = &DefaultMessageRoutine;
    }
}

void DispatchMessage(unsigned int untrusted_message_id, unsigned char *buffer, unsigned int buffer_size) {
    MESSAGE_ROUTINE routine;

    InitializeRoutine(&routine); // not inlined

    // SPECULATION BARRIER
    routine(buffer, buffer_size);
}

Варианты устранения ошибок

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

Барьер спекуляций с помощью ручного инструментирования

Барьер спекуляций можно вручную вставить разработчиком, чтобы предотвратить спекулятивное выполнение по не-архитектурному пути. Например, разработчик может вставить барьер спекуляции до опасного шаблона кодирования в теле условного блока либо в начале блока (после условной ветви), либо до первой нагрузки, которая вызывает озабоченность. Это позволит предотвратить неправильное выполнение опасного кода на не архитектурном пути путем сериализации выполнения. Последовательность барьеров спекуляций отличается аппаратной архитектурой, как описано в следующей таблице:

Архитектура Барьер спекуляции для CVE-2017-5753 Барьер спекуляций, встроенный для CVE-2018-3639
x86/x64 _mm_lfence() _mm_lfence()
ARM в настоящее время недоступно __dsb(0)
ARM64 в настоящее время недоступно __dsb(0)

Например, приведенный ниже шаблон кода можно устранить с помощью встроенной _mm_lfence функции, как показано ниже.

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ReadByte(unsigned char *buffer, unsigned int buffer_size, unsigned int untrusted_index) {
    if (untrusted_index < buffer_size) {
        _mm_lfence();
        unsigned char value = buffer[untrusted_index];
        return shared_buffer[value * 4096];
    }
}

Барьер спекуляций с помощью инструментирования во время компилятора

Компилятор Microsoft C++ в Visual Studio 2017 (начиная с версии 15.5.5) включает поддержку /Qspectre коммутатора, который автоматически вставляет барьер спекуляции для ограниченного набора потенциально уязвимых шаблонов программирования, связанных с CVE-2017-5753. Документация по флагу /Qspectre содержит дополнительные сведения о его влиянии и использовании. Важно отметить, что этот флаг не охватывает все потенциально уязвимые шаблоны программирования, и как такие разработчики не должны полагаться на него в качестве комплексного устранения уязвимостей этого класса уязвимостей.

Маскирование индексов массива

В случаях, когда может возникнуть спекулятивная внеграничная нагрузка, индекс массива может быть строго привязан как к архитектуре, так и к не-архитектурному пути, добавив логику для явной привязки индекса массива. Например, если массив можно выделить для размера, выровненного по мощности двух, можно ввести простую маску. Это иллюстрируется в приведенном ниже примере, где предполагается, что buffer_size оно выравнивается с мощностью двух. Это гарантирует, что untrusted_index всегда меньше buffer_size, даже если возникает неправильное определение условной ветви, и untrusted_index он был передан со значением больше или равно buffer_size.

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

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ReadByte(unsigned char *buffer, unsigned int buffer_size, unsigned int untrusted_index) {
    if (untrusted_index < buffer_size) {
        untrusted_index &= (buffer_size - 1);
        unsigned char value = buffer[untrusted_index];
        return shared_buffer[value * 4096];
    }
}

Удаление конфиденциальной информации из памяти

Другой способ, который можно использовать для устранения уязвимостей канала спекулятивного выполнения, заключается в удалении конфиденциальной информации из памяти. Разработчики программного обеспечения могут искать возможности рефакторинга приложения таким образом, чтобы конфиденциальная информация не была доступна во время спекулятивного выполнения. Это можно сделать, рефакторинг проектирования приложения для изоляции конфиденциальной информации в отдельных процессах. Например, приложение веб-браузера может попытаться изолировать данные, связанные с каждым веб-источником, в отдельные процессы, что предотвращает доступ к данным между источниками путем спекулятивного выполнения.

См. также

Руководство по устранению уязвимостей на стороне канала спекулятивного выполнения
Устранение уязвимостей оборудования аппаратного канала спекулятивного выполнения