Closed Bug 1715318 (CVE-2021-29982) Opened 3 years ago Closed 3 years ago

Differential Testing: Different output during TypeError

Categories

(Core :: JavaScript Engine: JIT, defect, P1)

defect

Tracking

()

RESOLVED FIXED
91 Branch
Tracking Status
firefox91 --- fixed

People

(Reporter: lukas.bernhard, Assigned: jandem)

References

(Blocks 1 open bug)

Details

(Keywords: csectype-disclosure, sec-low, Whiteboard: [adv-main91+])

Attachments

(3 files)

User Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0

Steps to reproduce:

The following testcase produces different results on mozilla-central git commit 0e97d943bc746e4fd92902b21d3d5bebfef885a1 depending on whether ion is enabled or disabled.

function main() {
    let v1 = 0;
    function v18() {
        for (let v35 = 0; v35 < 100; v35++) {}

        try {
            async function* v37() {}
            class V47 extends v37 {}; 
            v1 += 1;
        }   
        catch (e) {
            print("caught: " + e); 
        }   
    };  
    v18();
    v18();  // second call may or may not reach catch handler, approx 50/50
    print(v1); // either 0 or 1
}
main();

Actual results:

Running the sample with ion disabled (obj-x86_64-pc-linux-gnu/dist/bin/js --no-threads --cpu-count=1 --baseline-warmup-threshold=10 --fuzzing-safe --differential-testing --no-ion diff.js) causes two exceptions to be thrown, v1 is 0 at the end of main().

If ion is enabled (obj-x86_64-pc-linux-gnu/dist/bin/js --no-threads --cpu-count=1 --ion-offthread-compile=off --baseline-warmup-threshold=10 --ion-warmup-threshold=100 --ion-check-range-analysis --ion-extra-checks --fuzzing-safe --differential-testing diff.js), the second execution of v18() may or may not (approx. 50/50) throw an exception. v1 is either 0 or 1 at the end of main().

Marking as security as a few differential execution bugs were flagged in the past.

Group: firefox-core-security → core-security
Component: Untriaged → JavaScript Engine: JIT
Product: Firefox → Core
Version: Firefox 91 → Trunk
Flags: needinfo?(jdemooij)
Group: core-security → javascript-core-security

Sorry for the delay. I started looking into this today but had something urgent come up IRL.

The bug has to do with MacroAssembler::isCallableOrConstructor though, we call it with the same register for |obj| and |output| from Ion codegen and that's wrong because the function clobbers that register for the class check before we check the function flags (expecting the original value).

Great find, thanks for reporting this!

Assignee: nobody → jdemooij
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true
Flags: sec-bounty?

Opening this up. We end up reading a single bit from one of the pointers in the function JSClass and interpreting that as "is constructor", so ASLR causes the differential behavior, but this isn't exploitable.

Group: javascript-core-security

isCallableOrConstructor first does a JSClass check which clobbers the output register.
If the JSClass is the JSFunction class, it then checks the function's is-constructor
flag. If we pass the same register for object and output, we end up reading a garbage
bit of the JSFunction JSClass and interpreting that as is-constructor.

This is a bit simpler and faster.

Depends on D117634

Flags: needinfo?(jdemooij)
Pushed by jdemooij@mozilla.com:
https://hg.mozilla.org/integration/autoland/rev/7f9481bf5840
part 1 - Fix LCheckClassHeritage to not clobber the object register. r=iain
https://hg.mozilla.org/integration/autoland/rev/09af51138cf0
part 2 - Use fallibleUnboxObject for LCheckClassHeritage. r=iain
Severity: -- → S3
Priority: -- → P1
Status: ASSIGNED → RESOLVED
Closed: 3 years ago
Resolution: --- → FIXED
Target Milestone: --- → 91 Branch

A one-bit leak is still a leak so we'll tag this as a low-severity security bug.

Flags: sec-bounty? → sec-bounty+
Whiteboard: [adv-main90+]
Whiteboard: [adv-main90+] → [adv-main91+]
Alias: CVE-2021-29982
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: