From 64ea259561c033c404884d474df9fb0f90c13856 Mon Sep 17 00:00:00 2001 From: Robert Relyea Date: Mon, 12 Jan 2026 08:25:48 -0800 Subject: [PATCH] Related: RHEL-131347 - fix a null in ml-dsa pkcs12 decode - fix return code in ml-kem pct --- nss-3.112-pkcs12-ml-dsa-crash-fix.patch | 202 ++++++++++++++++++++++++ nss.spec | 7 +- 2 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 nss-3.112-pkcs12-ml-dsa-crash-fix.patch diff --git a/nss-3.112-pkcs12-ml-dsa-crash-fix.patch b/nss-3.112-pkcs12-ml-dsa-crash-fix.patch new file mode 100644 index 0000000..038dbc8 --- /dev/null +++ b/nss-3.112-pkcs12-ml-dsa-crash-fix.patch @@ -0,0 +1,202 @@ +# HG changeset patch +# User Robert Relyea +# Date 1767992040 28800 +# Fri Jan 09 12:54:00 2026 -0800 +# Branch RHEL10 +# Node ID 15f1129c29c037cce7913eb62c0eca06b5aa51d1 +# Parent 4bfb87c6e863957fc933e6dd5af8eae8a5cd0469 +nss-3.112-pkcs12-ml-dsa-crash-fix.patch + +diff --git a/gtests/pk11_gtest/pk11_der_private_key_import_unittest.cc b/gtests/pk11_gtest/pk11_der_private_key_import_unittest.cc +--- a/gtests/pk11_gtest/pk11_der_private_key_import_unittest.cc ++++ b/gtests/pk11_gtest/pk11_der_private_key_import_unittest.cc +@@ -7,16 +7,17 @@ + #include + #include + #include "nss.h" + #include "pk11pub.h" + #include "secutil.h" + + #include "gtest/gtest.h" + #include "nss_scoped_ptrs.h" ++#define SEC_OID_ML_DSA_44 SEC_OID_PRIVATE_3 + + namespace nss_test { + + const std::vector kValidP256Key = { + 0x30, 0x81, 0x87, 0x02, 0x01, 0x00, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, + 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, + 0x03, 0x01, 0x07, 0x04, 0x6d, 0x30, 0x6b, 0x02, 0x01, 0x01, 0x04, 0x20, + 0xc9, 0xaf, 0xa9, 0xd8, 0x45, 0xba, 0x75, 0x16, 0x6b, 0x5c, 0x21, 0x57, +@@ -133,16 +134,69 @@ class DERPrivateKeyImportTest : public : + // no cert. This is expected, so clear it. + if (PORT_GetError() == SSL_ERROR_NO_CERTIFICATE) { + PORT_SetError(0); + } + } + + return rv == SECSuccess; + } ++ ++ SECStatus BuildPrivateKeyInfoAndImportIt(SECOidTag algTag) { ++ ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); ++ EXPECT_TRUE(slot); ++ if (!slot) { ++ return SECFailure; ++ } ++ ++ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); ++ EXPECT_TRUE(arena); ++ if (!arena) { ++ return SECFailure; ++ } ++ ++ SECKEYPrivateKeyInfo* pki = (SECKEYPrivateKeyInfo*)PORT_ArenaZAlloc( ++ arena.get(), sizeof(SECKEYPrivateKeyInfo)); ++ EXPECT_TRUE(pki); ++ if (!pki) { ++ return SECFailure; ++ } ++ ++ pki->version.data = (unsigned char*)PORT_ArenaAlloc(arena.get(), 1); ++ EXPECT_TRUE(pki->version.data); ++ if (!pki->version.data) { ++ return SECFailure; ++ } ++ ++ pki->version.data[0] = 0x00; ++ pki->version.len = 1; ++ ++ EXPECT_EQ( ++ SECOID_SetAlgorithmID(arena.get(), &pki->algorithm, algTag, nullptr), ++ SECSuccess); ++ ++ // Empty private key ++ pki->privateKey.data = (unsigned char*)PORT_ArenaAlloc(arena.get(), 1); ++ EXPECT_TRUE(pki->privateKey.data); ++ if (!pki->privateKey.data) { ++ return SECFailure; ++ } ++ pki->privateKey.len = 0; ++ ++ SECKEYPrivateKey* privk = nullptr; ++ PORT_SetError(0); ++ SECStatus rv = PK11_ImportPrivateKeyInfoAndReturnKey( ++ slot.get(), pki, nullptr, nullptr, PR_FALSE, PR_FALSE, KU_ALL, &privk, ++ nullptr); ++ ++ if (privk) { ++ SECKEY_DestroyPrivateKey(privk); ++ } ++ return rv; ++ } + }; + + TEST_F(DERPrivateKeyImportTest, ImportPrivateRSAKey) { + EXPECT_TRUE(ParsePrivateKey(kValidRSAKey, true)); + EXPECT_FALSE(PORT_GetError()) << PORT_GetError(); + } + + TEST_F(DERPrivateKeyImportTest, ImportEcdsaKey) { +@@ -155,9 +209,15 @@ TEST_F(DERPrivateKeyImportTest, ImportIn + EXPECT_EQ(PORT_GetError(), SEC_ERROR_BAD_DER) << PORT_GetError(); + } + + TEST_F(DERPrivateKeyImportTest, ImportZeroLengthPrivateKey) { + EXPECT_FALSE(ParsePrivateKey(kInvalidZeroLengthKey, false)); + EXPECT_EQ(PORT_GetError(), SEC_ERROR_BAD_KEY) << PORT_GetError(); + } + ++TEST_F(DERPrivateKeyImportTest, ++ ImportZeroLengthMLDSAPrivateKey) { ++ EXPECT_EQ(BuildPrivateKeyInfoAndImportIt(SEC_OID_ML_DSA_44), SECFailure); ++ EXPECT_EQ(PORT_GetError(), SEC_ERROR_BAD_KEY); ++} ++ + } // namespace nss_test +diff --git a/lib/pk11wrap/pk11pk12.c b/lib/pk11wrap/pk11pk12.c +--- a/lib/pk11wrap/pk11pk12.c ++++ b/lib/pk11wrap/pk11pk12.c +@@ -323,17 +323,17 @@ PK11_ImportDERPrivateKeyInfoAndReturnKey + derPKI); + if (rv != SECSuccess) { + /* If SEC_ASN1DecodeItem fails, we cannot assume anything about the + * validity of the data in pki. The best we can do is free the arena + * and return. */ + PORT_FreeArena(temparena, PR_TRUE); + return rv; + } +- if (pki->privateKey.data == NULL) { ++ if (pki->privateKey.data == NULL || pki->privateKey.len == 0) { + /* If SEC_ASN1DecodeItems succeeds but SECKEYPrivateKeyInfo.privateKey + * is a zero-length octet string, free the arena and return a failure + * to avoid trying to zero the corresponding SECItem in + * SECKEY_DestroyPrivateKeyInfo(). */ + PORT_FreeArena(temparena, PR_TRUE); + PORT_SetError(SEC_ERROR_BAD_KEY); + return SECFailure; + } +@@ -753,16 +753,20 @@ PK11_ImportPrivateKeyInfoAndReturnKey(PK + keyTemplate = SECKEY_ECPrivateKeyExportTemplate; + paramTemplate = NULL; + paramDest = NULL; + lpk->keyType = ecKey; + break; + case SEC_OID_ML_DSA_44: + case SEC_OID_ML_DSA_65: + case SEC_OID_ML_DSA_87: ++ if (pki->privateKey.data == NULL || pki->privateKey.len == 0) { ++ PORT_SetError(SEC_ERROR_BAD_KEY); ++ goto loser; ++ } + /* choice */ + switch (pki->privateKey.data[0]) { + case SEC_ASN1_CONTEXT_SPECIFIC|0: + keyTemplate = SECKEY_MLDSAPrivateKeySeedExportTemplate; + break; + case SEC_ASN1_OCTET_STRING: + keyTemplate = SECKEY_MLDSAPrivateKeyKeyExportTemplate; + break; +diff --git a/lib/softoken/pkcs11c.c b/lib/softoken/pkcs11c.c +--- a/lib/softoken/pkcs11c.c ++++ b/lib/softoken/pkcs11c.c +@@ -5815,17 +5815,17 @@ sftk_PairwiseConsistencyCheck(CK_SESSION + } + crv = NSC_Decapsulate(hSession, &mech, privateKey->handle, + cipher_text, cipher_text_length, &template, 1, + &key2); + if (crv != CKR_OK) { + goto kem_done; + } + if (!sftk_compareKeysEqual(hSession, key1, key2)) { +- crv = CKR_DEVICE_ERROR; ++ crv = CKR_GENERAL_ERROR; + goto kem_done; + } + kem_done: + /* PORT_Free already checks for NULL */ + PORT_Free(cipher_text); + if (key1 != CK_INVALID_HANDLE) { + NSC_DestroyObject(hSession, key1); + } +@@ -7105,16 +7105,20 @@ sftk_unwrapPrivateKey(SFTKObject *key, S + paramSet = CKP_ML_DSA_44; + goto mldsa_next; + case SEC_OID_ML_DSA_65: + paramSet = CKP_ML_DSA_65; + goto mldsa_next; + case SEC_OID_ML_DSA_87: + paramSet = CKP_ML_DSA_87; + mldsa_next: ++ if (pki->privateKey.data == NULL || pki->privateKey.len == 0) { ++ PORT_SetError(SEC_ERROR_BAD_KEY); ++ goto loser; ++ } + switch (pki->privateKey.data[0]) { + case SEC_ASN1_CONTEXT_SPECIFIC|0: + keyTemplate = nsslowkey_PQSeedTemplate; + break; + case SEC_ASN1_OCTET_STRING: + keyTemplate = nsslowkey_PQPrivateKeyTemplate; + break; + case SEC_ASN1_CONSTRUCTED|SEC_ASN1_SEQUENCE: diff --git a/nss.spec b/nss.spec index 04ffd68..8eec0b2 100644 --- a/nss.spec +++ b/nss.spec @@ -3,7 +3,7 @@ # NOTE: To avoid NVR clashes of nspr* packages: # - reset %%{nspr_release} to 1, when updating %%{nspr_version} # - increment %%{nspr_version}, when updating the NSS part only -%global baserelease 5 +%global baserelease 6 %global nss_release %baserelease # use "%%global nspr_release %%[%%baserelease+n]" to handle offsets when # release number between nss and nspr are different. @@ -184,6 +184,7 @@ Patch88: nss-3.112-fix-get-interface.patch Patch89: nss-3.112-mlkem-fips-update.patch Patch90: nss-3.112-update-fixes.patch Patch91: nss-3.112-partial-pub-key-validate.patch +Patch92: nss-3.112-pkcs12-ml-dsa-crash-fix.patch # NSS reverse patches Patch300: nss-3.79-distrusted-certs.patch @@ -1171,6 +1172,10 @@ fi %changelog +* Fri Jan 9 2026 Bob Relyea - 3.112.0-6 +- fix a null in ml-dsa pkcs12 decode +- fix return code in ml-kem pct + * Mon Nov 3 2025 Bob Relyea - 3.112.0-5 - fips update - Fix indicators for the new post-quantum algorithms