diff options
| author | sakib.sajal@windriver.com <sakib.sajal@windriver.com> | 2020-04-29 15:36:08 -0700 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2020-05-02 11:04:26 -0400 |
| commit | 019be67a3f9e15718946902c086af1cdc65f1e9d (patch) | |
| tree | 0c8e5c0fbc74933f4e503ffdb93ed608921429b5 | |
| parent | 8f27aaa07c285713f5001b4734e32af12835698e (diff) | |
| download | meta-virtualization-019be67a3f9e15718946902c086af1cdc65f1e9d.tar.gz | |
ceph: backport CVE fixes
Fix CVE-2020-1759 and CVE-2020-1760
PR for fix: https://github.com/ceph/ceph/pull/34482
Signed-off-by: Sakib Sajal <sakib.sajal@windriver.com>
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
6 files changed, 455 insertions, 0 deletions
diff --git a/recipes-extended/ceph/ceph/0001-msg-async-ProtocolV2-avoid-AES-GCM-nonce-reuse-vulne.patch b/recipes-extended/ceph/ceph/0001-msg-async-ProtocolV2-avoid-AES-GCM-nonce-reuse-vulne.patch new file mode 100644 index 00000000..54156698 --- /dev/null +++ b/recipes-extended/ceph/ceph/0001-msg-async-ProtocolV2-avoid-AES-GCM-nonce-reuse-vulne.patch | |||
| @@ -0,0 +1,256 @@ | |||
| 1 | From 20b7bb685c5ea74c651ca1ea547ac66b0fee7035 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Ilya Dryomov <idryomov@gmail.com> | ||
| 3 | Date: Fri, 6 Mar 2020 20:16:45 +0100 | ||
| 4 | Subject: [PATCH] msg/async/ProtocolV2: avoid AES-GCM nonce reuse | ||
| 5 | vulnerabilities | ||
| 6 | |||
| 7 | The secure mode uses AES-128-GCM with 96-bit nonces consisting of a | ||
| 8 | 32-bit counter followed by a 64-bit salt. The counter is incremented | ||
| 9 | after processing each frame, the salt is fixed for the duration of | ||
| 10 | the session. Both are initialized from the session key generated | ||
| 11 | during session negotiation, so the counter starts with essentially | ||
| 12 | a random value. It is allowed to wrap, and, after 2**32 frames, it | ||
| 13 | repeats, resulting in nonce reuse (the actual sequence numbers that | ||
| 14 | the messenger works with are 64-bit, so the session continues on). | ||
| 15 | |||
| 16 | Because of how GCM works, this completely breaks both confidentiality | ||
| 17 | and integrity aspects of the secure mode. A single nonce reuse reveals | ||
| 18 | the XOR of two plaintexts and almost completely reveals the subkey | ||
| 19 | used for producing authentication tags. After a few nonces get used | ||
| 20 | twice, all confidentiality and integrity goes out the window and the | ||
| 21 | attacker can potentially encrypt-authenticate plaintext of their | ||
| 22 | choice. | ||
| 23 | |||
| 24 | We can't easily change the nonce format to extend the counter to | ||
| 25 | 64 bits (and possibly XOR it with a longer salt). Instead, just | ||
| 26 | remember the initial nonce and cut the session before it repeats, | ||
| 27 | forcing renegotiation. | ||
| 28 | |||
| 29 | Signed-off-by: Ilya Dryomov <idryomov@gmail.com> | ||
| 30 | Reviewed-by: Radoslaw Zarzynski <rzarzyns@redhat.com> | ||
| 31 | Reviewed-by: Sage Weil <sage@redhat.com> | ||
| 32 | |||
| 33 | Conflicts: | ||
| 34 | src/msg/async/ProtocolV2.h [ context: commit ed3ec4c01d17 | ||
| 35 | ("msg: Build target 'common' without using namespace in | ||
| 36 | headers") not in octopus ] | ||
| 37 | |||
| 38 | CVE: CVE-2020-1759 | ||
| 39 | Upstream Status: Backport [20b7bb685c5ea74c651ca1ea547ac66b0fee7035] | ||
| 40 | |||
| 41 | Signed-off-by: Sakib Sajal <sakib.sajal@windriver.com> | ||
| 42 | --- | ||
| 43 | src/msg/async/ProtocolV2.cc | 62 ++++++++++++++++++++++++---------- | ||
| 44 | src/msg/async/ProtocolV2.h | 5 +-- | ||
| 45 | src/msg/async/crypto_onwire.cc | 17 ++++++++-- | ||
| 46 | src/msg/async/crypto_onwire.h | 5 +++ | ||
| 47 | 4 files changed, 67 insertions(+), 22 deletions(-) | ||
| 48 | |||
| 49 | diff --git a/src/msg/async/ProtocolV2.cc b/src/msg/async/ProtocolV2.cc | ||
| 50 | index 8fc02db6e5..c69f2ccf79 100644 | ||
| 51 | --- a/src/msg/async/ProtocolV2.cc | ||
| 52 | +++ b/src/msg/async/ProtocolV2.cc | ||
| 53 | @@ -533,7 +533,10 @@ ssize_t ProtocolV2::write_message(Message *m, bool more) { | ||
| 54 | m->get_payload(), | ||
| 55 | m->get_middle(), | ||
| 56 | m->get_data()); | ||
| 57 | - connection->outgoing_bl.append(message.get_buffer(session_stream_handlers)); | ||
| 58 | + if (!append_frame(message)) { | ||
| 59 | + m->put(); | ||
| 60 | + return -EILSEQ; | ||
| 61 | + } | ||
| 62 | |||
| 63 | ldout(cct, 5) << __func__ << " sending message m=" << m | ||
| 64 | << " seq=" << m->get_seq() << " " << *m << dendl; | ||
| 65 | @@ -566,15 +569,17 @@ ssize_t ProtocolV2::write_message(Message *m, bool more) { | ||
| 66 | return rc; | ||
| 67 | } | ||
| 68 | |||
| 69 | -void ProtocolV2::append_keepalive() { | ||
| 70 | - ldout(cct, 10) << __func__ << dendl; | ||
| 71 | - auto keepalive_frame = KeepAliveFrame::Encode(); | ||
| 72 | - connection->outgoing_bl.append(keepalive_frame.get_buffer(session_stream_handlers)); | ||
| 73 | -} | ||
| 74 | - | ||
| 75 | -void ProtocolV2::append_keepalive_ack(utime_t ×tamp) { | ||
| 76 | - auto keepalive_ack_frame = KeepAliveFrameAck::Encode(timestamp); | ||
| 77 | - connection->outgoing_bl.append(keepalive_ack_frame.get_buffer(session_stream_handlers)); | ||
| 78 | +template <class F> | ||
| 79 | +bool ProtocolV2::append_frame(F& frame) { | ||
| 80 | + ceph::bufferlist bl; | ||
| 81 | + try { | ||
| 82 | + bl = frame.get_buffer(session_stream_handlers); | ||
| 83 | + } catch (ceph::crypto::onwire::TxHandlerError &e) { | ||
| 84 | + ldout(cct, 1) << __func__ << " " << e.what() << dendl; | ||
| 85 | + return false; | ||
| 86 | + } | ||
| 87 | + connection->outgoing_bl.append(bl); | ||
| 88 | + return true; | ||
| 89 | } | ||
| 90 | |||
| 91 | void ProtocolV2::handle_message_ack(uint64_t seq) { | ||
| 92 | @@ -612,7 +617,15 @@ void ProtocolV2::write_event() { | ||
| 93 | connection->write_lock.lock(); | ||
| 94 | if (can_write) { | ||
| 95 | if (keepalive) { | ||
| 96 | - append_keepalive(); | ||
| 97 | + ldout(cct, 10) << __func__ << " appending keepalive" << dendl; | ||
| 98 | + auto keepalive_frame = KeepAliveFrame::Encode(); | ||
| 99 | + if (!append_frame(keepalive_frame)) { | ||
| 100 | + connection->write_lock.unlock(); | ||
| 101 | + connection->lock.lock(); | ||
| 102 | + fault(); | ||
| 103 | + connection->lock.unlock(); | ||
| 104 | + return; | ||
| 105 | + } | ||
| 106 | keepalive = false; | ||
| 107 | } | ||
| 108 | |||
| 109 | @@ -663,13 +676,16 @@ void ProtocolV2::write_event() { | ||
| 110 | if (r == 0) { | ||
| 111 | uint64_t left = ack_left; | ||
| 112 | if (left) { | ||
| 113 | - auto ack = AckFrame::Encode(in_seq); | ||
| 114 | - connection->outgoing_bl.append(ack.get_buffer(session_stream_handlers)); | ||
| 115 | ldout(cct, 10) << __func__ << " try send msg ack, acked " << left | ||
| 116 | << " messages" << dendl; | ||
| 117 | - ack_left -= left; | ||
| 118 | - left = ack_left; | ||
| 119 | - r = connection->_try_send(left); | ||
| 120 | + auto ack_frame = AckFrame::Encode(in_seq); | ||
| 121 | + if (append_frame(ack_frame)) { | ||
| 122 | + ack_left -= left; | ||
| 123 | + left = ack_left; | ||
| 124 | + r = connection->_try_send(left); | ||
| 125 | + } else { | ||
| 126 | + r = -EILSEQ; | ||
| 127 | + } | ||
| 128 | } else if (is_queued()) { | ||
| 129 | r = connection->_try_send(); | ||
| 130 | } | ||
| 131 | @@ -769,7 +785,13 @@ template <class F> | ||
| 132 | CtPtr ProtocolV2::write(const std::string &desc, | ||
| 133 | CONTINUATION_TYPE<ProtocolV2> &next, | ||
| 134 | F &frame) { | ||
| 135 | - ceph::bufferlist bl = frame.get_buffer(session_stream_handlers); | ||
| 136 | + ceph::bufferlist bl; | ||
| 137 | + try { | ||
| 138 | + bl = frame.get_buffer(session_stream_handlers); | ||
| 139 | + } catch (ceph::crypto::onwire::TxHandlerError &e) { | ||
| 140 | + ldout(cct, 1) << __func__ << " " << e.what() << dendl; | ||
| 141 | + return _fault(); | ||
| 142 | + } | ||
| 143 | return write(desc, next, bl); | ||
| 144 | } | ||
| 145 | |||
| 146 | @@ -1672,7 +1694,11 @@ CtPtr ProtocolV2::handle_keepalive2(ceph::bufferlist &payload) | ||
| 147 | ldout(cct, 30) << __func__ << " got KEEPALIVE2 tag ..." << dendl; | ||
| 148 | |||
| 149 | connection->write_lock.lock(); | ||
| 150 | - append_keepalive_ack(keepalive_frame.timestamp()); | ||
| 151 | + auto keepalive_ack_frame = KeepAliveFrameAck::Encode(keepalive_frame.timestamp()); | ||
| 152 | + if (!append_frame(keepalive_ack_frame)) { | ||
| 153 | + connection->write_lock.unlock(); | ||
| 154 | + return _fault(); | ||
| 155 | + } | ||
| 156 | connection->write_lock.unlock(); | ||
| 157 | |||
| 158 | ldout(cct, 20) << __func__ << " got KEEPALIVE2 " | ||
| 159 | diff --git a/src/msg/async/ProtocolV2.h b/src/msg/async/ProtocolV2.h | ||
| 160 | index 2dbe647ae5..9897d18cf2 100644 | ||
| 161 | --- a/src/msg/async/ProtocolV2.h | ||
| 162 | +++ b/src/msg/async/ProtocolV2.h | ||
| 163 | @@ -129,6 +129,9 @@ private: | ||
| 164 | CONTINUATION_TYPE<ProtocolV2> &next, | ||
| 165 | bufferlist &buffer); | ||
| 166 | |||
| 167 | + template <class F> | ||
| 168 | + bool append_frame(F& frame); | ||
| 169 | + | ||
| 170 | void requeue_sent(); | ||
| 171 | uint64_t discard_requeued_up_to(uint64_t out_seq, uint64_t seq); | ||
| 172 | void reset_recv_state(); | ||
| 173 | @@ -140,8 +143,6 @@ private: | ||
| 174 | void prepare_send_message(uint64_t features, Message *m); | ||
| 175 | out_queue_entry_t _get_next_outgoing(); | ||
| 176 | ssize_t write_message(Message *m, bool more); | ||
| 177 | - void append_keepalive(); | ||
| 178 | - void append_keepalive_ack(utime_t ×tamp); | ||
| 179 | void handle_message_ack(uint64_t seq); | ||
| 180 | |||
| 181 | CONTINUATION_DECL(ProtocolV2, _wait_for_peer_banner); | ||
| 182 | diff --git a/src/msg/async/crypto_onwire.cc b/src/msg/async/crypto_onwire.cc | ||
| 183 | index acf3f66689..07e7fe6553 100644 | ||
| 184 | --- a/src/msg/async/crypto_onwire.cc | ||
| 185 | +++ b/src/msg/async/crypto_onwire.cc | ||
| 186 | @@ -22,6 +22,10 @@ static constexpr const std::size_t AESGCM_BLOCK_LEN{16}; | ||
| 187 | struct nonce_t { | ||
| 188 | std::uint32_t random_seq; | ||
| 189 | std::uint64_t random_rest; | ||
| 190 | + | ||
| 191 | + bool operator==(const nonce_t& rhs) const { | ||
| 192 | + return !memcmp(this, &rhs, sizeof(*this)); | ||
| 193 | + } | ||
| 194 | } __attribute__((packed)); | ||
| 195 | static_assert(sizeof(nonce_t) == AESGCM_IV_LEN); | ||
| 196 | |||
| 197 | @@ -35,7 +39,8 @@ class AES128GCM_OnWireTxHandler : public ceph::crypto::onwire::TxHandler { | ||
| 198 | CephContext* const cct; | ||
| 199 | std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)> ectx; | ||
| 200 | ceph::bufferlist buffer; | ||
| 201 | - nonce_t nonce; | ||
| 202 | + nonce_t nonce, initial_nonce; | ||
| 203 | + bool used_initial_nonce; | ||
| 204 | static_assert(sizeof(nonce) == AESGCM_IV_LEN); | ||
| 205 | |||
| 206 | public: | ||
| 207 | @@ -44,7 +49,7 @@ public: | ||
| 208 | const nonce_t& nonce) | ||
| 209 | : cct(cct), | ||
| 210 | ectx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free), | ||
| 211 | - nonce(nonce) { | ||
| 212 | + nonce(nonce), initial_nonce(nonce), used_initial_nonce(false) { | ||
| 213 | ceph_assert_always(ectx); | ||
| 214 | ceph_assert_always(key.size() * CHAR_BIT == 128); | ||
| 215 | |||
| 216 | @@ -61,6 +66,7 @@ public: | ||
| 217 | |||
| 218 | ~AES128GCM_OnWireTxHandler() override { | ||
| 219 | ::ceph::crypto::zeroize_for_security(&nonce, sizeof(nonce)); | ||
| 220 | + ::ceph::crypto::zeroize_for_security(&initial_nonce, sizeof(initial_nonce)); | ||
| 221 | } | ||
| 222 | |||
| 223 | std::uint32_t calculate_segment_size(std::uint32_t size) override | ||
| 224 | @@ -78,6 +84,13 @@ public: | ||
| 225 | void AES128GCM_OnWireTxHandler::reset_tx_handler( | ||
| 226 | std::initializer_list<std::uint32_t> update_size_sequence) | ||
| 227 | { | ||
| 228 | + if (nonce == initial_nonce) { | ||
| 229 | + if (used_initial_nonce) { | ||
| 230 | + throw ceph::crypto::onwire::TxHandlerError("out of nonces"); | ||
| 231 | + } | ||
| 232 | + used_initial_nonce = true; | ||
| 233 | + } | ||
| 234 | + | ||
| 235 | if(1 != EVP_EncryptInit_ex(ectx.get(), nullptr, nullptr, nullptr, | ||
| 236 | reinterpret_cast<const unsigned char*>(&nonce))) { | ||
| 237 | throw std::runtime_error("EVP_EncryptInit_ex failed"); | ||
| 238 | diff --git a/src/msg/async/crypto_onwire.h b/src/msg/async/crypto_onwire.h | ||
| 239 | index bd682e8c71..0c544f205a 100644 | ||
| 240 | --- a/src/msg/async/crypto_onwire.h | ||
| 241 | +++ b/src/msg/async/crypto_onwire.h | ||
| 242 | @@ -45,6 +45,11 @@ struct MsgAuthError : public std::runtime_error { | ||
| 243 | } | ||
| 244 | }; | ||
| 245 | |||
| 246 | +struct TxHandlerError : public std::runtime_error { | ||
| 247 | + TxHandlerError(const char* what) | ||
| 248 | + : std::runtime_error(std::string("tx handler error: ") + what) {} | ||
| 249 | +}; | ||
| 250 | + | ||
| 251 | struct TxHandler { | ||
| 252 | virtual ~TxHandler() = default; | ||
| 253 | |||
| 254 | -- | ||
| 255 | 2.20.1 | ||
| 256 | |||
diff --git a/recipes-extended/ceph/ceph/0001-msg-async-crypto_onwire-fix-endianness-of-nonce_t.patch b/recipes-extended/ceph/ceph/0001-msg-async-crypto_onwire-fix-endianness-of-nonce_t.patch new file mode 100644 index 00000000..ad8a2055 --- /dev/null +++ b/recipes-extended/ceph/ceph/0001-msg-async-crypto_onwire-fix-endianness-of-nonce_t.patch | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | From dfd1d81cec62e21e21696dc87d4db5f920e51a67 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Ilya Dryomov <idryomov@gmail.com> | ||
| 3 | Date: Fri, 6 Mar 2020 20:16:45 +0100 | ||
| 4 | Subject: [PATCH] msg/async/crypto_onwire: fix endianness of nonce_t | ||
| 5 | |||
| 6 | As a AES-GCM IV, nonce_t is implicitly shared between server and | ||
| 7 | client. Currently, if their endianness doesn't match, they are unable | ||
| 8 | to communicate in secure mode because each gets its own idea of what | ||
| 9 | the next nonce should be after the counter is incremented. | ||
| 10 | |||
| 11 | Several RFCs state that the nonce counter should be BE, but since we | ||
| 12 | use LE for everything on-disk and on-wire, make it LE. | ||
| 13 | |||
| 14 | Signed-off-by: Ilya Dryomov <idryomov@gmail.com> | ||
| 15 | Reviewed-by: Radoslaw Zarzynski <rzarzyns@redhat.com> | ||
| 16 | Reviewed-by: Sage Weil <sage@redhat.com> | ||
| 17 | |||
| 18 | CVE: CVE-2020-1759 | ||
| 19 | Upstream Status: Backport [dfd1d81cec62e21e21696dc87d4db5f920e51a67] | ||
| 20 | |||
| 21 | Signed-off-by: Sakib Sajal <sakib.sajal@windriver.com> | ||
| 22 | --- | ||
| 23 | src/msg/async/crypto_onwire.cc | 8 ++++---- | ||
| 24 | 1 file changed, 4 insertions(+), 4 deletions(-) | ||
| 25 | |||
| 26 | diff --git a/src/msg/async/crypto_onwire.cc b/src/msg/async/crypto_onwire.cc | ||
| 27 | index 07e7fe6553..c39632cbd6 100644 | ||
| 28 | --- a/src/msg/async/crypto_onwire.cc | ||
| 29 | +++ b/src/msg/async/crypto_onwire.cc | ||
| 30 | @@ -20,8 +20,8 @@ static constexpr const std::size_t AESGCM_TAG_LEN{16}; | ||
| 31 | static constexpr const std::size_t AESGCM_BLOCK_LEN{16}; | ||
| 32 | |||
| 33 | struct nonce_t { | ||
| 34 | - std::uint32_t random_seq; | ||
| 35 | - std::uint64_t random_rest; | ||
| 36 | + ceph_le32 random_seq; | ||
| 37 | + ceph_le64 random_rest; | ||
| 38 | |||
| 39 | bool operator==(const nonce_t& rhs) const { | ||
| 40 | return !memcmp(this, &rhs, sizeof(*this)); | ||
| 41 | @@ -99,7 +99,7 @@ void AES128GCM_OnWireTxHandler::reset_tx_handler( | ||
| 42 | buffer.reserve(std::accumulate(std::begin(update_size_sequence), | ||
| 43 | std::end(update_size_sequence), AESGCM_TAG_LEN)); | ||
| 44 | |||
| 45 | - ++nonce.random_seq; | ||
| 46 | + nonce.random_seq = nonce.random_seq + 1; | ||
| 47 | } | ||
| 48 | |||
| 49 | void AES128GCM_OnWireTxHandler::authenticated_encrypt_update( | ||
| 50 | @@ -204,7 +204,7 @@ void AES128GCM_OnWireRxHandler::reset_rx_handler() | ||
| 51 | reinterpret_cast<const unsigned char*>(&nonce))) { | ||
| 52 | throw std::runtime_error("EVP_DecryptInit_ex failed"); | ||
| 53 | } | ||
| 54 | - ++nonce.random_seq; | ||
| 55 | + nonce.random_seq = nonce.random_seq + 1; | ||
| 56 | } | ||
| 57 | |||
| 58 | ceph::bufferlist AES128GCM_OnWireRxHandler::authenticated_decrypt_update( | ||
| 59 | -- | ||
| 60 | 2.20.1 | ||
| 61 | |||
diff --git a/recipes-extended/ceph/ceph/0001-rgw-EPERM-to-ERR_INVALID_REQUEST.patch b/recipes-extended/ceph/ceph/0001-rgw-EPERM-to-ERR_INVALID_REQUEST.patch new file mode 100644 index 00000000..30906d7c --- /dev/null +++ b/recipes-extended/ceph/ceph/0001-rgw-EPERM-to-ERR_INVALID_REQUEST.patch | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | From 92da834cababc4dddd5dbbab5837310478d1e6d4 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Abhishek Lekshmanan <abhishek@suse.com> | ||
| 3 | Date: Fri, 27 Mar 2020 19:29:01 +0100 | ||
| 4 | Subject: [PATCH] rgw: EPERM to ERR_INVALID_REQUEST | ||
| 5 | |||
| 6 | As per Robin's comments and S3 spec | ||
| 7 | |||
| 8 | Signed-off-by: Abhishek Lekshmanan <abhishek@suse.com> | ||
| 9 | |||
| 10 | CVE: CVE-2020-1760 | ||
| 11 | Upstream Status: Backport [92da834cababc4dddd5dbbab5837310478d1e6d4] | ||
| 12 | |||
| 13 | Signed-off-by: Sakib Sajal <sakib.sajal@windriver.com> | ||
| 14 | --- | ||
| 15 | src/rgw/rgw_rest_s3.cc | 2 +- | ||
| 16 | 1 file changed, 1 insertion(+), 1 deletion(-) | ||
| 17 | |||
| 18 | diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc | ||
| 19 | index 1bfc8312de..f13ae23dd6 100644 | ||
| 20 | --- a/src/rgw/rgw_rest_s3.cc | ||
| 21 | +++ b/src/rgw/rgw_rest_s3.cc | ||
| 22 | @@ -301,7 +301,7 @@ int RGWGetObj_ObjStore_S3::send_response_data(bufferlist& bl, off_t bl_ofs, | ||
| 23 | /* reject unauthenticated response header manipulation, see | ||
| 24 | * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html */ | ||
| 25 | if (s->auth.identity->is_anonymous()) { | ||
| 26 | - return -EPERM; | ||
| 27 | + return -ERR_INVALID_REQUEST; | ||
| 28 | } | ||
| 29 | if (strcmp(p->param, "response-content-type") != 0) { | ||
| 30 | response_attrs[p->http_attr] = val; | ||
| 31 | -- | ||
| 32 | 2.20.1 | ||
| 33 | |||
diff --git a/recipes-extended/ceph/ceph/0001-rgw-reject-control-characters-in-response-header-act.patch b/recipes-extended/ceph/ceph/0001-rgw-reject-control-characters-in-response-header-act.patch new file mode 100644 index 00000000..af0fc79a --- /dev/null +++ b/recipes-extended/ceph/ceph/0001-rgw-reject-control-characters-in-response-header-act.patch | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | From be7679007c3dfab3e19c22c38c36ccac91828e3b Mon Sep 17 00:00:00 2001 | ||
| 2 | From: "Robin H. Johnson" <rjohnson@digitalocean.com> | ||
| 3 | Date: Fri, 27 Mar 2020 20:48:13 +0100 | ||
| 4 | Subject: [PATCH] rgw: reject control characters in response-header actions | ||
| 5 | |||
| 6 | S3 GetObject permits overriding response header values, but those inputs | ||
| 7 | need to be validated to insure only characters that are valid in an HTTP | ||
| 8 | header value are present. | ||
| 9 | |||
| 10 | Credit: Initial vulnerability discovery by William Bowling (@wcbowling) | ||
| 11 | Credit: Further vulnerability discovery by Robin H. Johnson <rjohnson@digitalocean.com> | ||
| 12 | Signed-off-by: Robin H. Johnson <rjohnson@digitalocean.com> | ||
| 13 | |||
| 14 | CVE: CVE-2020-1760 | ||
| 15 | Upstream Status: Backport [be7679007c3dfab3e19c22c38c36ccac91828e3b] | ||
| 16 | |||
| 17 | Signed-off-by: Sakib Sajal <sakib.sajal@windriver.com> | ||
| 18 | --- | ||
| 19 | src/rgw/rgw_rest_s3.cc | 22 ++++++++++++++++++++++ | ||
| 20 | 1 file changed, 22 insertions(+) | ||
| 21 | |||
| 22 | diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc | ||
| 23 | index f13ae23dd6..0de040968c 100644 | ||
| 24 | --- a/src/rgw/rgw_rest_s3.cc | ||
| 25 | +++ b/src/rgw/rgw_rest_s3.cc | ||
| 26 | @@ -189,6 +189,15 @@ int decode_attr_bl_single_value(map<string, bufferlist>& attrs, const char *attr | ||
| 27 | return 0; | ||
| 28 | } | ||
| 29 | |||
| 30 | +inline bool str_has_cntrl(const std::string s) { | ||
| 31 | + return std::any_of(s.begin(), s.end(), ::iscntrl); | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +inline bool str_has_cntrl(const char* s) { | ||
| 35 | + std::string _s(s); | ||
| 36 | + return str_has_cntrl(_s); | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | int RGWGetObj_ObjStore_S3::send_response_data(bufferlist& bl, off_t bl_ofs, | ||
| 40 | off_t bl_len) | ||
| 41 | { | ||
| 42 | @@ -303,6 +312,19 @@ int RGWGetObj_ObjStore_S3::send_response_data(bufferlist& bl, off_t bl_ofs, | ||
| 43 | if (s->auth.identity->is_anonymous()) { | ||
| 44 | return -ERR_INVALID_REQUEST; | ||
| 45 | } | ||
| 46 | + /* HTTP specification says no control characters should be present in | ||
| 47 | + * header values: https://tools.ietf.org/html/rfc7230#section-3.2 | ||
| 48 | + * field-vchar = VCHAR / obs-text | ||
| 49 | + * | ||
| 50 | + * Failure to validate this permits a CRLF injection in HTTP headers, | ||
| 51 | + * whereas S3 GetObject only permits specific headers. | ||
| 52 | + */ | ||
| 53 | + if(str_has_cntrl(val)) { | ||
| 54 | + /* TODO: return a more distinct error in future; | ||
| 55 | + * stating what the problem is */ | ||
| 56 | + return -ERR_INVALID_REQUEST; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | if (strcmp(p->param, "response-content-type") != 0) { | ||
| 60 | response_attrs[p->http_attr] = val; | ||
| 61 | } else { | ||
| 62 | -- | ||
| 63 | 2.20.1 | ||
| 64 | |||
diff --git a/recipes-extended/ceph/ceph/0001-rgw-reject-unauthenticated-response-header-actions.patch b/recipes-extended/ceph/ceph/0001-rgw-reject-unauthenticated-response-header-actions.patch new file mode 100644 index 00000000..ae241473 --- /dev/null +++ b/recipes-extended/ceph/ceph/0001-rgw-reject-unauthenticated-response-header-actions.patch | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | From 8f90658c731499722d5f4393c8ad70b971d05f77 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Matt Benjamin <mbenjamin@redhat.com> | ||
| 3 | Date: Fri, 27 Mar 2020 18:13:48 +0100 | ||
| 4 | Subject: [PATCH] rgw: reject unauthenticated response-header actions | ||
| 5 | |||
| 6 | Signed-off-by: Matt Benjamin <mbenjamin@redhat.com> | ||
| 7 | Reviewed-by: Casey Bodley <cbodley@redhat.com> | ||
| 8 | (cherry picked from commit d8dd5e513c0c62bbd7d3044d7e2eddcd897bd400) | ||
| 9 | |||
| 10 | CVE: CVE-2020-1760 | ||
| 11 | Upstream Status: Backport [8f90658c731499722d5f4393c8ad70b971d05f77] | ||
| 12 | |||
| 13 | Signed-off-by: Sakib Sajal <sakib.sajal@windriver.com> | ||
| 14 | --- | ||
| 15 | src/rgw/rgw_rest_s3.cc | 5 +++++ | ||
| 16 | 1 file changed, 5 insertions(+) | ||
| 17 | |||
| 18 | diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc | ||
| 19 | index 532d738b58..1bfc8312de 100644 | ||
| 20 | --- a/src/rgw/rgw_rest_s3.cc | ||
| 21 | +++ b/src/rgw/rgw_rest_s3.cc | ||
| 22 | @@ -298,6 +298,11 @@ int RGWGetObj_ObjStore_S3::send_response_data(bufferlist& bl, off_t bl_ofs, | ||
| 23 | bool exists; | ||
| 24 | string val = s->info.args.get(p->param, &exists); | ||
| 25 | if (exists) { | ||
| 26 | + /* reject unauthenticated response header manipulation, see | ||
| 27 | + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html */ | ||
| 28 | + if (s->auth.identity->is_anonymous()) { | ||
| 29 | + return -EPERM; | ||
| 30 | + } | ||
| 31 | if (strcmp(p->param, "response-content-type") != 0) { | ||
| 32 | response_attrs[p->http_attr] = val; | ||
| 33 | } else { | ||
| 34 | -- | ||
| 35 | 2.20.1 | ||
| 36 | |||
diff --git a/recipes-extended/ceph/ceph_15.2.0.bb b/recipes-extended/ceph/ceph_15.2.0.bb index 8ab58eb9..e41aa2f4 100644 --- a/recipes-extended/ceph/ceph_15.2.0.bb +++ b/recipes-extended/ceph/ceph_15.2.0.bb | |||
| @@ -12,6 +12,11 @@ SRC_URI = "http://download.ceph.com/tarballs/ceph-${PV}.tar.gz \ | |||
| 12 | file://0001-ceph-fix-build-errors-for-cross-compile.patch \ | 12 | file://0001-ceph-fix-build-errors-for-cross-compile.patch \ |
| 13 | file://0001-fix-host-library-paths-were-used.patch \ | 13 | file://0001-fix-host-library-paths-were-used.patch \ |
| 14 | file://ceph.conf \ | 14 | file://ceph.conf \ |
| 15 | file://0001-msg-async-ProtocolV2-avoid-AES-GCM-nonce-reuse-vulne.patch \ | ||
| 16 | file://0001-msg-async-crypto_onwire-fix-endianness-of-nonce_t.patch \ | ||
| 17 | file://0001-rgw-reject-unauthenticated-response-header-actions.patch \ | ||
| 18 | file://0001-rgw-EPERM-to-ERR_INVALID_REQUEST.patch \ | ||
| 19 | file://0001-rgw-reject-control-characters-in-response-header-act.patch \ | ||
| 15 | " | 20 | " |
| 16 | 21 | ||
| 17 | SRC_URI[md5sum] = "1f9af648b4c6d19975aab2583ab99710" | 22 | SRC_URI[md5sum] = "1f9af648b4c6d19975aab2583ab99710" |
