123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- From d34d9258b8420b19ec3f97b4cc5bf7aa7d98e35a Mon Sep 17 00:00:00 2001
- From: Michael Buckley <michael@buckleyisms.com>
- Date: Thu, 30 Nov 2023 15:08:02 -0800
- Subject: [PATCH] src: add 'strict KEX' to fix CVE-2023-48795 "Terrapin Attack"
- Refs:
- https://terrapin-attack.com/
- https://seclists.org/oss-sec/2023/q4/292
- https://osv.dev/list?ecosystem=&q=CVE-2023-48795
- https://github.com/advisories/GHSA-45x7-px36-x8w8
- https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-48795
- Fixes #1290
- Closes #1291
- Upstream: https://github.com/libssh2/libssh2/commit/d34d9258b8420b19ec3f97b4cc5bf7aa7d98e35a
- Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
- ---
- src/kex.c | 63 +++++++++++++++++++++++------------
- src/libssh2_priv.h | 18 +++++++---
- src/packet.c | 83 +++++++++++++++++++++++++++++++++++++++++++---
- src/packet.h | 2 +-
- src/session.c | 3 ++
- src/transport.c | 12 ++++++-
- 6 files changed, 149 insertions(+), 32 deletions(-)
- diff --git a/src/kex.c b/src/kex.c
- index 8e7b7f0af3..a7b301e157 100644
- --- a/src/kex.c
- +++ b/src/kex.c
- @@ -3032,6 +3032,13 @@ kex_method_extension_negotiation = {
- 0,
- };
-
- +static const LIBSSH2_KEX_METHOD
- +kex_method_strict_client_extension = {
- + "kex-strict-c-v00@openssh.com",
- + NULL,
- + 0,
- +};
- +
- static const LIBSSH2_KEX_METHOD *libssh2_kex_methods[] = {
- #if LIBSSH2_ED25519
- &kex_method_ssh_curve25519_sha256,
- @@ -3050,6 +3057,7 @@ static const LIBSSH2_KEX_METHOD *libssh2_kex_methods[] = {
- &kex_method_diffie_helman_group1_sha1,
- &kex_method_diffie_helman_group_exchange_sha1,
- &kex_method_extension_negotiation,
- + &kex_method_strict_client_extension,
- NULL
- };
-
- @@ -3302,13 +3310,13 @@ static int kexinit(LIBSSH2_SESSION * session)
- return 0;
- }
-
- -/* kex_agree_instr
- +/* _libssh2_kex_agree_instr
- * Kex specific variant of strstr()
- * Needle must be preceded by BOL or ',', and followed by ',' or EOL
- */
- -static unsigned char *
- -kex_agree_instr(unsigned char *haystack, size_t haystack_len,
- - const unsigned char *needle, size_t needle_len)
- +unsigned char *
- +_libssh2_kex_agree_instr(unsigned char *haystack, size_t haystack_len,
- + const unsigned char *needle, size_t needle_len)
- {
- unsigned char *s;
- unsigned char *end_haystack;
- @@ -3393,7 +3401,7 @@ static int kex_agree_hostkey(LIBSSH2_SESSION * session,
- while(s && *s) {
- unsigned char *p = (unsigned char *) strchr((char *) s, ',');
- size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s));
- - if(kex_agree_instr(hostkey, hostkey_len, s, method_len)) {
- + if(_libssh2_kex_agree_instr(hostkey, hostkey_len, s, method_len)) {
- const LIBSSH2_HOSTKEY_METHOD *method =
- (const LIBSSH2_HOSTKEY_METHOD *)
- kex_get_method_by_name((char *) s, method_len,
- @@ -3427,9 +3435,9 @@ static int kex_agree_hostkey(LIBSSH2_SESSION * session,
- }
-
- while(hostkeyp && (*hostkeyp) && (*hostkeyp)->name) {
- - s = kex_agree_instr(hostkey, hostkey_len,
- - (unsigned char *) (*hostkeyp)->name,
- - strlen((*hostkeyp)->name));
- + s = _libssh2_kex_agree_instr(hostkey, hostkey_len,
- + (unsigned char *) (*hostkeyp)->name,
- + strlen((*hostkeyp)->name));
- if(s) {
- /* So far so good, but does it suit our purposes? (Encrypting vs
- Signing) */
- @@ -3463,6 +3471,12 @@ static int kex_agree_kex_hostkey(LIBSSH2_SESSION * session, unsigned char *kex,
- {
- const LIBSSH2_KEX_METHOD **kexp = libssh2_kex_methods;
- unsigned char *s;
- + const unsigned char *strict =
- + (unsigned char *)"kex-strict-s-v00@openssh.com";
- +
- + if(_libssh2_kex_agree_instr(kex, kex_len, strict, 28)) {
- + session->kex_strict = 1;
- + }
-
- if(session->kex_prefs) {
- s = (unsigned char *) session->kex_prefs;
- @@ -3470,7 +3484,7 @@ static int kex_agree_kex_hostkey(LIBSSH2_SESSION * session, unsigned char *kex,
- while(s && *s) {
- unsigned char *q, *p = (unsigned char *) strchr((char *) s, ',');
- size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s));
- - q = kex_agree_instr(kex, kex_len, s, method_len);
- + q = _libssh2_kex_agree_instr(kex, kex_len, s, method_len);
- if(q) {
- const LIBSSH2_KEX_METHOD *method = (const LIBSSH2_KEX_METHOD *)
- kex_get_method_by_name((char *) s, method_len,
- @@ -3504,9 +3518,9 @@ static int kex_agree_kex_hostkey(LIBSSH2_SESSION * session, unsigned char *kex,
- }
-
- while(*kexp && (*kexp)->name) {
- - s = kex_agree_instr(kex, kex_len,
- - (unsigned char *) (*kexp)->name,
- - strlen((*kexp)->name));
- + s = _libssh2_kex_agree_instr(kex, kex_len,
- + (unsigned char *) (*kexp)->name,
- + strlen((*kexp)->name));
- if(s) {
- /* We've agreed on a key exchange method,
- * Can we agree on a hostkey that works with this kex?
- @@ -3550,7 +3564,7 @@ static int kex_agree_crypt(LIBSSH2_SESSION * session,
- unsigned char *p = (unsigned char *) strchr((char *) s, ',');
- size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s));
-
- - if(kex_agree_instr(crypt, crypt_len, s, method_len)) {
- + if(_libssh2_kex_agree_instr(crypt, crypt_len, s, method_len)) {
- const LIBSSH2_CRYPT_METHOD *method =
- (const LIBSSH2_CRYPT_METHOD *)
- kex_get_method_by_name((char *) s, method_len,
- @@ -3572,9 +3586,9 @@ static int kex_agree_crypt(LIBSSH2_SESSION * session,
- }
-
- while(*cryptp && (*cryptp)->name) {
- - s = kex_agree_instr(crypt, crypt_len,
- - (unsigned char *) (*cryptp)->name,
- - strlen((*cryptp)->name));
- + s = _libssh2_kex_agree_instr(crypt, crypt_len,
- + (unsigned char *) (*cryptp)->name,
- + strlen((*cryptp)->name));
- if(s) {
- endpoint->crypt = *cryptp;
- return 0;
- @@ -3614,7 +3628,7 @@ static int kex_agree_mac(LIBSSH2_SESSION * session,
- unsigned char *p = (unsigned char *) strchr((char *) s, ',');
- size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s));
-
- - if(kex_agree_instr(mac, mac_len, s, method_len)) {
- + if(_libssh2_kex_agree_instr(mac, mac_len, s, method_len)) {
- const LIBSSH2_MAC_METHOD *method = (const LIBSSH2_MAC_METHOD *)
- kex_get_method_by_name((char *) s, method_len,
- (const LIBSSH2_COMMON_METHOD **)
- @@ -3635,8 +3649,9 @@ static int kex_agree_mac(LIBSSH2_SESSION * session,
- }
-
- while(*macp && (*macp)->name) {
- - s = kex_agree_instr(mac, mac_len, (unsigned char *) (*macp)->name,
- - strlen((*macp)->name));
- + s = _libssh2_kex_agree_instr(mac, mac_len,
- + (unsigned char *) (*macp)->name,
- + strlen((*macp)->name));
- if(s) {
- endpoint->mac = *macp;
- return 0;
- @@ -3667,7 +3682,7 @@ static int kex_agree_comp(LIBSSH2_SESSION *session,
- unsigned char *p = (unsigned char *) strchr((char *) s, ',');
- size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s));
-
- - if(kex_agree_instr(comp, comp_len, s, method_len)) {
- + if(_libssh2_kex_agree_instr(comp, comp_len, s, method_len)) {
- const LIBSSH2_COMP_METHOD *method =
- (const LIBSSH2_COMP_METHOD *)
- kex_get_method_by_name((char *) s, method_len,
- @@ -3689,8 +3704,9 @@ static int kex_agree_comp(LIBSSH2_SESSION *session,
- }
-
- while(*compp && (*compp)->name) {
- - s = kex_agree_instr(comp, comp_len, (unsigned char *) (*compp)->name,
- - strlen((*compp)->name));
- + s = _libssh2_kex_agree_instr(comp, comp_len,
- + (unsigned char *) (*compp)->name,
- + strlen((*compp)->name));
- if(s) {
- endpoint->comp = *compp;
- return 0;
- @@ -3871,6 +3887,7 @@ _libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange,
- session->local.kexinit = key_state->oldlocal;
- session->local.kexinit_len = key_state->oldlocal_len;
- key_state->state = libssh2_NB_state_idle;
- + session->state &= ~LIBSSH2_STATE_INITIAL_KEX;
- session->state &= ~LIBSSH2_STATE_KEX_ACTIVE;
- session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS;
- return -1;
- @@ -3896,6 +3913,7 @@ _libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange,
- session->local.kexinit = key_state->oldlocal;
- session->local.kexinit_len = key_state->oldlocal_len;
- key_state->state = libssh2_NB_state_idle;
- + session->state &= ~LIBSSH2_STATE_INITIAL_KEX;
- session->state &= ~LIBSSH2_STATE_KEX_ACTIVE;
- session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS;
- return -1;
- @@ -3944,6 +3962,7 @@ _libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange,
- session->remote.kexinit = NULL;
- }
-
- + session->state &= ~LIBSSH2_STATE_INITIAL_KEX;
- session->state &= ~LIBSSH2_STATE_KEX_ACTIVE;
- session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS;
-
- diff --git a/src/libssh2_priv.h b/src/libssh2_priv.h
- index 7660366954..18d9ab2130 100644
- --- a/src/libssh2_priv.h
- +++ b/src/libssh2_priv.h
- @@ -736,6 +736,9 @@ struct _LIBSSH2_SESSION
- /* key signing algorithm preferences -- NULL yields server order */
- char *sign_algo_prefs;
-
- + /* Whether to use the OpenSSH Strict KEX extension */
- + int kex_strict;
- +
- /* (remote as source of data -- packet_read ) */
- libssh2_endpoint_data remote;
-
- @@ -908,6 +911,7 @@ struct _LIBSSH2_SESSION
- int fullpacket_macstate;
- size_t fullpacket_payload_len;
- int fullpacket_packet_type;
- + uint32_t fullpacket_required_type;
-
- /* State variables used in libssh2_sftp_init() */
- libssh2_nonblocking_states sftpInit_state;
- @@ -948,10 +952,11 @@ struct _LIBSSH2_SESSION
- };
-
- /* session.state bits */
- -#define LIBSSH2_STATE_EXCHANGING_KEYS 0x00000001
- -#define LIBSSH2_STATE_NEWKEYS 0x00000002
- -#define LIBSSH2_STATE_AUTHENTICATED 0x00000004
- -#define LIBSSH2_STATE_KEX_ACTIVE 0x00000008
- +#define LIBSSH2_STATE_INITIAL_KEX 0x00000001
- +#define LIBSSH2_STATE_EXCHANGING_KEYS 0x00000002
- +#define LIBSSH2_STATE_NEWKEYS 0x00000004
- +#define LIBSSH2_STATE_AUTHENTICATED 0x00000008
- +#define LIBSSH2_STATE_KEX_ACTIVE 0x00000010
-
- /* session.flag helpers */
- #ifdef MSG_NOSIGNAL
- @@ -1182,6 +1187,11 @@ ssize_t _libssh2_send(libssh2_socket_t socket, const void *buffer,
- int _libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange,
- key_exchange_state_t * state);
-
- +unsigned char *_libssh2_kex_agree_instr(unsigned char *haystack,
- + size_t haystack_len,
- + const unsigned char *needle,
- + size_t needle_len);
- +
- /* Let crypt.c/hostkey.c expose their method structs */
- const LIBSSH2_CRYPT_METHOD **libssh2_crypt_methods(void);
- const LIBSSH2_HOSTKEY_METHOD **libssh2_hostkey_methods(void);
- diff --git a/src/packet.c b/src/packet.c
- index eccb8c56a8..6da14e9fa1 100644
- --- a/src/packet.c
- +++ b/src/packet.c
- @@ -624,14 +624,13 @@ packet_authagent_open(LIBSSH2_SESSION * session,
- * layer when it has received a packet.
- *
- * The input pointer 'data' is pointing to allocated data that this function
- - * is asked to deal with so on failure OR success, it must be freed fine.
- - * The only exception is when the return code is LIBSSH2_ERROR_EAGAIN.
- + * will be freed unless return the code is LIBSSH2_ERROR_EAGAIN.
- *
- * This function will always be called with 'datalen' greater than zero.
- */
- int
- _libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data,
- - size_t datalen, int macstate)
- + size_t datalen, int macstate, uint32_t seq)
- {
- int rc = 0;
- unsigned char *message = NULL;
- @@ -676,6 +675,70 @@ _libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data,
- break;
- }
-
- + if(session->state & LIBSSH2_STATE_INITIAL_KEX) {
- + if(msg == SSH_MSG_KEXINIT) {
- + if(!session->kex_strict) {
- + if(datalen < 17) {
- + LIBSSH2_FREE(session, data);
- + session->packAdd_state = libssh2_NB_state_idle;
- + return _libssh2_error(session,
- + LIBSSH2_ERROR_BUFFER_TOO_SMALL,
- + "Data too short extracting kex");
- + }
- + else {
- + const unsigned char *strict =
- + (unsigned char *)"kex-strict-s-v00@openssh.com";
- + struct string_buf buf;
- + unsigned char *algs = NULL;
- + size_t algs_len = 0;
- +
- + buf.data = (unsigned char *)data;
- + buf.dataptr = buf.data;
- + buf.len = datalen;
- + buf.dataptr += 17; /* advance past type and cookie */
- +
- + if(_libssh2_get_string(&buf, &algs, &algs_len)) {
- + LIBSSH2_FREE(session, data);
- + session->packAdd_state = libssh2_NB_state_idle;
- + return _libssh2_error(session,
- + LIBSSH2_ERROR_BUFFER_TOO_SMALL,
- + "Algs too short");
- + }
- +
- + if(algs_len == 0 ||
- + _libssh2_kex_agree_instr(algs, algs_len, strict, 28)) {
- + session->kex_strict = 1;
- + }
- + }
- + }
- +
- + if(session->kex_strict && seq) {
- + LIBSSH2_FREE(session, data);
- + session->socket_state = LIBSSH2_SOCKET_DISCONNECTED;
- + session->packAdd_state = libssh2_NB_state_idle;
- + libssh2_session_disconnect(session, "strict KEX violation: "
- + "KEXINIT was not the first packet");
- +
- + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_DISCONNECT,
- + "strict KEX violation: "
- + "KEXINIT was not the first packet");
- + }
- + }
- +
- + if(session->kex_strict && session->fullpacket_required_type &&
- + session->fullpacket_required_type != msg) {
- + LIBSSH2_FREE(session, data);
- + session->socket_state = LIBSSH2_SOCKET_DISCONNECTED;
- + session->packAdd_state = libssh2_NB_state_idle;
- + libssh2_session_disconnect(session, "strict KEX violation: "
- + "unexpected packet type");
- +
- + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_DISCONNECT,
- + "strict KEX violation: "
- + "unexpected packet type");
- + }
- + }
- +
- if(session->packAdd_state == libssh2_NB_state_allocated) {
- /* A couple exceptions to the packet adding rule: */
- switch(msg) {
- @@ -1364,6 +1427,15 @@ _libssh2_packet_ask(LIBSSH2_SESSION * session, unsigned char packet_type,
-
- return 0;
- }
- + else if(session->kex_strict &&
- + (session->state & LIBSSH2_STATE_INITIAL_KEX)) {
- + libssh2_session_disconnect(session, "strict KEX violation: "
- + "unexpected packet type");
- +
- + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_DISCONNECT,
- + "strict KEX violation: "
- + "unexpected packet type");
- + }
- packet = _libssh2_list_next(&packet->node);
- }
- return -1;
- @@ -1425,7 +1497,10 @@ _libssh2_packet_require(LIBSSH2_SESSION * session, unsigned char packet_type,
- }
-
- while(session->socket_state == LIBSSH2_SOCKET_CONNECTED) {
- - int ret = _libssh2_transport_read(session);
- + int ret;
- + session->fullpacket_required_type = packet_type;
- + ret = _libssh2_transport_read(session);
- + session->fullpacket_required_type = 0;
- if(ret == LIBSSH2_ERROR_EAGAIN)
- return ret;
- else if(ret < 0) {
- diff --git a/src/packet.h b/src/packet.h
- index 1d90b8af12..955351e5f6 100644
- --- a/src/packet.h
- +++ b/src/packet.h
- @@ -72,6 +72,6 @@ int _libssh2_packet_burn(LIBSSH2_SESSION * session,
- int _libssh2_packet_write(LIBSSH2_SESSION * session, unsigned char *data,
- unsigned long data_len);
- int _libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data,
- - size_t datalen, int macstate);
- + size_t datalen, int macstate, uint32_t seq);
-
- #endif /* LIBSSH2_PACKET_H */
- diff --git a/src/session.c b/src/session.c
- index 35e7929fe7..9d89ade8ec 100644
- --- a/src/session.c
- +++ b/src/session.c
- @@ -469,6 +469,8 @@ libssh2_session_init_ex(LIBSSH2_ALLOC_FUNC((*my_alloc)),
- session->abstract = abstract;
- session->api_timeout = 0; /* timeout-free API by default */
- session->api_block_mode = 1; /* blocking API by default */
- + session->state = LIBSSH2_STATE_INITIAL_KEX;
- + session->fullpacket_required_type = 0;
- session->packet_read_timeout = LIBSSH2_DEFAULT_READ_TIMEOUT;
- session->flag.quote_paths = 1; /* default behavior is to quote paths
- for the scp subsystem */
- @@ -1223,6 +1225,7 @@ libssh2_session_disconnect_ex(LIBSSH2_SESSION *session, int reason,
- const char *desc, const char *lang)
- {
- int rc;
- + session->state &= ~LIBSSH2_STATE_INITIAL_KEX;
- session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS;
- BLOCK_ADJUST(rc, session,
- session_disconnect(session, reason, desc, lang));
- diff --git a/src/transport.c b/src/transport.c
- index 21be9d2b80..a8bb588a4b 100644
- --- a/src/transport.c
- +++ b/src/transport.c
- @@ -186,6 +186,7 @@ fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ )
- struct transportpacket *p = &session->packet;
- int rc;
- int compressed;
- + uint32_t seq = session->remote.seqno;
-
- if(session->fullpacket_state == libssh2_NB_state_idle) {
- session->fullpacket_macstate = LIBSSH2_MAC_CONFIRMED;
- @@ -317,7 +318,7 @@ fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ )
- if(session->fullpacket_state == libssh2_NB_state_created) {
- rc = _libssh2_packet_add(session, p->payload,
- session->fullpacket_payload_len,
- - session->fullpacket_macstate);
- + session->fullpacket_macstate, seq);
- if(rc == LIBSSH2_ERROR_EAGAIN)
- return rc;
- if(rc) {
- @@ -328,6 +329,11 @@ fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ )
-
- session->fullpacket_state = libssh2_NB_state_idle;
-
- + if(session->kex_strict &&
- + session->fullpacket_packet_type == SSH_MSG_NEWKEYS) {
- + session->remote.seqno = 0;
- + }
- +
- return session->fullpacket_packet_type;
- }
-
- @@ -1093,6 +1099,10 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session,
-
- session->local.seqno++;
-
- + if(session->kex_strict && data[0] == SSH_MSG_NEWKEYS) {
- + session->local.seqno = 0;
- + }
- +
- ret = LIBSSH2_SEND(session, p->outbuf, total_length,
- LIBSSH2_SOCKET_SEND_FLAGS(session));
- if(ret < 0)
|