From 20b7bb685c5ea74c651ca1ea547ac66b0fee7035 Mon Sep 17 00:00:00 2001 From: Ilya Dryomov Date: Fri, 6 Mar 2020 20:16:45 +0100 Subject: [PATCH] msg/async/ProtocolV2: avoid AES-GCM nonce reuse vulnerabilities The secure mode uses AES-128-GCM with 96-bit nonces consisting of a 32-bit counter followed by a 64-bit salt. The counter is incremented after processing each frame, the salt is fixed for the duration of the session. Both are initialized from the session key generated during session negotiation, so the counter starts with essentially a random value. It is allowed to wrap, and, after 2**32 frames, it repeats, resulting in nonce reuse (the actual sequence numbers that the messenger works with are 64-bit, so the session continues on). Because of how GCM works, this completely breaks both confidentiality and integrity aspects of the secure mode. A single nonce reuse reveals the XOR of two plaintexts and almost completely reveals the subkey used for producing authentication tags. After a few nonces get used twice, all confidentiality and integrity goes out the window and the attacker can potentially encrypt-authenticate plaintext of their choice. We can't easily change the nonce format to extend the counter to 64 bits (and possibly XOR it with a longer salt). Instead, just remember the initial nonce and cut the session before it repeats, forcing renegotiation. Signed-off-by: Ilya Dryomov Reviewed-by: Radoslaw Zarzynski Reviewed-by: Sage Weil Conflicts: src/msg/async/ProtocolV2.h [ context: commit ed3ec4c01d17 ("msg: Build target 'common' without using namespace in headers") not in octopus ] CVE: CVE-2020-1759 Upstream Status: Backport [20b7bb685c5ea74c651ca1ea547ac66b0fee7035] Signed-off-by: Sakib Sajal --- src/msg/async/ProtocolV2.cc | 62 ++++++++++++++++++++++++---------- src/msg/async/ProtocolV2.h | 5 +-- src/msg/async/crypto_onwire.cc | 17 ++++++++-- src/msg/async/crypto_onwire.h | 5 +++ 4 files changed, 67 insertions(+), 22 deletions(-) diff --git a/src/msg/async/ProtocolV2.cc b/src/msg/async/ProtocolV2.cc index 8fc02db6e5..c69f2ccf79 100644 --- a/src/msg/async/ProtocolV2.cc +++ b/src/msg/async/ProtocolV2.cc @@ -533,7 +533,10 @@ ssize_t ProtocolV2::write_message(Message *m, bool more) { m->get_payload(), m->get_middle(), m->get_data()); - connection->outgoing_bl.append(message.get_buffer(session_stream_handlers)); + if (!append_frame(message)) { + m->put(); + return -EILSEQ; + } ldout(cct, 5) << __func__ << " sending message m=" << m << " seq=" << m->get_seq() << " " << *m << dendl; @@ -566,15 +569,17 @@ ssize_t ProtocolV2::write_message(Message *m, bool more) { return rc; } -void ProtocolV2::append_keepalive() { - ldout(cct, 10) << __func__ << dendl; - auto keepalive_frame = KeepAliveFrame::Encode(); - connection->outgoing_bl.append(keepalive_frame.get_buffer(session_stream_handlers)); -} - -void ProtocolV2::append_keepalive_ack(utime_t ×tamp) { - auto keepalive_ack_frame = KeepAliveFrameAck::Encode(timestamp); - connection->outgoing_bl.append(keepalive_ack_frame.get_buffer(session_stream_handlers)); +template +bool ProtocolV2::append_frame(F& frame) { + ceph::bufferlist bl; + try { + bl = frame.get_buffer(session_stream_handlers); + } catch (ceph::crypto::onwire::TxHandlerError &e) { + ldout(cct, 1) << __func__ << " " << e.what() << dendl; + return false; + } + connection->outgoing_bl.append(bl); + return true; } void ProtocolV2::handle_message_ack(uint64_t seq) { @@ -612,7 +617,15 @@ void ProtocolV2::write_event() { connection->write_lock.lock(); if (can_write) { if (keepalive) { - append_keepalive(); + ldout(cct, 10) << __func__ << " appending keepalive" << dendl; + auto keepalive_frame = KeepAliveFrame::Encode(); + if (!append_frame(keepalive_frame)) { + connection->write_lock.unlock(); + connection->lock.lock(); + fault(); + connection->lock.unlock(); + return; + } keepalive = false; } @@ -663,13 +676,16 @@ void ProtocolV2::write_event() { if (r == 0) { uint64_t left = ack_left; if (left) { - auto ack = AckFrame::Encode(in_seq); - connection->outgoing_bl.append(ack.get_buffer(session_stream_handlers)); ldout(cct, 10) << __func__ << " try send msg ack, acked " << left << " messages" << dendl; - ack_left -= left; - left = ack_left; - r = connection->_try_send(left); + auto ack_frame = AckFrame::Encode(in_seq); + if (append_frame(ack_frame)) { + ack_left -= left; + left = ack_left; + r = connection->_try_send(left); + } else { + r = -EILSEQ; + } } else if (is_queued()) { r = connection->_try_send(); } @@ -769,7 +785,13 @@ template CtPtr ProtocolV2::write(const std::string &desc, CONTINUATION_TYPE &next, F &frame) { - ceph::bufferlist bl = frame.get_buffer(session_stream_handlers); + ceph::bufferlist bl; + try { + bl = frame.get_buffer(session_stream_handlers); + } catch (ceph::crypto::onwire::TxHandlerError &e) { + ldout(cct, 1) << __func__ << " " << e.what() << dendl; + return _fault(); + } return write(desc, next, bl); } @@ -1672,7 +1694,11 @@ CtPtr ProtocolV2::handle_keepalive2(ceph::bufferlist &payload) ldout(cct, 30) << __func__ << " got KEEPALIVE2 tag ..." << dendl; connection->write_lock.lock(); - append_keepalive_ack(keepalive_frame.timestamp()); + auto keepalive_ack_frame = KeepAliveFrameAck::Encode(keepalive_frame.timestamp()); + if (!append_frame(keepalive_ack_frame)) { + connection->write_lock.unlock(); + return _fault(); + } connection->write_lock.unlock(); ldout(cct, 20) << __func__ << " got KEEPALIVE2 " diff --git a/src/msg/async/ProtocolV2.h b/src/msg/async/ProtocolV2.h index 2dbe647ae5..9897d18cf2 100644 --- a/src/msg/async/ProtocolV2.h +++ b/src/msg/async/ProtocolV2.h @@ -129,6 +129,9 @@ private: CONTINUATION_TYPE &next, bufferlist &buffer); + template + bool append_frame(F& frame); + void requeue_sent(); uint64_t discard_requeued_up_to(uint64_t out_seq, uint64_t seq); void reset_recv_state(); @@ -140,8 +143,6 @@ private: void prepare_send_message(uint64_t features, Message *m); out_queue_entry_t _get_next_outgoing(); ssize_t write_message(Message *m, bool more); - void append_keepalive(); - void append_keepalive_ack(utime_t ×tamp); void handle_message_ack(uint64_t seq); CONTINUATION_DECL(ProtocolV2, _wait_for_peer_banner); diff --git a/src/msg/async/crypto_onwire.cc b/src/msg/async/crypto_onwire.cc index acf3f66689..07e7fe6553 100644 --- a/src/msg/async/crypto_onwire.cc +++ b/src/msg/async/crypto_onwire.cc @@ -22,6 +22,10 @@ static constexpr const std::size_t AESGCM_BLOCK_LEN{16}; struct nonce_t { std::uint32_t random_seq; std::uint64_t random_rest; + + bool operator==(const nonce_t& rhs) const { + return !memcmp(this, &rhs, sizeof(*this)); + } } __attribute__((packed)); static_assert(sizeof(nonce_t) == AESGCM_IV_LEN); @@ -35,7 +39,8 @@ class AES128GCM_OnWireTxHandler : public ceph::crypto::onwire::TxHandler { CephContext* const cct; std::unique_ptr ectx; ceph::bufferlist buffer; - nonce_t nonce; + nonce_t nonce, initial_nonce; + bool used_initial_nonce; static_assert(sizeof(nonce) == AESGCM_IV_LEN); public: @@ -44,7 +49,7 @@ public: const nonce_t& nonce) : cct(cct), ectx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free), - nonce(nonce) { + nonce(nonce), initial_nonce(nonce), used_initial_nonce(false) { ceph_assert_always(ectx); ceph_assert_always(key.size() * CHAR_BIT == 128); @@ -61,6 +66,7 @@ public: ~AES128GCM_OnWireTxHandler() override { ::ceph::crypto::zeroize_for_security(&nonce, sizeof(nonce)); + ::ceph::crypto::zeroize_for_security(&initial_nonce, sizeof(initial_nonce)); } std::uint32_t calculate_segment_size(std::uint32_t size) override @@ -78,6 +84,13 @@ public: void AES128GCM_OnWireTxHandler::reset_tx_handler( std::initializer_list update_size_sequence) { + if (nonce == initial_nonce) { + if (used_initial_nonce) { + throw ceph::crypto::onwire::TxHandlerError("out of nonces"); + } + used_initial_nonce = true; + } + if(1 != EVP_EncryptInit_ex(ectx.get(), nullptr, nullptr, nullptr, reinterpret_cast(&nonce))) { throw std::runtime_error("EVP_EncryptInit_ex failed"); diff --git a/src/msg/async/crypto_onwire.h b/src/msg/async/crypto_onwire.h index bd682e8c71..0c544f205a 100644 --- a/src/msg/async/crypto_onwire.h +++ b/src/msg/async/crypto_onwire.h @@ -45,6 +45,11 @@ struct MsgAuthError : public std::runtime_error { } }; +struct TxHandlerError : public std::runtime_error { + TxHandlerError(const char* what) + : std::runtime_error(std::string("tx handler error: ") + what) {} +}; + struct TxHandler { virtual ~TxHandler() = default; -- 2.20.1