1From d34d9258b8420b19ec3f97b4cc5bf7aa7d98e35a Mon Sep 17 00:00:00 2001 2From: Michael Buckley <michael@buckleyisms.com> 3Date: Thu, 30 Nov 2023 15:08:02 -0800 4Subject: [PATCH] src: add 'strict KEX' to fix CVE-2023-48795 "Terrapin Attack" 5 6Refs: 7https://terrapin-attack.com/ 8https://seclists.org/oss-sec/2023/q4/292 9https://osv.dev/list?ecosystem=&q=CVE-2023-48795 10https://github.com/advisories/GHSA-45x7-px36-x8w8 11https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-48795 12 13Fixes #1290 14Closes #1291 15 16Upstream: https://github.com/libssh2/libssh2/commit/d34d9258b8420b19ec3f97b4cc5bf7aa7d98e35a 17Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com> 18--- 19 src/kex.c | 63 +++++++++++++++++++++++------------ 20 src/libssh2_priv.h | 18 +++++++--- 21 src/packet.c | 83 +++++++++++++++++++++++++++++++++++++++++++--- 22 src/packet.h | 2 +- 23 src/session.c | 3 ++ 24 src/transport.c | 12 ++++++- 25 6 files changed, 149 insertions(+), 32 deletions(-) 26 27diff --git a/src/kex.c b/src/kex.c 28index 8e7b7f0af3..a7b301e157 100644 29--- a/src/kex.c 30+++ b/src/kex.c 31@@ -3032,6 +3032,13 @@ kex_method_extension_negotiation = { 32 0, 33 }; 34 35+static const LIBSSH2_KEX_METHOD 36+kex_method_strict_client_extension = { 37+ "kex-strict-c-v00@openssh.com", 38+ NULL, 39+ 0, 40+}; 41+ 42 static const LIBSSH2_KEX_METHOD *libssh2_kex_methods[] = { 43 #if LIBSSH2_ED25519 44 &kex_method_ssh_curve25519_sha256, 45@@ -3050,6 +3057,7 @@ static const LIBSSH2_KEX_METHOD *libssh2_kex_methods[] = { 46 &kex_method_diffie_helman_group1_sha1, 47 &kex_method_diffie_helman_group_exchange_sha1, 48 &kex_method_extension_negotiation, 49+ &kex_method_strict_client_extension, 50 NULL 51 }; 52 53@@ -3302,13 +3310,13 @@ static int kexinit(LIBSSH2_SESSION * session) 54 return 0; 55 } 56 57-/* kex_agree_instr 58+/* _libssh2_kex_agree_instr 59 * Kex specific variant of strstr() 60 * Needle must be preceded by BOL or ',', and followed by ',' or EOL 61 */ 62-static unsigned char * 63-kex_agree_instr(unsigned char *haystack, size_t haystack_len, 64- const unsigned char *needle, size_t needle_len) 65+unsigned char * 66+_libssh2_kex_agree_instr(unsigned char *haystack, size_t haystack_len, 67+ const unsigned char *needle, size_t needle_len) 68 { 69 unsigned char *s; 70 unsigned char *end_haystack; 71@@ -3393,7 +3401,7 @@ static int kex_agree_hostkey(LIBSSH2_SESSION * session, 72 while(s && *s) { 73 unsigned char *p = (unsigned char *) strchr((char *) s, ','); 74 size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); 75- if(kex_agree_instr(hostkey, hostkey_len, s, method_len)) { 76+ if(_libssh2_kex_agree_instr(hostkey, hostkey_len, s, method_len)) { 77 const LIBSSH2_HOSTKEY_METHOD *method = 78 (const LIBSSH2_HOSTKEY_METHOD *) 79 kex_get_method_by_name((char *) s, method_len, 80@@ -3427,9 +3435,9 @@ static int kex_agree_hostkey(LIBSSH2_SESSION * session, 81 } 82 83 while(hostkeyp && (*hostkeyp) && (*hostkeyp)->name) { 84- s = kex_agree_instr(hostkey, hostkey_len, 85- (unsigned char *) (*hostkeyp)->name, 86- strlen((*hostkeyp)->name)); 87+ s = _libssh2_kex_agree_instr(hostkey, hostkey_len, 88+ (unsigned char *) (*hostkeyp)->name, 89+ strlen((*hostkeyp)->name)); 90 if(s) { 91 /* So far so good, but does it suit our purposes? (Encrypting vs 92 Signing) */ 93@@ -3463,6 +3471,12 @@ static int kex_agree_kex_hostkey(LIBSSH2_SESSION * session, unsigned char *kex, 94 { 95 const LIBSSH2_KEX_METHOD **kexp = libssh2_kex_methods; 96 unsigned char *s; 97+ const unsigned char *strict = 98+ (unsigned char *)"kex-strict-s-v00@openssh.com"; 99+ 100+ if(_libssh2_kex_agree_instr(kex, kex_len, strict, 28)) { 101+ session->kex_strict = 1; 102+ } 103 104 if(session->kex_prefs) { 105 s = (unsigned char *) session->kex_prefs; 106@@ -3470,7 +3484,7 @@ static int kex_agree_kex_hostkey(LIBSSH2_SESSION * session, unsigned char *kex, 107 while(s && *s) { 108 unsigned char *q, *p = (unsigned char *) strchr((char *) s, ','); 109 size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); 110- q = kex_agree_instr(kex, kex_len, s, method_len); 111+ q = _libssh2_kex_agree_instr(kex, kex_len, s, method_len); 112 if(q) { 113 const LIBSSH2_KEX_METHOD *method = (const LIBSSH2_KEX_METHOD *) 114 kex_get_method_by_name((char *) s, method_len, 115@@ -3504,9 +3518,9 @@ static int kex_agree_kex_hostkey(LIBSSH2_SESSION * session, unsigned char *kex, 116 } 117 118 while(*kexp && (*kexp)->name) { 119- s = kex_agree_instr(kex, kex_len, 120- (unsigned char *) (*kexp)->name, 121- strlen((*kexp)->name)); 122+ s = _libssh2_kex_agree_instr(kex, kex_len, 123+ (unsigned char *) (*kexp)->name, 124+ strlen((*kexp)->name)); 125 if(s) { 126 /* We've agreed on a key exchange method, 127 * Can we agree on a hostkey that works with this kex? 128@@ -3550,7 +3564,7 @@ static int kex_agree_crypt(LIBSSH2_SESSION * session, 129 unsigned char *p = (unsigned char *) strchr((char *) s, ','); 130 size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); 131 132- if(kex_agree_instr(crypt, crypt_len, s, method_len)) { 133+ if(_libssh2_kex_agree_instr(crypt, crypt_len, s, method_len)) { 134 const LIBSSH2_CRYPT_METHOD *method = 135 (const LIBSSH2_CRYPT_METHOD *) 136 kex_get_method_by_name((char *) s, method_len, 137@@ -3572,9 +3586,9 @@ static int kex_agree_crypt(LIBSSH2_SESSION * session, 138 } 139 140 while(*cryptp && (*cryptp)->name) { 141- s = kex_agree_instr(crypt, crypt_len, 142- (unsigned char *) (*cryptp)->name, 143- strlen((*cryptp)->name)); 144+ s = _libssh2_kex_agree_instr(crypt, crypt_len, 145+ (unsigned char *) (*cryptp)->name, 146+ strlen((*cryptp)->name)); 147 if(s) { 148 endpoint->crypt = *cryptp; 149 return 0; 150@@ -3614,7 +3628,7 @@ static int kex_agree_mac(LIBSSH2_SESSION * session, 151 unsigned char *p = (unsigned char *) strchr((char *) s, ','); 152 size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); 153 154- if(kex_agree_instr(mac, mac_len, s, method_len)) { 155+ if(_libssh2_kex_agree_instr(mac, mac_len, s, method_len)) { 156 const LIBSSH2_MAC_METHOD *method = (const LIBSSH2_MAC_METHOD *) 157 kex_get_method_by_name((char *) s, method_len, 158 (const LIBSSH2_COMMON_METHOD **) 159@@ -3635,8 +3649,9 @@ static int kex_agree_mac(LIBSSH2_SESSION * session, 160 } 161 162 while(*macp && (*macp)->name) { 163- s = kex_agree_instr(mac, mac_len, (unsigned char *) (*macp)->name, 164- strlen((*macp)->name)); 165+ s = _libssh2_kex_agree_instr(mac, mac_len, 166+ (unsigned char *) (*macp)->name, 167+ strlen((*macp)->name)); 168 if(s) { 169 endpoint->mac = *macp; 170 return 0; 171@@ -3667,7 +3682,7 @@ static int kex_agree_comp(LIBSSH2_SESSION *session, 172 unsigned char *p = (unsigned char *) strchr((char *) s, ','); 173 size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); 174 175- if(kex_agree_instr(comp, comp_len, s, method_len)) { 176+ if(_libssh2_kex_agree_instr(comp, comp_len, s, method_len)) { 177 const LIBSSH2_COMP_METHOD *method = 178 (const LIBSSH2_COMP_METHOD *) 179 kex_get_method_by_name((char *) s, method_len, 180@@ -3689,8 +3704,9 @@ static int kex_agree_comp(LIBSSH2_SESSION *session, 181 } 182 183 while(*compp && (*compp)->name) { 184- s = kex_agree_instr(comp, comp_len, (unsigned char *) (*compp)->name, 185- strlen((*compp)->name)); 186+ s = _libssh2_kex_agree_instr(comp, comp_len, 187+ (unsigned char *) (*compp)->name, 188+ strlen((*compp)->name)); 189 if(s) { 190 endpoint->comp = *compp; 191 return 0; 192@@ -3871,6 +3887,7 @@ _libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange, 193 session->local.kexinit = key_state->oldlocal; 194 session->local.kexinit_len = key_state->oldlocal_len; 195 key_state->state = libssh2_NB_state_idle; 196+ session->state &= ~LIBSSH2_STATE_INITIAL_KEX; 197 session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; 198 session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; 199 return -1; 200@@ -3896,6 +3913,7 @@ _libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange, 201 session->local.kexinit = key_state->oldlocal; 202 session->local.kexinit_len = key_state->oldlocal_len; 203 key_state->state = libssh2_NB_state_idle; 204+ session->state &= ~LIBSSH2_STATE_INITIAL_KEX; 205 session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; 206 session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; 207 return -1; 208@@ -3944,6 +3962,7 @@ _libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange, 209 session->remote.kexinit = NULL; 210 } 211 212+ session->state &= ~LIBSSH2_STATE_INITIAL_KEX; 213 session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; 214 session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; 215 216diff --git a/src/libssh2_priv.h b/src/libssh2_priv.h 217index 7660366954..18d9ab2130 100644 218--- a/src/libssh2_priv.h 219+++ b/src/libssh2_priv.h 220@@ -736,6 +736,9 @@ struct _LIBSSH2_SESSION 221 /* key signing algorithm preferences -- NULL yields server order */ 222 char *sign_algo_prefs; 223 224+ /* Whether to use the OpenSSH Strict KEX extension */ 225+ int kex_strict; 226+ 227 /* (remote as source of data -- packet_read ) */ 228 libssh2_endpoint_data remote; 229 230@@ -908,6 +911,7 @@ struct _LIBSSH2_SESSION 231 int fullpacket_macstate; 232 size_t fullpacket_payload_len; 233 int fullpacket_packet_type; 234+ uint32_t fullpacket_required_type; 235 236 /* State variables used in libssh2_sftp_init() */ 237 libssh2_nonblocking_states sftpInit_state; 238@@ -948,10 +952,11 @@ struct _LIBSSH2_SESSION 239 }; 240 241 /* session.state bits */ 242-#define LIBSSH2_STATE_EXCHANGING_KEYS 0x00000001 243-#define LIBSSH2_STATE_NEWKEYS 0x00000002 244-#define LIBSSH2_STATE_AUTHENTICATED 0x00000004 245-#define LIBSSH2_STATE_KEX_ACTIVE 0x00000008 246+#define LIBSSH2_STATE_INITIAL_KEX 0x00000001 247+#define LIBSSH2_STATE_EXCHANGING_KEYS 0x00000002 248+#define LIBSSH2_STATE_NEWKEYS 0x00000004 249+#define LIBSSH2_STATE_AUTHENTICATED 0x00000008 250+#define LIBSSH2_STATE_KEX_ACTIVE 0x00000010 251 252 /* session.flag helpers */ 253 #ifdef MSG_NOSIGNAL 254@@ -1182,6 +1187,11 @@ ssize_t _libssh2_send(libssh2_socket_t socket, const void *buffer, 255 int _libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange, 256 key_exchange_state_t * state); 257 258+unsigned char *_libssh2_kex_agree_instr(unsigned char *haystack, 259+ size_t haystack_len, 260+ const unsigned char *needle, 261+ size_t needle_len); 262+ 263 /* Let crypt.c/hostkey.c expose their method structs */ 264 const LIBSSH2_CRYPT_METHOD **libssh2_crypt_methods(void); 265 const LIBSSH2_HOSTKEY_METHOD **libssh2_hostkey_methods(void); 266diff --git a/src/packet.c b/src/packet.c 267index eccb8c56a8..6da14e9fa1 100644 268--- a/src/packet.c 269+++ b/src/packet.c 270@@ -624,14 +624,13 @@ packet_authagent_open(LIBSSH2_SESSION * session, 271 * layer when it has received a packet. 272 * 273 * The input pointer 'data' is pointing to allocated data that this function 274- * is asked to deal with so on failure OR success, it must be freed fine. 275- * The only exception is when the return code is LIBSSH2_ERROR_EAGAIN. 276+ * will be freed unless return the code is LIBSSH2_ERROR_EAGAIN. 277 * 278 * This function will always be called with 'datalen' greater than zero. 279 */ 280 int 281 _libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data, 282- size_t datalen, int macstate) 283+ size_t datalen, int macstate, uint32_t seq) 284 { 285 int rc = 0; 286 unsigned char *message = NULL; 287@@ -676,6 +675,70 @@ _libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data, 288 break; 289 } 290 291+ if(session->state & LIBSSH2_STATE_INITIAL_KEX) { 292+ if(msg == SSH_MSG_KEXINIT) { 293+ if(!session->kex_strict) { 294+ if(datalen < 17) { 295+ LIBSSH2_FREE(session, data); 296+ session->packAdd_state = libssh2_NB_state_idle; 297+ return _libssh2_error(session, 298+ LIBSSH2_ERROR_BUFFER_TOO_SMALL, 299+ "Data too short extracting kex"); 300+ } 301+ else { 302+ const unsigned char *strict = 303+ (unsigned char *)"kex-strict-s-v00@openssh.com"; 304+ struct string_buf buf; 305+ unsigned char *algs = NULL; 306+ size_t algs_len = 0; 307+ 308+ buf.data = (unsigned char *)data; 309+ buf.dataptr = buf.data; 310+ buf.len = datalen; 311+ buf.dataptr += 17; /* advance past type and cookie */ 312+ 313+ if(_libssh2_get_string(&buf, &algs, &algs_len)) { 314+ LIBSSH2_FREE(session, data); 315+ session->packAdd_state = libssh2_NB_state_idle; 316+ return _libssh2_error(session, 317+ LIBSSH2_ERROR_BUFFER_TOO_SMALL, 318+ "Algs too short"); 319+ } 320+ 321+ if(algs_len == 0 || 322+ _libssh2_kex_agree_instr(algs, algs_len, strict, 28)) { 323+ session->kex_strict = 1; 324+ } 325+ } 326+ } 327+ 328+ if(session->kex_strict && seq) { 329+ LIBSSH2_FREE(session, data); 330+ session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; 331+ session->packAdd_state = libssh2_NB_state_idle; 332+ libssh2_session_disconnect(session, "strict KEX violation: " 333+ "KEXINIT was not the first packet"); 334+ 335+ return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_DISCONNECT, 336+ "strict KEX violation: " 337+ "KEXINIT was not the first packet"); 338+ } 339+ } 340+ 341+ if(session->kex_strict && session->fullpacket_required_type && 342+ session->fullpacket_required_type != msg) { 343+ LIBSSH2_FREE(session, data); 344+ session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; 345+ session->packAdd_state = libssh2_NB_state_idle; 346+ libssh2_session_disconnect(session, "strict KEX violation: " 347+ "unexpected packet type"); 348+ 349+ return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_DISCONNECT, 350+ "strict KEX violation: " 351+ "unexpected packet type"); 352+ } 353+ } 354+ 355 if(session->packAdd_state == libssh2_NB_state_allocated) { 356 /* A couple exceptions to the packet adding rule: */ 357 switch(msg) { 358@@ -1364,6 +1427,15 @@ _libssh2_packet_ask(LIBSSH2_SESSION * session, unsigned char packet_type, 359 360 return 0; 361 } 362+ else if(session->kex_strict && 363+ (session->state & LIBSSH2_STATE_INITIAL_KEX)) { 364+ libssh2_session_disconnect(session, "strict KEX violation: " 365+ "unexpected packet type"); 366+ 367+ return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_DISCONNECT, 368+ "strict KEX violation: " 369+ "unexpected packet type"); 370+ } 371 packet = _libssh2_list_next(&packet->node); 372 } 373 return -1; 374@@ -1425,7 +1497,10 @@ _libssh2_packet_require(LIBSSH2_SESSION * session, unsigned char packet_type, 375 } 376 377 while(session->socket_state == LIBSSH2_SOCKET_CONNECTED) { 378- int ret = _libssh2_transport_read(session); 379+ int ret; 380+ session->fullpacket_required_type = packet_type; 381+ ret = _libssh2_transport_read(session); 382+ session->fullpacket_required_type = 0; 383 if(ret == LIBSSH2_ERROR_EAGAIN) 384 return ret; 385 else if(ret < 0) { 386diff --git a/src/packet.h b/src/packet.h 387index 1d90b8af12..955351e5f6 100644 388--- a/src/packet.h 389+++ b/src/packet.h 390@@ -72,6 +72,6 @@ int _libssh2_packet_burn(LIBSSH2_SESSION * session, 391 int _libssh2_packet_write(LIBSSH2_SESSION * session, unsigned char *data, 392 unsigned long data_len); 393 int _libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data, 394- size_t datalen, int macstate); 395+ size_t datalen, int macstate, uint32_t seq); 396 397 #endif /* LIBSSH2_PACKET_H */ 398diff --git a/src/session.c b/src/session.c 399index 35e7929fe7..9d89ade8ec 100644 400--- a/src/session.c 401+++ b/src/session.c 402@@ -469,6 +469,8 @@ libssh2_session_init_ex(LIBSSH2_ALLOC_FUNC((*my_alloc)), 403 session->abstract = abstract; 404 session->api_timeout = 0; /* timeout-free API by default */ 405 session->api_block_mode = 1; /* blocking API by default */ 406+ session->state = LIBSSH2_STATE_INITIAL_KEX; 407+ session->fullpacket_required_type = 0; 408 session->packet_read_timeout = LIBSSH2_DEFAULT_READ_TIMEOUT; 409 session->flag.quote_paths = 1; /* default behavior is to quote paths 410 for the scp subsystem */ 411@@ -1223,6 +1225,7 @@ libssh2_session_disconnect_ex(LIBSSH2_SESSION *session, int reason, 412 const char *desc, const char *lang) 413 { 414 int rc; 415+ session->state &= ~LIBSSH2_STATE_INITIAL_KEX; 416 session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; 417 BLOCK_ADJUST(rc, session, 418 session_disconnect(session, reason, desc, lang)); 419diff --git a/src/transport.c b/src/transport.c 420index 21be9d2b80..a8bb588a4b 100644 421--- a/src/transport.c 422+++ b/src/transport.c 423@@ -186,6 +186,7 @@ fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ ) 424 struct transportpacket *p = &session->packet; 425 int rc; 426 int compressed; 427+ uint32_t seq = session->remote.seqno; 428 429 if(session->fullpacket_state == libssh2_NB_state_idle) { 430 session->fullpacket_macstate = LIBSSH2_MAC_CONFIRMED; 431@@ -317,7 +318,7 @@ fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ ) 432 if(session->fullpacket_state == libssh2_NB_state_created) { 433 rc = _libssh2_packet_add(session, p->payload, 434 session->fullpacket_payload_len, 435- session->fullpacket_macstate); 436+ session->fullpacket_macstate, seq); 437 if(rc == LIBSSH2_ERROR_EAGAIN) 438 return rc; 439 if(rc) { 440@@ -328,6 +329,11 @@ fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ ) 441 442 session->fullpacket_state = libssh2_NB_state_idle; 443 444+ if(session->kex_strict && 445+ session->fullpacket_packet_type == SSH_MSG_NEWKEYS) { 446+ session->remote.seqno = 0; 447+ } 448+ 449 return session->fullpacket_packet_type; 450 } 451 452@@ -1093,6 +1099,10 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session, 453 454 session->local.seqno++; 455 456+ if(session->kex_strict && data[0] == SSH_MSG_NEWKEYS) { 457+ session->local.seqno = 0; 458+ } 459+ 460 ret = LIBSSH2_SEND(session, p->outbuf, total_length, 461 LIBSSH2_SOCKET_SEND_FLAGS(session)); 462 if(ret < 0) 463