Closed Bug 2008112 (CVE-2026-4727) Opened 10 months ago Closed 9 months ago

Out-of-Bounds Read in ML-DSA Private Key Parsing (zero-length privateKey)

Categories

(NSS :: Libraries, defect)

3.11.9
defect

Tracking

(firefox-esr115 wontfix, firefox-esr140 wontfix, firefox147 wontfix, firefox148 wontfix, firefox149 fixed)

RESOLVED FIXED
Tracking Status
firefox-esr115 --- wontfix
firefox-esr140 --- wontfix
firefox147 --- wontfix
firefox148 --- wontfix
firefox149 --- fixed

People

(Reporter: cy1yang, Assigned: anna.weine, NeedInfo)

References

Details

(5 keywords, Whiteboard: [adv-main149+])

Attachments

(2 files, 1 external file)

459 bytes, application/x-pkcs12
Details
7.83 KB, application/octet-stream
Details
Attached file malformed_mldsa.p12 —

Steps to reproduce:

  1. Use our generated P12 file (malformed_mldsa.p12)

  2. Open Firefox (Nightly or a build with ML-DSA support)

  3. Navigate to Certificate Manager:

    Settings → Privacy & Security → Certificates → View Certificates...
    

    Or enter about:preferences#privacy in the address bar and scroll to "Certificates"

  4. Click "Import..." Under "Your Certificates" button in the Certificate Manager dialog

  5. Select the file: malformed_mldsa.p12

  6. Enter password: Leave empty (just press Enter/OK)

Actual results:

Firefox will crash or become unresponsive when attempting to import the malformed certificate. On systems with crash reporting enabled, a crash report will be generated.

Analysis

Out-of-bounds read in NSS when parsing ML-DSA (post-quantum) private keys. Both PK11_ImportPrivateKeyInfoAndReturnKey() (pk11pk12.c:763) and sftk_unwrapPrivateKey() (pkcs11c.c:7215) access pki->privateKey.data[0] without validating pki->privateKey.len > 0. A PKCS#12 file with an empty privateKey OCTET STRING (04 00) triggers the bug during import.

Affected Code

Location 1: pk11pk12.c:763

case SEC_OID_ML_DSA_44:
case SEC_OID_ML_DSA_65:
case SEC_OID_ML_DSA_87:
    /* choice */
    switch (pki->privateKey.data[0]) {  // OOB READ if privateKey.len == 0
        case SEC_ASN1_CONTEXT_SPECIFIC | 0:
            keyTemplate = SECKEY_MLDSAPrivateKeySeedExportTemplate;
            break;
        // ...
    }

Location 2: pkcs11c.c:7215

case SEC_OID_ML_DSA_44_PUBLIC_KEY:
case SEC_OID_ML_DSA_65_PUBLIC_KEY:
case SEC_OID_ML_DSA_87_PUBLIC_KEY:
mldsa_next:
    switch (pki->privateKey.data[0]) {  // OOB READ if privateKey.len == 0
        // ...
    }

Expected results:

The code should validate pki->privateKey.len > 0 before accessing pki->privateKey.data[0] and return an appropriate error (e.g., SEC_ERROR_BAD_DER).

Attached file generate_poc.py —

Hi,
Thanks for the report. I can reproduce the bug. We will take a look at it.

Bob, FYI

Flags: needinfo?(rrelyea)
Attached file (secure) —

Anna and I took a very quick look at the pkcs12 fuzzer which uses the same set of functions as the certificate import code. The provided test case doesn't error for the fuzzer, so we should figure out why we're missing that coverage.

Dennis, is the fuzzer completely random, or does it work off valid data that is then corrupted?

If it's the former, then it probably didn't trigger because you need most of the input to be valid, including a valid ml-dsa oid (which is one of 3 very specific values).
If it's the latter, we probably don't have a valid ml-dsa p12 as a base. If you didn't have the ml-dsa as the base, then you're unlikely to get one of the ml-dsa oids and thus unlikely to trip the code.

Flags: needinfo?(rrelyea)

I didn't have time to look in detail yet, but I provided the attached test case to the fuzzer directly so it's not a question of randomness. The code run by the fuzzer and by the Fx import dialog should be equivalent (but presumably aren't).

(In reply to Dennis Jackson from comment #7)

I didn't have time to look in detail yet, but I provided the attached test case to the fuzzer directly so it's not a question of randomness. The code run by the fuzzer and by the Fx import dialog should be equivalent (but presumably aren't).

Just to be sure, you're able to reproduce the bug?

Status: UNCONFIRMED → NEW
Ever confirmed: true

Pushed by nkulatova@mozilla.com:
https://hg.mozilla.org/projects/nss/rev/a4e63cad50cc
Out-of-Bounds Read in ML-DSA Private Key Parsing (zero-length privateKey) r=nss-reviewers,rrelyea

Status: NEW → RESOLVED
Closed: 9 months ago
Resolution: --- → FIXED
Group: crypto-core-security → core-security-release

AFAIU the problematic function is SEC_PKCS12DecoderImportBags, but when running the the PKCS12 fuzzer with the certificate generated by generate_poc.py, it doesn't get pass SEC_PKCS12DecoderVerify. sec_pkcs12_decoder_verify_mac returns SECFailure, but I can't figure out why. Am I missing something?

QA Whiteboard: [sec] [qa-triage-done-c150/b149]
Whiteboard: [adv-main149+]

Cody / cy1yang: By default our advisories will credit you as Cody using your Bugzilla profile "real name" field. If you would like to be credited differently please let us know here. If you do, check the "request information from" box below, leave the "other" input as-is, and enter :tsmith in the blank input field. It should autocomplete with Tyson's mozilla.com address.

See Also: → 2025098

The report says OOB read, but if pki->privateKey.len == 0, then pki->privateKey.data should be NULL, or am I missing something?

Flags: needinfo?(anna.weine)
Alias: CVE-2026-4727

status-firefox-esr140: --- → fix-optional

shouldn't this be "unaffected" ? AFAICT, esr140 doesn't support ML-DSA.

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

Attachment

General

Creator:
Created:
Updated:
Size: