import changes from mozilla nss repo:

- Bug 1641480, TLS 1.3: tighten CCS handling in compatibility mode, r=mt
- Bug 1672703, always tolerate the first CCS in TLS 1.3, r=mt
- Bug 1663661 - Guard against NULL token in nssSlot_IsTokenPresent. r=jcj
- Bug 1607449 - Lock cert->nssCertificate to prevent data race. r=jcj,keeler
- Bug 1679290 - Don't hold slot lock when taking session lock r=bbeurdouche
This commit is contained in:
roytam1 2020-12-02 10:52:47 +08:00
commit ecf2071a46
9 changed files with 184 additions and 29 deletions

View file

@ -347,6 +347,85 @@ TEST_F(TlsConnectStreamTls13, ChangeCipherSpecBeforeClientHelloTwice) {
client_->CheckErrorCode(SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT); client_->CheckErrorCode(SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT);
} }
// The server accepts a ChangeCipherSpec even if the client advertises
// an empty session ID.
TEST_F(TlsConnectStreamTls13, ChangeCipherSpecAfterClientHelloEmptySid) {
EnsureTlsSetup();
ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
StartConnect();
client_->Handshake(); // Send ClientHello
client_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs))); // Send CCS
Handshake();
CheckConnected();
}
// The server rejects multiple ChangeCipherSpec even if the client
// indicates compatibility mode with non-empty session ID.
TEST_F(Tls13CompatTest, ChangeCipherSpecAfterClientHelloTwice) {
EnsureTlsSetup();
ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
EnableCompatMode();
StartConnect();
client_->Handshake(); // Send ClientHello
// Send CCS twice in a row
client_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
client_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
server_->ExpectSendAlert(kTlsAlertUnexpectedMessage);
server_->Handshake(); // Consume ClientHello and CCS.
server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER);
}
// The client accepts a ChangeCipherSpec even if it advertises an empty
// session ID.
TEST_F(TlsConnectStreamTls13, ChangeCipherSpecAfterServerHelloEmptySid) {
EnsureTlsSetup();
ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
// To replace Finished with a CCS below
auto filter = MakeTlsFilter<TlsHandshakeDropper>(server_);
filter->SetHandshakeTypes({kTlsHandshakeFinished});
filter->EnableDecryption();
StartConnect();
client_->Handshake(); // Send ClientHello
server_->Handshake(); // Consume ClientHello, and
// send ServerHello..CertificateVerify
// Send CCS
server_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
// No alert is sent from the client. As Finished is dropped, we
// can't use Handshake() and CheckConnected().
client_->Handshake();
}
// The client rejects multiple ChangeCipherSpec in a row even if the
// client indicates compatibility mode with non-empty session ID.
TEST_F(Tls13CompatTest, ChangeCipherSpecAfterServerHelloTwice) {
EnsureTlsSetup();
ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
EnableCompatMode();
// To replace Finished with a CCS below
auto filter = MakeTlsFilter<TlsHandshakeDropper>(server_);
filter->SetHandshakeTypes({kTlsHandshakeFinished});
filter->EnableDecryption();
StartConnect();
client_->Handshake(); // Send ClientHello
server_->Handshake(); // Consume ClientHello, and
// send ServerHello..CertificateVerify
// the ServerHello is followed by CCS
// Send another CCS
server_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
client_->ExpectSendAlert(kTlsAlertUnexpectedMessage);
client_->Handshake(); // Consume ClientHello and CCS
client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER);
}
// If we negotiate 1.2, we abort. // If we negotiate 1.2, we abort.
TEST_F(TlsConnectStreamTls13, ChangeCipherSpecBeforeClientHello12) { TEST_F(TlsConnectStreamTls13, ChangeCipherSpecBeforeClientHello12) {
EnsureTlsSetup(); EnsureTlsSetup();

View file

@ -2908,16 +2908,27 @@ CERT_LockCertTrust(const CERTCertificate *cert)
PZ_Lock(certTrustLock); PZ_Lock(certTrustLock);
} }
static PZLock *certTempPermLock = NULL; static PZLock *certTempPermCertLock = NULL;
/* /*
* Acquire the cert temp/perm lock * Acquire the cert temp/perm/nssCert lock
*/ */
void void
CERT_LockCertTempPerm(const CERTCertificate *cert) CERT_LockCertTempPerm(const CERTCertificate *cert)
{ {
PORT_Assert(certTempPermLock != NULL); PORT_Assert(certTempPermCertLock != NULL);
PZ_Lock(certTempPermLock); PZ_Lock(certTempPermCertLock);
}
/* Maybe[Lock, Unlock] variants are only to be used by
* CERT_DestroyCertificate, since an application could
* call this after NSS_Shutdown destroys cert locks. */
void
CERT_MaybeLockCertTempPerm(const CERTCertificate *cert)
{
if (certTempPermCertLock) {
PZ_Lock(certTempPermCertLock);
}
} }
SECStatus SECStatus
@ -2941,10 +2952,10 @@ cert_InitLocks(void)
} }
} }
if (certTempPermLock == NULL) { if (certTempPermCertLock == NULL) {
certTempPermLock = PZ_NewLock(nssILockCertDB); certTempPermCertLock = PZ_NewLock(nssILockCertDB);
PORT_Assert(certTempPermLock != NULL); PORT_Assert(certTempPermCertLock != NULL);
if (!certTempPermLock) { if (!certTempPermCertLock) {
PZ_DestroyLock(certTrustLock); PZ_DestroyLock(certTrustLock);
PZ_DestroyLock(certRefCountLock); PZ_DestroyLock(certRefCountLock);
certRefCountLock = NULL; certRefCountLock = NULL;
@ -2977,10 +2988,10 @@ cert_DestroyLocks(void)
rv = SECFailure; rv = SECFailure;
} }
PORT_Assert(certTempPermLock != NULL); PORT_Assert(certTempPermCertLock != NULL);
if (certTempPermLock) { if (certTempPermCertLock) {
PZ_DestroyLock(certTempPermLock); PZ_DestroyLock(certTempPermCertLock);
certTempPermLock = NULL; certTempPermCertLock = NULL;
} else { } else {
rv = SECFailure; rv = SECFailure;
} }
@ -2999,16 +3010,24 @@ CERT_UnlockCertTrust(const CERTCertificate *cert)
} }
/* /*
* Free the temp/perm lock * Free the temp/perm/nssCert lock
*/ */
void void
CERT_UnlockCertTempPerm(const CERTCertificate *cert) CERT_UnlockCertTempPerm(const CERTCertificate *cert)
{ {
PORT_Assert(certTempPermLock != NULL); PORT_Assert(certTempPermCertLock != NULL);
PRStatus prstat = PZ_Unlock(certTempPermLock); PRStatus prstat = PZ_Unlock(certTempPermCertLock);
PORT_AssertArg(prstat == PR_SUCCESS); PORT_AssertArg(prstat == PR_SUCCESS);
} }
void
CERT_MaybeUnlockCertTempPerm(const CERTCertificate *cert)
{
if (certTempPermCertLock) {
PZ_Unlock(certTempPermCertLock);
}
}
/* /*
* Get the StatusConfig data for this handle * Get the StatusConfig data for this handle
*/ */

View file

@ -32,6 +32,9 @@
#include "dev.h" #include "dev.h"
#include "secmodi.h" #include "secmodi.h"
extern void CERT_MaybeLockCertTempPerm(const CERTCertificate *cert);
extern void CERT_MaybeUnlockCertTempPerm(const CERTCertificate *cert);
PRBool PRBool
SEC_CertNicknameConflict(const char *nickname, const SECItem *derSubject, SEC_CertNicknameConflict(const char *nickname, const SECItem *derSubject,
CERTCertDBHandle *handle) CERTCertDBHandle *handle)
@ -311,7 +314,9 @@ __CERT_AddTempCertToPerm(CERTCertificate *cert, char *nickname,
nssPKIObject_AddInstance(&c->object, permInstance); nssPKIObject_AddInstance(&c->object, permInstance);
nssTrustDomain_AddCertsToCache(STAN_GetDefaultTrustDomain(), &c, 1); nssTrustDomain_AddCertsToCache(STAN_GetDefaultTrustDomain(), &c, 1);
/* reset the CERTCertificate fields */ /* reset the CERTCertificate fields */
CERT_LockCertTempPerm(cert);
cert->nssCertificate = NULL; cert->nssCertificate = NULL;
CERT_UnlockCertTempPerm(cert);
cert = STAN_GetCERTCertificateOrRelease(c); /* should return same pointer */ cert = STAN_GetCERTCertificateOrRelease(c); /* should return same pointer */
if (!cert) { if (!cert) {
CERT_MapStanError(); CERT_MapStanError();
@ -808,9 +813,17 @@ CERT_DestroyCertificate(CERTCertificate *cert)
/* don't use STAN_GetNSSCertificate because we don't want to /* don't use STAN_GetNSSCertificate because we don't want to
* go to the trouble of translating the CERTCertificate into * go to the trouble of translating the CERTCertificate into
* an NSSCertificate just to destroy it. If it hasn't been done * an NSSCertificate just to destroy it. If it hasn't been done
* yet, don't do it at all. * yet, don't do it at all
*/ *
* cert->nssCertificate contains its own locks and refcount, but as it
* may be NULL, the pointer itself must be guarded by some other lock.
* Rather than creating a new global lock for only this purpose, share
* an existing global lock that happens to be taken near the write in
* fill_CERTCertificateFields(). The longer-term goal is to refactor
* all these global locks to be certificate-scoped. */
CERT_MaybeLockCertTempPerm(cert);
NSSCertificate *tmp = cert->nssCertificate; NSSCertificate *tmp = cert->nssCertificate;
CERT_MaybeUnlockCertTempPerm(cert);
if (tmp) { if (tmp) {
/* delete the NSSCertificate */ /* delete the NSSCertificate */
NSSCertificate_Destroy(tmp); NSSCertificate_Destroy(tmp);

View file

@ -171,11 +171,12 @@ nssSlot_IsTokenPresent(
nssSlot_EnterMonitor(slot); nssSlot_EnterMonitor(slot);
ckrv = CKAPI(epv)->C_GetSlotInfo(slot->slotID, &slotInfo); ckrv = CKAPI(epv)->C_GetSlotInfo(slot->slotID, &slotInfo);
nssSlot_ExitMonitor(slot);
if (ckrv != CKR_OK) { if (ckrv != CKR_OK) {
slot->token->base.name[0] = 0; /* XXX */ if (slot->token) {
slot->token->base.name[0] = 0; /* XXX */
}
isPresent = PR_FALSE; isPresent = PR_FALSE;
goto done; goto done; /* slot lock held */
} }
slot->ckFlags = slotInfo.flags; slot->ckFlags = slotInfo.flags;
/* check for the presence of the token */ /* check for the presence of the token */
@ -183,10 +184,11 @@ nssSlot_IsTokenPresent(
if (!slot->token) { if (!slot->token) {
/* token was never present */ /* token was never present */
isPresent = PR_FALSE; isPresent = PR_FALSE;
goto done; goto done; /* slot lock held */
} }
session = nssToken_GetDefaultSession(slot->token); session = nssToken_GetDefaultSession(slot->token);
if (session) { if (session) {
nssSlot_ExitMonitor(slot);
nssSession_EnterMonitor(session); nssSession_EnterMonitor(session);
/* token is not present */ /* token is not present */
if (session->handle != CK_INVALID_SESSION) { if (session->handle != CK_INVALID_SESSION) {
@ -196,6 +198,12 @@ nssSlot_IsTokenPresent(
session->handle = CK_INVALID_SESSION; session->handle = CK_INVALID_SESSION;
} }
nssSession_ExitMonitor(session); nssSession_ExitMonitor(session);
nssSlot_EnterMonitor(slot);
if (!slot->token) {
/* Check token presence after re-acquiring lock */
isPresent = PR_FALSE;
goto done; /* slot lock held */
}
} }
if (slot->token->base.name[0] != 0) { if (slot->token->base.name[0] != 0) {
/* notify the high-level cache that the token is removed */ /* notify the high-level cache that the token is removed */
@ -206,14 +214,23 @@ nssSlot_IsTokenPresent(
/* clear the token cache */ /* clear the token cache */
nssToken_Remove(slot->token); nssToken_Remove(slot->token);
isPresent = PR_FALSE; isPresent = PR_FALSE;
goto done; goto done; /* slot lock held */
} }
if (!slot->token) {
/* This should not occur, based on the fact that the
* below calls will dereference NULL otherwise. */
PORT_Assert(0);
isPresent = PR_FALSE;
goto done; /* slot lock held */
}
/* token is present, use the session info to determine if the card /* token is present, use the session info to determine if the card
* has been removed and reinserted. * has been removed and reinserted.
*/ */
session = nssToken_GetDefaultSession(slot->token); session = nssToken_GetDefaultSession(slot->token);
if (session) { if (session) {
PRBool tokenRemoved; PRBool tokenRemoved;
nssSlot_ExitMonitor(slot);
nssSession_EnterMonitor(session); nssSession_EnterMonitor(session);
if (session->handle != CK_INVALID_SESSION) { if (session->handle != CK_INVALID_SESSION) {
CK_SESSION_INFO sessionInfo; CK_SESSION_INFO sessionInfo;
@ -227,10 +244,16 @@ nssSlot_IsTokenPresent(
} }
tokenRemoved = (session->handle == CK_INVALID_SESSION); tokenRemoved = (session->handle == CK_INVALID_SESSION);
nssSession_ExitMonitor(session); nssSession_ExitMonitor(session);
nssSlot_EnterMonitor(slot);
/* token not removed, finished */ /* token not removed, finished */
if (!tokenRemoved) { if (!tokenRemoved) {
isPresent = PR_TRUE; isPresent = PR_TRUE;
goto done; goto done; /* slot lock held */
}
if (!slot->token) {
/* Check token presence after re-acquiring lock */
isPresent = PR_FALSE;
goto done; /* slot lock held */
} }
} }
/* the token has been removed, and reinserted, or the slot contains /* the token has been removed, and reinserted, or the slot contains
@ -248,6 +271,7 @@ nssSlot_IsTokenPresent(
isPresent = PR_FALSE; isPresent = PR_FALSE;
} }
done: done:
nssSlot_ExitMonitor(slot);
/* Once we've set up the condition variable, /* Once we've set up the condition variable,
* Before returning, it's necessary to: * Before returning, it's necessary to:
* 1) Set the lastTokenPingTime so that any other threads waiting on this * 1) Set the lastTokenPingTime so that any other threads waiting on this

View file

@ -96,6 +96,9 @@ struct NSSSlotStr {
}; };
struct nssSessionStr { struct nssSessionStr {
/* Must not hold slot->lock when taking lock.
* See ordering in nssSlot_IsTokenPresent.
*/
PZLock *lock; PZLock *lock;
CK_SESSION_HANDLE handle; CK_SESSION_HANDLE handle;
NSSSlot *slot; NSSSlot *slot;

View file

@ -1146,8 +1146,11 @@ PK11_ImportCert(PK11SlotInfo *slot, CERTCertificate *cert,
} }
/* need to get the cert as a stan cert */ /* need to get the cert as a stan cert */
if (cert->nssCertificate) { CERT_LockCertTempPerm(cert);
c = cert->nssCertificate; NSSCertificate *nssCert = cert->nssCertificate;
CERT_UnlockCertTempPerm(cert);
if (nssCert) {
c = nssCert;
} else { } else {
c = STAN_GetNSSCertificate(cert); c = STAN_GetNSSCertificate(cert);
if (c == NULL) { if (c == NULL) {

View file

@ -866,9 +866,9 @@ fill_CERTCertificateFields(NSSCertificate *c, CERTCertificate *cc, PRBool forced
CERT_LockCertTempPerm(cc); CERT_LockCertTempPerm(cc);
cc->istemp = PR_FALSE; /* CERT_NewTemp will override this */ cc->istemp = PR_FALSE; /* CERT_NewTemp will override this */
cc->isperm = PR_TRUE; /* by default */ cc->isperm = PR_TRUE; /* by default */
CERT_UnlockCertTempPerm(cc);
/* pointer back */ /* pointer back */
cc->nssCertificate = c; cc->nssCertificate = c;
CERT_UnlockCertTempPerm(cc);
if (trust) { if (trust) {
/* force the cert type to be recomputed to include trust info */ /* force the cert type to be recomputed to include trust info */
PRUint32 nsCertType = cert_ComputeCertType(cc); PRUint32 nsCertType = cert_ComputeCertType(cc);
@ -919,7 +919,10 @@ stan_GetCERTCertificate(NSSCertificate *c, PRBool forceUpdate)
nss_SetError(NSS_ERROR_INTERNAL_ERROR); nss_SetError(NSS_ERROR_INTERNAL_ERROR);
goto loser; goto loser;
} }
if (!cc->nssCertificate || forceUpdate) { CERT_LockCertTempPerm(cc);
NSSCertificate *nssCert = cc->nssCertificate;
CERT_UnlockCertTempPerm(cc);
if (!nssCert || forceUpdate) {
fill_CERTCertificateFields(c, cc, forceUpdate); fill_CERTCertificateFields(c, cc, forceUpdate);
} else if (CERT_GetCertTrust(cc, &certTrust) != SECSuccess) { } else if (CERT_GetCertTrust(cc, &certTrust) != SECSuccess) {
CERTCertTrust *trust; CERTCertTrust *trust;
@ -1018,7 +1021,9 @@ STAN_GetNSSCertificate(CERTCertificate *cc)
nssCryptokiInstance *instance; nssCryptokiInstance *instance;
nssPKIObject *pkiob; nssPKIObject *pkiob;
NSSArena *arena; NSSArena *arena;
CERT_LockCertTempPerm(cc);
c = cc->nssCertificate; c = cc->nssCertificate;
CERT_UnlockCertTempPerm(cc);
if (c) { if (c) {
return c; return c;
} }
@ -1083,7 +1088,9 @@ STAN_GetNSSCertificate(CERTCertificate *cc)
nssPKIObject_AddInstance(&c->object, instance); nssPKIObject_AddInstance(&c->object, instance);
} }
c->decoding = create_decoded_pkix_cert_from_nss3cert(NULL, cc); c->decoding = create_decoded_pkix_cert_from_nss3cert(NULL, cc);
CERT_LockCertTempPerm(cc);
cc->nssCertificate = c; cc->nssCertificate = c;
CERT_UnlockCertTempPerm(cc);
return c; return c;
} }

View file

@ -12993,8 +12993,14 @@ ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cText)
ss->ssl3.hs.ws != idle_handshake && ss->ssl3.hs.ws != idle_handshake &&
cText->buf->len == 1 && cText->buf->len == 1 &&
cText->buf->buf[0] == change_cipher_spec_choice) { cText->buf->buf[0] == change_cipher_spec_choice) {
/* Ignore the CCS. */ if (!ss->ssl3.hs.rejectCcs) {
return SECSuccess; /* Allow only the first CCS. */
ss->ssl3.hs.rejectCcs = PR_TRUE;
return SECSuccess;
} else {
alert = unexpected_message;
PORT_SetError(SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER);
}
} }
if (IS_DTLS(ss) || if (IS_DTLS(ss) ||

View file

@ -710,6 +710,7 @@ typedef struct SSL3HandshakeStateStr {
* or received. */ * or received. */
PRBool receivedCcs; /* A server received ChangeCipherSpec PRBool receivedCcs; /* A server received ChangeCipherSpec
* before the handshake started. */ * before the handshake started. */
PRBool rejectCcs; /* Excessive ChangeCipherSpecs are rejected. */
PRBool clientCertRequested; /* True if CertificateRequest received. */ PRBool clientCertRequested; /* True if CertificateRequest received. */
ssl3KEADef kea_def_mutable; /* Used to hold the writable kea_def ssl3KEADef kea_def_mutable; /* Used to hold the writable kea_def
* we use for TLS 1.3 */ * we use for TLS 1.3 */