[$11000][356196918] High CVE-2024-7965: Inappropriate implementation in V8(exploited ITW). Reported by TheDog on 2024-07-30
https://chromereleases.googleblog.com/2024/08/stable-channel-update-for-desktop_21.html
Уязвимость находится в компиляторе V8 в обработке функции ZeroExtendsWord32ToWord64 и связана с неправильной очисткой данных phi_states_, что приводит к неверным предположениям о верхних 32 битах определенных нод. Данная бага может быть вызвана манипулированием состоянием узлов phi таким образом, что получаются неверные результаты, что создает потенциальную уязвимость, которая может привести к повреждению памяти или обходу определенных механизмов безопасности, таких как нулевые расширения (movl zero-extend elision). Но на самом деле не похоже на то, чтобы данную багу можно экслуатировать независимо(скорее всего - взаимодействовать с выводом компилятора для чейна с другой багой).
Уязвимый код:
Анализ проблемы.
1. Phi-ноды и управление состоянием:
Он не очищает данные из предыдущих вызовов -> ошибочные оптимизации.
Создав цикл phi-нод таким образом, что одна из phi-нод некорректно переходит в состояние kUpperBitsGuaranteedZero, мы можем манипулировать процессом оптимизации, что собвственно может привести к невозможности использования нулевого расширения movl -> использовать верхние 32 бита 64-разрядного регистра для хранения непреднамеренных данных. С этого момента нам открываются двери с эксплуатацией, где нулевое расширение имеет решающее значение, например, для обхода проверок безопасности или повреждения памяти.
Исходя из вышесказанного:
Один из способов триггеринга баги:
Исправление:
В патче добавили логику для очистки phi_states_ при глубине рекурсии 0 -> предотвращает повреждение устаревших данных при будущих оптимизациях.
Больше спасибо за незаконченный PoC @buptsb
gist.github.com
"I have a unfinished PoC for a while, as it's a DFS recursion, we just need to craft a Phi cycle to make one of the Phi node's state into `kUpperBitsGuaranteedZero` incorrectly, then we could elide a movl zero-extend." -
Для того, чтобы запустить триггер:
https://chromereleases.googleblog.com/2024/08/stable-channel-update-for-desktop_21.html
Уязвимость находится в компиляторе V8 в обработке функции ZeroExtendsWord32ToWord64 и связана с неправильной очисткой данных phi_states_, что приводит к неверным предположениям о верхних 32 битах определенных нод. Данная бага может быть вызвана манипулированием состоянием узлов phi таким образом, что получаются неверные результаты, что создает потенциальную уязвимость, которая может привести к повреждению памяти или обходу определенных механизмов безопасности, таких как нулевые расширения (movl zero-extend elision). Но на самом деле не похоже на то, чтобы данную багу можно экслуатировать независимо(скорее всего - взаимодействовать с выводом компилятора для чейна с другой багой).
Уязвимый код:
C++:
//https://chromium.googlesource.com/v8/v8/+/0ae8b7389f188e6e7bff3163e766841bc9bfe06d/src/compiler/backend/instruction-selector.cc#5719
#if V8_TARGET_ARCH_64_BIT
template <typename Adapter>
bool InstructionSelectorT<Adapter>::ZeroExtendsWord32ToWord64(
node_t node, int recursion_depth) {
// To compute whether a Node sets its upper 32 bits to zero, there are three
// cases.
// 1. Phi node, with a computed result already available in phi_states_:
// Read the value from phi_states_.
// 2. Phi node, with no result available in phi_states_ yet:
// Recursively check its inputs, and store the result in phi_states_.
// 3. Anything else:
// Call the architecture-specific ZeroExtendsWord32ToWord64NoPhis.
// Limit recursion depth to avoid the possibility of stack overflow on very
// large functions.
// Обработка трех вариантов для обнуления верхних 32 бит
const int kMaxRecursionDepth = 100;
if (this->IsPhi(node)) {
Upper32BitsState current = phi_states_[this->id(node)];
if (current != Upper32BitsState::kNotYetChecked) {
return current == Upper32BitsState::kUpperBitsGuaranteedZero;
}
// If further recursion is prevented, we can't make any assumptions about
// the output of this phi node.
if (recursion_depth >= kMaxRecursionDepth) {
return false;
}
// Mark the current node so that we skip it if we recursively visit it
// again. Or, said differently, we compute a largest fixed-point so we can
// be optimistic when we hit cycles.
// Обновление состояния phi до "нулевых верхних битов"
phi_states_[this->id(node)] = Upper32BitsState::kUpperBitsGuaranteedZero;
int input_count = this->value_input_count(node);
for (int i = 0; i < input_count; ++i) {
node_t input = this->input_at(node, i);
if (!ZeroExtendsWord32ToWord64(input, recursion_depth + 1)) {
phi_states_[this->id(node)] = Upper32BitsState::kNoGuarantee;
return false;
}
}
return true;
}
return ZeroExtendsWord32ToWord64NoPhis(node);
}
#endif // V8_TARGET_ARCH_64_BIT
Анализ проблемы.
1. Phi-ноды и управление состоянием:
- Phi-нода - это конструкция, используемая при разработке компилятора для представления путей слияния потоков управления. "When multiple paths merge, values in abstract interpreter registers are merged by inserting so-called Phi nodes: value nodes that know which value to pick depending on which path was taken at runtime." - https://v8.dev/blog/maglev
- В V8 массив phi_states_ отслеживает, гарантированно ли равны нулю верхние 32 бита 64-разрядного значения. При возникновении рекурсивного цикла Phi-нод значение phi_states_ должно быть должным образом сброшено, чтобы избежать неправильного распространения состояния по нодам.
- Бага возникает из-за того, что массив phi_states_ не всегда должным образом очищается между последовательными вызовами ZeroExtendsWord32ToWord64. Если состояние не сброшено должным образом, устаревшее значение из предыдущего вычисления может быть использовано повторно, что приведет к неверным предположениям о значении старших 32 бит.
- Неправильное состояние в phi_states_ может заставить компилятор предположить, что верхние 32 бита гарантированно равны нулю, даже если это может быть не так. Это может привести к тому, что команда movl (которая обнуляет верхние 32 бита) будет пропущена, что приведет к неправильному выполнению последующих инструкций и потенциальным вулнам.
C++:
if (this-> IsPhi(узел)) {
Upper32BitsState current = phi_states_[это-> идентификатор(узла)];
... }
https:// chromium.googlesource.com/v8/v8/+/0ae8b7 389f188e6e7bff3163e766841bc9bfe06d/src/компилятор/серверная часть/селектор инструкций.cc#5737
Создав цикл phi-нод таким образом, что одна из phi-нод некорректно переходит в состояние kUpperBitsGuaranteedZero, мы можем манипулировать процессом оптимизации, что собвственно может привести к невозможности использования нулевого расширения movl -> использовать верхние 32 бита 64-разрядного регистра для хранения непреднамеренных данных. С этого момента нам открываются двери с эксплуатацией, где нулевое расширение имеет решающее значение, например, для обхода проверок безопасности или повреждения памяти.
Исходя из вышесказанного:
- ZeroExtendsWord32ToWord64 делает неверные предположения -> компилятор может сгенерировать небезопасный код, который пропускает критические операции
- Заставляя V8 обрабатывать сложный поток управления / вложенные циклы, мы можем заставить компилятор использовать устаревшие состояния phi (phi_states_)
Один из способов триггеринга баги:
- Манипулирование младшими 32-битами, чтобы вызвать потенциальные проблемы при обработке старших 32-бит
- Сброс phiState -> устаревшие phi_states_(рекурсивные вызовы в JIT-компиляции) -> некорректное поведение
Исправление:
C++:
diff --git a/src/compiler/backend/instruction-selector.cc b/src/compiler/backend/instruction-selector.cc
index da24485..b00ac82 100644
--- a/src/compiler/backend/instruction-selector.cc
+++ b/src/compiler/backend/instruction-selector.cc
@@ -5734,6 +5734,14 @@
const int kMaxRecursionDepth = 100;
if (this->IsPhi(node)) {
+ // Intermediate results from previous calls are not necessarily correct.
+ if (recursion_depth == 0) {
+ static_assert(sizeof(Upper32BitsState) == 1);
+ memset(phi_states_.data(),
+ static_cast<int>(Upper32BitsState::kNotYetChecked),
+ phi_states_.size());
+ }
+
Upper32BitsState current = phi_states_[this->id(node)];
if (current != Upper32BitsState::kNotYetChecked) {
return current == Upper32BitsState::kUpperBitsGuaranteedZero;
В патче добавили логику для очистки phi_states_ при глубине рекурсии 0 -> предотвращает повреждение устаревших данных при будущих оптимизациях.
Больше спасибо за незаконченный PoC @buptsb
CVE-2024-7965 unfinished poc
CVE-2024-7965 unfinished poc. GitHub Gist: instantly share code, notes, and snippets.
Для того, чтобы запустить триггер:
Bash:
$ cd ~
$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
$ export PATH=~/depot_tools:$PATH
$ mkdir v8
$ cd v8
$ fetch v8
$ cd v8
$ git checkout 0ae8b7389f188e6e7bff3163e766841bc9bfe06d
$ gclient sync -D
$ ./build/install-build-deps.sh
$ gn gen out/debug --args="v8_no_inline=true v8_optimized_debug=false is_component_build=false v8_expose_memory_corruption_api=true"
$ gn gen out/release --args="v8_no_inline=true is_debug=false v8_expose_memory_corruption_api=true"
$ ninja -C out/debug d8; ninja -C out/release d8
$ ./d8 --allow-natives-syntax --trace-turbo --trace_turbo_graph --trace_deopt --trace_compilation_dependencies --trace_wasm_compiler --code-comments --compile_hints_magic --trace-wasm-decoder --soft-abort --expose-gc poc.js
Последнее редактирование: