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