Commit 4b6ee115 authored by Clemens Backes's avatar Clemens Backes Committed by V8 LUCI CQ

[asm] Fix importing monkey-patched objects

This fixes a long-standing TODO to disallow importing receivers that
have "toString" or "valueOf" patched. Calling those methods could have
observable side effects, so allowing that would require bigger
refactorings to ensure that we only call each such function exactly once
per import, and in the right order.
Since this use case is rare, we just forbid importing such receivers.

R=jkummerow@chromium.org

Bug: chromium:1248677
Change-Id: I99bbd7db950ec3c7ac9cc1f59e8c476688e7d7b6
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3190475Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#77149}
parent 3600aabf
......@@ -848,6 +848,39 @@ MaybeHandle<Object> InstanceBuilder::LookupImport(uint32_t index,
return result;
}
namespace {
bool HasDefaultToNumberBehaviour(Isolate* isolate,
Handle<JSFunction> function) {
// Disallow providing a [Symbol.toPrimitive] member.
LookupIterator to_primitive_it{isolate, function,
isolate->factory()->to_primitive_symbol()};
if (to_primitive_it.state() != LookupIterator::NOT_FOUND) return false;
// The {valueOf} member must be the default "ObjectPrototypeValueOf".
LookupIterator value_of_it{isolate, function,
isolate->factory()->valueOf_string()};
if (value_of_it.state() != LookupIterator::DATA) return false;
Handle<Object> value_of = value_of_it.GetDataValue();
if (!value_of->IsJSFunction()) return false;
Builtin value_of_builtin_id =
Handle<JSFunction>::cast(value_of)->code().builtin_id();
if (value_of_builtin_id != Builtin::kObjectPrototypeValueOf) return false;
// The {toString} member must be the default "FunctionPrototypeToString".
LookupIterator to_string_it{isolate, function,
isolate->factory()->toString_string()};
if (to_string_it.state() != LookupIterator::DATA) return false;
Handle<Object> to_string = to_string_it.GetDataValue();
if (!to_string->IsJSFunction()) return false;
Builtin to_string_builtin_id =
Handle<JSFunction>::cast(to_string)->code().builtin_id();
if (to_string_builtin_id != Builtin::kFunctionPrototypeToString) return false;
// Just a default function, which will convert to "Nan". Accept this.
return true;
}
} // namespace
// Look up an import value in the {ffi_} object specifically for linking an
// asm.js module. This only performs non-observable lookups, which allows
// falling back to JavaScript proper (and hence re-executing all lookups) if
......@@ -862,7 +895,6 @@ MaybeHandle<Object> InstanceBuilder::LookupImportAsm(
// Perform lookup of the given {import_name} without causing any observable
// side-effect. We only accept accesses that resolve to data properties,
// which is indicated by the asm.js spec in section 7 ("Linking") as well.
Handle<Object> result;
PropertyKey key(isolate_, Handle<Name>::cast(import_name));
LookupIterator it(isolate_, ffi_.ToHandleChecked(), key);
switch (it.state()) {
......@@ -876,14 +908,23 @@ MaybeHandle<Object> InstanceBuilder::LookupImportAsm(
case LookupIterator::NOT_FOUND:
// Accepting missing properties as undefined does not cause any
// observable difference from JavaScript semantics, we are lenient.
result = isolate_->factory()->undefined_value();
break;
case LookupIterator::DATA:
result = it.GetDataValue();
break;
return isolate_->factory()->undefined_value();
case LookupIterator::DATA: {
Handle<Object> value = it.GetDataValue();
// For legacy reasons, we accept functions for imported globals (see
// {ProcessImportedGlobal}), but only if we can easily determine that
// their Number-conversion is side effect free and returns NaN (which is
// the case as long as "valueOf" (or others) are not overwritten).
if (value->IsJSFunction() &&
module_->import_table[index].kind == kExternalGlobal &&
!HasDefaultToNumberBehaviour(isolate_,
Handle<JSFunction>::cast(value))) {
return ReportLinkError("function has special ToNumber behaviour", index,
import_name);
}
return value;
}
}
return result;
}
// Load data segments into the memory.
......@@ -1359,9 +1400,9 @@ bool InstanceBuilder::ProcessImportedGlobal(Handle<WasmInstanceObject> instance,
// Accepting {JSFunction} on top of just primitive values here is a
// workaround to support legacy asm.js code with broken binding. Note
// that using {NaN} (or Smi::zero()) here is what using the observable
// conversion via {ToPrimitive} would produce as well.
// TODO(wasm): Still observable if Function.prototype.valueOf or friends
// are patched, we might need to check for that as well.
// conversion via {ToPrimitive} would produce as well. {LookupImportAsm}
// checked via {HasDefaultToNumberBehaviour} that "valueOf" or friends have
// not been patched.
if (value->IsJSFunction()) value = isolate_->factory()->nan_value();
if (value->IsPrimitive()) {
MaybeHandle<Object> converted = global.type == kWasmI32
......
// Copyright 2021 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.
function setup_proxy() {
// Mess with the prototype to get funky conversion behavior.
Function.prototype.__proto__ = new Proxy(setup_proxy, {
get: async (target, key) => {
console.log(key);
}
});
}
setup_proxy();
function asm(global, imports) {
'use asm';
// Trigger proxy trap when looking up #toPrimitive:
var bar = +imports.bar;
function f() {}
return {f: f};
}
assertThrows(() => asm(undefined, {bar: setup_proxy}), TypeError);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment