Closed Bug 2014593 (CVE-2026-2771) Opened 9 months ago Closed 8 months ago

SimpleHTMLCollection::GetElementAt() null deref with SafeElementAt()

Categories

(Core :: DOM: Core & HTML, defect)

x86_64
Linux
defect

Tracking

()

VERIFIED FIXED
149 Branch
Tracking Status
firefox-esr115 148+ fixed
firefox-esr140 148+ fixed
firefox147 --- wontfix
firefox148 + fixed
firefox149 + verified

People

(Reporter: jkratzer, Assigned: smaug, NeedInfo)

Details

(5 keywords, Whiteboard: [bugmon:bisected,confirmed][adv-main148+] [adv-esr115.33+] [adv-esr140.8+])

Crash Data

Attachments

(3 files, 4 external files)

Reproduced on mozilla-central rev ab79c347a784 (built with: --enable-debug --enable-fuzzing).

Testcase can be reproduced using the following commands:

$ pip install fuzzfetch grizzly-framework pipx --upgrade
$ python -m pipx ensurepath
$ fuzzfetch --build ab79c347a784 --debug --fuzzing  -n firefox
$ grizzly-replay-bugzilla ./firefox/firefox <bugid>
[@ GetBoolFlag]

    ==1381310==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x00000000001c (pc 0x70ac256252c0 bp 0x7ffd5f430960 sp 0x7ffd5f430940 T1381310)
    ==1381310==The signal is caused by a READ memory access.
    ==1381310==Hint: address points to the zero page.
        #0 0x70ac256252c0 in GetBoolFlag /dom/base/nsINode.h:2149:12
        #1 0x70ac256252c0 in IsElement /dom/base/nsINode.h:594:35
        #2 0x70ac256252c0 in AsElement /builds/worker/workspace/obj-build/dist/include/mozilla/dom/Element.h:2560:3
        #3 0x70ac256252c0 in GetElementAt /dom/base/Document.cpp:696:45
        #4 0x70ac256252c0 in non-virtual thunk to mozilla::dom::SimpleHTMLCollection::GetElementAt(unsigned int) /dom/base/Document.cpp:0:0
        #5 0x70ac259d4437 in Item /dom/html/nsIHTMLCollection.h:40:56
        #6 0x70ac259d4437 in mozilla::dom::HTMLCollection_Binding::item(JSContext*, JS::Handle<JSObject*>, void*, JSJitMethodCallArgs const&) /builds/worker/workspace/obj-build/dom/bindings/./HTMLCollectionBinding.cpp:84:74
        #7 0x70ac26a4f966 in bool mozilla::dom::binding_detail::GenericMethod<mozilla::dom::binding_detail::NormalThisPolicy, mozilla::dom::binding_detail::ThrowExceptions>(JSContext*, unsigned int, JS::Value*) /dom/bindings/BindingUtils.cpp:3309:13
        #8 0x70ac2b615b24 in CallJSNative(JSContext*, bool (*)(JSContext*, unsigned int, JS::Value*), js::CallReason, JS::CallArgs const&) /js/src/vm/Interpreter.cpp:490:13
        #9 0x70ac2b6153cf in js::InternalCallOrConstruct(JSContext*, JS::CallArgs const&, js::MaybeConstruct, js::CallReason) /js/src/vm/Interpreter.cpp:586:12
        #10 0x70ac2b6260b2 in CallFromStack /js/src/vm/Interpreter.cpp:658:10
        #11 0x70ac2b6260b2 in js::Interpret(JSContext*, js::RunState&) /js/src/vm/Interpreter.cpp:3272:16
        #12 0x70ac2b614a4a in js::RunScript(JSContext*, js::RunState&) /js/src/vm/Interpreter.cpp:460:13
        #13 0x70ac2b617db0 in js::ExecuteKernel(JSContext*, JS::Handle<JSScript*>, JS::Handle<JSObject*>, js::AbstractFramePtr, JS::MutableHandle<JS::Value>) /js/src/vm/Interpreter.cpp:850:10
        #14 0x70ac2b61826e in js::Execute(JSContext*, JS::Handle<JSScript*>, JS::Handle<JSObject*>, JS::MutableHandle<JS::Value>) /js/src/vm/Interpreter.cpp:880:10
        #15 0x70ac2b875181 in ExecuteScript(JSContext*, JS::Handle<JSObject*>, JS::Handle<JSScript*>, JS::MutableHandle<JS::Value>) /js/src/vm/CompilationAndEvaluation.cpp:548:10
        #16 0x70ac2b875363 in JS_ExecuteScript(JSContext*, JS::Handle<JSScript*>) /js/src/vm/CompilationAndEvaluation.cpp:572:10
        #17 0x70ac28ffe92b in ExecuteCompiledScript /dom/script/ScriptLoader.cpp:2992:8
        #18 0x70ac28ffe92b in mozilla::dom::ScriptLoader::EvaluateScript(nsIGlobalObject*, JS::loader::ScriptLoadRequest*) /dom/script/ScriptLoader.cpp:3588:5
        #19 0x70ac28ffd83e in mozilla::dom::ScriptLoader::EvaluateScriptElement(JS::loader::ScriptLoadRequest*) /dom/script/ScriptLoader.cpp:3097:10
        #20 0x70ac28ff9e96 in mozilla::dom::ScriptLoader::ProcessRequest(JS::loader::ScriptLoadRequest*) /dom/script/ScriptLoader.cpp:2520:17
        #21 0x70ac28ff83f9 in mozilla::dom::ScriptLoader::ProcessInlineScript(nsIScriptElement*, JS::loader::ScriptKind, nsTSubstring<char16_t> const&) /dom/script/ScriptLoader.cpp:1788:10
        #22 0x70ac28fea7f6 in mozilla::dom::ScriptLoader::ProcessScriptElement(nsIScriptElement*, nsTSubstring<char16_t> const&) /dom/script/ScriptLoader.cpp:1319:10
        #23 0x70ac28fea1dc in mozilla::dom::ScriptElement::MaybeProcessScript(nsTSubstring<char16_t> const&) /dom/script/ScriptElement.cpp:289:18
        #24 0x70ac28fe9b6d in mozilla::dom::ScriptElement::MaybeProcessScript(nsCOMPtr<nsIParser>) /dom/script/ScriptElement.cpp:162:18
        #25 0x70ac2900c9c3 in nsIScriptElement::AttemptToExecute(nsCOMPtr<nsIParser>) /dom/script/nsIScriptElement.cpp:78:16
        #26 0x70ac24566b24 in nsHtml5TreeOpExecutor::RunScript(nsIContent*, bool) /parser/html/nsHtml5TreeOpExecutor.cpp:935:22
        #27 0x70ac2456429e in nsHtml5TreeOpExecutor::RunFlushLoop() /parser/html/nsHtml5TreeOpExecutor.cpp:715:7
        #28 0x70ac2456a82c in nsHtml5ExecutorFlusher::Run() /parser/html/nsHtml5StreamParser.cpp:158:18
        #29 0x70ac23540097 in mozilla::RunnableTask::Run() /xpcom/threads/TaskController.cpp:705:16
        #30 0x70ac2353a764 in mozilla::TaskController::DoExecuteNextTaskOnlyMainThreadInternal(mozilla::detail::BaseAutoLock<mozilla::Mutex&> const&) /xpcom/threads/TaskController.cpp:1325:20
        #31 0x70ac235393e7 in mozilla::TaskController::ExecuteNextTaskOnlyMainThreadInternal(mozilla::detail::BaseAutoLock<mozilla::Mutex&> const&) /xpcom/threads/TaskController.cpp:1148:15
        #32 0x70ac23539865 in mozilla::TaskController::ProcessPendingMTTask(bool) /xpcom/threads/TaskController.cpp:641:36
        #33 0x70ac23549376 in operator() /xpcom/threads/TaskController.cpp:333:37
        #34 0x70ac23549376 in mozilla::detail::RunnableFunction<mozilla::TaskController::TaskController()::$_0>::Run() /xpcom/threads/nsThreadUtils.h:549:5
        #35 0x70ac2355b8f3 in nsThread::ProcessNextEvent(bool, bool*) /xpcom/threads/nsThread.cpp:1168:16
        #36 0x70ac2356154f in NS_ProcessNextEvent(nsIThread*, bool) /xpcom/threads/nsThreadUtils.cpp:461:10
        #37 0x70ac241506c7 in mozilla::ipc::MessagePump::Run(base::MessagePump::Delegate*) /ipc/glue/MessagePump.cpp:85:21
        #38 0x70ac240a9ad1 in RunHandler /ipc/chromium/src/base/message_loop.cc:366:3
        #39 0x70ac240a9ad1 in MessageLoop::Run() /ipc/chromium/src/base/message_loop.cc:348:3
        #40 0x70ac2925b948 in nsBaseAppShell::Run() /widget/nsBaseAppShell.cpp:152:27
        #41 0x70ac29327264 in nsAppShell::Run() /widget/gtk/nsAppShell.cpp:555:33
        #42 0x70ac2a36e9cb in XRE_RunAppShell() /toolkit/xre/nsEmbedFunctions.cpp:656:20
        #43 0x70ac24151574 in mozilla::ipc::MessagePumpForChildProcess::Run(base::MessagePump::Delegate*) /ipc/glue/MessagePump.cpp:235:9
        #44 0x70ac240a9ad1 in RunHandler /ipc/chromium/src/base/message_loop.cc:366:3
        #45 0x70ac240a9ad1 in MessageLoop::Run() /ipc/chromium/src/base/message_loop.cc:348:3
        #46 0x70ac2a36e12f in XRE_InitChildProcess(int, char**, XREChildData const*) /toolkit/xre/nsEmbedFunctions.cpp:594:34
        #47 0x62c613ba335c in main /browser/app/nsBrowserApp.cpp:465:22
        #48 0x70ac34c2a1c9 in __libc_start_call_main ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16
        #49 0x70ac34c2a28a in __libc_start_main ./csu/../csu/libc-start.c:360:3
        #50 0x62c613b771b8 in _start ??:0:0
    
    ==1381310==Register values:
    rax = 0x0000000000000000  rbx = 0x0000000000000000  rcx = 0x0000000000000000  rdx = 0x000062c614594200
    rdi = 0x0000000000000001  rsi = 0x0000000000000a48  rbp = 0x00007ffd5f430960  rsp = 0x00007ffd5f430940
     r8 = 0x0000000000000a40   r9 = 0x0000000000000000  r10 = 0x000070ac2f720ec0  r11 = 0x0000000000000000
    r12 = 0x000062c618225708  r13 = 0x0000000000000182  r14 = 0x0000000000000000  r15 = 0x000062c618767358
    UndefinedBehaviorSanitizer can not provide additional info.
    SUMMARY: UndefinedBehaviorSanitizer: SEGV (/home/jkratzer/builds/m-c-20260204094524-fuzzing-debug/libxul.so+0x6a252c0) (BuildId: 6324a18a77ec64a491609780586d8badab323adb)
    ==1381310==ABORTING
Attached file Testcase —

As the comment on the test case says, it looks like this is calling SimpleHTMLCollection::GetElementAt with an out of bounds index, which calls mElements.SafeElementAt(aIndex)->AsElement(); which is unfortunately not actually safe. I think GetElementAt needs to null check the result and return null in that case. At least along this call path, I think that will bubble up to the bindings layer which handles null properly.

Summary: Crash [@ GetBoolFlag] → SimpleHTMLCollection::GetElementAt() null crash with SafeElementAt()

Hmm it is possible this is actually "okay" because AsElement() just does a cast on this in an opt build, so the compiler won't be allowed to delete the bounds check because maybe that won't crash? Depends on whether null this is always undefined.

Keywords: sec-low

(In reply to Andrew McCreight [:mccr8] from comment #5)

Hmm it is possible this is actually "okay" because AsElement() just does a cast on this in an opt build, so the compiler won't be allowed to delete the bounds check because maybe that won't crash? Depends on whether null this is always undefined.

Unfortunately, this is still unsafe in an opt build, as calling a method makes it UB for this to be nullptr, even if the method never deferences this: https://gcc.godbolt.org/z/9ja4os891

Keywords: bugmon

Verified bug as reproducible on mozilla-central 20260205042023-4779d1a57487.
Unable to bisect testcase (Testcase reproduces on start build!):

Start: 5223a9e8f62812d59d3daaedadb64d1be3c8757b (20250206161648)
End: ab79c347a7844435bf155b6ff78757fdd6a605d1 (20260204094524)
BuildFlags: BuildFlags(asan=False, tsan=False, debug=True, fuzzing=True, coverage=False, valgrind=False, no_opt=False, fuzzilli=False, nyx=False, searchfox=False, afl=False)

Whiteboard: [bugmon:confirm] → [bugmon:bisected,confirmed]
Assignee: nobody → smaug
Keywords: sec-low → sec-high
Summary: SimpleHTMLCollection::GetElementAt() null crash with SafeElementAt() → SimpleHTMLCollection::GetElementAt() null deref with SafeElementAt()
Component: DOM: Bindings (WebIDL) → DOM: Core & HTML

I changed this to sec-high because in an opt build the compiler would be allowed to delete the bounds check due to the missing null check. I have not confirmed that it actually crashes with an out-of-bounds error. I've also moved it to DOM Core because this is a problem with SimpleHTMLCollection::GetElementAt(). The bindings code does the right thing and null checks the result.

Maybe there's also a problem that leads to us trying to get an element from an empty array but I'm not sure.

(In reply to Nika Layzell [:nika] (ni? for response) from comment #6)

(In reply to Andrew McCreight [:mccr8] from comment #5)

Hmm it is possible this is actually "okay" because AsElement() just does a cast on this in an opt build, so the compiler won't be allowed to delete the bounds check because maybe that won't crash? Depends on whether null this is always undefined.

Unfortunately, this is still unsafe in an opt build, as calling a method makes it UB for this to be nullptr, even if the method never deferences this: https://gcc.godbolt.org/z/9ja4os891

I would expect the whole method call to be optimized out in this case in opt builds. Didn't check though. And fix should be easy

Attached file (secure) —

Comment on attachment 9542792 [details]
(secure)

Security Approval Request

  • How easily could an exploit be constructed based on the patch?: I'm not 100% there is an exploit. But if there is, it is quite obvious where it happens and how to trigger it.
  • Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: Unknown
  • Which branches (beta, release, and/or ESR) are affected by this flaw, and do the release status flags reflect this affected/unaffected state correctly?: All
  • If not all supported branches, which bug introduced the flaw?: None
  • Do you have backports for the affected branches?: No
  • If not, how different, hard to create, and risky will they be?: The code is old and seems to be the same in esr140 at least.
  • How likely is this patch to cause regressions; how much testing does it need?: This is a null check.
  • Is the patch ready to land after security approval is given?: Yes
  • Is Android affected?: Yes

Comment on attachment 9542792 [details]
(secure)

How did we get comment 11 without a sec-approval? flag?
[updated to add: I'm not expecting an answer here, but it's the second one this week. I'll need to check with the BMO team]

Attachment #9542792 - Flags: sec-approval?

Comment on attachment 9542792 [details]
(secure)

sec-approval+, a=dveditz
please wait until at least 2026-04-07 to land a test

Attachment #9542792 - Flags: sec-approval? → sec-approval+
Whiteboard: [bugmon:bisected,confirmed] → [bugmon:bisected,confirmed][reminder-test 2026-04-07]

Please request beta and ESR uplift requests ASAP so we can make sure this gets into beta this week. Thanks.

Flags: needinfo?(smaug)
Group: dom-core-security → core-security-release
Status: NEW → RESOLVED
Closed: 8 months ago
Resolution: --- → FIXED
Target Milestone: --- → 149 Branch

Verified bug as fixed on rev mozilla-central 20260211162125-7df40d9149a3.
Removing bugmon keyword as no further action possible. Please review the bug and re-add the keyword for further analysis.

Status: RESOLVED → VERIFIED
Keywords: bugmon
Flags: needinfo?(smaug)

firefox-beta Uplift Approval Request

  • User impact if declined: (I don't know if esr115 is still supported. I assume no based on https://whattrainisitnow.com/calendar/ )
  • Code covered by automated testing: no
  • Fix verified in Nightly: yes
  • Needs manual QE test: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: null check
  • String changes made/needed: NA
  • Is Android affected?: yes
Attachment #9544531 - Flags: approval-mozilla-beta?

firefox-esr140 Uplift Approval Request

  • User impact if declined: (I don't know if esr115 is still supported. I assume no based on https://whattrainisitnow.com/calendar/ )
  • Code covered by automated testing: no
  • Fix verified in Nightly: yes
  • Needs manual QE test: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: null check
  • String changes made/needed: NA
  • Is Android affected?: yes
Attachment #9544532 - Flags: approval-mozilla-esr140?
Attachment #9544531 - Flags: approval-mozilla-beta? → approval-mozilla-beta+

:smaug, this will also need an ESR115 uplift request.
115.33esr releases alongside Fx148 and is the last release of ER115

Flags: needinfo?(smaug)
Attachment #9544555 - Flags: approval-mozilla-esr115?

firefox-esr115 Uplift Approval Request

  • User impact if declined: crash
  • Code covered by automated testing: no
  • Fix verified in Nightly: yes
  • Needs manual QE test: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: null check
  • String changes made/needed: NA
  • Is Android affected?: yes
Attachment #9544532 - Flags: approval-mozilla-esr140? → approval-mozilla-esr140+
Attachment #9544555 - Flags: approval-mozilla-esr115? → approval-mozilla-esr115+
Whiteboard: [bugmon:bisected,confirmed][reminder-test 2026-04-07] → [bugmon:bisected,confirmed][reminder-test 2026-04-07][adv-main148+]
Whiteboard: [bugmon:bisected,confirmed][reminder-test 2026-04-07][adv-main148+] → [bugmon:bisected,confirmed][reminder-test 2026-04-07][adv-main148+] [adv-esr115.33+]
Whiteboard: [bugmon:bisected,confirmed][reminder-test 2026-04-07][adv-main148+] [adv-esr115.33+] → [bugmon:bisected,confirmed][reminder-test 2026-04-07][adv-main148+] [adv-esr115.33+] [adv-esr140.8+]
Alias: CVE-2026-2771

2 months ago, dveditz placed a reminder on the bug using the whiteboard tag [reminder-test 2026-04-07] .

smaug, please refer to the original comment to better understand the reason for the reminder.

Flags: needinfo?(smaug)
Whiteboard: [bugmon:bisected,confirmed][reminder-test 2026-04-07][adv-main148+] [adv-esr115.33+] [adv-esr140.8+] → [bugmon:bisected,confirmed][adv-main148+] [adv-esr115.33+] [adv-esr140.8+]
Group: core-security-release
Keywords: keep-hidden
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: