
* `closeSessionOnErrorLater` introduces a race where another thread can post a task to the networking thread while a session is being destroyed. This was avoided in the past by not deleting the sockets until the session destructor is called. However, https://chromium-review.googlesource.com/c/chromium/src/+/4570128 destroyed the sockets during `onConnectionClosed()` which has led to crashes happening due the race condition surfacing. This CL only adds tests to make sure the crash does not resurface in the future. A proper fix would be deleting all usages of `closeSessionOnErrorLater` and replacing it with `closeSessionOnError` so we can avoid the race completely. * Added 5 different unit-tests all using the same scenario where another thread posts a network change task immediately on the networking thread but each unit-test go through different codepaths. * Added a test for failing connection migration due to lack of spare CID. * Added a test for failing connection migration due to UDP socket connect error. * Added a test for failing connection migration due to no active streams. * Added a test for failing connection migration due to handshake not confirmed. * Added a test for two sessions failing connection migration due to handshake not confirmed. This is to ensure that removing sessions while iterating does not cause crashes. Bug: 1465889 Change-Id: Id44c3e5f506c9f3ada43ad993ed90748f5a7b95c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4705611 Auto-Submit: Mohannad Farrag <aymanm@google.com> Reviewed-by: Fan Yang <fayang@chromium.org> Commit-Queue: Fan Yang <fayang@chromium.org> Cr-Commit-Position: refs/heads/main@{#1177223}
403 lines
14 KiB
C++
403 lines
14 KiB
C++
// Copyright 2013 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "net/quic/mock_crypto_client_stream.h"
|
|
|
|
#include "net/base/ip_endpoint.h"
|
|
#include "net/quic/address_utils.h"
|
|
#include "net/quic/mock_decrypter.h"
|
|
#include "net/quic/mock_encrypter.h"
|
|
#include "net/quic/quic_chromium_client_session.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/core/crypto/quic_decrypter.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/core/crypto/quic_encrypter.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_client_session_base.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/core/quic_utils.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/test_tools/quic_config_peer.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/test_tools/quic_connection_peer.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/test_tools/quic_test_utils.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "third_party/boringssl/src/include/openssl/ssl.h"
|
|
|
|
using quic::CLIENT;
|
|
using quic::ConnectionCloseBehavior;
|
|
using quic::CryptoHandshakeMessage;
|
|
using quic::CryptoMessageParser;
|
|
using quic::ENCRYPTION_FORWARD_SECURE;
|
|
using quic::ENCRYPTION_INITIAL;
|
|
using quic::ENCRYPTION_ZERO_RTT;
|
|
using quic::kAESG;
|
|
using quic::kC255;
|
|
using quic::kDefaultMaxStreamsPerConnection;
|
|
using quic::kQBIC;
|
|
using quic::Perspective;
|
|
using quic::ProofVerifyContext;
|
|
using quic::QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE;
|
|
using quic::QUIC_NO_ERROR;
|
|
using quic::QUIC_PROOF_INVALID;
|
|
using quic::QuicConfig;
|
|
using quic::QuicCryptoClientConfig;
|
|
using quic::QuicCryptoNegotiatedParameters;
|
|
using quic::QuicErrorCode;
|
|
using quic::QuicServerId;
|
|
using quic::QuicSession;
|
|
using quic::QuicSpdyClientSessionBase;
|
|
using quic::QuicTagVector;
|
|
using quic::QuicTime;
|
|
using quic::TransportParameters;
|
|
using quic::test::StrictTaggingDecrypter;
|
|
using quic::test::TaggingEncrypter;
|
|
using std::string;
|
|
|
|
namespace net {
|
|
namespace {
|
|
|
|
static constexpr int k8ByteConnectionId = 8;
|
|
|
|
} // namespace
|
|
|
|
MockCryptoClientStream::MockCryptoClientStream(
|
|
const QuicServerId& server_id,
|
|
QuicSpdyClientSessionBase* session,
|
|
std::unique_ptr<ProofVerifyContext> verify_context,
|
|
const QuicConfig& config,
|
|
QuicCryptoClientConfig* crypto_config,
|
|
HandshakeMode handshake_mode,
|
|
const net::ProofVerifyDetailsChromium* proof_verify_details,
|
|
bool use_mock_crypter)
|
|
: QuicCryptoClientStream(server_id,
|
|
session,
|
|
std::move(verify_context),
|
|
crypto_config,
|
|
session,
|
|
/*has_application_state = */ true),
|
|
QuicCryptoHandshaker(this, session),
|
|
handshake_mode_(handshake_mode),
|
|
crypto_negotiated_params_(new QuicCryptoNegotiatedParameters),
|
|
use_mock_crypter_(use_mock_crypter),
|
|
server_id_(server_id),
|
|
proof_verify_details_(proof_verify_details),
|
|
config_(config) {
|
|
crypto_framer_.set_visitor(this);
|
|
// Simulate a negotiated cipher_suite with a fake value.
|
|
crypto_negotiated_params_->cipher_suite = 1;
|
|
}
|
|
|
|
MockCryptoClientStream::~MockCryptoClientStream() = default;
|
|
|
|
void MockCryptoClientStream::OnHandshakeMessage(
|
|
const CryptoHandshakeMessage& message) {
|
|
OnUnrecoverableError(QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE,
|
|
"Forced mock failure");
|
|
}
|
|
|
|
bool MockCryptoClientStream::CryptoConnect() {
|
|
DCHECK(session()->version().UsesTls());
|
|
IPEndPoint local_ip;
|
|
static_cast<QuicChromiumClientSession*>(session())
|
|
->GetDefaultSocket()
|
|
->GetLocalAddress(&local_ip);
|
|
session()->connection()->SetSelfAddress(ToQuicSocketAddress(local_ip));
|
|
|
|
IPEndPoint peer_ip;
|
|
static_cast<QuicChromiumClientSession*>(session())
|
|
->GetDefaultSocket()
|
|
->GetPeerAddress(&peer_ip);
|
|
quic::test::QuicConnectionPeer::SetEffectivePeerAddress(
|
|
session()->connection(), ToQuicSocketAddress(peer_ip));
|
|
|
|
if (session()->connection()->version().KnowsWhichDecrypterToUse()) {
|
|
session()->connection()->InstallDecrypter(
|
|
ENCRYPTION_FORWARD_SECURE,
|
|
std::make_unique<StrictTaggingDecrypter>(ENCRYPTION_FORWARD_SECURE));
|
|
} else {
|
|
session()->connection()->SetAlternativeDecrypter(
|
|
ENCRYPTION_FORWARD_SECURE,
|
|
std::make_unique<StrictTaggingDecrypter>(ENCRYPTION_FORWARD_SECURE),
|
|
/*latch_once_used=*/false);
|
|
}
|
|
if (proof_verify_details_) {
|
|
if (!proof_verify_details_->cert_verify_result.verified_cert
|
|
->VerifyNameMatch(server_id_.host())) {
|
|
handshake_confirmed_ = false;
|
|
encryption_established_ = false;
|
|
session()->connection()->CloseConnection(
|
|
QUIC_PROOF_INVALID, "proof invalid",
|
|
ConnectionCloseBehavior::SILENT_CLOSE);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
switch (handshake_mode_) {
|
|
case ZERO_RTT: {
|
|
encryption_established_ = true;
|
|
handshake_confirmed_ = false;
|
|
FillCryptoParams();
|
|
if (proof_verify_details_) {
|
|
reinterpret_cast<QuicSpdyClientSessionBase*>(session())
|
|
->OnProofVerifyDetailsAvailable(*proof_verify_details_);
|
|
}
|
|
if (use_mock_crypter_) {
|
|
if (session()->connection()->version().KnowsWhichDecrypterToUse()) {
|
|
session()->connection()->InstallDecrypter(
|
|
ENCRYPTION_ZERO_RTT,
|
|
std::make_unique<MockDecrypter>(Perspective::IS_CLIENT));
|
|
} else {
|
|
session()->connection()->SetDecrypter(
|
|
ENCRYPTION_ZERO_RTT,
|
|
std::make_unique<MockDecrypter>(Perspective::IS_CLIENT));
|
|
}
|
|
session()->connection()->SetEncrypter(
|
|
ENCRYPTION_ZERO_RTT,
|
|
std::make_unique<MockEncrypter>(Perspective::IS_CLIENT));
|
|
} else {
|
|
if (session()->connection()->version().KnowsWhichDecrypterToUse()) {
|
|
session()->connection()->InstallDecrypter(
|
|
ENCRYPTION_ZERO_RTT,
|
|
std::make_unique<StrictTaggingDecrypter>(ENCRYPTION_ZERO_RTT));
|
|
} else {
|
|
session()->connection()->SetDecrypter(
|
|
ENCRYPTION_ZERO_RTT,
|
|
std::make_unique<StrictTaggingDecrypter>(ENCRYPTION_ZERO_RTT));
|
|
}
|
|
SetConfigNegotiated();
|
|
session()->OnNewEncryptionKeyAvailable(
|
|
ENCRYPTION_ZERO_RTT,
|
|
std::make_unique<TaggingEncrypter>(ENCRYPTION_ZERO_RTT));
|
|
}
|
|
if (!session()->connection()->connected()) {
|
|
break;
|
|
}
|
|
session()->DiscardOldEncryptionKey(ENCRYPTION_INITIAL);
|
|
break;
|
|
}
|
|
|
|
case ASYNC_ZERO_RTT: {
|
|
handshake_confirmed_ = false;
|
|
FillCryptoParams();
|
|
if (proof_verify_details_) {
|
|
reinterpret_cast<QuicSpdyClientSessionBase*>(session())
|
|
->OnProofVerifyDetailsAvailable(*proof_verify_details_);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CONFIRM_HANDSHAKE: {
|
|
encryption_established_ = true;
|
|
handshake_confirmed_ = true;
|
|
FillCryptoParams();
|
|
if (proof_verify_details_) {
|
|
reinterpret_cast<QuicSpdyClientSessionBase*>(session())
|
|
->OnProofVerifyDetailsAvailable(*proof_verify_details_);
|
|
}
|
|
SetConfigNegotiated();
|
|
if (use_mock_crypter_) {
|
|
if (session()->connection()->version().KnowsWhichDecrypterToUse()) {
|
|
session()->connection()->InstallDecrypter(
|
|
ENCRYPTION_FORWARD_SECURE,
|
|
std::make_unique<MockDecrypter>(Perspective::IS_CLIENT));
|
|
} else {
|
|
session()->connection()->SetDecrypter(
|
|
ENCRYPTION_FORWARD_SECURE,
|
|
std::make_unique<MockDecrypter>(Perspective::IS_CLIENT));
|
|
}
|
|
session()->connection()->SetEncrypter(
|
|
ENCRYPTION_FORWARD_SECURE,
|
|
std::make_unique<MockEncrypter>(Perspective::IS_CLIENT));
|
|
} else {
|
|
if (session()->connection()->version().KnowsWhichDecrypterToUse()) {
|
|
session()->connection()->InstallDecrypter(
|
|
ENCRYPTION_FORWARD_SECURE,
|
|
std::make_unique<StrictTaggingDecrypter>(
|
|
ENCRYPTION_FORWARD_SECURE));
|
|
} else {
|
|
session()->connection()->SetDecrypter(
|
|
ENCRYPTION_FORWARD_SECURE,
|
|
std::make_unique<StrictTaggingDecrypter>(
|
|
ENCRYPTION_FORWARD_SECURE));
|
|
}
|
|
session()->connection()->SetEncrypter(ENCRYPTION_INITIAL, nullptr);
|
|
}
|
|
session()->OnNewEncryptionKeyAvailable(
|
|
ENCRYPTION_FORWARD_SECURE,
|
|
std::make_unique<TaggingEncrypter>(ENCRYPTION_FORWARD_SECURE));
|
|
if (!session()->connection()->connected()) {
|
|
break;
|
|
}
|
|
session()->OnTlsHandshakeComplete();
|
|
session()->DiscardOldEncryptionKey(ENCRYPTION_INITIAL);
|
|
session()->NeuterHandshakeData();
|
|
break;
|
|
}
|
|
|
|
case COLD_START: {
|
|
handshake_confirmed_ = false;
|
|
encryption_established_ = false;
|
|
break;
|
|
}
|
|
|
|
case COLD_START_WITH_CHLO_SENT: {
|
|
handshake_confirmed_ = false;
|
|
encryption_established_ = false;
|
|
SendHandshakeMessage(GetDummyCHLOMessage(), ENCRYPTION_INITIAL);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return session()->connection()->connected();
|
|
}
|
|
|
|
bool MockCryptoClientStream::encryption_established() const {
|
|
return encryption_established_;
|
|
}
|
|
|
|
bool MockCryptoClientStream::one_rtt_keys_available() const {
|
|
return handshake_confirmed_;
|
|
}
|
|
|
|
quic::HandshakeState MockCryptoClientStream::GetHandshakeState() const {
|
|
return handshake_confirmed_ ? quic::HANDSHAKE_CONFIRMED
|
|
: quic::HANDSHAKE_START;
|
|
}
|
|
|
|
void MockCryptoClientStream::setHandshakeConfirmedForce(bool state) {
|
|
handshake_confirmed_ = state;
|
|
}
|
|
|
|
bool MockCryptoClientStream::EarlyDataAccepted() const {
|
|
// This value is only used for logging. The return value doesn't matter.
|
|
return false;
|
|
}
|
|
|
|
const QuicCryptoNegotiatedParameters&
|
|
MockCryptoClientStream::crypto_negotiated_params() const {
|
|
return *crypto_negotiated_params_;
|
|
}
|
|
|
|
CryptoMessageParser* MockCryptoClientStream::crypto_message_parser() {
|
|
return &crypto_framer_;
|
|
}
|
|
|
|
// Tests using MockCryptoClientStream() do not care about the handshaker's
|
|
// state. Intercept and ignore the calls calls to prevent DCHECKs within the
|
|
// handshaker from failing.
|
|
void MockCryptoClientStream::OnOneRttPacketAcknowledged() {}
|
|
|
|
std::unique_ptr<quic::QuicDecrypter>
|
|
MockCryptoClientStream::AdvanceKeysAndCreateCurrentOneRttDecrypter() {
|
|
return std::make_unique<StrictTaggingDecrypter>(ENCRYPTION_FORWARD_SECURE);
|
|
}
|
|
|
|
void MockCryptoClientStream::NotifySessionZeroRttComplete() {
|
|
DCHECK(session()->version().UsesTls());
|
|
encryption_established_ = true;
|
|
handshake_confirmed_ = false;
|
|
session()->connection()->InstallDecrypter(
|
|
ENCRYPTION_ZERO_RTT,
|
|
std::make_unique<StrictTaggingDecrypter>(ENCRYPTION_ZERO_RTT));
|
|
SetConfigNegotiated();
|
|
session()->OnNewEncryptionKeyAvailable(
|
|
ENCRYPTION_ZERO_RTT,
|
|
std::make_unique<TaggingEncrypter>(ENCRYPTION_ZERO_RTT));
|
|
|
|
session()->DiscardOldEncryptionKey(ENCRYPTION_INITIAL);
|
|
}
|
|
|
|
void MockCryptoClientStream::NotifySessionOneRttKeyAvailable() {
|
|
encryption_established_ = true;
|
|
handshake_confirmed_ = true;
|
|
DCHECK(session()->version().UsesTls());
|
|
if (use_mock_crypter_) {
|
|
if (session()->connection()->version().KnowsWhichDecrypterToUse()) {
|
|
session()->connection()->InstallDecrypter(
|
|
ENCRYPTION_FORWARD_SECURE,
|
|
std::make_unique<MockDecrypter>(Perspective::IS_CLIENT));
|
|
} else {
|
|
session()->connection()->SetDecrypter(
|
|
ENCRYPTION_FORWARD_SECURE,
|
|
std::make_unique<MockDecrypter>(Perspective::IS_CLIENT));
|
|
}
|
|
session()->connection()->SetEncrypter(
|
|
ENCRYPTION_FORWARD_SECURE,
|
|
std::make_unique<MockEncrypter>(Perspective::IS_CLIENT));
|
|
} else {
|
|
if (session()->connection()->version().KnowsWhichDecrypterToUse()) {
|
|
session()->connection()->InstallDecrypter(
|
|
ENCRYPTION_FORWARD_SECURE,
|
|
std::make_unique<StrictTaggingDecrypter>(ENCRYPTION_FORWARD_SECURE));
|
|
} else {
|
|
session()->connection()->SetDecrypter(
|
|
ENCRYPTION_FORWARD_SECURE,
|
|
std::make_unique<StrictTaggingDecrypter>(ENCRYPTION_FORWARD_SECURE));
|
|
}
|
|
session()->connection()->SetEncrypter(ENCRYPTION_INITIAL, nullptr);
|
|
session()->OnNewEncryptionKeyAvailable(
|
|
ENCRYPTION_FORWARD_SECURE,
|
|
std::make_unique<TaggingEncrypter>(ENCRYPTION_FORWARD_SECURE));
|
|
}
|
|
SetConfigNegotiated();
|
|
session()->OnTlsHandshakeComplete();
|
|
session()->DiscardOldEncryptionKey(ENCRYPTION_INITIAL);
|
|
session()->DiscardOldEncryptionKey(ENCRYPTION_ZERO_RTT);
|
|
session()->NeuterHandshakeData();
|
|
}
|
|
|
|
// static
|
|
CryptoHandshakeMessage MockCryptoClientStream::GetDummyCHLOMessage() {
|
|
CryptoHandshakeMessage message;
|
|
message.set_tag(quic::kCHLO);
|
|
return message;
|
|
}
|
|
|
|
void MockCryptoClientStream::SetConfigNegotiated() {
|
|
DCHECK(session()->version().UsesTls());
|
|
QuicTagVector cgst;
|
|
// TODO(rtenneti): Enable the following code after BBR code is checked in.
|
|
#if 0
|
|
cgst.push_back(kTBBR);
|
|
#endif
|
|
cgst.push_back(kQBIC);
|
|
QuicConfig config(config_);
|
|
config.SetBytesForConnectionIdToSend(k8ByteConnectionId);
|
|
config.SetMaxBidirectionalStreamsToSend(kDefaultMaxStreamsPerConnection / 2);
|
|
config.SetMaxUnidirectionalStreamsToSend(kDefaultMaxStreamsPerConnection / 2);
|
|
config.SetInitialMaxStreamDataBytesIncomingBidirectionalToSend(
|
|
quic::kMinimumFlowControlSendWindow);
|
|
config.SetInitialMaxStreamDataBytesOutgoingBidirectionalToSend(
|
|
quic::kMinimumFlowControlSendWindow);
|
|
config.SetInitialMaxStreamDataBytesUnidirectionalToSend(
|
|
quic::kMinimumFlowControlSendWindow);
|
|
|
|
auto connection_id = quic::test::TestConnectionId();
|
|
config.SetStatelessResetTokenToSend(
|
|
quic::QuicUtils::GenerateStatelessResetToken(connection_id));
|
|
if (session()->perspective() == Perspective::IS_CLIENT) {
|
|
config.SetOriginalConnectionIdToSend(
|
|
session()->connection()->connection_id());
|
|
config.SetInitialSourceConnectionIdToSend(
|
|
session()->connection()->connection_id());
|
|
} else {
|
|
config.SetInitialSourceConnectionIdToSend(
|
|
session()->connection()->client_connection_id());
|
|
}
|
|
|
|
TransportParameters params;
|
|
ASSERT_TRUE(config.FillTransportParameters(¶ms));
|
|
std::string error_details;
|
|
QuicErrorCode error = session()->config()->ProcessTransportParameters(
|
|
params, /*is_resumption=*/false, &error_details);
|
|
ASSERT_EQ(QUIC_NO_ERROR, error);
|
|
ASSERT_TRUE(session()->config()->negotiated());
|
|
session()->OnConfigNegotiated();
|
|
}
|
|
|
|
void MockCryptoClientStream::FillCryptoParams() {
|
|
DCHECK(session()->version().UsesTls());
|
|
crypto_negotiated_params_->cipher_suite = TLS1_CK_AES_128_GCM_SHA256 & 0xffff;
|
|
crypto_negotiated_params_->key_exchange_group = SSL_CURVE_X25519;
|
|
crypto_negotiated_params_->peer_signature_algorithm =
|
|
SSL_SIGN_ECDSA_SECP256R1_SHA256;
|
|
}
|
|
|
|
} // namespace net
|