Browse Source

chore: cherry-pick 2 changes from 3-M129 (#44233)

* chore: [30-x-y] cherry-pick 2 changes from 3-M129

* 9542895cdd3d from v8
* 81155a8f3b20 from v8

* chore: update patches
Keeley Hammond 6 months ago
parent
commit
c936feeb98

+ 2 - 0
patches/v8/.patches

@@ -9,3 +9,5 @@ cherry-pick-901377bb2f3b.patch
 cherry-pick-bb28367eed73.patch
 cherry-pick-bc545b15a0ee.patch
 spill_all_loop_inputs_before_entering_loop.patch
+cherry-pick-9542895cdd3d.patch
+cherry-pick-81155a8f3b20.patch

+ 282 - 0
patches/v8/cherry-pick-81155a8f3b20.patch

@@ -0,0 +1,282 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Victor Gomes <[email protected]>
+Date: Wed, 2 Oct 2024 10:59:42 +0200
+Subject: Consider WasmStruct in InferHasInPrototypeChain
+
+Drive-by: add some CHECKs in not _clearly_ safe uses of AsJSObject
+to turn possible vulnerablities into crashes.
+
+Fixed: 367818758
+Change-Id: Ib0464658152ce87141fa137dc6562f17b84bb6be
+Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5901846
+Reviewed-by: Nico Hartmann <[email protected]>
+Auto-Submit: Victor Gomes <[email protected]>
+Commit-Queue: Nico Hartmann <[email protected]>
+Cr-Commit-Position: refs/heads/main@{#96386}
+
+diff --git a/src/compiler/access-info.cc b/src/compiler/access-info.cc
+index 9d022ba402d7bfdd5ca745a4193ee0de0e3d755e..54729abd77742fdd244e691606c86b308fdfa29c 100644
+--- a/src/compiler/access-info.cc
++++ b/src/compiler/access-info.cc
+@@ -915,6 +915,7 @@ PropertyAccessInfo AccessInfoFactory::ComputePropertyAccessInfo(
+       return PropertyAccessInfo::NotFound(zone(), receiver_map, holder);
+     }
+ 
++    CHECK(prototype.IsJSObject());
+     holder = prototype.AsJSObject();
+     map = map_prototype_map;
+ 
+diff --git a/src/compiler/heap-refs.cc b/src/compiler/heap-refs.cc
+index e31c586757b855ff713b702443e466364707d3c3..8f1022467f05285608329d2e14820e6f058cc634 100644
+--- a/src/compiler/heap-refs.cc
++++ b/src/compiler/heap-refs.cc
+@@ -1655,6 +1655,7 @@ HolderLookupResult FunctionTemplateInfoRef::LookupHolderOfExpectedType(
+   if (!expected_receiver_type->IsTemplateFor(prototype.object()->map())) {
+     return not_found;
+   }
++  CHECK(prototype.IsJSObject());
+   return HolderLookupResult(CallOptimization::kHolderFound,
+                             prototype.AsJSObject());
+ }
+diff --git a/src/compiler/js-native-context-specialization.cc b/src/compiler/js-native-context-specialization.cc
+index 2066b3f101e8b9111c0eb7f3bfc15470fc993d9d..f95a5c4094c4fa3a20d54551652fb00bbdbf6616 100644
+--- a/src/compiler/js-native-context-specialization.cc
++++ b/src/compiler/js-native-context-specialization.cc
+@@ -880,7 +880,9 @@ JSNativeContextSpecialization::InferHasInPrototypeChain(
+       // might be a different object each time, so it's much simpler to include
+       // {prototype}. That does, however, mean that we must check {prototype}'s
+       // map stability.
+-      if (!prototype.map(broker()).is_stable()) return kMayBeInPrototypeChain;
++      if (!prototype.IsJSObject() || !prototype.map(broker()).is_stable()) {
++        return kMayBeInPrototypeChain;
++      }
+       last_prototype = prototype.AsJSObject();
+     }
+     WhereToStart start = result == NodeProperties::kUnreliableMaps
+diff --git a/test/mjsunit/wasm/regress-367818758.js b/test/mjsunit/wasm/regress-367818758.js
+new file mode 100644
+index 0000000000000000000000000000000000000000..69e8290c88d85699f9845ed5dddb767f1be40441
+--- /dev/null
++++ b/test/mjsunit/wasm/regress-367818758.js
+@@ -0,0 +1,221 @@
++// Copyright 2024 the V8 project authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++//
++// Flags: --allow-natives-syntax
++
++var kWasmH0 = 0;
++var kWasmH1 = 0x61;
++var kWasmH2 = 0x73;
++var kWasmH3 = 0x6d;
++var kWasmV0 = 0x1;
++var kWasmV1 = 0;
++var kWasmV2 = 0;
++var kWasmV3 = 0;
++let kTypeSectionCode = 1;        // Function signature declarations
++let kFunctionSectionCode = 3;    // Function declarations
++let kExportSectionCode = 7;      // Exports
++let kCodeSectionCode = 10;       // Function code
++let kWasmFunctionTypeForm = 0x60;
++let kWasmStructTypeForm = 0x5f;
++let kNoSuperType = 0xFFFFFFFF;
++let kWasmI32 = 0x7f;
++let kWasmExternRef = -0x11;
++let kLeb128Mask = 0x7f;
++let kExternalFunction = 0;
++function makeSig(params, results) {
++  return {params: params, results: results};
++}
++const kWasmOpcodes = {
++  'End': 0x0b,
++  'I32Const': 0x41,
++};
++function defineWasmOpcode(name, value) {
++  Object.defineProperty(globalThis, name, {value: value});
++}
++for (let name in kWasmOpcodes) {
++  defineWasmOpcode(`kExpr${name}`, kWasmOpcodes[name]);
++}
++const kPrefixOpcodes = {
++  'GC': 0xfb,
++};
++for (let prefix in kPrefixOpcodes) {
++  defineWasmOpcode(`k${prefix}Prefix`, kPrefixOpcodes[prefix]);
++}
++let kExprStructNew = 0x00;
++let kExprExternConvertAny = 0x1b;
++class Binary {
++  constructor() {
++    this.length = 0;
++    this.buffer = new Uint8Array(8192);
++  }
++  trunc_buffer() {
++    return new Uint8Array(this.buffer.buffer, 0, this.length);
++  }
++  emit_u8(val) {
++    this.buffer[this.length++] = val;
++  }
++  emit_leb_u(val) {
++      let v = val & 0xff;
++        this.buffer[this.length++] = v;
++  }
++  emit_u32v(val) {
++    this.emit_leb_u(val);
++  }
++  emit_bytes(data) {
++    this.buffer.set(data, this.length);
++    this.length += data.length;
++  }
++  emit_string(string) {
++    let string_utf8 = string;
++    this.emit_u32v(string_utf8.length);
++    for (let i = 0; i < string_utf8.length; i++) {
++      this.emit_u8(string_utf8.charCodeAt(i));
++    }
++  }
++  emit_type(type) {
++      this.emit_u8(type >= 0 ? type : type & kLeb128Mask);
++  }
++  emit_header() {
++    this.emit_bytes([
++      kWasmH0, kWasmH1, kWasmH2, kWasmH3, kWasmV0, kWasmV1, kWasmV2, kWasmV3
++    ]);
++  }
++  emit_section(section_code, content_generator) {
++    this.emit_u8(section_code);
++    const section = new Binary;
++    content_generator(section);
++    this.emit_u32v(section.length);
++    this.emit_bytes(section.trunc_buffer());
++  }
++}
++class WasmFunctionBuilder {
++  constructor(module, name, type_index, arg_names) {
++    this.module = module;
++    this.name = name;
++    this.type_index = type_index;
++  }
++  exportAs(name) {
++    this.module.addExport(name, this.index);
++  }
++  exportFunc() {
++    this.exportAs(this.name);
++    return this;
++  }
++  addBody(body) {
++    this.body = body.concat([kExprEnd]);
++  }
++}
++function makeField(type, mutability) {
++  return {type: type, mutability: mutability};
++}
++class WasmStruct {
++  constructor(fields) {
++    this.fields = fields;
++  }
++}
++class WasmModuleBuilder {
++  constructor() {
++    this.types = [];
++    this.exports = [];
++    this.functions = [];
++  }
++  addType(type, supertype_idx = kNoSuperType, is_final = true,
++      is_shared = false) {
++    var type_copy = {params: type.params, results: type.results,
++                     is_final: is_final, is_shared: is_shared,
++                     supertype: supertype_idx};
++    this.types.push(type_copy);
++    return this.types.length - 1;
++  }
++  addStruct(fields = kNoSuperType = false, is_shared = false) {
++    this.types.push(new WasmStruct(fields));
++  }
++  addFunction(name, type, arg_names) {
++    let type_index =typeof type == 'number' ? type : this.addType(type);
++    let func = new WasmFunctionBuilder(this, name, type_index);
++    this.functions.push(func);
++    return func;
++  }
++  addExport(name, index) {
++    this.exports.push({name: name, kind: kExternalFunction, index: index});
++  }
++  toBuffer() {
++    let binary = new Binary;
++    let wasm = this;
++    binary.emit_header();
++      binary.emit_section(kTypeSectionCode, section => {
++        let length_with_groups = wasm.types.length;
++        section.emit_u32v(length_with_groups);
++        for (let i = 0; i < wasm.types.length; i++) {
++          let type = wasm.types[i];
++          if (type instanceof WasmStruct) {
++            section.emit_u8(kWasmStructTypeForm);
++            section.emit_u32v(type.fields.length);
++            for (let field of type.fields) {
++              section.emit_type(field.type);
++              section.emit_u8();
++            }
++          } else {
++            section.emit_u8(kWasmFunctionTypeForm);
++            section.emit_u32v();
++            section.emit_u32v(type.results.length);
++            for (let result of type.results) {
++              section.emit_type(result);
++            }
++          }
++        }
++      });
++      binary.emit_section(kFunctionSectionCode, section => {
++        section.emit_u32v(wasm.functions.length);
++        for (let func of wasm.functions) {
++          section.emit_u32v(func.type_index);
++        }
++      });
++    var exports_count = wasm.exports.length;
++      binary.emit_section(kExportSectionCode, section => {
++        section.emit_u32v(exports_count);
++        for (let exp of wasm.exports) {
++          section.emit_string(exp.name);
++          section.emit_u8();
++          section.emit_u32v();
++        }
++      });
++      binary.emit_section(kCodeSectionCode, section => {
++        section.emit_u32v(wasm.functions.length);
++        for (let func of wasm.functions) {
++            section.emit_u32v(func.body.length + 1);
++            section.emit_u8();  // 0 locals.
++          section.emit_bytes(func.body);
++        }
++      });
++    return binary.trunc_buffer();
++  }
++  instantiate() {
++    let module = this.toModule();
++    let instance = new WebAssembly.Instance(module);
++    return instance;
++  }
++  toModule() {
++    return new WebAssembly.Module(this.toBuffer());
++  }
++}
++let builder = new WasmModuleBuilder();
++let struct_type = builder.addStruct([makeField(kWasmI32)]);
++builder.addFunction('MakeStruct', makeSig([], [kWasmExternRef])).exportFunc()
++       .addBody([kExprI32Const, 42, kGCPrefix, kExprStructNew, struct_type,
++                 kGCPrefix, kExprExternConvertAny]);
++let instance = builder.instantiate();
++let evil_wasm_object = instance.exports.MakeStruct();
++function evil_ctor(){
++}
++function evil_cast_jit(evil_o){
++    global_collect_node_info = evil_o; // get nodeinfo from PropertyCellStore
++    return evil_o instanceof evil_ctor;
++}
++evil_ctor.prototype = evil_wasm_object;
++%PrepareFunctionForOptimization(evil_cast_jit);
++evil_cast_jit(new evil_ctor());
++evil_cast_jit(new evil_ctor());
++%OptimizeFunctionOnNextCall(evil_cast_jit);
++evil_cast_jit();

+ 145 - 0
patches/v8/cherry-pick-9542895cdd3d.patch

@@ -0,0 +1,145 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jakob Kummerow <[email protected]>
+Date: Tue, 24 Sep 2024 17:34:49 +0200
+Subject: Properly check max module size
+
+and allow d8-based tests for it.
+
+Fixed: 368241697
+Change-Id: Iddc9f7e669de7a1d79dccbc99bcc5fb43dad67a1
+Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5886728
+Reviewed-by: Clemens Backes <[email protected]>
+Reviewed-by: Matthias Liedtke <[email protected]>
+Auto-Submit: Jakob Kummerow <[email protected]>
+Commit-Queue: Jakob Kummerow <[email protected]>
+Cr-Commit-Position: refs/heads/main@{#96272}
+
+diff --git a/src/wasm/streaming-decoder.cc b/src/wasm/streaming-decoder.cc
+index 786c5aa250f055a0f69ca28403bfa679638e6465..9eb2d2fb9f1e973b04c8cf7829cc7b2849b632f9 100644
+--- a/src/wasm/streaming-decoder.cc
++++ b/src/wasm/streaming-decoder.cc
+@@ -294,6 +294,10 @@ void AsyncStreamingDecoder::Finish(bool can_use_compiled_module) {
+   if (!full_wire_bytes_.back().empty()) {
+     size_t total_length = 0;
+     for (auto& bytes : full_wire_bytes_) total_length += bytes.size();
++    if (ok()) {
++      // {DecodeSectionLength} enforces this with graceful error reporting.
++      CHECK_LE(total_length, max_module_size());
++    }
+     auto all_bytes = base::OwnedVector<uint8_t>::NewForOverwrite(total_length);
+     uint8_t* ptr = all_bytes.begin();
+     for (auto& bytes : full_wire_bytes_) {
+@@ -627,6 +631,18 @@ std::unique_ptr<AsyncStreamingDecoder::DecodingState>
+ AsyncStreamingDecoder::DecodeSectionLength::NextWithValue(
+     AsyncStreamingDecoder* streaming) {
+   TRACE_STREAMING("DecodeSectionLength(%zu)\n", value_);
++  // Check if this section fits into the overall module length limit.
++  // Note: {this->module_offset_} is the position of the section ID byte,
++  // {streaming->module_offset_} is the start of the section's payload (i.e.
++  // right after the just-decoded section length varint).
++  // The latter can already exceed the max module size, when the previous
++  // section barely fit into it, and this new section's ID or length crossed
++  // the threshold.
++  uint32_t payload_start = streaming->module_offset();
++  size_t max_size = max_module_size();
++  if (payload_start > max_size || max_size - payload_start < value_) {
++    return streaming->ToErrorState();
++  }
+   SectionBuffer* buf =
+       streaming->CreateNewBuffer(module_offset_, section_id_, value_,
+                                  buffer().SubVector(0, bytes_consumed_));
+diff --git a/src/wasm/wasm-engine.cc b/src/wasm/wasm-engine.cc
+index 68d01897f7b6be5d19c74eac71851694c260ac8b..f79578c29e162ee64285bc757aa716cb7fdea645 100644
+--- a/src/wasm/wasm-engine.cc
++++ b/src/wasm/wasm-engine.cc
+@@ -1911,10 +1911,11 @@ uint32_t max_table_init_entries() {
+ 
+ // {max_module_size} is declared in wasm-limits.h.
+ size_t max_module_size() {
+-  // Clamp the value of --wasm-max-module-size between 16 and just below 2GB.
++  // Clamp the value of --wasm-max-module-size between 16 and the maximum
++  // that the implementation supports.
+   constexpr size_t kMin = 16;
+-  constexpr size_t kMax = RoundDown<kSystemPointerSize>(size_t{kMaxInt});
+-  static_assert(kMin <= kV8MaxWasmModuleSize && kV8MaxWasmModuleSize <= kMax);
++  constexpr size_t kMax = kV8MaxWasmModuleSize;
++  static_assert(kMin <= kV8MaxWasmModuleSize);
+   return std::clamp(v8_flags.wasm_max_module_size.value(), kMin, kMax);
+ }
+ 
+diff --git a/src/wasm/wasm-js.cc b/src/wasm/wasm-js.cc
+index dc5cf36c26326c5787fe99b17651bc2af5d157ea..52e0bcf5b71f76891c257b36a0eb9b36178ab9d3 100644
+--- a/src/wasm/wasm-js.cc
++++ b/src/wasm/wasm-js.cc
+@@ -194,8 +194,8 @@ GET_FIRST_ARGUMENT_AS(Tag)
+ #undef GET_FIRST_ARGUMENT_AS
+ 
+ i::wasm::ModuleWireBytes GetFirstArgumentAsBytes(
+-    const v8::FunctionCallbackInfo<v8::Value>& info, ErrorThrower* thrower,
+-    bool* is_shared) {
++    const v8::FunctionCallbackInfo<v8::Value>& info, size_t max_length,
++    ErrorThrower* thrower, bool* is_shared) {
+   DCHECK(i::ValidateCallbackInfo(info));
+   const uint8_t* start = nullptr;
+   size_t length = 0;
+@@ -226,7 +226,6 @@ i::wasm::ModuleWireBytes GetFirstArgumentAsBytes(
+   if (length == 0) {
+     thrower->CompileError("BufferSource argument is empty");
+   }
+-  size_t max_length = i::wasm::max_module_size();
+   if (length > max_length) {
+     // The spec requires a CompileError for implementation-defined limits, see
+     // https://webassembly.github.io/spec/js-api/index.html#limits.
+@@ -609,7 +608,8 @@ void WebAssemblyCompileImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
+       new AsyncCompilationResolver(isolate, context, promise_resolver));
+ 
+   bool is_shared = false;
+-  auto bytes = GetFirstArgumentAsBytes(info, &thrower, &is_shared);
++  auto bytes = GetFirstArgumentAsBytes(info, i::wasm::max_module_size(),
++                                       &thrower, &is_shared);
+   if (thrower.error()) {
+     resolver->OnCompilationFailed(thrower.Reify());
+     return;
+@@ -641,8 +641,11 @@ void WasmStreamingCallbackForTesting(
+       v8::WasmStreaming::Unpack(info.GetIsolate(), info.Data());
+ 
+   bool is_shared = false;
++  // We don't check the buffer length up front, to allow d8 to test that the
++  // streaming decoder implementation handles overly large inputs correctly.
++  size_t unlimited = std::numeric_limits<size_t>::max();
+   i::wasm::ModuleWireBytes bytes =
+-      GetFirstArgumentAsBytes(info, &thrower, &is_shared);
++      GetFirstArgumentAsBytes(info, unlimited, &thrower, &is_shared);
+   if (thrower.error()) {
+     streaming->Abort(Utils::ToLocal(thrower.Reify()));
+     return;
+@@ -744,7 +747,8 @@ void WebAssemblyValidateImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
+   ErrorThrower thrower(i_isolate, "WebAssembly.validate()");
+ 
+   bool is_shared = false;
+-  auto bytes = GetFirstArgumentAsBytes(info, &thrower, &is_shared);
++  auto bytes = GetFirstArgumentAsBytes(info, i::wasm::max_module_size(),
++                                       &thrower, &is_shared);
+ 
+   v8::ReturnValue<v8::Value> return_value = info.GetReturnValue();
+ 
+@@ -823,7 +827,8 @@ void WebAssemblyModuleImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
+   }
+ 
+   bool is_shared = false;
+-  auto bytes = GetFirstArgumentAsBytes(info, &thrower, &is_shared);
++  auto bytes = GetFirstArgumentAsBytes(info, i::wasm::max_module_size(),
++                                       &thrower, &is_shared);
+ 
+   if (thrower.error()) {
+     return;
+@@ -1140,7 +1145,8 @@ void WebAssemblyInstantiateImpl(
+   }
+ 
+   bool is_shared = false;
+-  auto bytes = GetFirstArgumentAsBytes(info, &thrower, &is_shared);
++  auto bytes = GetFirstArgumentAsBytes(info, i::wasm::max_module_size(),
++                                       &thrower, &is_shared);
+   if (thrower.error()) {
+     resolver->OnInstantiationFailed(thrower.Reify());
+     return;