Ion skips proxy-get validation for immutable typed-array elements
Categories
(Core :: JavaScript Engine, defect, P3)
Tracking
()
People
(Reporter: bugmon, Assigned: iain)
References
(Blocks 1 open bug)
Details
(Keywords: testcase)
Attachments
(5 files, 2 external files)
NewTypedArrayObject initializes immutable typed-array shapes without NeedsProxyGetSetResultValidation, although their indexed properties are non-configurable and non-writable. Their class also has no resolve hook. Consequently, MacroAssembler::branchTestObjectNeedsProxyResultValidation lets Ion omit descriptor validation and accept a proxy-get result that should cause a TypeError.
A web page can make the same JavaScript operation behave differently after JIT compilation by accessing an immutable typed array through a scripted proxy. This requires Nightly’s disabled-by-default javascript.options.experimental.arraybuffer_immutable preference. The demonstrated impact is a missing required exception; the evidence does not establish memory corruption or a process-boundary escape.
This finding was discovered during variant analysis seeded by bug 1536768.
Build Info
- Branch: main
- Revision: f384c89fc98f8cf5e6ffa0caf568868b29fba90a
- Timestamp: 2026-09-30T04:43:22+00:00
Affected Code
File: js/src/vm/TypedArrayObject.cpp, line 388-391
Rooted<SharedShape*> shape(
cx,
SharedShape::getInitialShape(cx, clasp, cx->realm(), AsTaggedProto(proto),
nfixed, ObjectFlags()));
File: js/src/vm/Shape-inl.h, line 94-99
if (prop.isTypedArrayElement()) {
if (obj->is<ImmutableTypedArrayObject>()) {
return {JS::PropertyAttribute::Enumerable};
}
return {JS::PropertyAttribute::Configurable,
JS::PropertyAttribute::Enumerable, JS::PropertyAttribute::Writable};
File: js/src/jit/MacroAssembler.cpp, line 6218-6235
loadPtr(Address(obj, JSObject::offsetOfShape()), scratch);
branchTest32(Assembler::Zero,
Address(scratch, Shape::offsetOfImmutableFlags()),
Imm32(Shape::isNativeBit()), doValidation);
static_assert(sizeof(ObjectFlags) == sizeof(uint32_t));
load32(Address(scratch, Shape::offsetOfObjectFlags()), scratch);
branchTest32(Assembler::NonZero, scratch,
Imm32(uint32_t(ObjectFlag::NeedsProxyGetSetResultValidation)),
doValidation);
loadPtr(Address(obj, JSObject::offsetOfShape()), scratch);
loadPtr(Address(scratch, Shape::offsetOfBaseShape()), scratch);
loadPtr(Address(scratch, BaseShape::offsetOfClasp()), scratch);
loadPtr(Address(scratch, offsetof(JSClass, cOps)), scratch);
branchTestPtr(Assembler::Zero, scratch, scratch, skipValidation);
loadPtr(Address(scratch, offsetof(JSClassOps, resolve)), scratch);
branchTestPtr(Assembler::NonZero, scratch, scratch, doValidation);
bind(&done);
File: js/src/jit/CodeGenerator.cpp, line 18012-18020
using Fn = bool (*)(JSContext*, HandleObject, HandleValue, HandleValue,
MutableHandleValue);
OutOfLineCode* ool = oolCallVM<Fn, CheckProxyGetByValueResult>(
ins, ArgList(scratch, id, value), StoreValueTo(value));
masm.unboxObject(target, scratch);
masm.branchTestObjectNeedsProxyResultValidation(Assembler::NonZero, scratch,
scratch2, ool->entry());
masm.bind(ool->rejoin());
At revision f384c89fc98f8cf5e6ffa0caf568868b29fba90a, GetPropIRGenerator::tryAttachScriptedProxy guards the target only as a native object (js/src/jit/CacheIR.cpp:1746). ImmutableTypedArrayClassOps has no resolve hook (js/src/vm/TypedArrayObject.cpp:6311), so the missing shape flag makes the shared predicate skip validation. The ordinary validator checks the actual descriptor and rejects an unequal protected-property value (js/src/proxy/ScriptedProxyHandler.cpp:1194). The browser feature is Nightly-only and disabled by default (modules/libpref/init/StaticPrefList.yaml:9864).
Exploit Chain
- Run JavaScript with immutable ArrayBuffers enabled.
- Warm an indexed getter using a scripted proxy whose target is an ordinary object and whose get trap returns 71.
- Pass the same getter a proxy over a Uint8Array backed by an immutable, zero-initialized ArrayBuffer.
- Ion’s native-object validation predicate skips the descriptor check, so the operation completes instead of throwing the TypeError produced by the interpreter.
Steps to Reproduce
- Use the unmodified Firefox source at revision f384c89fc98f8cf5e6ffa0caf568868b29fba90a. Build with build_firefox using /app/mozconfigs/mozconfig.js.linux.debug.
- Pass the exact contents of /tmp/tmph_ombtrt/proxy-immutable.js to js_shell_differential_evaluator. Set both shell paths to /firefox/obj-js-debug/dist/bin/js and timeout to 60 seconds.
- Use flags_a=["--enable-arraybuffer-immutable","--no-jit-backend","--no-threads"] and flags_b=["--enable-arraybuffer-immutable","--ion-eager","--no-threads"]. The evaluator adds --fuzzing-safe. Observe exit codes 0/3 and differs: true.
- Repeat with --differential-testing added to both flag sets. Observe exit codes 0/3 and differs: true again.
- For corroboration, build with /app/mozconfigs/mozconfig.linux.asan.fuzzing and repeat the normal comparison using /firefox/obj-firefox-asan/dist/bin/js. Observe the same 0/3 divergence.
- Replacing Ion with --no-ion --baseline-eager, adding --cache-ir-stubs=off, or using /tmp/tmph_ombtrt/proxy-immutable-control.js produces exit codes 0/0. No source patches, browser preference changes, or external services are required for the shell reproduction.
Security Impact
- Severity: Low
- Attacker capability: Cause an optimized JavaScript proxy access to omit a required exception. No memory corruption, sensitive-data disclosure, or security-boundary crossing was demonstrated.
- Preconditions: Immutable ArrayBuffers must be enabled, and the scripted-proxy getter must reach the affected Ion path. Browser exposure requires Nightly’s disabled-by-default javascript.options.experimental.arraybuffer_immutable preference.
ASAN Report
No sanitizer crash occurred; this is a js_differential finding.
Independent debug run:
argv_a: /firefox/obj-js-debug/dist/bin/js --fuzzing-safe --enable-arraybuffer-immutable --no-jit-backend --no-threads /tmp/fx_audit_jsdiff_46texbqc/testcase.js
argv_b: /firefox/obj-js-debug/dist/bin/js --fuzzing-safe --enable-arraybuffer-immutable --ion-eager --no-threads /tmp/fx_audit_jsdiff_46texbqc/testcase.js
exit_codes: 0/3
differs: true
stderr_b:
/tmp/fx_audit_jsdiff_46texbqc/testcase.js:11:19 Error: missing TypeError for immutable element
Stack:
@/tmp/fx_audit_jsdiff_46texbqc/testcase.js:11:19
Independent differential-testing run:
argv_a: /firefox/obj-js-debug/dist/bin/js --fuzzing-safe --enable-arraybuffer-immutable --no-jit-backend --no-threads --differential-testing /tmp/fx_audit_jsdiff_9lborgjh/testcase.js
argv_b: /firefox/obj-js-debug/dist/bin/js --fuzzing-safe --enable-arraybuffer-immutable --ion-eager --no-threads --differential-testing /tmp/fx_audit_jsdiff_9lborgjh/testcase.js
exit_codes: 0/3
differs: true
stderr_b:
/tmp/fx_audit_jsdiff_9lborgjh/testcase.js:11:19 Error: missing TypeError for immutable element
Stack:
@/tmp/fx_audit_jsdiff_9lborgjh/testcase.js:11:1
Independent ASAN-build run:
argv_a: /firefox/obj-firefox-asan/dist/bin/js --fuzzing-safe --enable-arraybuffer-immutable --no-jit-backend --no-threads /tmp/fx_audit_jsdiff_28kq0er8/testcase.js
argv_b: /firefox/obj-firefox-asan/dist/bin/js --fuzzing-safe --enable-arraybuffer-immutable --ion-eager --no-threads /tmp/fx_audit_jsdiff_28kq0er8/testcase.js
exit_codes: 0/3
differs: true
stderr_b:
/tmp/fx_audit_jsdiff_28kq0er8/testcase.js:11:19 Error: missing TypeError for immutable element
Stack:
@/tmp/fx_audit_jsdiff_28kq0er8/testcase.js:11:19
| Reporter | ||
Comment 1•1 month ago
|
||
| Reporter | ||
Comment 2•1 month ago
|
||
| Reporter | ||
Comment 3•1 month ago
|
||
| Reporter | ||
Comment 4•1 month ago
|
||
| Reporter | ||
Comment 5•1 month ago
|
||
Comment 6•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
|
| Assignee | ||
Comment 7•1 month ago
|
||
Skipping proxy get validation is not exploitable. The patch seems fine, though.
| Assignee | ||
Comment 8•1 month ago
|
||
We are required to throw an exception if a Proxy's get trap returns a value that doesn't match the underlying object for a non-writable, non-configurable property. Our proxy fast path depends on the NeedsProxyGetSetResultValidation flag being set on any object that has non-writable, non-configurable properties. Normally this is handled automatically in GetObjectFlagsForNewProperty, but immutable typed array elements don't take that path.
Updated•1 month ago
|
Comment 9•1 month ago
|
||
:iain, please review the release status flags for beta, release, and ESR and update each one as appropriate.
For each version, indicate whether it is affected, unaffected, disabled, or wontfix if it is affected but does not need to be fixed on that branch.
Updated•1 month ago
|
| Assignee | ||
Comment 10•1 month ago
|
||
Immutable typed arrays haven't shipped yet, and this is just a corner-case correctness issue. It can ride the trains.
| Assignee | ||
Updated•1 month ago
|
Updated•1 month ago
|
| Assignee | ||
Comment 11•1 month ago
|
||
Note that when we're deciding whether we have to do this check we also check for a resolve hook, but here we're already looking at a concrete property that should have set the flag when it was added, so having a resolve hook isn't an excuse (and therefore doesn't need to be part of the assertion).
Comment 12•1 month ago
|
||
Comment 13•1 month ago
|
||
| bugherder | ||
https://hg.mozilla.org/mozilla-central/rev/61fc5c0b30ca
https://hg.mozilla.org/mozilla-central/rev/610b22ef6cc3
Comment 14•1 month ago
|
||
Since nightly and release are affected, beta will likely be affected too.
For more information, please visit BugBot documentation.
Comment 15•1 month ago
|
||
The patch landed in nightly and beta is affected.
:iain, please make an uplift decision for beta:
- For beta, nominate the patch for beta uplift approval if the fix should be included in this release, or set
status-firefox159towontfixif the fix can wait for the next release.
See Requesting an Uplift for documentation on how to request an uplift.
For more information, please visit BugBot documentation.
| Assignee | ||
Updated•1 month ago
|
Updated•17 days ago
|
Description
•