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

CVE-2024-7965 - Root Cause Analysis

xvonfers

RAID-массив
Пользователь
Регистрация
08.06.2024
Сообщения
53
Реакции
58
[$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). Но на самом деле не похоже на то, чтобы данную багу можно экслуатировать независимо(скорее всего - взаимодействовать с выводом компилятора для чейна с другой багой).

Уязвимый код:
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 бит.
2.Верхние 32 бита не обнуляются:
  • Неправильное состояние в phi_states_ может заставить компилятор предположить, что верхние 32 бита гарантированно равны нулю, даже если это может быть не так. Это может привести к тому, что команда movl (которая обнуляет верхние 32 бита) будет пропущена, что приведет к неправильному выполнению последующих инструкций и потенциальным вулнам.
Уязвимому коду не удается сбросить phi_states_ перед рекурсивными вызовами:
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
"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." -

Для того, чтобы запустить триггер:
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
 
Последнее редактирование:


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