Coverage Report

Created: 2026-04-06 07:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libssh/tests/fuzz/ssh_scp_fuzzer.c
Line
Count
Source
1
/*
2
 * Copyright 2026 libssh authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#include <assert.h>
18
#include <pthread.h>
19
#include <stdint.h>
20
#include <stdio.h>
21
#include <stdlib.h>
22
#include <string.h>
23
#include <sys/socket.h>
24
#include <unistd.h>
25
26
#define LIBSSH_STATIC 1
27
#include <libssh/libssh.h>
28
#include <libssh/scp.h>
29
30
#include "nallocinc.c"
31
#include "ssh_server_mock.h"
32
33
static void _fuzz_finalize(void)
34
4
{
35
4
    ssh_finalize();
36
4
}
37
38
int LLVMFuzzerInitialize(int *argc, char ***argv)
39
40
{
40
40
    (void)argc;
41
40
    nalloc_init(*argv[0]);
42
40
    ssh_init();
43
40
    atexit(_fuzz_finalize);
44
40
    ssh_mock_write_hostkey(SSH_MOCK_HOSTKEY_PATH);
45
40
    return 0;
46
40
}
47
48
/* Helper function to test one cipher/HMAC combination */
49
static int test_scp_with_cipher(const uint8_t *data,
50
                                size_t size,
51
                                const char *cipher,
52
                                const char *hmac)
53
17.0k
{
54
17.0k
    int socket_fds[2] = {-1, -1};
55
17.0k
    ssh_session client_session = NULL;
56
17.0k
    ssh_scp scp = NULL, scp_recursive = NULL;
57
17.0k
    char buf[256] = {0};
58
17.0k
    pthread_t srv_thread;
59
60
    /* Configure mock SSH server with fuzzer data */
61
17.0k
    struct ssh_mock_server_config server_config = {
62
17.0k
        .protocol_data = data,
63
17.0k
        .protocol_data_size = size,
64
17.0k
        .exec_callback = ssh_mock_send_raw_data,
65
17.0k
        .subsystem_callback = NULL,
66
17.0k
        .callback_userdata = NULL,
67
17.0k
        .cipher = cipher,
68
17.0k
        .hmac = hmac,
69
17.0k
        .server_socket = -1,
70
17.0k
        .client_socket = -1,
71
17.0k
        .server_ready = false,
72
17.0k
        .server_error = false,
73
17.0k
    };
74
75
17.0k
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds) != 0) {
76
0
        goto cleanup;
77
0
    }
78
79
17.0k
    server_config.server_socket = socket_fds[0];
80
17.0k
    server_config.client_socket = socket_fds[1];
81
82
17.0k
    if (ssh_mock_server_start(&server_config, &srv_thread) != 0) {
83
1
        goto cleanup;
84
1
    }
85
86
17.0k
    client_session = ssh_new();
87
17.0k
    if (client_session == NULL) {
88
4.33k
        goto cleanup_thread;
89
4.33k
    }
90
91
    /* Configure client with specified cipher/HMAC */
92
12.6k
    ssh_options_set(client_session, SSH_OPTIONS_FD, &socket_fds[1]);
93
12.6k
    ssh_options_set(client_session, SSH_OPTIONS_HOST, "localhost");
94
12.6k
    ssh_options_set(client_session, SSH_OPTIONS_USER, "fuzz");
95
12.6k
    ssh_options_set(client_session, SSH_OPTIONS_CIPHERS_C_S, cipher);
96
12.6k
    ssh_options_set(client_session, SSH_OPTIONS_CIPHERS_S_C, cipher);
97
12.6k
    ssh_options_set(client_session, SSH_OPTIONS_HMAC_C_S, hmac);
98
12.6k
    ssh_options_set(client_session, SSH_OPTIONS_HMAC_S_C, hmac);
99
100
    /* Set timeout for operations (1 second) */
101
12.6k
    long timeout = 1;
102
12.6k
    ssh_options_set(client_session, SSH_OPTIONS_TIMEOUT, &timeout);
103
104
12.6k
    if (ssh_connect(client_session) != SSH_OK) {
105
0
        goto cleanup_thread;
106
0
    }
107
108
12.6k
    if (ssh_userauth_none(client_session, NULL) != SSH_AUTH_SUCCESS) {
109
0
        goto cleanup_thread;
110
0
    }
111
112
12.6k
    scp = ssh_scp_new(client_session, SSH_SCP_READ, "/tmp/fuzz");
113
12.6k
    if (scp == NULL) {
114
0
        goto cleanup_thread;
115
0
    }
116
117
12.6k
    if (ssh_scp_init(scp) != SSH_OK) {
118
12.6k
        goto cleanup_thread;
119
12.6k
    }
120
121
8
    if (size > 0) {
122
8
        size_t copy_size = size < sizeof(buf) ? size : sizeof(buf);
123
8
        memcpy(buf, data, copy_size);
124
8
    }
125
126
    /* Fuzz all SCP API functions in read mode */
127
8
    ssh_scp_pull_request(scp);
128
8
    ssh_scp_request_get_filename(scp);
129
8
    ssh_scp_request_get_permissions(scp);
130
8
    ssh_scp_request_get_size64(scp);
131
8
    ssh_scp_request_get_size(scp);
132
8
    ssh_scp_request_get_warning(scp);
133
8
    ssh_scp_accept_request(scp);
134
8
    ssh_scp_deny_request(scp, "Denied by fuzzer");
135
8
    ssh_scp_read(scp, buf, sizeof(buf));
136
137
    /* Final fuzz of scp pull request after all the calls */
138
8
    ssh_scp_pull_request(scp);
139
140
    /* Fuzz SCP in write/upload + recursive directory mode. */
141
8
    scp_recursive = ssh_scp_new(client_session,
142
8
                                SSH_SCP_WRITE | SSH_SCP_RECURSIVE,
143
8
                                "/tmp/fuzz-recursive");
144
8
    if (scp_recursive != NULL) {
145
8
        if (ssh_scp_init(scp_recursive) == SSH_OK) {
146
0
            ssh_scp_push_directory(scp_recursive, "fuzz-dir", 0755);
147
0
            ssh_scp_push_file(scp_recursive, "fuzz-file", sizeof(buf), 0644);
148
0
            ssh_scp_write(scp_recursive, buf, sizeof(buf));
149
0
            ssh_scp_leave_directory(scp_recursive);
150
0
        }
151
8
    }
152
153
17.0k
cleanup_thread:
154
17.0k
    shutdown(socket_fds[0], SHUT_RDWR);
155
17.0k
    shutdown(socket_fds[1], SHUT_RDWR);
156
17.0k
    pthread_join(srv_thread, NULL);
157
158
17.0k
cleanup:
159
17.0k
    if (scp_recursive != NULL) {
160
8
        ssh_scp_close(scp_recursive);
161
8
        ssh_scp_free(scp_recursive);
162
8
    }
163
17.0k
    if (scp) {
164
12.6k
        ssh_scp_close(scp);
165
12.6k
        ssh_scp_free(scp);
166
12.6k
    }
167
17.0k
    if (client_session) {
168
12.6k
        ssh_disconnect(client_session);
169
12.6k
        ssh_free(client_session);
170
12.6k
    }
171
17.0k
    if (socket_fds[0] >= 0)
172
17.0k
        close(socket_fds[0]);
173
17.0k
    if (socket_fds[1] >= 0)
174
17.0k
        close(socket_fds[1]);
175
176
17.0k
    return 0;
177
17.0k
}
178
179
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
180
1.41k
{
181
1.41k
    assert(nalloc_start(data, size) > 0);
182
183
    /* Test all cipher/HMAC combinations exhaustively */
184
1.41k
    const char *ciphers[] = {
185
1.41k
        "none",
186
1.41k
        "aes128-ctr",
187
1.41k
        "aes256-ctr",
188
1.41k
        "aes128-cbc",
189
1.41k
    };
190
191
1.41k
    const char *hmacs[] = {
192
1.41k
        "none",
193
1.41k
        "hmac-sha1",
194
1.41k
        "hmac-sha2-256",
195
1.41k
    };
196
197
1.41k
    int num_ciphers = sizeof(ciphers) / sizeof(ciphers[0]);
198
1.41k
    int num_hmacs = sizeof(hmacs) / sizeof(hmacs[0]);
199
200
7.09k
    for (int i = 0; i < num_ciphers; i++) {
201
22.7k
        for (int j = 0; j < num_hmacs; j++) {
202
17.0k
            test_scp_with_cipher(data, size, ciphers[i], hmacs[j]);
203
17.0k
        }
204
5.67k
    }
205
206
1.41k
    nalloc_end();
207
1.41k
    return 0;
208
1.41k
}