Closed Bug 2025899 Opened 16 days ago Closed 8 days ago

Content-process null dereference in the popover opening path via re-entrant `removeAttribute("popover")` during autofocus handling

Categories

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

defect

Tracking

()

RESOLVED FIXED
151 Branch
Tracking Status
firefox151 --- fixed

People

(Reporter: liberty_rapid, Assigned: ltenenbaum)

References

Details

(4 keywords, Whiteboard: [client-bounty-form], [wptsync upstream])

Crash Data

Attachments

(2 files)

Attached file testcase.html

Description

I discovered this issue by running a minimized HTML testcase against an official Linux x86_64 Firefox Nightly AddressSanitizer (ASan) build, following Mozilla's ASan documentation and using fuzzfetch to obtain the build artifact. The testcase reliably triggers a null-pointer dereference in a content process when a popover is opened, its autofocus descendant receives focus, and the focus handler removes the popover attribute re-entrantly during showPopover().

Environment

  • Product: Firefox Nightly (ASan CI build)
  • Version: 151.0a1
  • Build ID: 20260324002715
  • Source revision: acbfb05622cd827a7418d36628f75857628a81ce
  • Platform: Linux x86_64
  • OS: Ubuntu 24.04.4 LTS

Reproduction

  1. Download an official Linux ASan Nightly build. For example:

    python -m fuzzfetch --asan --build acbfb05622cd827a7418d36628f75857628a81ce -n firefox-asan
    
  2. Save the following testcase as testcase.html:

    <!doctype html>
    <meta charset="utf-8">
    <body>
    <script>
    "use strict";
    
    addEventListener("load", () => {
      const popover = document.createElement("div");
      popover.setAttribute("popover", "auto");
    
      const input = document.createElement("input");
      input.setAttribute("autofocus", "");
      popover.append(input);
      document.body.append(popover);
    
      input.addEventListener(
        "focus",
        () => {
          popover.removeAttribute("popover");
        },
        { once: true }
      );
    
      popover.showPopover();
    });
    </script>
    </body>
    
  3. Run Firefox with a fresh profile and symbolized ASan output enabled:

    export ASAN_SYMBOLIZER_PATH=/path/to/firefox-asan/llvm-symbolizer
    export ASAN_OPTIONS='symbolize=1:abort_on_error=1:disable_coredump=0:fast_unwind_on_malloc=0'
    export MOZ_CRASHREPORTER_DISABLE=1
    export MOZ_DISABLE_CONTENT_SANDBOX=1
    
    /path/to/firefox-asan/firefox \
      --headless \
      --no-remote \
      --profile /tmp/fx-asan-profile \
      file:///absolute/path/to/testcase.html
    
  4. Observe that the content process aborts with an ASan-reported SIGSEGV / null dereference.

Observed Result

The crash is reproducible and symbolized. A representative excerpt is:

AddressSanitizer:DEADLYSIGNAL
ERROR: AddressSanitizer: SEGV on unknown address 0x000000000020
The signal is caused by a READ memory access.

#0 ... in GetToggleEventTask ... PopoverData.h:90
#1 ... in nsGenericHTMLElement::QueuePopoverEventTask(...) ... nsGenericHTMLElement.cpp:3479
#2 ... in nsGenericHTMLElement::ShowPopoverInternal(...) ... nsGenericHTMLElement.cpp:3755

The read from 0x20 is consistent with dereferencing a null PopoverData*.

Expected Result

Firefox should safely handle this re-entrant state change and either abort the popover operation or revalidate the element state before continuing. Web content should not be able to trigger a null-pointer dereference in the content process by removing the popover attribute during autofocus handling.

Root Cause Analysis

The issue is caused by a re-entrancy gap in the popover opening path.

nsGenericHTMLElement::ShowPopoverInternal() queues the popover toggle task only after it runs popover focusing steps:

  • dom/html/nsGenericHTMLElement.cpp:3742-3755

Specifically:

  • FocusPopover() is called at 3743
  • QueuePopoverEventTask(PopoverVisibilityState::Hidden, aSource) is called at 3755

FocusPopover() chooses the autofocus target and synchronously focuses it:

  • dom/html/nsGenericHTMLElement.cpp:3848-3864

In this testcase, the focused element's focus handler executes:

popover.removeAttribute("popover");

That mutation reaches nsGenericHTMLElement::AfterSetAttr(), which schedules AfterSetPopoverAttr() through nsContentUtils::AddScriptRunner(...):

  • dom/html/nsGenericHTMLElement.cpp:736-753

Mozilla's own AddScriptRunner() contract explicitly notes that if it is currently safe to run script, the runnable executes synchronously before returning:

  • dom/base/nsContentUtils.h:2328-2337
  • dom/base/nsContentUtils.cpp:7266-7280

AfterSetPopoverAttr() then observes that the popover attribute transitioned to the missing state. If the popover is open, it hides it, re-reads the current attribute state, and when the state is None it clears the backing PopoverData:

  • state transition logic: dom/html/nsGenericHTMLElement.cpp:698-723
  • hide call: 704-706
  • re-read after script effects: 707-710
  • ClearPopoverData(): 713-715

ClearPopoverData() is implemented here:

  • dom/base/Element.cpp:5739-5743

After this re-entrant cleanup completes, execution returns to the original ShowPopoverInternal() call frame, which still assumes PopoverData exists and calls QueuePopoverEventTask().

QueuePopoverEventTask() does:

auto* data = GetPopoverData();
MOZ_ASSERT(data, "Should have popover data");
if (auto* queuedToggleEventTask = data->GetToggleEventTask()) {
  • dom/html/nsGenericHTMLElement.cpp:3474-3486

In the optimized ASan build, the assertion does not prevent continued execution, so data->GetToggleEventTask() dereferences a null pointer and crashes.

Impact

This is a reliable content-process null-pointer dereference reachable from web content via popover, autofocus, and a focus event handler. I have not investigated exploitability beyond the reliable crash / abort primitive.

Flags: sec-bounty?
Group: firefox-core-security → dom-core-security
Component: Security → DOM: Core & HTML
Product: Firefox → Core
Group: dom-core-security
Crash Signature: [@ mozilla::dom::PopoverData::GetToggleEventTask ]

The bug has a crash signature, thus the bug will be considered confirmed.

Status: UNCONFIRMED → NEW
Ever confirmed: true
Flags: sec-bounty? → sec-bounty-
Severity: -- → S3
Assignee: nobody → ltenenbaum
Status: NEW → ASSIGNED
Pushed by rperta@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/e21625adee41 https://hg.mozilla.org/integration/autoland/rev/4cd676bd2708 Revert "Bug 2025899 - Bring popover toggle events closer in line with spec. r=layout-reviewers,emilio" for causing wc failures at popover-focus-blur-crash.html

Backed out for causing wc failures at popover-focus-blur-crash.html
Backout link
Push with failures
Failure log(s)
Failure line: TEST-UNEXPECTED-PASS | /html/semantics/popovers/popover-focus-blur-crash.html | expected CRASH

Flags: needinfo?(ltenenbaum)

also this
Failure line: TEST-UNEXPECTED-OK | /html/semantics/popovers/popover-events.html | expected CRASH

See Also: → 2027806

Should just be a matter of updating the test expectations because of the recent wptsync…

Flags: needinfo?(ltenenbaum)
Created web-platform-tests PR https://github.com/web-platform-tests/wpt/pull/58901 for changes under testing/web-platform/tests
Whiteboard: [client-bounty-form] → [client-bounty-form], [wptsync upstream]
Status: ASSIGNED → RESOLVED
Closed: 8 days ago
Resolution: --- → FIXED
Target Milestone: --- → 151 Branch
Upstream PR merged by moz-wptsync-bot
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: