Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье содержатся рекомендации для разработчиков по выявлению и устранению уязвимостей побочных каналов спекулятивного выполнения в аппаратном обеспечении программного обеспечения C++. Эти уязвимости могут раскрывать конфиденциальную информацию через границы доверия и влиять на программное обеспечение, которое работает на процессорах, поддерживающих спекулятивное, внеочередное выполнение инструкций. Этот класс уязвимостей был впервые описан в январе 2018 г., а дополнительные сведения и рекомендации можно найти в рекомендациях по безопасности Майкрософт.
Рекомендации, предоставляемые этой статьей, связаны с классами уязвимостей, представленными следующими способами:
CVE-2017-5753, также известный как Spectre variant 1. Этот класс уязвимостей оборудования связан с побочными каналами, которые могут возникнуть из-за спекулятивного выполнения, происходящего в результате неправильного предсказания условной ветви. Компилятор Microsoft C++ в Visual Studio 2017 (начиная с версии 15.5.5) включает поддержку ключа
/Qspectre, который предоставляет средства смягчения риска во время компиляции для ограниченного набора потенциально уязвимых шаблонов кода, связанных с CVE-2017-5753. Этот/Qspectreпереключатель также доступен в Visual Studio 2015 Update 3 через KB 4338871. Документация по флагу/Qspectreсодержит дополнительные сведения о его влиянии и использовании.CVE-2018-3639, также известный как спекулятивный обход магазина (SSB). Этот класс уязвимостей оборудования связан с побочными каналами, которые могут возникнуть из-за спекулятивного выполнения нагрузки перед зависимым хранилищем в результате неправильного представления доступа к памяти.
Доступное введение в побочные уязвимости канала, связанные со спекулятивным выполнением, можно найти в презентации под названием Дело 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 кэша. Ниже приведены действия, которые можно выполнить для выполнения следующих действий.
Вызывайте
ReadByteнесколько раз, еслиuntrusted_indexменьше чемbuffer_size. Контекст атаки может привести к вызовуReadByteконтекста жертвы (например, через RPC), таким образом, что прогнозатор ветви обучен не принимать какuntrusted_indexменьшеbuffer_size.Очистка всех строк кэша в
shared_buffer. Контекст атаки должен сбрасывать все строки кэша в общем регионе памяти, на который ссылаетсяshared_buffer. Так как область памяти является общей, это просто и может быть выполнено с помощью встроенных механизмов, таких как_mm_clflush.Вызвать
ReadByte, еслиuntrusted_indexбольшеbuffer_size. Контекст атаки заставляет контекст жертвы вызыватьReadByte, таким образом, что он делает неверный прогноз о том, что ветвь не будет выполнена. Это приводит к тому, что процессор спекулятивно выполняет тело блока if, гдеuntrusted_indexбольше, чемbuffer_size, что ведет к чтению за пределами границbuffer. Следовательно,shared_bufferиндексируется с использованием потенциально секретного значения, которое было прочитано из-за пределов допустимого диапазона, что приводит к загрузке соответствующей строки кеша процессора.Считывайте каждую строку кэша,
shared_bufferчтобы узнать, к чему осуществляется доступ быстрее. Контекст атаки может считывать каждую строку кэша вshared_bufferи обнаруживать строку кэша, которая загружается значительно быстрее, чем остальные. Это строка кэша, которая, скорее всего, была доставлена на шаге 3. Так как в этом примере между значением байтов и строкой кэша существует связь 1:1, злоумышленник может определить фактическое значение байта, считываемого вне границ.
Приведенные выше действия содержат пример использования метода, известного как FLUSH+RELOAD, в сочетании с эксплойтом экземпляра CVE-2017-5753.
Какие сценарии программного обеспечения могут быть затронуты?
Разработка безопасного программного обеспечения с помощью такого процесса, как жизненный цикл разработки безопасности (SDL), обычно требует от разработчиков определить границы доверия, которые существуют в приложении. Граница доверия существует в тех местах, где приложение может взаимодействовать с данными, предоставляемыми менее доверенным контекстом, например другим процессом в системе или в не административном режиме пользователя в случае драйвера устройства в режиме ядра. Новый класс уязвимостей с участием спекулятивных каналов выполнения имеет отношение ко многим границам доверия в существующих моделях безопасности программного обеспечения, которые изолируют код и данные на устройстве.
В следующей таблице представлена сводка моделей безопасности программного обеспечения, в которых разработчикам может потребоваться беспокоиться об этих уязвимостях:
| Граница доверия | Описание |
|---|---|
| Граница виртуальной машины | Приложения, которые изолируют рабочие нагрузки в отдельных виртуальных машинах, получающих ненадежные данные из другой виртуальной машины, могут быть подвержены риску. |
| Граница ядра | Драйвер устройства в режиме ядра, получающий ненадежные данные из процесса, отличного от административного режима пользователя, может быть подвержен риску. |
| Граница процесса | Приложение, которое получает ненадежные данные из другого процесса, работающего в локальной системе, например через удаленный вызов процедуры (RPC), общую память или другие механизмы взаимодействия между процессами (IPC) могут быть подвержены риску. |
| Граница анклава | Приложение, которое выполняется в защищенном анклавах (например, Intel SGX), которое получает ненадежные данные из-за пределов анклава, может быть подвержено риску. |
| Граница языка | Приложение, которое интерпретирует или JIT-компилирует и выполняет ненадежный код, написанный на языке более высокого уровня, может подвергаться риску. |
Приложения, область атаки которых доступна любой из указанных выше границ доверия, должны просмотреть код в этой области, чтобы идентифицировать и устранить возможные уязвимости, связанные с побочными каналами спекулятивного выполнения. Следует отметить, что границы доверия, подвергаемые воздействиям удаленных атак, таких как удаленные сетевые протоколы, не продемонстрировали уязвимость к уязвимостям побочных каналов спекулятивного выполнения.
Потенциально уязвимые шаблоны программирования
Уязвимости бокового канала спекулятивного выполнения могут возникнуть в результате различных шаблонов программирования. В этом разделе описываются потенциально уязвимые шаблоны программирования и приведены примеры для каждого из них, но следует признать, что варианты этих тем могут существовать. Таким образом, разработчикам рекомендуется использовать эти шаблоны в качестве примеров, а не как исчерпывающий список всех потенциально уязвимых шаблонов программирования. Те же классы уязвимостей безопасности памяти, которые могут существовать в программном обеспечении сегодня, могут существовать вместе с спекулятивными и внеупорядоченными путями выполнения, включая, но не только переполнение буфера, доступ к массивам вне границ, использование неинициализированной памяти, путаницу типов и т. д. Те же примитивы, которые злоумышленники могут использовать для использования уязвимостей безопасности памяти вдоль архитектурных путей, также могут применяться к спекулятивным путям.
Как правило, спекулятивные каналы выполнения, связанные с неверным предсказанием условного перехода, могут возникать, когда условное выражение работает с данными, которые могут контролироваться или модифицироваться менее доверенным контекстом. Например, это может включать условные выражения, используемые в if, for, while, switch или тернарных операторах. Для каждого из этих утверждений компилятор может создать условную ветвь, целевой адрес которой ЦП может затем прогнозировать во время выполнения.
В каждом примере вставляется комментарий с фразой "БАРЬЕР СПЕКУЛЯЦИЙ", где разработчик может ввести барьер в качестве смягчения последствий. Это подробно описано в разделе по устранению рисков.
Спекулятивная внеграничная нагрузка
Эта категория шаблонов кодирования включает неправильное предсказание условной ветви, которое приводит к спекулятивному доступу к памяти за пределами допустимого диапазона.
Загрузка из массива за пределами допустимого диапазона, приводящая к необоснованной загрузке
Этот шаблон кодирования является первоначально описанным уязвимым шаблоном кодирования для 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 элементов), то указанное значение указателя записывается ptr в pointers массив. Этот код безопасен с архитектурной точки зрения, но если ЦП ошибочно прогнозирует условную ветвь, это может привести к спекулятивной записи 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, доступ к CType1::field2 будет приводить к спекулятивной загрузке данных вне пределов, которые могут быть секретными. Затем это значение используется при извлечении из shared_buffer, что может создавать наблюдаемые побочные эффекты, как в описанном ранее примере выхода за пределы массива.
Путаница типов приводит к косвенной ветви
Этот шаблон кодирования включает в себя ситуацию, когда путаница спекулятивного типа может привести к небезопасной косвенной ветви во время спекулятивного выполнения. В этом примере контекст атаки может привести к тому, что контекст жертвы выполнит ProcessType несколько раз с объектом типа CType2 (поле type равно Type2). Это приведет к обучению условной ветви так, чтобы первая инструкция if была выполнена, а инструкция else if не была выполнена. Затем контекст атаки может привести к тому, что контекст жертвы выполнит ProcessType с объектом типа CType1. Это может привести к спекулятивной путанице типов, если условная ветвь для первой if инструкции предсказывается как взятая, а else if оператор предсказывается как невзятый, таким образом, выполняется тело else if, и объект типа CType1 приводится к типу CType2.
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() |
| РУКА | в настоящее время недоступно | __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];
}
}
Удаление конфиденциальной информации из памяти
Другой способ, который можно использовать для устранения уязвимостей канала спекулятивного выполнения, заключается в удалении конфиденциальной информации из памяти. Разработчики программного обеспечения могут искать возможности рефакторинга приложения таким образом, чтобы конфиденциальная информация не была доступна во время спекулятивного выполнения. Это можно сделать, переработав архитектуру приложения для выделения конфиденциальной информации в отдельные процессы. Например, приложение веб-браузера может попытаться изолировать данные, связанные с каждым веб-источником, в отдельные процессы, что предотвращает доступ к данным между источниками путем спекулятивного выполнения.
См. также
Руководство по смягчению уязвимостей побочного канала спекулятивного выполнения
Устранение уязвимостей оборудования аппаратного канала спекулятивного выполнения