Skip to content

Commit

Permalink
Cherry-pick 272448.103@safari-7618-branch (e3a7580). https://bugs.web…
Browse files Browse the repository at this point in the history
…kit.org/show_bug.cgi?id=267425

    [JSC] get_by_id_with_this + ProxyObject can leak JSScope objects
    https://bugs.webkit.org/show_bug.cgi?id=267425
    <rdar://120777816>

    Reviewed by Yusuke Suzuki and Justin Michaud.

    According to the spec [1], `var base = { foo }; with (base) foo();` should be called with `this`
    value of `base`, which is why FunctionCallResolveNode moves resolved scope to thisRegister().
    That is arguably a bad design, and there is an effort [2] to abolish using JSScope as `this` value.

    When `this` value is accessed by JS code, it's being sanitized via ToThis (JSScope replaced with
    `undefined`), yet not in case of `super.property` access calling into ProxyObject `get` trap,
    which passes raw `this` value as receiver parameter, leaking JSScope to be exploited.

    For performance reasons, we can't call toThis() whenever `get_by_id_with_this` is used, so this
    change introduces @tothis() intrinsic specifically for ProxyObject IC helpers, tweaks DFG to respect
    `m_srcDst`, and also fixes baseline code.

    Inlineability of ProxyObject IC helpers was verified to remain unaffected (`performProxyObjectGet`
    is smaller then 120 while other helpers were already exceeding inline size limit).

    [1]: https://tc39.es/ecma262/#sec-evaluatecall (step 1.b.iii)
    [2]: https://bugs.webkit.org/show_bug.cgi?id=225397

    * JSTests/stress/regress-120777816.js: Added.
    * Source/JavaScriptCore/builtins/ProxyHelpers.js:
    (linkTimeConstant.performProxyObjectGet):
    (linkTimeConstant.performProxyObjectGetByVal):
    (linkTimeConstant.performProxyObjectSetSloppy):
    (linkTimeConstant.performProxyObjectSetStrict):
    * Source/JavaScriptCore/bytecode/BytecodeIntrinsicRegistry.h:
    * Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:
    (JSC::BytecodeGenerator::emitToThis):
    * Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h:
    (JSC::BytecodeGenerator::emitToThis):
    * Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp:
    (JSC::BytecodeIntrinsicNode::emit_intrinsic_toThis):
    * Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp:
    (JSC::DFG::ByteCodeParser::parseBlock):
    (JSC::DFG::ByteCodeParser::getThis): Deleted.
    (JSC::DFG::ByteCodeParser::setThis): Deleted.
    * Source/JavaScriptCore/runtime/ProxyObject.cpp:
    (JSC::performProxyGet):
    (JSC::ProxyObject::performPut):

    Canonical link: https://commits.webkit.org/272448.103@safari-7618-branch

Canonical link: https://commits.webkit.org/274313.65@webkitglib/2.44
  • Loading branch information
Alexey Shvayka authored and aperezdc committed Mar 11, 2024
1 parent b1cf6ce commit a625ceb
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 22 deletions.
96 changes: 96 additions & 0 deletions JSTests/stress/regress-120777816.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
function assert(x) {
if (!x)
throw new Error("Bad assertion!");
}

(function() {
function tryToLeakThisViaGetById() {
class Leaker {
leak() {
return super.foo;
}
}

Leaker.prototype.__proto__ = new Proxy({}, {
get(target, propertyName, receiver) {
return receiver;
}
});

const foo = 42;
const {leak} = Leaker.prototype;

return (() => leak())();
}

function tryToLeakThisViaGetByVal() {
class Leaker {
leak() {
return super[Math.random() < 0.5 ? "foo" : "bar"];
}
}

Leaker.prototype.__proto__ = new Proxy({}, {
get(target, propertyName, receiver) {
return receiver;
}
});

const foo = 42;
const bar = 84;
const {leak} = Leaker.prototype;

return (() => leak())();
}

function tryToLeakThisViaSetById() {
let receiver;
class Leaker {
leak() {
super.foo = {};
return receiver;
}
}
Leaker.prototype.__proto__ = new Proxy({}, {
set(target, propertyName, value, __receiver) {
receiver = __receiver;
return true;
}
});

const foo = 42;
const {leak} = Leaker.prototype;

return (() => leak())();
}

function tryToLeakThisViaSetByVal() {
let receiver;
class Leaker {
leak() {
super[Math.random() < 0.5 ? "foo" : "bar"] = {};
return receiver;
}
}

Leaker.prototype.__proto__ = new Proxy({}, {
set(target, propertyName, value, __receiver) {
receiver = __receiver;
return true;
}
});

const foo = 42;
const bar = 84;
const {leak} = Leaker.prototype;

return (() => leak())();
}

for (var i = 0; i < 1e5; i++) {
assert(tryToLeakThisViaGetById() === undefined);
assert(tryToLeakThisViaGetByVal() === undefined);
assert(tryToLeakThisViaSetById() === undefined);
assert(tryToLeakThisViaSetByVal() === undefined);
}
})();
8 changes: 4 additions & 4 deletions Source/JavaScriptCore/builtins/ProxyHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function performProxyObjectGet(propertyName, receiver)
if (!@isCallable(trap))
@throwTypeError("'get' property of a Proxy's handler should be callable");

var trapResult = trap.@call(handler, target, propertyName, receiver);
var trapResult = trap.@call(handler, target, propertyName, @toThis(receiver));

if (@mustValidateResultOfProxyGetAndSetTraps(target))
@handleProxyGetTrapResult(trapResult, target, propertyName);
Expand All @@ -95,7 +95,7 @@ function performProxyObjectGetByVal(propertyName, receiver)
if (!@isCallable(trap))
@throwTypeError("'get' property of a Proxy's handler should be callable");

var trapResult = trap.@call(handler, target, propertyName, receiver);
var trapResult = trap.@call(handler, target, propertyName, @toThis(receiver));

if (@mustValidateResultOfProxyGetAndSetTraps(target))
@handleProxyGetTrapResult(trapResult, target, propertyName);
Expand Down Expand Up @@ -123,7 +123,7 @@ function performProxyObjectSetSloppy(propertyName, receiver, value)
if (!@isCallable(trap))
@throwTypeError("'set' property of a Proxy's handler should be callable");

if (!trap.@call(handler, target, propertyName, value, receiver))
if (!trap.@call(handler, target, propertyName, value, @toThis(receiver)))
return;

if (@mustValidateResultOfProxyGetAndSetTraps(target))
Expand All @@ -150,7 +150,7 @@ function performProxyObjectSetStrict(propertyName, receiver, value)
if (!@isCallable(trap))
@throwTypeError("'set' property of a Proxy's handler should be callable");

if (!trap.@call(handler, target, propertyName, value, receiver))
if (!trap.@call(handler, target, propertyName, value, @toThis(receiver)))
@throwTypeError("Proxy object's 'set' trap returned falsy value for property '" + @String(propertyName) + "'");

if (@mustValidateResultOfProxyGetAndSetTraps(target))
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/bytecode/BytecodeIntrinsicRegistry.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ enum class LinkTimeConstant : int32_t;
macro(toString) \
macro(toPropertyKey) \
macro(toObject) \
macro(toThis) \
macro(mustValidateResultOfProxyGetAndSetTraps) \
macro(mustValidateResultOfProxyTrapsExceptGetAndSet) \
macro(newArrayWithSize) \
Expand Down
5 changes: 3 additions & 2 deletions Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5584,9 +5584,10 @@ void StaticPropertyAnalysis::record()
}
}

void BytecodeGenerator::emitToThis()
RegisterID* BytecodeGenerator::emitToThis(RegisterID* srcDst)
{
OpToThis::emit(this, kill(&m_thisRegister), ecmaMode(), nextValueProfileIndex());
OpToThis::emit(this, kill(srcDst), ecmaMode(), nextValueProfileIndex());
return srcDst;
}

ForInContext* BytecodeGenerator::findForInContext(RegisterID* property)
Expand Down
3 changes: 2 additions & 1 deletion Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,7 @@ namespace JSC {
RegisterID* emitToNumeric(RegisterID* dst, RegisterID* src);
RegisterID* emitToString(RegisterID* dst, RegisterID* src);
RegisterID* emitToObject(RegisterID* dst, RegisterID* src, const Identifier& message);
RegisterID* emitToThis(RegisterID* srcDst);
RegisterID* emitInc(RegisterID* srcDst);
RegisterID* emitDec(RegisterID* srcDst);

Expand Down Expand Up @@ -1078,7 +1079,7 @@ namespace JSC {
bool isNewTargetUsedInInnerArrowFunction();
bool isArgumentsUsedInInnerArrowFunction();

void emitToThis();
void emitToThis() { emitToThis(&m_thisRegister); }

RegisterID* emitMove(RegisterID* dst, RegisterID* src);

Expand Down
9 changes: 9 additions & 0 deletions Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2027,6 +2027,15 @@ RegisterID* BytecodeIntrinsicNode::emit_intrinsic_toObject(BytecodeGenerator& ge
return generator.move(dst, generator.emitToObject(temp.get(), src.get(), generator.vm().propertyNames->emptyIdentifier));
}

RegisterID* BytecodeIntrinsicNode::emit_intrinsic_toThis(BytecodeGenerator& generator, RegisterID* dst)
{
ArgumentListNode* node = m_args->m_listNode;
RefPtr<RegisterID> src = generator.emitNode(node);
ASSERT(!node->m_next);

return generator.move(dst, generator.emitToThis(src.get()));
}

RegisterID* BytecodeIntrinsicNode::emit_intrinsic_idWithProfile(BytecodeGenerator& generator, RegisterID* dst)
{
ArgumentListNode* node = m_args->m_listNode;
Expand Down
15 changes: 2 additions & 13 deletions Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -769,17 +769,6 @@ class ByteCodeParser {
return jsConstant(m_graph.freeze(constantValue));
}

// Helper functions to get/set the this value.
Node* getThis()
{
return get(m_inlineStackTop->m_codeBlock->thisRegister());
}

void setThis(Node* value)
{
set(m_inlineStackTop->m_codeBlock->thisRegister(), value);
}

InlineCallFrame* inlineCallFrame()
{
return m_inlineStackTop->m_inlineCallFrame;
Expand Down Expand Up @@ -6227,8 +6216,8 @@ void ByteCodeParser::parseBlock(unsigned limit)
}

case op_to_this: {
Node* op1 = getThis();
auto bytecode = currentInstruction->as<OpToThis>();
Node* op1 = get(VirtualRegister(bytecode.m_srcDst));
auto& metadata = bytecode.metadata(codeBlock);
StructureID cachedStructureID = metadata.m_cachedStructureID;
Structure* cachedStructure = nullptr;
Expand All @@ -6240,7 +6229,7 @@ void ByteCodeParser::parseBlock(unsigned limit)
|| cachedStructure->classInfoForCells()->isSubClassOf(JSScope::info())
|| m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCache)
|| (op1->op() == GetLocal && op1->variableAccessData()->structureCheckHoistingFailed())) {
setThis(addToGraph(ToThis, OpInfo(bytecode.m_ecmaMode), OpInfo(getPrediction()), op1));
set(bytecode.m_srcDst, addToGraph(ToThis, OpInfo(bytecode.m_ecmaMode), OpInfo(getPrediction()), op1));
} else {
addToGraph(
CheckStructure,
Expand Down
4 changes: 2 additions & 2 deletions Source/JavaScriptCore/runtime/ProxyObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ static JSValue performProxyGet(JSGlobalObject* globalObject, ProxyObject* proxyO
MarkedArgumentBuffer arguments;
arguments.append(target);
arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(vm, propertyName.uid())));
arguments.append(receiver);
arguments.append(receiver.toThis(globalObject, ECMAMode::strict()));
ASSERT(!arguments.hasOverflowed());
JSValue trapResult = call(globalObject, getHandler, callData, handler, arguments);
RETURN_IF_EXCEPTION(scope, { });
Expand Down Expand Up @@ -517,7 +517,7 @@ bool ProxyObject::performPut(JSGlobalObject* globalObject, JSValue putValue, JSV
arguments.append(target);
arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(vm, propertyName.uid())));
arguments.append(putValue);
arguments.append(thisValue);
arguments.append(thisValue.toThis(globalObject, ECMAMode::strict()));
ASSERT(!arguments.hasOverflowed());
JSValue trapResult = call(globalObject, setMethod, callData, handler, arguments);
RETURN_IF_EXCEPTION(scope, false);
Expand Down

0 comments on commit a625ceb

Please sign in to comment.