Open Bug 2078796 Opened 1 month ago Updated 1 month ago

Assertion failure: id.isAtom() && IsIdentifierNameOrPrivateName(id.toAtom()) in [@ js::IdToPrintableUTF8] via shell getModuleEnvironmentValue(m, "*namespace*")

Categories

(Core :: JavaScript Engine, defect, P3)

defect

Tracking

()

UNCONFIRMED

People

(Reporter: bugmon, Unassigned)

References

(Blocks 1 open bug)

Details

(Keywords: assertion, testcase)

Attachments

(3 files)

The shell testing function GetModuleEnvironmentValue (js/src/shell/js.cpp) accepts any string as the binding name, including the internal module pseudo-bindings *namespace* and *deferred-namespace*, and when the binding holds the uninitialized-lexical magic value it passes that id to ReportRuntimeLexicalError. That function calls IdToPrintableUTF8 with IdToPrintableBehavior::IdIsIdentifier, whose debug assertion requires the id to be an identifier name; *namespace* is not one, so the assertion fails and a debug shell aborts.

A script author with access to the SpiderMonkey shell's testing functions can abort a debug-build shell process by calling getModuleEnvironmentValue on a linked module with the name *namespace* or *deferred-namespace*. The only demonstrated effect is a debug-only assertion failure; on the non-debug ASAN shell the same testcase throws a ReferenceError and exits without a sanitizer report. The function is defined in the shell only, and no route from web content and no memory corruption was demonstrated.

Build Info

Affected Code

File: js/src/shell/js.cpp, line 6330-6347

  Rooted<ModuleEnvironmentObject*> env(cx,
                                       GetModuleInitialEnvironment(cx, module));
  RootedString name(cx, args[1].toString());
  RootedId id(cx);
  if (!JS_StringToId(cx, name, &id)) {
    return false;
  }

  if (!GetProperty(cx, env, env, id, args.rval())) {
    return false;
  }

  if (args.rval().isMagic(JS_UNINITIALIZED_LEXICAL)) {
    ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, id);
    return false;
  }

  return true;

File: js/src/vm/Interpreter.cpp, line 5281-5290

void js::ReportRuntimeLexicalError(JSContext* cx, unsigned errorNumber,
                                   HandleId id) {
  MOZ_ASSERT(errorNumber == JSMSG_UNINITIALIZED_LEXICAL ||
             errorNumber == JSMSG_BAD_CONST_ASSIGN);
  if (UniqueChars printable =
          IdToPrintableUTF8(cx, id, IdToPrintableBehavior::IdIsIdentifier)) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber,
                             printable.get());
  }
}

File: js/src/vm/StringType.cpp, line 3196-3201

UniqueChars js::IdToPrintableUTF8(JSContext* cx, HandleId id,
                                  IdToPrintableBehavior behavior) {
  // ToString(<symbol>) throws a TypeError, therefore require that callers
  // request source representation when |id| is a property key.
  MOZ_ASSERT_IF(behavior == IdToPrintableBehavior::IdIsIdentifier,
                id.isAtom() && IsIdentifierNameOrPrivateName(id.toAtom()));

File: js/src/shell/js.cpp, line 6277-6280

  // The "*namespace*" and "*deferred-namespace*" bindings are implementation
  // details, hide them to give stable results in tests.
  ids.eraseIfEqual(NameToId(cx->names().star_namespace_star_));
  ids.eraseIfEqual(NameToId(cx->names().star_deferred_namespace_star_));

File: js/src/frontend/Parser.cpp, line 1953-1964

  // Reserve an environment slot for a "*namespace*" psuedo-binding and mark as
  // closed-over. We do not know until module linking if this will be used.
  if (!noteDeclaredName(
          TaggedParserAtomIndex::WellKnown::star_namespace_star_(),
          DeclarationKind::Const, pos())) {
    return errorResult();
  }
  modulepc.varScope()
      .lookupDeclaredName(
          TaggedParserAtomIndex::WellKnown::star_namespace_star_())
      ->value()
      ->setClosedOver();

File: js/src/builtin/ModuleObject.cpp, line 520-528

static void ReportUninitializedModuleBinding(JSContext* cx, HandleId id) {
  // Module export names are property keys and may not be identifier names,
  // so use IdIsPropertyKey rather than IdIsIdentifier.
  if (UniqueChars printable =
          IdToPrintableUTF8(cx, id, IdToPrintableBehavior::IdIsPropertyKey)) {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_UNINITIALIZED_LEXICAL, printable.get());
  }
}

Parser::moduleBody declares *namespace* and *deferred-namespace* as closed-over Const bindings in every source-text module environment. GetModuleEnvironmentNames filters these two names from its result, but GetModuleEnvironmentValue does not reject them and reports the uninitialized binding through ReportRuntimeLexicalError, which assumes the id is an identifier. ModuleObject.cpp has a separate helper, ReportUninitializedModuleBinding, that uses IdIsPropertyKey for names that may not be identifiers; the shell function does not use it.

Exploit Chain

  1. Script in the JS shell parses and registers a source-text module with parseModule/registerModule; Parser::moduleBody (js/src/frontend/Parser.cpp:1953-1978) declares the *namespace* and *deferred-namespace* pseudo-bindings in its environment.
  2. Script calls moduleLoadAndLink(m), so the module is no longer Unlinked and GetModuleEnvironmentValue's status check (js/src/shell/js.cpp:6324-6328) passes.
  3. Script calls getModuleEnvironmentValue(m, "namespace"); GetProperty on the module environment returns the JS_UNINITIALIZED_LEXICAL magic value because nothing has initialized the pseudo-binding.
  4. GetModuleEnvironmentValue calls ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, id) (js/src/shell/js.cpp:6342-6343), which calls IdToPrintableUTF8 with IdIsIdentifier (js/src/vm/Interpreter.cpp:5286).
  5. The MOZ_ASSERT_IF at js/src/vm/StringType.cpp:3200-3201 fails because *namespace* is not an identifier name, and the debug shell aborts. In the non-debug ASAN shell the assertion is absent and the call throws ReferenceError: can't access lexical declaration '*namespace*' before initialization.

Steps to Reproduce

  1. Build the SpiderMonkey debug shell from an unmodified tree with /app/mozconfigs/mozconfig.js.linux.debug (build_dir /firefox/obj-js-debug).
  2. Save as testcase.js:
    let m = registerModule("m", parseModule("export let x = 1;"));
    moduleLoadAndLink(m);
    getModuleEnvironmentValue(m, "namespace");
  3. Run it with js_shell_evaluator using js_binary /firefox/obj-js-debug/dist/bin/js and no extra flags (the evaluator adds --fuzzing-safe). No prefs or source patches are needed.
  4. Observe Assertion failure: id.isAtom() && IsIdentifierNameOrPrivateName(id.toAtom()), at js/src/vm/StringType.cpp:3201. Replacing the name with "deferred-namespace" produces the same assertion.

Security Impact

  • Severity: Low
  • Attacker capability: Abort a debug-build SpiderMonkey shell process through a failed MOZ_ASSERT. No memory corruption was demonstrated: the non-debug ASAN shell runs the same testcase to a thrown ReferenceError with no sanitizer report.
  • Preconditions: Requires a debug build and the shell-only testing functions parseModule, registerModule, moduleLoadAndLink and getModuleEnvironmentValue, which are defined in js/src/shell/js.cpp. No route from web content was demonstrated.

ASAN Report

[2120] Assertion failure: id.isAtom() && IsIdentifierNameOrPrivateName(id.toAtom()), at js/src/vm/StringType.cpp:3201
UndefinedBehaviorSanitizer:DEADLYSIGNAL
==2120==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x000000000000 (pc 0x557a4e23c7be bp 0x7ef8d3438f90 sp 0x7ef8d3438ef0 T2120)
==2120==The signal is caused by a READ memory access.
==2120==Hint: address points to the zero page.
    #0 0x557a4e23c7be in MOZ_CrashSequence(void*, long) /firefox/obj-js-debug/dist/include/mozilla/Assertions.h:261:3
    #1 0x557a4e23c7be in js::IdToPrintableUTF8(JSContext*, JS::Handle<JS::PropertyKey>, js::IdToPrintableBehavior) /firefox/obj-js-debug/js/src/vm/./../../../../js/src/vm/StringType.cpp:3200:3
    #2 0x557a4e0d1fa2 in js::ReportRuntimeLexicalError(JSContext*, unsigned int, JS::Handle<JS::PropertyKey>) /firefox/js/src/vm/Interpreter.cpp:5286:11
    #3 0x557a4cb6fbf1 in GetModuleEnvironmentValue(JSContext*, unsigned int, JS::Value*) /firefox/obj-js-debug/js/src/shell/./../../../../js/src/shell/js.cpp:6343:5
    #4 0x557a4e0d3c94 in CallJSNative(JSContext*, bool (*)(JSContext*, unsigned int, JS::Value*), js::CallReason, JS::CallArgs const&) /firefox/js/src/vm/Interpreter.cpp:528:13
    #5 0x557a4e0b06a7 in js::InternalCallOrConstruct(JSContext*, JS::CallArgs const&, js::MaybeConstruct, js::CallReason) /firefox/js/src/vm/Interpreter.cpp:624:12
    #6 0x557a4e0bf107 in js::CallFromStack(JSContext*, JS::CallArgs const&, js::CallReason) /firefox/js/src/vm/Interpreter.cpp:696:10
    #7 0x557a4e0bf107 in js::Interpret(JSContext*, js::RunState&) /firefox/js/src/vm/Interpreter.cpp:3278:16
    #8 0x557a4e0b0106 in MaybeEnterInterpreterTrampoline(JSContext*, js::RunState&) /firefox/js/src/vm/Interpreter.cpp:420:10
    #9 0x557a4e0afd48 in js::RunScript(JSContext*, js::RunState&) /firefox/js/src/vm/Interpreter.cpp:498:13
    #10 0x557a4e0b27e0 in js::ExecuteKernel(JSContext*, JS::Handle<JSScript*>, JS::Handle<JSObject*>, js::AbstractFramePtr, JS::MutableHandle<JS::Value>) /firefox/js/src/vm/Interpreter.cpp:888:10
    #11 0x557a4e0b2c3e in js::Execute(JSContext*, JS::Handle<JSScript*>, JS::Handle<JSObject*>, JS::MutableHandle<JS::Value>) /firefox/js/src/vm/Interpreter.cpp:918:10
(remaining frames: ExecuteScript, JS_ExecuteScript, shell RunFile/Process/Shell/main; debug build with UBSan signal handler, not an AddressSanitizer report)
Attached patch fix.patch — — Splinter Review
Attached file crash_stack.txt —
Attached file testcase.js —

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.

Group: core-security → javascript-core-security
Blocks: sm-security
Severity: -- → S3
Priority: -- → P3

This is just a bug in a shell testing function. It's not security sensitive.

Group: javascript-core-security
Keywords: sec-other
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: