From dc109a7f518757741590bb993c0c8412928ccec2 Mon Sep 17 00:00:00 2001 From: Will Cosgrove Date: Thu, 14 Mar 2019 15:22:13 -0700 Subject: [PATCH] Security fixes (#315) * Bounds checks Fixes for CVEs https://www.libssh2.org/CVE-2019-3863.html https://www.libssh2.org/CVE-2019-3856.html * Packet length bounds check CVE https://www.libssh2.org/CVE-2019-3855.html * Response length check CVE https://www.libssh2.org/CVE-2019-3859.html * Bounds check CVE https://www.libssh2.org/CVE-2019-3857.html * Bounds checking CVE https://www.libssh2.org/CVE-2019-3859.html and additional data validation * Check bounds before reading into buffers * Bounds checking CVE https://www.libssh2.org/CVE-2019-3859.html * declare SIZE_MAX and UINT_MAX if needed --- include/libssh2.h | 12 +++ src/channel.c | 129 +++++++++++++++++++++++----- src/hostkey.c | 211 +++++++++++++++++++++++----------------------- src/kex.c | 156 +++++++++++++++++++++++++--------- src/packet.c | 11 ++- src/session.c | 5 ++ src/transport.c | 6 +- src/userauth.c | 75 +++++++++++++--- 8 files changed, 419 insertions(+), 186 deletions(-) diff --git a/include/libssh2.h b/include/libssh2.h index 187539c6..cbf86e33 100644 --- a/include/libssh2.h +++ b/include/libssh2.h @@ -154,6 +154,18 @@ typedef int libssh2_socket_t; #define LIBSSH2_INVALID_SOCKET -1 #endif /* WIN32 */ +#ifndef SIZE_MAX +#if _WIN64 +#define SIZE_MAX 0xFFFFFFFFFFFFFFFF +#else +#define SIZE_MAX 0xFFFFFFFF +#endif +#endif + +#ifndef UINT_MAX +#define UINT_MAX 0xFFFFFFFF +#endif + /* * Determine whether there is small or large file support on windows. */ diff --git a/src/channel.c b/src/channel.c index f16412ce..98eb5199 100644 --- a/src/channel.c +++ b/src/channel.c @@ -239,7 +239,20 @@ _libssh2_channel_open(LIBSSH2_SESSION * session, const char *channel_type, goto channel_error; } + if(session->open_data_len < 1) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet size"); + goto channel_error; + } + if(session->open_data[0] == SSH_MSG_CHANNEL_OPEN_CONFIRMATION) { + + if(session->open_data_len < 17) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet size"); + goto channel_error; + } + session->open_channel->remote.id = _libssh2_ntohu32(session->open_data + 5); session->open_channel->local.window_size = @@ -520,7 +533,7 @@ channel_forward_listen(LIBSSH2_SESSION * session, const char *host, _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block"); return NULL; } - else if(rc) { + else if(rc || (data_len < 1)) { _libssh2_error(session, LIBSSH2_ERROR_PROTO, "Unknown"); session->fwdLstn_state = libssh2_NB_state_idle; return NULL; @@ -858,6 +871,11 @@ static int channel_setenv(LIBSSH2_CHANNEL *channel, if(rc) { channel->setenv_state = libssh2_NB_state_idle; return rc; + } + else if(data_len < 1) { + channel->setenv_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet size"); } if(data[0] == SSH_MSG_CHANNEL_SUCCESS) { @@ -977,7 +995,7 @@ static int channel_request_pty(LIBSSH2_CHANNEL *channel, if(rc == LIBSSH2_ERROR_EAGAIN) { return rc; } - else if(rc) { + else if(rc || data_len < 1) { channel->reqPTY_state = libssh2_NB_state_idle; return _libssh2_error(session, LIBSSH2_ERROR_PROTO, "Failed to require the PTY package"); @@ -1206,7 +1224,7 @@ channel_x11_req(LIBSSH2_CHANNEL *channel, int single_connection, if(rc == LIBSSH2_ERROR_EAGAIN) { return rc; } - else if(rc) { + else if(rc || data_len < 1) { channel->reqX11_state = libssh2_NB_state_idle; return _libssh2_error(session, rc, "waiting for x11-req response packet"); @@ -1334,7 +1352,7 @@ _libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, if(rc == LIBSSH2_ERROR_EAGAIN) { return rc; } - else if(rc) { + else if(rc || data_len < 1) { channel->process_state = libssh2_NB_state_end; return _libssh2_error(session, rc, "Failed waiting for channel success"); @@ -1404,23 +1422,45 @@ _libssh2_channel_flush(LIBSSH2_CHANNEL *channel, int streamid) channel->flush_flush_bytes = 0; while(packet) { + unsigned char packet_type; LIBSSH2_PACKET *next = _libssh2_list_next(&packet->node); - unsigned char packet_type = packet->data[0]; + + if(packet->data_len < 1) { + packet = next; + _libssh2_debug(channel->session, LIBSSH2_TRACE_ERROR, + "Unexpected packet length"); + continue; + } + + packet_type = packet->data[0]; if(((packet_type == SSH_MSG_CHANNEL_DATA) - || (packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA)) - && (_libssh2_ntohu32(packet->data + 1) == channel->local.id)) { + || (packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA)) + && ((packet->data_len >= 5) + && (_libssh2_ntohu32(packet->data + 1) == channel->local.id))) { /* It's our channel at least */ - long packet_stream_id = - (packet_type == SSH_MSG_CHANNEL_DATA) ? 0 : - _libssh2_ntohu32(packet->data + 5); + unsigned int packet_stream_id; + + if(packet_type == SSH_MSG_CHANNEL_DATA) { + packet_stream_id = 0; + } + else if(packet->data_len >= 9) { + packet_stream_id = _libssh2_ntohu32(packet->data + 5); + } + else { + channel->flush_state = libssh2_NB_state_idle; + return _libssh2_error(channel->session, + LIBSSH2_ERROR_PROTO, + "Unexpected packet length"); + } + if((streamid == LIBSSH2_CHANNEL_FLUSH_ALL) || ((packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA) && ((streamid == LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA) || (streamid == packet_stream_id))) || ((packet_type == SSH_MSG_CHANNEL_DATA) && (streamid == 0))) { - int bytes_to_flush = packet->data_len - packet->data_head; + size_t bytes_to_flush = packet->data_len - packet->data_head; _libssh2_debug(channel->session, LIBSSH2_TRACE_CONN, "Flushing %d bytes of data from stream " @@ -1777,8 +1817,8 @@ ssize_t _libssh2_channel_read(LIBSSH2_CHANNEL *channel, int stream_id, { LIBSSH2_SESSION *session = channel->session; int rc; - int bytes_read = 0; - int bytes_want; + size_t bytes_read = 0; + size_t bytes_want; int unlink_packet; LIBSSH2_PACKET *read_packet; LIBSSH2_PACKET *read_next; @@ -1818,7 +1858,7 @@ ssize_t _libssh2_channel_read(LIBSSH2_CHANNEL *channel, int stream_id, return _libssh2_error(session, rc, "transport read"); read_packet = _libssh2_list_first(&session->packets); - while(read_packet && (bytes_read < (int) buflen)) { + while(read_packet && (bytes_read < buflen)) { /* previously this loop condition also checked for !channel->remote.close but we cannot let it do this: @@ -1832,6 +1872,13 @@ ssize_t _libssh2_channel_read(LIBSSH2_CHANNEL *channel, int stream_id, /* In case packet gets destroyed during this iteration */ read_next = _libssh2_list_next(&readpkt->node); + if(readpkt->data_len < 5) { + read_packet = read_next; + _libssh2_debug(channel->session, LIBSSH2_TRACE_ERROR, + "Unexpected packet length"); + continue; + } + channel->read_local_id = _libssh2_ntohu32(readpkt->data + 1); @@ -1845,6 +1892,7 @@ ssize_t _libssh2_channel_read(LIBSSH2_CHANNEL *channel, int stream_id, if((stream_id && (readpkt->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) && (channel->local.id == channel->read_local_id) + && (readpkt->data_len >= 9) && (stream_id == (int) _libssh2_ntohu32(readpkt->data + 5))) || (!stream_id && (readpkt->data[0] == SSH_MSG_CHANNEL_DATA) && (channel->local.id == channel->read_local_id)) @@ -1858,7 +1906,7 @@ ssize_t _libssh2_channel_read(LIBSSH2_CHANNEL *channel, int stream_id, bytes_want = buflen - bytes_read; unlink_packet = FALSE; - if(bytes_want >= (int) (readpkt->data_len - readpkt->data_head)) { + if(bytes_want >= (readpkt->data_len - readpkt->data_head)) { /* we want more than this node keeps, so adjust the number and delete this node after the copy */ bytes_want = readpkt->data_len - readpkt->data_head; @@ -1961,6 +2009,7 @@ _libssh2_channel_packet_data_len(LIBSSH2_CHANNEL * channel, int stream_id) { LIBSSH2_SESSION *session = channel->session; LIBSSH2_PACKET *read_packet; + LIBSSH2_PACKET *next_packet; uint32_t read_local_id; read_packet = _libssh2_list_first(&session->packets); @@ -1968,6 +2017,16 @@ _libssh2_channel_packet_data_len(LIBSSH2_CHANNEL * channel, int stream_id) return 0; while(read_packet) { + + next_packet = _libssh2_list_next(&read_packet->node); + + if(read_packet->data_len < 5) { + read_packet = next_packet; + _libssh2_debug(channel->session, LIBSSH2_TRACE_ERROR, + "Unexpected packet length"); + continue; + } + read_local_id = _libssh2_ntohu32(read_packet->data + 1); /* @@ -1980,6 +2039,7 @@ _libssh2_channel_packet_data_len(LIBSSH2_CHANNEL * channel, int stream_id) if((stream_id && (read_packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) && (channel->local.id == read_local_id) + && (read_packet->data_len >= 9) && (stream_id == (int) _libssh2_ntohu32(read_packet->data + 5))) || (!stream_id @@ -1993,7 +2053,8 @@ _libssh2_channel_packet_data_len(LIBSSH2_CHANNEL * channel, int stream_id) == LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE))) { return (read_packet->data_len - read_packet->data_head); } - read_packet = _libssh2_list_next(&read_packet->node); + + read_packet = next_packet; } return 0; @@ -2220,6 +2281,7 @@ libssh2_channel_eof(LIBSSH2_CHANNEL * channel) { LIBSSH2_SESSION *session; LIBSSH2_PACKET *packet; + LIBSSH2_PACKET *next_packet; if(!channel) return LIBSSH2_ERROR_BAD_USE; @@ -2228,13 +2290,24 @@ libssh2_channel_eof(LIBSSH2_CHANNEL * channel) packet = _libssh2_list_first(&session->packets); while(packet) { + + next_packet = _libssh2_list_next(&packet->node); + + if(packet->data_len < 1) { + packet = next_packet; + _libssh2_debug(channel->session, LIBSSH2_TRACE_ERROR, + "Unexpected packet length"); + continue; + } + if(((packet->data[0] == SSH_MSG_CHANNEL_DATA) - || (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA)) - && (channel->local.id == _libssh2_ntohu32(packet->data + 1))) { + || (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA)) + && ((packet->data_len >= 5) + && (channel->local.id == _libssh2_ntohu32(packet->data + 1)))) { /* There's data waiting to be read yet, mask the EOF status */ return 0; } - packet = _libssh2_list_next(&packet->node); + packet = next_packet; } return channel->remote.eof; @@ -2594,19 +2667,31 @@ libssh2_channel_window_read_ex(LIBSSH2_CHANNEL *channel, if(read_avail) { size_t bytes_queued = 0; + LIBSSH2_PACKET *next_packet; LIBSSH2_PACKET *packet = _libssh2_list_first(&channel->session->packets); while(packet) { - unsigned char packet_type = packet->data[0]; + unsigned char packet_type; + next_packet = _libssh2_list_next(&packet->node); + + if(packet->data_len < 1) { + packet = next_packet; + _libssh2_debug(channel->session, LIBSSH2_TRACE_ERROR, + "Unexpected packet length"); + continue; + } + + packet_type = packet->data[0]; if(((packet_type == SSH_MSG_CHANNEL_DATA) || (packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA)) - && (_libssh2_ntohu32(packet->data + 1) == channel->local.id)) { + && ((packet->data_len >= 5) + && (_libssh2_ntohu32(packet->data + 1) == channel->local.id))) { bytes_queued += packet->data_len - packet->data_head; } - packet = _libssh2_list_next(&packet->node); + packet = next_packet; } *read_avail = bytes_queued; diff --git a/src/hostkey.c b/src/hostkey.c index 78471c3f..cdcf124e 100644 --- a/src/hostkey.c +++ b/src/hostkey.c @@ -64,38 +64,36 @@ hostkey_method_ssh_rsa_init(LIBSSH2_SESSION * session, void **abstract) { libssh2_rsa_ctx *rsactx; - const unsigned char *s, *e, *n; - unsigned long len, e_len, n_len; - int ret; - - (void) hostkey_data_len; + unsigned char *e, *n; + unsigned int e_len, n_len; + struct string_buf buf = { .len = 0, .offset = 0 }; if(*abstract) { hostkey_method_ssh_rsa_dtor(session, abstract); *abstract = NULL; } - s = hostkey_data; - len = _libssh2_ntohu32(s); - s += 4; - - if(len != 7 || strncmp((char *) s, "ssh-rsa", 7) != 0) { + if(hostkey_data_len < 19) { + _libssh2_debug(session, LIBSSH2_TRACE_ERROR, + "host key length too short"); return -1; } - s += 7; - e_len = _libssh2_ntohu32(s); - s += 4; + buf.data = (unsigned char*)hostkey_data; + buf.dataptr = buf.data; + buf.len = hostkey_data_len; - e = s; - s += e_len; - n_len = _libssh2_ntohu32(s); - s += 4; - n = s; + if(_libssh2_match_string(&buf, "ssh-rsa") != 0) + return -1; - ret = _libssh2_rsa_new(&rsactx, e, e_len, n, n_len, NULL, 0, - NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0); - if(ret) { + if((e_len = _libssh2_get_c_string(&buf, &e)) <= 0) + return -1; + + if((n_len = _libssh2_get_c_string(&buf, &n)) <= 0) + return -1; + + if(_libssh2_rsa_new(&rsactx, e, e_len, n, n_len, NULL, 0, + NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0)) { return -1; } @@ -181,6 +179,9 @@ hostkey_method_ssh_rsa_sig_verify(LIBSSH2_SESSION * session, (void) session; /* Skip past keyname_len(4) + keyname(7){"ssh-rsa"} + signature_len(4) */ + if(sig_len < 15) + return -1; + sig += 15; sig_len -= 15; return _libssh2_rsa_sha1_verify(rsactx, sig, sig_len, m, m_len); @@ -281,45 +282,42 @@ hostkey_method_ssh_dss_init(LIBSSH2_SESSION * session, void **abstract) { libssh2_dsa_ctx *dsactx; - const unsigned char *p, *q, *g, *y, *s; - unsigned long p_len, q_len, g_len, y_len, len; - int ret; - - (void) hostkey_data_len; - + unsigned char *p, *q, *g, *y; + unsigned long p_len, q_len, g_len, y_len; + struct string_buf buf = { .len = 0, .offset = 0 }; + if(*abstract) { hostkey_method_ssh_dss_dtor(session, abstract); *abstract = NULL; } - s = hostkey_data; - len = _libssh2_ntohu32(s); - s += 4; - if(len != 7 || strncmp((char *) s, "ssh-dss", 7) != 0) { + if(hostkey_data_len < 27) { + _libssh2_debug(session, LIBSSH2_TRACE_ERROR, + "host key length too short"); return -1; } - s += 7; + + buf.data = (unsigned char*)hostkey_data; + buf.dataptr = buf.data; + buf.len = hostkey_data_len; + + if(_libssh2_match_string(&buf, "ssh-dss") != 0) + return -1; + + if((p_len = _libssh2_get_c_string(&buf, &p)) < 0) + return -1; + + if((q_len = _libssh2_get_c_string(&buf, &q)) < 0) + return -1; + + if((g_len = _libssh2_get_c_string(&buf, &g)) < 0) + return -1; + + if((y_len = _libssh2_get_c_string(&buf, &y)) < 0) + return -1; - p_len = _libssh2_ntohu32(s); - s += 4; - p = s; - s += p_len; - q_len = _libssh2_ntohu32(s); - s += 4; - q = s; - s += q_len; - g_len = _libssh2_ntohu32(s); - s += 4; - g = s; - s += g_len; - y_len = _libssh2_ntohu32(s); - s += 4; - y = s; - /* s += y_len; */ - - ret = _libssh2_dsa_new(&dsactx, p, p_len, q, q_len, - g, g_len, y, y_len, NULL, 0); - if(ret) { + if(_libssh2_dsa_new(&dsactx, p, p_len, q, q_len, + g, g_len, y, y_len, NULL, 0)) { return -1; } @@ -404,12 +402,14 @@ hostkey_method_ssh_dss_sig_verify(LIBSSH2_SESSION * session, libssh2_dsa_ctx *dsactx = (libssh2_dsa_ctx *) (*abstract); /* Skip past keyname_len(4) + keyname(7){"ssh-dss"} + signature_len(4) */ - sig += 15; - sig_len -= 15; - if(sig_len != 40) { + if(sig_len != 55) { return _libssh2_error(session, LIBSSH2_ERROR_PROTO, "Invalid DSS signature length"); } + + sig += 15; + sig_len -= 15; + return _libssh2_dsa_sha1_verify(dsactx, sig, m, m_len); } @@ -505,65 +505,55 @@ hostkey_method_ssh_ecdsa_init(LIBSSH2_SESSION * session, void **abstract) { libssh2_ecdsa_ctx *ecdsactx = NULL; - const unsigned char *s, *k; - size_t len, key_len, n_len; + unsigned char *type_str, *domain, *public_key; + unsigned int key_len; libssh2_curve_type type; + struct string_buf buf = { .len = 0, .offset = 0 }; if(abstract != NULL && *abstract) { hostkey_method_ssh_ecdsa_dtor(session, abstract); *abstract = NULL; } - if(hostkey_data_len < 23) + if(hostkey_data_len < 39) { + _libssh2_debug(session, LIBSSH2_TRACE_ERROR, + "host key length too short"); return -1; + } - s = hostkey_data; - len = _libssh2_ntohu32(s); - s += 4; + buf.data = (unsigned char*)hostkey_data; + buf.dataptr = buf.data; + buf.len = hostkey_data_len; - if(len != 19) + if(_libssh2_get_c_string(&buf, &type_str) != 19) return -1; - - if(strncmp((char *) s, "ecdsa-sha2-nistp256", 19) == 0) { + + if (strncmp((char*) type_str, "ecdsa-sha2-nistp256", 19) == 0 ){ type = LIBSSH2_EC_CURVE_NISTP256; - } - else if(strncmp((char *) s, "ecdsa-sha2-nistp384", 19) == 0) { + }else if(strncmp((char*) type_str, "ecdsa-sha2-nistp384", 19) == 0 ){ type = LIBSSH2_EC_CURVE_NISTP384; - } - else if(strncmp((char *) s, "ecdsa-sha2-nistp521", 19) == 0) { + }else if(strncmp((char*) type_str, "ecdsa-sha2-nistp521", 19) == 0 ){ type = LIBSSH2_EC_CURVE_NISTP521; - } - else { + }else{ return -1; } - s += 19; - - /* Domain length */ - n_len = _libssh2_ntohu32(s); - s += 4; - - if(n_len != 8) + + if(_libssh2_get_c_string(&buf, &domain) != 8) return -1; - - if(type == LIBSSH2_EC_CURVE_NISTP256 && strncmp((char *)s, "nistp256", 8) != 0) { + + if ( type == LIBSSH2_EC_CURVE_NISTP256 && strncmp((char*)domain, "nistp256", 8) != 0){ + return -1; + }else if ( type == LIBSSH2_EC_CURVE_NISTP384 && strncmp((char*)domain, "nistp384", 8) != 0){ + return -1; + }else if ( type == LIBSSH2_EC_CURVE_NISTP521 && strncmp((char*)domain, "nistp521", 8) != 0){ return -1; } - else if(type == LIBSSH2_EC_CURVE_NISTP384 && strncmp((char *)s, "nistp384", 8) != 0) { - return -1; - } - else if(type == LIBSSH2_EC_CURVE_NISTP521 && strncmp((char *)s, "nistp521", 8) != 0) { - return -1; - } - - s += 8; - + /* public key */ - key_len = _libssh2_ntohu32(s); - s += 4; + if((key_len = _libssh2_get_c_string(&buf, &public_key)) <= 0) + return -1; - k = s; - - if(_libssh2_ecdsa_curve_name_with_octal_new(&ecdsactx, k, key_len, type) ) + if(_libssh2_ecdsa_curve_name_with_octal_new(&ecdsactx, public_key, key_len, type) ) return -1; if(abstract != NULL) @@ -644,8 +634,9 @@ hostkey_method_ssh_ecdsa_sig_verify(LIBSSH2_SESSION * session, const unsigned char *m, size_t m_len, void **abstract) { - const unsigned char *r, *s, *p; - size_t r_len, s_len; + unsigned char *r, *s, *name; + unsigned int r_len, s_len, len; + struct string_buf buf = { .len = 0, .offset = 0 }; libssh2_ecdsa_ctx *ctx = (libssh2_ecdsa_ctx *) (*abstract); (void) session; @@ -653,18 +644,22 @@ hostkey_method_ssh_ecdsa_sig_verify(LIBSSH2_SESSION * session, if(sig_len < 35) return -1; - /* Skip past keyname_len(4) + keyname(19){"ecdsa-sha2-nistp256"} + signature_len(4) */ - p = sig; - p += 27; + /* keyname_len(4) + keyname(19){"ecdsa-sha2-nistp256"} + signature_len(4) */ + buf.data = (unsigned char*)sig; + buf.dataptr = buf.data; + buf.len = sig_len; - r_len = _libssh2_ntohu32(p); - p += 4; - r = p; - p += r_len; - - s_len = _libssh2_ntohu32(p); - p += 4; - s = p; + if(_libssh2_get_c_string(&buf, &name) != 19) + return -1; + + if(_libssh2_get_u32(&buf, &len) != 0 || len < 8) + return -1; + + if((r_len = _libssh2_get_c_string(&buf, &r)) <= 0) + return -1; + + if((s_len = _libssh2_get_c_string(&buf, &s)) <= 0) + return -1; return _libssh2_ecdsa_verify(ctx, r, r_len, s, s_len, m, m_len); } @@ -804,7 +799,9 @@ hostkey_method_ssh_ed25519_init(LIBSSH2_SESSION * session, *abstract = NULL; } - if(hostkey_data_len < 15) { + if(hostkey_data_len < 19) { + _libssh2_debug(session, LIBSSH2_TRACE_ERROR, + "host key length too short"); return -1; } diff --git a/src/kex.c b/src/kex.c index 5b73927f..7cc93a7d 100644 --- a/src/kex.c +++ b/src/kex.c @@ -247,11 +247,23 @@ static int diffie_hellman_sha1(LIBSSH2_SESSION *session, } /* Parse KEXDH_REPLY */ + if(exchange_state->s_packet_len < 5) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet length"); + goto clean_exit; + } + exchange_state->s = exchange_state->s_packet + 1; session->server_hostkey_len = _libssh2_ntohu32(exchange_state->s); exchange_state->s += 4; + if(session->server_hostkey_len > exchange_state->s_packet_len - 5) { + ret = _libssh2_error(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, + "Host key length out of bounds"); + goto clean_exit; + } + if(session->server_hostkey) LIBSSH2_FREE(session, session->server_hostkey); @@ -908,11 +920,23 @@ static int diffie_hellman_sha256(LIBSSH2_SESSION *session, } /* Parse KEXDH_REPLY */ + if(exchange_state->s_packet_len < 5) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet length"); + goto clean_exit; + } + exchange_state->s = exchange_state->s_packet + 1; session->server_hostkey_len = _libssh2_ntohu32(exchange_state->s); exchange_state->s += 4; + if(session->server_hostkey_len > exchange_state->s_packet_len - 5) { + ret = _libssh2_error(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, + "Host key length out of bounds"); + goto clean_exit; + } + if(session->server_hostkey) LIBSSH2_FREE(session, session->server_hostkey); @@ -1583,7 +1607,6 @@ static int kex_method_diffie_hellman_group_exchange_sha1_key_exchange (LIBSSH2_SESSION * session, key_exchange_state_low_t * key_state) { - unsigned long p_len, g_len; int ret = 0; int rc; @@ -1642,15 +1665,36 @@ kex_method_diffie_hellman_group_exchange_sha1_key_exchange } if(key_state->state == libssh2_NB_state_sent1) { - unsigned char *s = key_state->data + 1; - p_len = _libssh2_ntohu32(s); - s += 4; - _libssh2_bn_from_bin(key_state->p, p_len, s); - s += p_len; + unsigned int p_len, g_len; + unsigned char *p, *g; + struct string_buf buf = { .len = 0, .offset = 0 }; + + if(key_state->data_len < 9) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected key length"); + goto dh_gex_clean_exit; + } - g_len = _libssh2_ntohu32(s); - s += 4; - _libssh2_bn_from_bin(key_state->g, g_len, s); + buf.data = key_state->data; + buf.dataptr = buf.data; + buf.len = key_state->data_len; + + buf.dataptr++; /* increment to big num */ + + if((p_len = _libssh2_get_bignum_bytes(&buf, &p)) <= 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected value"); + goto dh_gex_clean_exit; + } + + if((g_len = _libssh2_get_bignum_bytes(&buf, &g)) <= 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected value"); + goto dh_gex_clean_exit; + } + + _libssh2_bn_from_bin(key_state->p, p_len, p); + _libssh2_bn_from_bin(key_state->g, g_len, g); ret = diffie_hellman_sha1(session, key_state->g, key_state->p, p_len, SSH_MSG_KEX_DH_GEX_INIT, @@ -1685,7 +1729,6 @@ static int kex_method_diffie_hellman_group_exchange_sha256_key_exchange (LIBSSH2_SESSION * session, key_exchange_state_low_t * key_state) { - unsigned long p_len, g_len; int ret = 0; int rc; @@ -1744,15 +1787,36 @@ kex_method_diffie_hellman_group_exchange_sha256_key_exchange } if(key_state->state == libssh2_NB_state_sent1) { - unsigned char *s = key_state->data + 1; - p_len = _libssh2_ntohu32(s); - s += 4; - _libssh2_bn_from_bin(key_state->p, p_len, s); - s += p_len; + unsigned char *p, *g; + unsigned long p_len, g_len; + struct string_buf buf = { .len = 0, .offset = 0 }; + + if(key_state->data_len < 9) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected key length"); + goto dh_gex_clean_exit; + } - g_len = _libssh2_ntohu32(s); - s += 4; - _libssh2_bn_from_bin(key_state->g, g_len, s); + buf.data = key_state->data; + buf.dataptr = buf.data; + buf.len = key_state->data_len; + + buf.dataptr++; /* increment to big num */ + + if((p_len = _libssh2_get_bignum_bytes(&buf, &p)) <= 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected value"); + goto dh_gex_clean_exit; + } + + if((g_len = _libssh2_get_bignum_bytes(&buf, &g)) <= 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected value"); + goto dh_gex_clean_exit; + } + + _libssh2_bn_from_bin(key_state->p, p_len, p); + _libssh2_bn_from_bin(key_state->g, g_len, g); ret = diffie_hellman_sha256(session, key_state->g, key_state->p, p_len, SSH_MSG_KEX_DH_GEX_INIT, @@ -2534,28 +2598,38 @@ static int curve25519_sha256(LIBSSH2_SESSION *session, unsigned char *data, exchange_state->state = libssh2_NB_state_created; } - if( exchange_state->state == libssh2_NB_state_created) { + if(exchange_state->state == libssh2_NB_state_created) { /* parse INIT reply data */ + unsigned char *server_public_key, *server_host_key; + unsigned int server_public_key_len; + struct string_buf buf = { .len = 0, .offset = 0 }; - /*host key K_S*/ - unsigned char *s = data + 1; //advance past packet type - unsigned char *server_public_key; - size_t server_public_key_len; - size_t host_sig_len; - - session->server_hostkey_len = _libssh2_ntohu32((const unsigned char*)s); - s += 4; + if(data_len < 5) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected key length"); + goto clean_exit; + } + + buf.data = data; + buf.len = data_len; + buf.dataptr = buf.data; + buf.dataptr++; /* advance past packet type */ + if((session->server_hostkey_len = _libssh2_get_c_string(&buf, &server_host_key)) <= 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected key length"); + goto clean_exit; + } + session->server_hostkey = LIBSSH2_ALLOC(session, session->server_hostkey_len); - if(!session->server_hostkey) { + if (!session->server_hostkey) { ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for a copy " "of the host key"); goto clean_exit; } - memcpy(session->server_hostkey, s, session->server_hostkey_len); - s += session->server_hostkey_len; + memcpy(session->server_hostkey, server_host_key, session->server_hostkey_len); #if LIBSSH2_MD5 { @@ -2647,8 +2721,12 @@ static int curve25519_sha256(LIBSSH2_SESSION *session, unsigned char *data, } /* server public key Q_S */ - server_public_key_len = _libssh2_ntohu32((const unsigned char*)s); - s += 4; + if((server_public_key_len = + _libssh2_get_c_string(&buf, &server_public_key)) <= 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected key length"); + goto clean_exit; + } if( server_public_key_len != LIBSSH2_ED25519_KEY_LEN) { ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, @@ -2656,16 +2734,14 @@ static int curve25519_sha256(LIBSSH2_SESSION *session, unsigned char *data, goto clean_exit; } - server_public_key = s; - s += server_public_key_len; - /* server signature */ - host_sig_len = _libssh2_ntohu32((const unsigned char*)s); - s += 4; + if((exchange_state->h_sig_len = + _libssh2_get_c_string(&buf, &exchange_state->h_sig)) <= 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, + "Unexpected curve25519 server sig length"); + goto clean_exit; - exchange_state->h_sig = s; - exchange_state->h_sig_len = host_sig_len; - s += host_sig_len; + } // Compute the shared secret K rc = _libssh2_curve25519_gen_k(&exchange_state->k, private_key, server_public_key); diff --git a/src/packet.c b/src/packet.c index 39390bcd..38611051 100644 --- a/src/packet.c +++ b/src/packet.c @@ -819,8 +819,15 @@ _libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data, /* set signal name (without SIG prefix) */ uint32_t namelen = _libssh2_ntohu32(data + 9 + sizeof("exit-signal")); - channelp->exit_signal = - LIBSSH2_ALLOC(session, namelen + 1); + + if(namelen <= UINT_MAX - 1) { + channelp->exit_signal = + LIBSSH2_ALLOC(session, namelen + 1); + } + else { + channelp->exit_signal = NULL; + } + if(!channelp->exit_signal) rc = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, "memory for signal name"); diff --git a/src/session.c b/src/session.c index 1aee4293..bec76525 100644 --- a/src/session.c +++ b/src/session.c @@ -774,6 +774,11 @@ session_startup(LIBSSH2_SESSION *session, libssh2_socket_t sock) &session->startup_req_state); if(rc) return rc; + + if(session->startup_data_len < 5) { + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet length"); + } session->startup_service_length = _libssh2_ntohu32(session->startup_data + 1); diff --git a/src/transport.c b/src/transport.c index 804c9eac..c78f71d4 100644 --- a/src/transport.c +++ b/src/transport.c @@ -437,8 +437,12 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) * and we can extract packet and padding length from it */ p->packet_length = _libssh2_ntohu32(block); - if(p->packet_length < 1) + if(p->packet_length < 1) { return LIBSSH2_ERROR_DECRYPT; + } + else if(p->packet_length > LIBSSH2_PACKET_MAXPAYLOAD) { + return LIBSSH2_ERROR_OUT_OF_BOUNDARY; + } p->padding_length = block[4]; diff --git a/src/userauth.c b/src/userauth.c index 37a08dd6..90ef3296 100644 --- a/src/userauth.c +++ b/src/userauth.c @@ -128,7 +128,7 @@ static char *userauth_list(LIBSSH2_SESSION *session, const char *username, "Would block requesting userauth list"); return NULL; } - else if(rc) { + else if(rc || (session->userauth_list_data_len < 1)) { _libssh2_error(session, rc, "Failed getting response"); session->userauth_list_state = libssh2_NB_state_idle; return NULL; @@ -144,8 +144,21 @@ static char *userauth_list(LIBSSH2_SESSION *session, const char *username, return NULL; } - methods_len = _libssh2_ntohu32(session->userauth_list_data + 1); + if(session->userauth_list_data_len < 5) { + LIBSSH2_FREE(session, session->userauth_list_data); + session->userauth_list_data = NULL; + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet size"); + return NULL; + } + methods_len = _libssh2_ntohu32(session->userauth_list_data + 1); + if(methods_len >= session->userauth_list_data_len - 5) { + _libssh2_error(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, + "Unexpected userauth list size"); + return NULL; + } + /* Do note that the memory areas overlap! */ memmove(session->userauth_list_data, session->userauth_list_data + 5, methods_len); @@ -286,6 +299,11 @@ userauth_password(LIBSSH2_SESSION *session, return _libssh2_error(session, rc, "Waiting for password response"); } + else if(session->userauth_pswd_data_len < 1) { + session->userauth_pswd_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet size"); + } if(session->userauth_pswd_data[0] == SSH_MSG_USERAUTH_SUCCESS) { _libssh2_debug(session, LIBSSH2_TRACE_AUTH, @@ -314,6 +332,12 @@ userauth_password(LIBSSH2_SESSION *session, session->userauth_pswd_state = libssh2_NB_state_sent1; } + if(session->userauth_pswd_data_len < 1) { + session->userauth_pswd_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet size"); + } + if((session->userauth_pswd_data[0] == SSH_MSG_USERAUTH_PASSWD_CHANGEREQ) || (session->userauth_pswd_data0 == @@ -342,12 +366,18 @@ userauth_password(LIBSSH2_SESSION *session, } /* basic data_len + newpw_len(4) */ - session->userauth_pswd_data_len = - username_len + password_len + 44; + if(username_len + password_len + 44 <= UINT_MAX) { + session->userauth_pswd_data_len = + username_len + password_len + 44; + s = session->userauth_pswd_data = + LIBSSH2_ALLOC(session, + session->userauth_pswd_data_len); + } + else { + s = session->userauth_pswd_data = NULL; + session->userauth_pswd_data_len = 0; + } - s = session->userauth_pswd_data = - LIBSSH2_ALLOC(session, - session->userauth_pswd_data_len); if(!session->userauth_pswd_data) { LIBSSH2_FREE(session, session->userauth_pswd_newpw); @@ -988,7 +1018,7 @@ userauth_hostbased_fromfile(LIBSSH2_SESSION *session, } session->userauth_host_state = libssh2_NB_state_idle; - if(rc) { + if(rc || data_len < 1) { return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, "Auth failed"); } @@ -1077,7 +1107,7 @@ _libssh2_userauth_publickey(LIBSSH2_SESSION *session, if(!session->userauth_pblc_method) { session->userauth_pblc_method_len = _libssh2_ntohu32(pubkeydata); - if(session->userauth_pblc_method_len > pubkeydata_len) + if(session->userauth_pblc_method_len > pubkeydata_len - 4) /* the method length simply cannot be longer than the entire passed in data, so we use this to detect crazy input data */ @@ -1184,7 +1214,7 @@ _libssh2_userauth_publickey(LIBSSH2_SESSION *session, if(rc == LIBSSH2_ERROR_EAGAIN) { return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block"); } - else if(rc) { + else if(rc || (session->userauth_pblc_data_len < 1)) { LIBSSH2_FREE(session, session->userauth_pblc_packet); session->userauth_pblc_packet = NULL; LIBSSH2_FREE(session, session->userauth_pblc_method); @@ -1347,7 +1377,7 @@ _libssh2_userauth_publickey(LIBSSH2_SESSION *session, return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block requesting userauth list"); } - else if(rc) { + else if(rc || session->userauth_pblc_data_len < 1) { session->userauth_pblc_state = libssh2_NB_state_idle; return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, "Waiting for publickey USERAUTH response"); @@ -1681,7 +1711,7 @@ userauth_keyboard_interactive(LIBSSH2_SESSION * session, return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block"); } - else if(rc) { + else if(rc || session->userauth_kybd_data_len < 1) { session->userauth_kybd_state = libssh2_NB_state_idle; return _libssh2_error(session, LIBSSH2_ERROR_AUTHENTICATION_FAILED, @@ -1762,6 +1792,14 @@ userauth_keyboard_interactive(LIBSSH2_SESSION * session, session->userauth_kybd_num_prompts = _libssh2_ntohu32(s); s += 4; + if(session->userauth_kybd_num_prompts && + session->userauth_kybd_num_prompts > 100) { + _libssh2_error(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, + "Too many replies for " + "keyboard-interactive prompts"); + goto cleanup; + } + if(session->userauth_kybd_num_prompts) { session->userauth_kybd_prompts = LIBSSH2_CALLOC(session, @@ -1828,8 +1866,17 @@ userauth_keyboard_interactive(LIBSSH2_SESSION * session, for(i = 0; i < session->userauth_kybd_num_prompts; i++) { /* string response[1] (ISO-10646 UTF-8) */ - session->userauth_kybd_packet_len += - 4 + session->userauth_kybd_responses[i].length; + if(session->userauth_kybd_responses[i].length <= + (SIZE_MAX - 4 - session->userauth_kybd_packet_len) ) { + session->userauth_kybd_packet_len += + 4 + session->userauth_kybd_responses[i].length; + } + else { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for keyboard-" + "interactive response packet"); + goto cleanup; + } } /* A new userauth_kybd_data area is to be allocated, free the