Strict global assignment recreates a deleted binding instead of throwing
Categories
(Core :: JavaScript Engine: JIT, defect, P3)
Tracking
()
People
(Reporter: bugmon, Unassigned)
References
(Blocks 1 open bug)
Details
(Keywords: testcase)
Attachments
(3 files)
|
1.06 KB,
patch
|
Details | Diff | Splinter Review | |
|
1.87 KB,
text/plain
|
Details | |
|
462 bytes,
text/plain
|
Details |
SetPropIRGenerator::tryAttachMegamorphicSetSlot admits strict name assignments to an ordinary property-store stub without preserving their unqualified-name semantics. SetPropertyMegamorphic can consequently add a missing property instead of producing the ReferenceError enforced by SetNameOperation.
A web page can bypass strict-mode error handling and introduce an undeclared global by triggering a megamorphic global-name store after its binding has been removed. Independent testing confirms that the optimized call returns normally and creates an own global property with value 5. No memory corruption or privilege-boundary crossing was demonstrated.
This finding was discovered during variant analysis seeded by bug 1883542.
Build Info
- Branch: main
- Revision: 0a5c5bffebd796cedba281419efd2f8f3cc7c85d
- Timestamp: 2026-09-25T04:35:53+00:00
Affected Code
File: js/src/jit/CacheIR.cpp, line 5361-5370
AttachDecision SetPropIRGenerator::tryAttachMegamorphicSetSlot(
HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) {
if (mode_ != ICState::Mode::Megamorphic || cacheKind_ != CacheKind::SetProp) {
return AttachDecision::NoAction;
}
writer.megamorphicStoreSlot(objId, id, rhsId, IsStrictSetPC(pc_));
trackAttached("SetProp.MegamorphicNativeSlot");
return AttachDecision::Attach;
}
File: js/src/jit/CacheIRCompiler.cpp, line 9873-9875
using Fn = bool (*)(JSContext*, HandleObject, HandleId, HandleValue, bool);
callvm.callNoResult<Fn, SetPropertyMegamorphic<false>>();
return true;
File: js/src/jit/VMFunctions.cpp, line 2451-2469
template <bool Cached>
bool SetPropertyMegamorphic(JSContext* cx, HandleObject obj, HandleId id,
HandleValue value, bool strict) {
if (obj->is<NativeObject>() &&
obj.as<NativeObject>()->canDoSetPropertyFastpath()) {
bool optimized = false;
if (!TryAddOrSetNativeObjectProperty<Cached>(cx, obj.as<NativeObject>(), id,
value, &optimized)) {
return false;
}
if (optimized) {
return true;
}
}
Rooted<Value> receiver(cx, ObjectValue(*obj));
ObjectOpResult result;
return SetProperty(cx, obj, id, value, receiver, result) &&
result.checkStrictModeError(cx, obj, id, strict);
}
File: js/src/vm/Interpreter-inl.h, line 243-247
ok = NativeSetProperty<Unqualified>(cx, varobj, id, val, receiver, result);
} else {
ok = SetProperty(cx, env, id, val, receiver, result);
}
return ok && result.checkStrictModeError(cx, env, id, strict);
DoSetPropFallback constructs the shared CacheKind::SetProp generator for name assignments at js/src/jit/BaselineIC.cpp:1482. The megamorphic attachment retains strictness but loses the distinction between qualified property stores and unqualified name assignments. Its VM helper can add a property directly at js/src/jit/VMFunctions.cpp:2402 or use ordinary SetProperty at line 2467. The normal name-assignment path passes Unqualified, enabling the missing-name check at js/src/vm/NativeObject.cpp:2572.
Exploit Chain
- Create a configurable global property and a strict-mode function that assigns to that name.
- Call the function while changing the global object's shape until its name-store inline cache becomes megamorphic.
- Delete the global property.
- Call the optimized function again.
- The property-store helper recreates the global and returns normally instead of raising ReferenceError.
Steps to Reproduce
- Use source revision 0a5c5bffebd796cedba281419efd2f8f3cc7c85d and the submitted testcase at /tmp/tmplydms8mo/strict-setname-megamorphic.js.
- Build the clean source with build_firefox using firefox_dir=/firefox and mozconfig_path=/app/mozconfigs/mozconfig.js.linux.debug.
- Run js_shell_differential_evaluator with the testcase content, /firefox/obj-js-debug/dist/bin/js for both shells, flags_a=[--no-jit-backend, --no-threads], and flags_b=[--baseline-eager, --no-ion, --no-threads]. The evaluator adds --fuzzing-safe. Confirm differs=true, exit_code_a=0, and exit_code_b=3.
- Repeat with --differential-testing added to both flag sets. Confirm the same divergence and the failing-side error 'missing ReferenceError'.
- For isolation, compare Baseline with and without --cache-ir-stubs=off. The configuration with CacheIR stubs disabled succeeds.
- An additional diagnostic requiring normal return and a newly created own global property equal to 5 succeeds under Baseline and fails under the interpreter.
Security Impact
- Severity: Low
- Attacker capability: Cause strict-mode JavaScript to continue after an unresolved global assignment and create the corresponding property in the affected global object. The demonstrated capability is confined to JavaScript semantics; no memory corruption, cross-origin access, or sandbox escape was established.
- Preconditions: Attacker-supplied JavaScript executes in the affected realm, the global-name-store CacheIR becomes megamorphic, and the binding is removed before a later store. Reproduction forces Baseline with --baseline-eager --no-ion; no source patch or custom preference is required.
ASAN Report
No sanitizer crash was claimed or observed. This finding is supported by differential-testing evidence.
Without --differential-testing:
differs: true
shell_a argv: ["/firefox/obj-js-debug/dist/bin/js","--fuzzing-safe","--no-jit-backend","--no-threads","/tmp/fx_audit_jsdiff_wfqifqu4/testcase.js"]
shell_a exit_code: 0
shell_b argv: ["/firefox/obj-js-debug/dist/bin/js","--fuzzing-safe","--baseline-eager","--no-ion","--no-threads","/tmp/fx_audit_jsdiff_wfqifqu4/testcase.js"]
shell_b exit_code: 3
shell_b stderr:
/tmp/fx_audit_jsdiff_wfqifqu4/testcase.js:18:9 Error: missing ReferenceError
Stack:
@/tmp/fx_audit_jsdiff_wfqifqu4/testcase.js:18:9
With --differential-testing:
differs: true
shell_a argv: ["/firefox/obj-js-debug/dist/bin/js","--fuzzing-safe","--no-jit-backend","--no-threads","--differential-testing","/tmp/fx_audit_jsdiff_cjwsb68v/testcase.js"]
shell_a exit_code: 0
shell_b argv: ["/firefox/obj-js-debug/dist/bin/js","--fuzzing-safe","--baseline-eager","--no-ion","--no-threads","--differential-testing","/tmp/fx_audit_jsdiff_cjwsb68v/testcase.js"]
shell_b exit_code: 3
shell_b stderr:
/tmp/fx_audit_jsdiff_cjwsb68v/testcase.js:18:9 Error: missing ReferenceError
Stack:
@/tmp/fx_audit_jsdiff_cjwsb68v/testcase.js:18:1
| Reporter | ||
Comment 1•1 month ago
|
||
| Reporter | ||
Comment 2•1 month ago
|
||
| Reporter | ||
Comment 3•1 month ago
|
||
Comment 4•1 month ago
|
||
This bug was filed automatically by an AI-powered security testing tool. The analysis, testcase, and any suggested patches were generated by an AI model and have NOT been verified by a human.
Please review all details carefully before triaging. The issue was confirmed by an independent verifier, but for non-ASAN issues, the verifier has been made less strict. Additionally, the root cause analysis and suggested fix may contain errors.
Updated•1 month ago
|
Updated•1 month ago
|
Comment 5•1 month ago
|
||
The severity field is not set for this bug.
:gtonietto, could you have a look please?
For more information, please visit BugBot documentation.
Updated•1 month ago
|
Comment 6•1 month ago
|
||
This isn't security sensitive. It also doesn't seem too severe; real-world code is not going to depend on this behaviour.
Description
•