diff --git a/README b/README index f093359..865a2cb 100644 --- a/README +++ b/README @@ -10,6 +10,10 @@ Version 0.14 Swap ordering of packet_add/packet-inspection to avoid inspect after free. (Selcuk) + Swap KEX_INIT ordering, send our KEX_INIT first. + + Add check for oportunistic KEX_INIT packets. Burn bad guess if necessary. + Fix OpenSSL detection using pkg-config. (Dan Casey) Version 0.13 diff --git a/include/libssh2_priv.h b/include/libssh2_priv.h index 351f3a4..2701729 100644 --- a/include/libssh2_priv.h +++ b/include/libssh2_priv.h @@ -199,6 +199,7 @@ struct _LIBSSH2_SESSION { /* Agreed Key Exchange Method */ LIBSSH2_KEX_METHOD *kex; + int burn_optimistic_kexinit:1; unsigned char *session_id; unsigned long session_id_len; @@ -462,6 +463,7 @@ int libssh2_packet_require_ex(LIBSSH2_SESSION *session, unsigned char packet_typ int libssh2_packet_requirev_ex(LIBSSH2_SESSION *session, unsigned char *packet_types, unsigned char **data, unsigned long *data_len, unsigned long match_ofs, const unsigned char *match_buf, unsigned long match_len); #define libssh2_packet_requirev(session, packet_types, data, data_len) \ libssh2_packet_requirev_ex((session), (packet_types), (data), (data_len), 0, NULL, 0) +int libssh2_packet_burn(LIBSSH2_SESSION *session); int libssh2_packet_write(LIBSSH2_SESSION *session, unsigned char *data, unsigned long data_len); int libssh2_kex_exchange(LIBSSH2_SESSION *session, int reexchange); unsigned long libssh2_channel_nextid(LIBSSH2_SESSION *session); diff --git a/src/kex.c b/src/kex.c index a810828..3a15a84 100644 --- a/src/kex.c +++ b/src/kex.c @@ -118,6 +118,26 @@ static int libssh2_kex_method_diffie_hellman_groupGP_sha1_key_exchange(LIBSSH2_S goto clean_exit; } + if (session->burn_optimistic_kexinit) { + /* The first KEX packet to come along will be the guess initially sent by the server + * That guess turned out to be wrong so we need to silently ignore it */ + int burn_type; +#ifdef LIBSSH2_DEBUG_KEX + _libssh2_debug(session, LIBSSH2_DBG_KEX, "Waiting for badly guessed KEX packet (to be ignored)"); +#endif + burn_type = libssh2_packet_burn(session); + if (burn_type <= 0) { + /* Failed to receive a packet */ + ret = -1; + goto clean_exit; + } + session->burn_optimistic_kexinit = 0; + +#ifdef LIBSSH2_DEBUG_KEX + _libssh2_debug(session, LIBSSH2_DBG_KEX, "Burnt packet of type: %02x", (unsigned int)burn_type); +#endif + } + /* Wait for KEX reply */ if (libssh2_packet_require(session, packet_type_reply, &s_packet, &s_packet_len)) { libssh2_error(session, LIBSSH2_ERROR_TIMEOUT, "Timed out waiting for KEX reply", 0); @@ -967,9 +987,9 @@ static int libssh2_kex_agree_kex_hostkey(LIBSSH2_SESSION *session, unsigned char s = session->kex_prefs; while (s && *s) { - unsigned char *p = strchr(s, ','); + unsigned char *q, *p = strchr(s, ','); int method_len = (p ? (p - s) : strlen(s)); - if (libssh2_kex_agree_instr(kex, kex_len, s, method_len)) { + if ((q = libssh2_kex_agree_instr(kex, kex_len, s, method_len))) { LIBSSH2_KEX_METHOD *method = (LIBSSH2_KEX_METHOD*)libssh2_get_method_by_name(s, method_len, (LIBSSH2_COMMON_METHOD**)kexp); if (!method) { @@ -982,6 +1002,12 @@ static int libssh2_kex_agree_kex_hostkey(LIBSSH2_SESSION *session, unsigned char */ if (libssh2_kex_agree_hostkey(session, method->flags, hostkey, hostkey_len) == 0) { session->kex = method; + if (session->burn_optimistic_kexinit && (kex == q)) { + /* Server sent an optimistic packet, + * and client agrees with preference + * cancel burning the first KEX_INIT packet that comes in */ + session->burn_optimistic_kexinit = 0; + } return 0; } } @@ -999,6 +1025,12 @@ static int libssh2_kex_agree_kex_hostkey(LIBSSH2_SESSION *session, unsigned char */ if (libssh2_kex_agree_hostkey(session, (*kexp)->flags, hostkey, hostkey_len) == 0) { session->kex = *kexp; + if (session->burn_optimistic_kexinit && (kex == s)) { + /* Server sent an optimistic packet, + * and client agrees with preference + * cancel burning the first KEX_INIT packet that comes in */ + session->burn_optimistic_kexinit = 0; + } return 0; } } @@ -1174,6 +1206,12 @@ static int libssh2_kex_agree_methods(LIBSSH2_SESSION *session, unsigned char *da lang_cs_len = libssh2_ntohu32(s); lang_cs = s + 4; s += 4 + lang_cs_len; lang_sc_len = libssh2_ntohu32(s); lang_sc = s + 4; s += 4 + lang_sc_len; + /* If the server sent an optimistic packet, assume that it guessed wrong. + * If the guess is determined to be right (by libssh2_kex_agree_kex_hostkey) + * This flag will be reset to zero so that it's not ignored */ + session->burn_optimistic_kexinit = *(s++); + /* Next uint32 in packet is all zeros (reserved) */ + if (libssh2_kex_agree_kex_hostkey(session, kex, kex_len, hostkey, hostkey_len)) { return -1; } @@ -1248,7 +1286,23 @@ int libssh2_kex_exchange(LIBSSH2_SESSION *session, int reexchange) /* session->f } if (!session->kex || !session->hostkey) { + /* Preserve in case of failure */ + unsigned char *oldlocal = session->local.kexinit; + unsigned long oldlocal_len = session->local.kexinit_len; + + session->local.kexinit = NULL; + if (libssh2_kexinit(session)) { + session->local.kexinit = oldlocal; + session->local.kexinit_len = oldlocal_len; + return -1; + } + if (libssh2_packet_require(session, SSH_MSG_KEXINIT, &data, &data_len)) { + if (session->local.kexinit) { + LIBSSH2_FREE(session, session->local.kexinit); + } + session->local.kexinit = oldlocal; + session->local.kexinit_len = oldlocal_len; return -1; } @@ -1258,10 +1312,6 @@ int libssh2_kex_exchange(LIBSSH2_SESSION *session, int reexchange) /* session->f session->remote.kexinit = data; session->remote.kexinit_len = data_len; - if (libssh2_kexinit(session)) { - return -1; - } - if (libssh2_kex_agree_methods(session, data, data_len)) { return -1; } diff --git a/src/packet.c b/src/packet.c index 369d98c..859dde9 100644 --- a/src/packet.c +++ b/src/packet.c @@ -1017,6 +1017,48 @@ int libssh2_packet_require_ex(LIBSSH2_SESSION *session, unsigned char packet_typ } /* }}} */ +/* {{{ libssh2_packet_burn + * Loops libssh2_packet_read() until any packet is available and promptly discards it + * Used during KEX exchange to discard badly guessed KEX_INIT packets + */ +int libssh2_packet_burn(LIBSSH2_SESSION *session) +{ + unsigned char *data; + unsigned long data_len; + char all_packets[255]; + int i; + for(i = 1; i < 256; i++) all_packets[i - 1] = i; + + if (libssh2_packet_askv_ex(session, all_packets, &data, &data_len, 0, NULL, 0, 0) == 0) { + i = data[0]; + /* A packet was available in the packet brigade, burn it */ + LIBSSH2_FREE(session, data); + return i; + } + +#ifdef LIBSSH2_DEBUG_TRANSPORT + _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Blocking until packet becomes available to burn"); +#endif + while (session->socket_state == LIBSSH2_SOCKET_CONNECTED) { + int ret = libssh2_packet_read(session, 1); + if (ret < 0) { + return -1; + } + if (ret == 0) continue; + + /* Be lazy, let packet_ask pull it out of the brigade */ + if (0 == libssh2_packet_ask_ex(session, ret, &data, &data_len, 0, NULL, 0, 0)) { + /* Smoke 'em if you got 'em */ + LIBSSH2_FREE(session, data); + return ret; + } + } + + /* Only reached if the socket died */ + return -1; +} +/* }}} */ + /* {{{ libssh2_packet_requirev * Loops libssh2_packet_read() until one of a list of packet types requested is available * SSH_DISCONNECT or a SOCKET_DISCONNECTED will cause a bailout