Coverage Report

Created: 2026-07-05 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libssh/src/packet_cb.c
Line
Count
Source
1
/*
2
 * packet_cb.c - packet building functions
3
 *
4
 * This file is part of the SSH Library
5
 *
6
 * Copyright (c) 2011      Aris Adamantiadis
7
 *
8
 * The SSH Library is free software; you can redistribute it and/or modify
9
 * it under the terms of the GNU Lesser General Public License as published by
10
 * the Free Software Foundation; either version 2.1 of the License, or (at your
11
 * option) any later version.
12
 *
13
 * The SSH Library is distributed in the hope that it will be useful, but
14
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
16
 * License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public License
19
 * along with the SSH Library; see the file COPYING.  If not, write to
20
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
21
 * MA 02111-1307, USA.
22
 */
23
24
#include "config.h"
25
26
#include <stdlib.h>
27
#ifdef HAVE_ARPA_INET_H
28
#include <arpa/inet.h>
29
#endif
30
#ifdef WITH_GSSAPI
31
#include "libssh/gssapi.h"
32
#include <gssapi/gssapi.h>
33
#endif
34
35
#include "libssh/priv.h"
36
#include "libssh/buffer.h"
37
#include "libssh/crypto.h"
38
#include "libssh/dh.h"
39
#include "libssh/misc.h"
40
#include "libssh/packet.h"
41
#include "libssh/pki.h"
42
#include "libssh/session.h"
43
#include "libssh/socket.h"
44
#include "libssh/ssh2.h"
45
#include "libssh/curve25519.h"
46
47
/**
48
 * @internal
49
 *
50
 * @brief Handle a SSH_DISCONNECT packet.
51
 */
52
SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback)
53
23
{
54
23
    int rc;
55
23
    uint32_t code = 0;
56
23
    char *error = NULL;
57
23
    ssh_string error_s = NULL;
58
59
23
    (void)user;
60
23
    (void)type;
61
62
23
    rc = ssh_buffer_get_u32(packet, &code);
63
23
    if (rc != 0) {
64
19
        code = ntohl(code);
65
19
    }
66
67
23
    error_s = ssh_buffer_get_ssh_string(packet);
68
23
    if (error_s != NULL) {
69
6
        error = ssh_string_to_char(error_s);
70
6
        SSH_STRING_FREE(error_s);
71
6
    }
72
73
23
    if (error != NULL) {
74
6
        session->peer_discon_msg = strdup(error);
75
6
    }
76
77
23
    SSH_LOG(SSH_LOG_PACKET,
78
23
            "Received SSH_MSG_DISCONNECT %" PRIu32 ":%s",
79
23
            code,
80
23
            error != NULL ? error : "no error");
81
23
    ssh_set_error(session,
82
23
                  SSH_FATAL,
83
23
                  "Received SSH_MSG_DISCONNECT: %" PRIu32 ":%s",
84
23
                  code,
85
23
                  error != NULL ? error : "no error");
86
23
    SAFE_FREE(error);
87
88
23
    ssh_session_socket_close(session);
89
    /* correctly handle disconnect during authorization */
90
23
    session->auth.state = SSH_AUTH_STATE_FAILED;
91
92
    /* TODO: handle a graceful disconnect */
93
23
    return SSH_PACKET_USED;
94
23
}
95
96
/**
97
 * @internal
98
 *
99
 * @brief Handle a SSH_IGNORE packet.
100
 */
101
SSH_PACKET_CALLBACK(ssh_packet_ignore_callback)
102
315
{
103
315
    (void)session; /* unused */
104
315
    (void)user;
105
315
    (void)type;
106
315
    (void)packet;
107
108
315
    SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_IGNORE packet");
109
110
315
    return SSH_PACKET_USED;
111
315
}
112
113
/**
114
 * @internal
115
 *
116
 * @brief Handle a SSH_DEBUG packet.
117
 */
118
SSH_PACKET_CALLBACK(ssh_packet_debug_callback)
119
92.9k
{
120
92.9k
    uint8_t always_display = -1;
121
92.9k
    char *message = NULL;
122
92.9k
    int rc;
123
124
92.9k
    (void)session; /* unused */
125
92.9k
    (void)type;
126
92.9k
    (void)user;
127
128
92.9k
    rc = ssh_buffer_unpack(packet, "bs", &always_display, &message);
129
92.9k
    if (rc != SSH_OK) {
130
92.7k
        SSH_LOG(SSH_LOG_PACKET, "Error reading debug message");
131
92.7k
        return SSH_PACKET_USED;
132
92.7k
    }
133
183
    SSH_LOG(SSH_LOG_DEBUG,
134
183
            "Received SSH_MSG_DEBUG packet with message %s%s",
135
183
            message,
136
183
            always_display != 0 ? " (always display)" : "");
137
183
    SAFE_FREE(message);
138
139
183
    return SSH_PACKET_USED;
140
92.9k
}
141
142
SSH_PACKET_CALLBACK(ssh_packet_newkeys)
143
6.09k
{
144
6.09k
    ssh_string sig_blob = NULL;
145
6.09k
    ssh_signature sig = NULL;
146
6.09k
    int rc;
147
148
6.09k
    (void)packet;
149
6.09k
    (void)user;
150
6.09k
    (void)type;
151
152
6.09k
    SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_NEWKEYS");
153
154
6.09k
    if (session->session_state != SSH_SESSION_STATE_DH ||
155
6.09k
        session->dh_handshake_state != DH_STATE_NEWKEYS_SENT) {
156
0
        ssh_set_error(session,
157
0
                      SSH_FATAL,
158
0
                      "ssh_packet_newkeys called in wrong state : %d:%d",
159
0
                      session->session_state,
160
0
                      session->dh_handshake_state);
161
0
        goto error;
162
0
    }
163
164
6.09k
    if (session->flags & SSH_SESSION_FLAG_KEX_STRICT) {
165
        /* reset packet sequence number when running in strict kex mode */
166
183
        session->recv_seq = 0;
167
        /* Check that we aren't tainted */
168
183
        if (session->flags & SSH_SESSION_FLAG_KEX_TAINTED) {
169
0
            ssh_set_error(session,
170
0
                          SSH_FATAL,
171
0
                          "Received unexpected packets in strict KEX mode.");
172
0
            goto error;
173
0
        }
174
183
    }
175
176
6.09k
    if (session->server) {
177
        /* server things are done in server.c */
178
6.09k
        session->dh_handshake_state=DH_STATE_FINISHED;
179
6.09k
    } else {
180
#ifdef WITH_GSSAPI
181
        if (ssh_kex_is_gss(session->next_crypto)) {
182
            OM_uint32 maj_stat, min_stat;
183
            gss_buffer_desc mic = GSS_C_EMPTY_BUFFER, msg = GSS_C_EMPTY_BUFFER;
184
185
            if (session->gssapi == NULL || session->gssapi->ctx == NULL) {
186
                ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized");
187
                goto error;
188
            }
189
190
            if (session->gssapi_key_exchange_mic == NULL) {
191
                ssh_set_error(session,
192
                              SSH_FATAL,
193
                              "GSSAPI mic not set");
194
                goto error;
195
            }
196
197
            mic.length = ssh_string_len(session->gssapi_key_exchange_mic);
198
            mic.value = ssh_string_data(session->gssapi_key_exchange_mic);
199
200
            msg.length = session->next_crypto->digest_len;
201
            msg.value = session->next_crypto->secret_hash;
202
203
            maj_stat = gss_verify_mic(&min_stat,
204
                                      session->gssapi->ctx,
205
                                      &msg,
206
                                      &mic,
207
                                      NULL);
208
            if (maj_stat != GSS_S_COMPLETE) {
209
                ssh_set_error(session,
210
                              SSH_FATAL,
211
                              "Failed to verify mic after GSSAPI Key Exchange");
212
                goto error;
213
            }
214
            SSH_STRING_FREE(session->gssapi_key_exchange_mic);
215
        } else
216
#endif
217
0
        {
218
0
            ssh_key server_key = NULL;
219
220
            /* client */
221
222
            /* Verify the host's signature. FIXME do it sooner */
223
0
            sig_blob = session->next_crypto->dh_server_signature;
224
0
            session->next_crypto->dh_server_signature = NULL;
225
226
            /* get the server public key */
227
0
            server_key = ssh_dh_get_next_server_publickey(session);
228
0
            if (server_key == NULL) {
229
0
                goto error;
230
0
            }
231
232
0
            rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig);
233
0
            ssh_string_burn(sig_blob);
234
0
            SSH_STRING_FREE(sig_blob);
235
0
            if (rc != SSH_OK) {
236
0
                goto error;
237
0
            }
238
239
            /* Check if signature from server matches user preferences */
240
0
            if (session->opts.wanted_methods[SSH_HOSTKEYS]) {
241
0
                rc = match_group(session->opts.wanted_methods[SSH_HOSTKEYS],
242
0
                                 sig->type_c);
243
0
                if (rc == 0) {
244
0
                    ssh_set_error(
245
0
                        session,
246
0
                        SSH_FATAL,
247
0
                        "Public key from server (%s) doesn't match user "
248
0
                        "preference (%s)",
249
0
                        sig->type_c,
250
0
                        session->opts.wanted_methods[SSH_HOSTKEYS]);
251
0
                    goto error;
252
0
                }
253
0
            }
254
255
0
            rc = ssh_pki_signature_verify(session,
256
0
                                          sig,
257
0
                                          server_key,
258
0
                                          session->next_crypto->secret_hash,
259
0
                                          session->next_crypto->digest_len);
260
0
            SSH_SIGNATURE_FREE(sig);
261
0
            if (rc == SSH_ERROR) {
262
0
                ssh_set_error(session,
263
0
                              SSH_FATAL,
264
0
                              "Failed to verify server hostkey signature");
265
0
                goto error;
266
0
            }
267
0
        }
268
0
        SSH_LOG(SSH_LOG_DEBUG, "Signature verified and valid");
269
270
        /* When receiving this packet, we switch on the incoming crypto. */
271
0
        rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN);
272
0
        if (rc != SSH_OK) {
273
0
            goto error;
274
0
        }
275
0
    }
276
6.09k
    session->dh_handshake_state = DH_STATE_FINISHED;
277
6.09k
    session->ssh_connection_callback(session);
278
6.09k
    return SSH_PACKET_USED;
279
280
0
error:
281
#ifdef WITH_GSSAPI
282
    SSH_STRING_FREE(session->gssapi_key_exchange_mic);
283
#endif
284
0
    SSH_SIGNATURE_FREE(sig);
285
0
    ssh_string_burn(sig_blob);
286
0
    SSH_STRING_FREE(sig_blob);
287
0
    session->session_state = SSH_SESSION_STATE_ERROR;
288
0
    return SSH_PACKET_USED;
289
6.09k
}
290
291
/**
292
 * @internal
293
 * @brief handles a SSH_SERVICE_ACCEPT packet
294
 *
295
 */
296
SSH_PACKET_CALLBACK(ssh_packet_service_accept)
297
0
{
298
0
    (void)packet;
299
0
    (void)type;
300
0
    (void)user;
301
302
0
    session->auth.service_state = SSH_AUTH_SERVICE_ACCEPTED;
303
0
    SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_SERVICE_ACCEPT");
304
305
0
    return SSH_PACKET_USED;
306
0
}
307
308
/**
309
 * @internal
310
 * @brief handles a SSH2_MSG_EXT_INFO packet defined in RFC 8308
311
 *
312
 */
313
SSH_PACKET_CALLBACK(ssh_packet_ext_info)
314
3.27k
{
315
3.27k
    int rc;
316
3.27k
    uint32_t nr_extensions = 0;
317
3.27k
    uint32_t i;
318
319
3.27k
    (void)type;
320
3.27k
    (void)user;
321
322
3.27k
    SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_EXT_INFO");
323
324
3.27k
    rc = ssh_buffer_get_u32(packet, &nr_extensions);
325
3.27k
    if (rc == 0) {
326
233
        SSH_LOG(SSH_LOG_PACKET, "Failed to read number of extensions");
327
233
        return SSH_PACKET_USED;
328
233
    }
329
330
3.03k
    nr_extensions = ntohl(nr_extensions);
331
3.03k
    if (nr_extensions > 128) {
332
537
        SSH_LOG(SSH_LOG_PACKET, "Invalid number of extensions");
333
537
        return SSH_PACKET_USED;
334
537
    }
335
336
2.50k
    SSH_LOG(SSH_LOG_PACKET, "Follows %" PRIu32 " extensions", nr_extensions);
337
338
4.81k
    for (i = 0; i < nr_extensions; i++) {
339
3.79k
        char *name = NULL;
340
3.79k
        char *value = NULL;
341
342
3.79k
        rc = ssh_buffer_unpack(packet, "ss", &name, &value);
343
3.79k
        if (rc != SSH_OK) {
344
1.48k
            SSH_LOG(SSH_LOG_PACKET, "Error reading extension name-value pair");
345
1.48k
            return SSH_PACKET_USED;
346
1.48k
        }
347
348
2.31k
        if (strcmp(name, "server-sig-algs") == 0) {
349
            /* TODO check for NULL bytes */
350
839
            SSH_LOG(SSH_LOG_PACKET, "Extension: %s=<%s>", name, value);
351
352
839
            rc = match_group(value, "rsa-sha2-512");
353
839
            if (rc == 1) {
354
618
                session->extensions |= SSH_EXT_SIG_RSA_SHA512;
355
618
            }
356
357
839
            rc = match_group(value, "rsa-sha2-256");
358
839
            if (rc == 1) {
359
617
                session->extensions |= SSH_EXT_SIG_RSA_SHA256;
360
617
            }
361
1.47k
        } else if (strcmp(name, "publickey-hostbound@openssh.com") == 0) {
362
19
            SSH_LOG(SSH_LOG_PACKET, "Extension: %s=<%s>", name, value);
363
19
            session->extensions |= SSH_EXT_PUBLICKEY_HOSTBOUND;
364
1.45k
        } else if (strcmp(name, "ping@openssh.com") == 0) {
365
6
            if (strcmp(value, "0") == 0) {
366
3
                SSH_LOG(SSH_LOG_PACKET,
367
3
                        "Extension: %s=<%s> (supported)",
368
3
                        name,
369
3
                        value);
370
3
                session->extensions |= SSH_EXT_PING;
371
3
            } else {
372
3
                SSH_LOG(SSH_LOG_PACKET,
373
3
                        "Extension: %s=<%s> (unsupported version, expected 0)",
374
3
                        name,
375
3
                        value);
376
3
            }
377
1.44k
        } else {
378
1.44k
            SSH_LOG(SSH_LOG_PACKET, "Unknown extension: %s", name);
379
1.44k
        }
380
2.31k
        free(name);
381
2.31k
        free(value);
382
2.31k
    }
383
384
1.01k
    return SSH_PACKET_USED;
385
2.50k
}
386
387
SSH_PACKET_CALLBACK(ssh_packet_ping)
388
248
{
389
248
    int rc;
390
248
    ssh_string payload = NULL;
391
392
248
    (void)type;
393
248
    (void)user;
394
395
248
    SSH_LOG(SSH_LOG_PACKET, "Received SSH2_MSG_PING");
396
397
    /* Drop PING before the initial key exchange completes. During rekeying,
398
     * ssh_packet_send() queues the PONG response automatically via out_queue.
399
     */
400
248
    if (!(session->flags & SSH_SESSION_FLAG_AUTHENTICATED) &&
401
192
        session->dh_handshake_state != DH_STATE_FINISHED) {
402
177
        SSH_LOG(SSH_LOG_DEBUG,
403
177
                "Ignoring PING before initial key exchange is complete");
404
177
        return SSH_PACKET_USED;
405
177
    }
406
407
71
    payload = ssh_buffer_get_ssh_string(packet);
408
71
    if (payload == NULL) {
409
1
        SSH_LOG(SSH_LOG_PACKET, "SSH2_MSG_PING: missing string payload");
410
1
        ssh_set_error(session,
411
1
                      SSH_FATAL,
412
1
                      "SSH2_MSG_PING: missing string payload");
413
1
        session->session_state = SSH_SESSION_STATE_ERROR;
414
1
        return SSH_PACKET_USED;
415
1
    }
416
417
70
    if (ssh_buffer_get_len(packet) != 0) {
418
5
        SSH_LOG(SSH_LOG_PACKET,
419
5
                "SSH2_MSG_PING: unexpected trailing data after payload");
420
5
        ssh_set_error(session,
421
5
                      SSH_FATAL,
422
5
                      "SSH2_MSG_PING: unexpected trailing data after payload");
423
5
        session->session_state = SSH_SESSION_STATE_ERROR;
424
5
        SSH_STRING_FREE(payload);
425
5
        return SSH_PACKET_USED;
426
5
    }
427
428
65
    rc = ssh_buffer_pack(session->out_buffer, "bS", SSH2_MSG_PONG, payload);
429
65
    SSH_STRING_FREE(payload);
430
65
    if (rc != SSH_OK) {
431
0
        ssh_set_error_oom(session);
432
0
        ssh_buffer_reinit(session->out_buffer);
433
0
        return SSH_PACKET_USED;
434
0
    }
435
436
65
    rc = ssh_packet_send(session);
437
65
    if (rc != SSH_OK) {
438
0
        SSH_LOG(SSH_LOG_PACKET, "Failed to send SSH2_MSG_PONG");
439
0
        return SSH_PACKET_USED;
440
0
    }
441
442
65
    return SSH_PACKET_USED;
443
65
}
444
445
/**
446
 * @internal
447
 *
448
 * @brief Handle a SSH2_MSG_PONG packet.
449
 *
450
 * This handler consumes PONG messages and invokes the user callback if
451
 * provided.
452
 */
453
SSH_PACKET_CALLBACK(ssh_packet_pong)
454
0
{
455
0
    const void *payload_data = NULL;
456
0
    ssh_string payload = NULL;
457
0
    size_t payload_len = 0;
458
459
0
    (void)type;
460
0
    (void)user;
461
462
0
    SSH_LOG(SSH_LOG_DEBUG, "Received SSH2_MSG_PONG");
463
464
0
    if (session->pending_pings == 0) {
465
0
        SSH_LOG(SSH_LOG_PACKET, "Received unsolicited SSH2_MSG_PONG");
466
0
        ssh_set_error(session, SSH_FATAL, "Received unsolicited SSH2_MSG_PONG");
467
0
        session->session_state = SSH_SESSION_STATE_ERROR;
468
0
        return SSH_PACKET_USED;
469
0
    }
470
471
0
    payload = ssh_buffer_get_ssh_string(packet);
472
0
    if (payload == NULL) {
473
0
        SSH_LOG(SSH_LOG_PACKET, "SSH2_MSG_PONG: missing string payload");
474
0
        ssh_set_error(session,
475
0
                      SSH_FATAL,
476
0
                      "SSH2_MSG_PONG: missing string payload");
477
0
        session->session_state = SSH_SESSION_STATE_ERROR;
478
0
        return SSH_PACKET_USED;
479
0
    }
480
481
0
    if (ssh_buffer_get_len(packet) != 0) {
482
0
        SSH_LOG(SSH_LOG_PACKET,
483
0
                "SSH2_MSG_PONG: unexpected trailing data after payload");
484
0
        ssh_set_error(session,
485
0
                      SSH_FATAL,
486
0
                      "SSH2_MSG_PONG: unexpected trailing data after payload");
487
0
        session->session_state = SSH_SESSION_STATE_ERROR;
488
0
        SSH_STRING_FREE(payload);
489
0
        return SSH_PACKET_USED;
490
0
    }
491
492
0
    payload_data = ssh_string_data(payload);
493
0
    payload_len = ssh_string_len(payload);
494
495
    /* If user provided a high-level PONG callback, decode and invoke it. */
496
0
    if (ssh_callbacks_exists(session->common.callbacks, pong_function)) {
497
0
        session->common.callbacks->pong_function(
498
0
            session,
499
0
            payload_data,
500
0
            payload_len,
501
0
            session->common.callbacks->userdata);
502
0
    }
503
504
0
    SSH_STRING_FREE(payload);
505
0
    session->pending_pings--;
506
507
0
    return SSH_PACKET_USED;
508
0
}