Skip to content
This repository was archived by the owner on Jan 26, 2026. It is now read-only.

Commit fc1a8bb

Browse files
Jakujecryptomilk
authored andcommitted
CVE-2023-1667:kex: Correctly handle last fields of KEXINIT also in the client side
Previously, the last two fields of KEXINIT were considered as always zero for the key exchange. This was true for the sending side, but might have not been true for the received KEXINIT from the peer. This moves the construction of these two fields closer to their reading or writing, instead of hardcoding them on the last possible moment before they go as input to the hashing function. This also allows accepting the first_kex_packet_follows on the client side, even though there is no kex algorithm now that would allow this. It also avoid memory leaks in case the server_set_kex() or ssh_set_client_kex() gets called multiple times, ensuring the algorithms will not change under our hands. It also makes use of a new flag to track if we sent KEXINIT. Previously, this was tracked only implicitly by the content of the session->next_crypto->{server,client}_kex (local kex). If it was not set, we considered it was not send. But given that we need to check the local kex even before sending it when we receive first_kex_packet_follows flag in the KEXINIT, this can no longer be used. Signed-off-by: Jakub Jelen <jjelen@redhat.com> Reviewed-by: Norbert Pocs <npocs@redhat.com> Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
1 parent b759ae5 commit fc1a8bb

4 files changed

Lines changed: 80 additions & 59 deletions

File tree

include/libssh/session.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ enum ssh_pending_call_e {
7676
/* Client successfully authenticated */
7777
#define SSH_SESSION_FLAG_AUTHENTICATED 2
7878

79+
/* The KEXINIT message can be sent first by either of the parties so this flag
80+
* indicates that the message was already sent to make sure it is sent and avoid
81+
* sending it twice during key exchange to simplify the state machine. */
82+
#define SSH_SESSION_FLAG_KEXINIT_SENT 4
83+
7984
/* codes to use with ssh_handle_packets*() */
8085
/* Infinite timeout */
8186
#define SSH_TIMEOUT_INFINITE -1

src/client.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ static void ssh_client_connection_callback(ssh_session session)
438438
case SSH_SESSION_STATE_KEXINIT_RECEIVED:
439439
set_status(session, 0.6f);
440440
ssh_list_kex(&session->next_crypto->server_kex);
441-
if (session->next_crypto->client_kex.methods[0] == NULL) {
441+
if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) {
442442
/* in rekeying state if next_crypto client_kex might be empty */
443443
rc = ssh_set_client_kex(session);
444444
if (rc != SSH_OK) {

src/kex.c

Lines changed: 68 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -375,14 +375,25 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit)
375375
(void)user;
376376

377377
if (session->session_state == SSH_SESSION_STATE_AUTHENTICATED) {
378-
SSH_LOG(SSH_LOG_INFO, "Initiating key re-exchange");
378+
if (session->dh_handshake_state == DH_STATE_FINISHED) {
379+
SSH_LOG(SSH_LOG_DEBUG, "Peer initiated key re-exchange");
380+
/* Reset the sent flag if the re-kex was initiated by the peer */
381+
session->flags &= ~SSH_SESSION_FLAG_KEXINIT_SENT;
382+
} else if (session->dh_handshake_state == DH_STATE_INIT_SENT) {
383+
SSH_LOG(SSH_LOG_DEBUG, "Receeved peer kexinit answer");
384+
} else {
385+
ssh_set_error(session, SSH_FATAL,
386+
"SSH_KEXINIT received in wrong state");
387+
goto error;
388+
}
379389
} else if (session->session_state != SSH_SESSION_STATE_INITIAL_KEX) {
380390
ssh_set_error(session, SSH_FATAL,
381391
"SSH_KEXINIT received in wrong state");
382392
goto error;
383393
}
384394

385395
if (server_kex) {
396+
#ifdef WITH_SERVER
386397
len = ssh_buffer_get_data(packet, crypto->client_kex.cookie, 16);
387398
if (len != 16) {
388399
ssh_set_error(session, SSH_FATAL,
@@ -396,6 +407,12 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit)
396407
"ssh_packet_kexinit: adding cookie failed");
397408
goto error;
398409
}
410+
411+
ok = server_set_kex(session);
412+
if (ok == SSH_ERROR) {
413+
goto error;
414+
}
415+
#endif
399416
} else {
400417
len = ssh_buffer_get_data(packet, crypto->server_kex.cookie, 16);
401418
if (len != 16) {
@@ -410,6 +427,11 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit)
410427
"ssh_packet_kexinit: adding cookie failed");
411428
goto error;
412429
}
430+
431+
ok = ssh_set_client_kex(session);
432+
if (ok == SSH_ERROR) {
433+
goto error;
434+
}
413435
}
414436

415437
for (i = 0; i < SSH_KEX_METHODS; i++) {
@@ -455,22 +477,37 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit)
455477
* that its value is included when computing the session ID (see
456478
* 'make_sessionid').
457479
*/
458-
if (server_kex) {
459-
rc = ssh_buffer_get_u8(packet, &first_kex_packet_follows);
460-
if (rc != 1) {
461-
goto error;
462-
}
480+
rc = ssh_buffer_get_u8(packet, &first_kex_packet_follows);
481+
if (rc != 1) {
482+
goto error;
483+
}
463484

464-
rc = ssh_buffer_add_u8(session->in_hashbuf, first_kex_packet_follows);
465-
if (rc < 0) {
466-
goto error;
467-
}
485+
rc = ssh_buffer_add_u8(session->in_hashbuf, first_kex_packet_follows);
486+
if (rc < 0) {
487+
goto error;
488+
}
468489

469-
rc = ssh_buffer_add_u32(session->in_hashbuf, kexinit_reserved);
470-
if (rc < 0) {
471-
goto error;
472-
}
490+
rc = ssh_buffer_add_u32(session->in_hashbuf, kexinit_reserved);
491+
if (rc < 0) {
492+
goto error;
493+
}
473494

495+
/*
496+
* Remember whether 'first_kex_packet_follows' was set and the client
497+
* guess was wrong: in this case the next SSH_MSG_KEXDH_INIT message
498+
* must be ignored.
499+
*/
500+
if (first_kex_packet_follows) {
501+
char **client_methods = crypto->client_kex.methods;
502+
char **server_methods = crypto->server_kex.methods;
503+
session->first_kex_follows_guess_wrong =
504+
cmp_first_kex_algo(client_methods[SSH_KEX],
505+
server_methods[SSH_KEX]) ||
506+
cmp_first_kex_algo(client_methods[SSH_HOSTKEYS],
507+
server_methods[SSH_HOSTKEYS]);
508+
}
509+
510+
if (server_kex) {
474511
/*
475512
* If client sent a ext-info-c message in the kex list, it supports
476513
* RFC 8308 extension negotiation.
@@ -542,19 +579,6 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit)
542579
session->extensions & SSH_EXT_SIG_RSA_SHA256 ? "SHA256" : "",
543580
session->extensions & SSH_EXT_SIG_RSA_SHA512 ? " SHA512" : "");
544581
}
545-
546-
/*
547-
* Remember whether 'first_kex_packet_follows' was set and the client
548-
* guess was wrong: in this case the next SSH_MSG_KEXDH_INIT message
549-
* must be ignored.
550-
*/
551-
if (first_kex_packet_follows) {
552-
session->first_kex_follows_guess_wrong =
553-
cmp_first_kex_algo(session->next_crypto->client_kex.methods[SSH_KEX],
554-
session->next_crypto->server_kex.methods[SSH_KEX]) ||
555-
cmp_first_kex_algo(session->next_crypto->client_kex.methods[SSH_HOSTKEYS],
556-
session->next_crypto->server_kex.methods[SSH_HOSTKEYS]);
557-
}
558582
}
559583

560584
/* Note, that his overwrites authenticated state in case of rekeying */
@@ -707,14 +731,18 @@ int ssh_set_client_kex(ssh_session session)
707731
int i;
708732
size_t kex_len, len;
709733

734+
/* Skip if already set, for example for the rekey or when we do the guessing
735+
* it could have been already used to make some protocol decisions. */
736+
if (client->methods[0] != NULL) {
737+
return SSH_OK;
738+
}
739+
710740
ok = ssh_get_random(client->cookie, 16, 0);
711741
if (!ok) {
712742
ssh_set_error(session, SSH_FATAL, "PRNG error");
713743
return SSH_ERROR;
714744
}
715745

716-
memset(client->methods, 0, SSH_KEX_METHODS * sizeof(char **));
717-
718746
/* Set the list of allowed algorithms in order of preference, if it hadn't
719747
* been set yet. */
720748
for (i = 0; i < SSH_KEX_METHODS; i++) {
@@ -965,11 +993,22 @@ int ssh_send_kex(ssh_session session)
965993
goto error;
966994
}
967995

996+
/* Prepare also the first_kex_packet_follows and reserved to 0 */
997+
rc = ssh_buffer_add_u8(session->out_hashbuf, 0);
998+
if (rc < 0) {
999+
goto error;
1000+
}
1001+
rc = ssh_buffer_add_u32(session->out_hashbuf, 0);
1002+
if (rc < 0) {
1003+
goto error;
1004+
}
1005+
9681006
rc = ssh_packet_send(session);
9691007
if (rc == SSH_ERROR) {
9701008
return -1;
9711009
}
9721010

1011+
session->flags |= SSH_SESSION_FLAG_KEXINIT_SENT;
9731012
SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_KEXINIT sent");
9741013
return 0;
9751014

@@ -1106,33 +1145,6 @@ int ssh_make_sessionid(ssh_session session)
11061145
client_hash = session->in_hashbuf;
11071146
}
11081147

1109-
/*
1110-
* Handle the two final fields for the KEXINIT message (RFC 4253 7.1):
1111-
*
1112-
* boolean first_kex_packet_follows
1113-
* uint32 0 (reserved for future extension)
1114-
*/
1115-
rc = ssh_buffer_add_u8(server_hash, 0);
1116-
if (rc < 0) {
1117-
goto error;
1118-
}
1119-
rc = ssh_buffer_add_u32(server_hash, 0);
1120-
if (rc < 0) {
1121-
goto error;
1122-
}
1123-
1124-
/* These fields are handled for the server case in ssh_packet_kexinit. */
1125-
if (session->client) {
1126-
rc = ssh_buffer_add_u8(client_hash, 0);
1127-
if (rc < 0) {
1128-
goto error;
1129-
}
1130-
rc = ssh_buffer_add_u32(client_hash, 0);
1131-
if (rc < 0) {
1132-
goto error;
1133-
}
1134-
}
1135-
11361148
rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob);
11371149
if (rc != SSH_OK) {
11381150
goto error;

src/server.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,11 @@ int server_set_kex(ssh_session session)
9292
size_t len;
9393
int ok;
9494

95-
ZERO_STRUCTP(server);
95+
/* Skip if already set, for example for the rekey or when we do the guessing
96+
* it could have been already used to make some protocol decisions. */
97+
if (server->methods[0] != NULL) {
98+
return SSH_OK;
99+
}
96100

97101
ok = ssh_get_random(server->cookie, 16, 0);
98102
if (!ok) {
@@ -376,7 +380,7 @@ static void ssh_server_connection_callback(ssh_session session)
376380
break;
377381
case SSH_SESSION_STATE_KEXINIT_RECEIVED:
378382
set_status(session, 0.6f);
379-
if (session->next_crypto->server_kex.methods[0] == NULL) {
383+
if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) {
380384
if (server_set_kex(session) == SSH_ERROR)
381385
goto error;
382386
/* We are in a rekeying, so we need to send the server kex */

0 commit comments

Comments
 (0)