Closed Bug 1760944 (CVE-2022-31745) Opened 3 years ago Closed 3 years ago

Disabling shift optimization asserts in js::jit::AssertValidObjectPtr

Categories

(Core :: JavaScript: GC, defect, P1)

defect

Tracking

()

RESOLVED FIXED
101 Branch
Tracking Status
firefox-esr91 100+ wontfix
firefox98 --- wontfix
firefox99 --- wontfix
firefox100 + wontfix
firefox101 --- fixed

People

(Reporter: lukas.bernhard, Assigned: jandem)

References

(Blocks 1 open bug)

Details

(Keywords: sec-moderate, Whiteboard: [post-critsmash-triage][adv-main101+])

Attachments

(2 files)

Steps to reproduce:

In a non-vanilla version of spidermonkey, I discovered an assertion violation. This bug is filed as the modification to spidermonkey is probably not the root-cause but instead exposes an underlying issue affecting the vanilla engine as well.

When disabling the array shift optimizations in NativeObject::{tryShiftDenseElements,tryUnshiftDenseElements} by letting these two function immediately return false, one of the jit-tests (js/src/jit-test/tests/basic/shifted-elements6.js) crashes in js::jit::AssertValidObjectPtr.
The crash happens in a small percentage of executions only, hence reproducing the issue might require some repetition.

As @iain pointed out: reproducing the issue in rr is possible if the rr flag --chaos is used.

Group: firefox-core-security → core-security
Component: Untriaged → JavaScript: GC
Product: Firefox → Core

Looked into this. I've worked out what's happening, but somebody who knows the GC better than I do will have to figure out the fix.

We fill an array with objects. In a loop, we call shift on the array to remove the objects one by one.
At some point in this loop:

  • we start a new major gc
  • we pop the array from the mark stack and start marking its children
  • with a single child left to mark, our budget runs out
  • we push a range (start: 146, end: 147) onto the mark stack
  • we return to scripted code and call shift again
  • in moveDenseElements, we shift everything over by one (emitting pre-barriers on all elements except the last)
  • we decrement the initialized length by 1, to 146
  • we call GCSlice
  • we pop the range off the mark stack
  • there are no elements after 146 left to mark, so we skip the range
  • the last element is never marked.
  • eventually we finalize and poison the last element
  • later on we try accessing the poisoned element and crash.

This normally doesn't happen because the shifted elements optimization kicks in instead.

I think the bug is probably in moveDenseElements?

Group: core-security → javascript-core-security

(In reply to Iain Ireland [:iain] from comment #1)
Thanks for the analysis.

  • in moveDenseElements, we shift everything over by one (emitting pre-barriers on all elements except the last)

This is a classic incremental marking bug. We move a pointer behind the marking wavefront but don't trigger a barrier on the previous location.

(In reply to Iain Ireland [:iain] from comment #1)

  • we decrement the initialized length by 1, to 146

Looking at the code, this should trigger the required barrier - NativeObject::setDenseInitializedLengthInternal calls prepareElementRangeForOverwrite. I wonder what's going wrong?

Oh, I might know the answer to that. In this case, the dense initialized length is only being updated after we return to jit code.

(In reply to Iain Ireland [:iain] from comment #4)
Aha, that looks like the problem.

Flags: needinfo?(jdemooij)
Assignee: nobody → jdemooij
Status: NEW → ASSIGNED
Severity: -- → S3
Priority: -- → P1

Comment on attachment 9269223 [details]
Bug 1760944 - Update array length in ArrayShiftMoveElements. r?iain!,jonco!

Security Approval Request

  • How easily could an exploit be constructed based on the patch?: This fixes some code that was incorrect but this was hidden by a different code path. I think this issue isn't exploitable at the moment.
  • Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: No
  • Which older supported branches are affected by this flaw?: All
  • If not all supported branches, which bug introduced the flaw?: None
  • Do you have backports for the affected branches?: Yes
  • If not, how different, hard to create, and risky will they be?: Should apply
  • How likely is this patch to cause regressions; how much testing does it need?: Low risk
Flags: needinfo?(jdemooij)
Attachment #9269223 - Flags: sec-approval?

The severity field for this bug is set to S3. However, the bug is flagged with the sec-high keyword.
:jandem, could you consider increasing the severity of this security bug?

For more information, please visit auto_nag documentation.

Flags: needinfo?(jdemooij)

The buggy code path is only reachable if initializedLength == 1 and that case is barriered in JIT code, so I think this is a sec-moderate rather than a sec-high.

Flags: needinfo?(jdemooij)

Comment on attachment 9269223 [details]
Bug 1760944 - Update array length in ArrayShiftMoveElements. r?iain!,jonco!

sec-approval not needed for moderate

Attachment #9269223 - Flags: sec-approval?
Group: javascript-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 3 years ago
Resolution: --- → FIXED
Target Milestone: --- → 101 Branch

This can ride the trains.

Flags: qe-verify-
Whiteboard: [post-critsmash-triage]
Whiteboard: [post-critsmash-triage] → [post-critsmash-triage][adv-main101+]
Attached file advisory.txt

Really not sure how to write this one up....

Alias: CVE-2022-31745
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: