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