Coverage Report

Created: 2026-06-10 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libssh/tests/fuzz/ssh_server_mock.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 "ssh_server_mock.h"
18
19
#include <pthread.h>
20
#include <stdio.h>
21
#include <stdlib.h>
22
#include <string.h>
23
#include <unistd.h>
24
25
#define LIBSSH_STATIC 1
26
#include <libssh/callbacks.h>
27
#include <libssh/libssh.h>
28
#include <libssh/server.h>
29
30
/* Fixed ed25519 key for all mock servers */
31
const char *ssh_mock_ed25519_key_pem =
32
    "-----BEGIN OPENSSH PRIVATE KEY-----\n"
33
    "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n"
34
    "QyNTUxOQAAACBpFO8/JfYlIqg6+vqx1vDKWDqxJHxw4tBqnQfiOjf2zAAAAJgbsYq1G7GK\n"
35
    "tQAAAAtzc2gtZWQyNTUxOQAAACBpFO8/JfYlIqg6+vqx1vDKWDqxJHxw4tBqnQfiOjf2zA\n"
36
    "AAAEAkGaLvQwKMbGVRk2M8cz7gqWvpBKuHkuekJxIBQrUJl2kU7z8l9iUiqDr6+rHW8MpY\n"
37
    "OrEkfHDi0GqdB+I6N/bMAAAAEGZ1enotZWQyNTUxOS1rZXkBAgMEBQ==\n"
38
    "-----END OPENSSH PRIVATE KEY-----\n";
39
40
/* Internal server session data */
41
struct mock_session_data {
42
    ssh_channel channel;
43
    struct ssh_mock_server_config *config;
44
};
45
46
/* Auth callback - always accepts "none" auth */
47
static int mock_auth_none(ssh_session session, const char *user, void *userdata)
48
962
{
49
962
    (void)session;
50
962
    (void)user;
51
962
    (void)userdata;
52
962
    return SSH_AUTH_SUCCESS;
53
962
}
54
55
/* Channel open callback */
56
static ssh_channel mock_channel_open(ssh_session session, void *userdata)
57
962
{
58
962
    struct mock_session_data *sdata = (struct mock_session_data *)userdata;
59
962
    sdata->channel = ssh_channel_new(session);
60
962
    return sdata->channel;
61
962
}
62
63
/* Exec request callback - for SCP */
64
static int mock_channel_exec(ssh_session session,
65
                             ssh_channel channel,
66
                             const char *command,
67
                             void *userdata)
68
962
{
69
962
    struct mock_session_data *sdata = (struct mock_session_data *)userdata;
70
962
    (void)session;
71
962
    (void)command;
72
73
962
    if (sdata->config->exec_callback) {
74
962
        return sdata->config->exec_callback(channel,
75
962
                                            sdata->config->protocol_data,
76
962
                                            sdata->config->protocol_data_size,
77
962
                                            sdata->config->callback_userdata);
78
962
    }
79
0
    return SSH_OK;
80
962
}
81
82
/* Subsystem request callback - for SFTP */
83
static int mock_channel_subsystem(ssh_session session,
84
                                  ssh_channel channel,
85
                                  const char *subsystem,
86
                                  void *userdata)
87
0
{
88
0
    struct mock_session_data *sdata = (struct mock_session_data *)userdata;
89
0
    (void)session;
90
0
    (void)subsystem;
91
92
0
    if (sdata->config->subsystem_callback) {
93
0
        return sdata->config->subsystem_callback(
94
0
            channel,
95
0
            sdata->config->protocol_data,
96
0
            sdata->config->protocol_data_size,
97
0
            sdata->config->callback_userdata);
98
0
    }
99
0
    return SSH_OK;
100
0
}
101
102
/* Consolidated cleanup for the server thread */
103
struct server_resources {
104
    ssh_bind sshbind;
105
    ssh_session session;
106
    ssh_event event;
107
};
108
109
static void cleanup_server_resources(void *arg)
110
1.26k
{
111
1.26k
    struct server_resources *res = (struct server_resources *)arg;
112
1.26k
    ssh_event_free(res->event);
113
1.26k
    if (res->session) {
114
1.26k
        ssh_disconnect(res->session);
115
1.26k
        ssh_free(res->session);
116
1.26k
    }
117
1.26k
    ssh_bind_free(res->sshbind);
118
1.26k
}
119
120
/* Server thread implementation */
121
static void *server_thread_func(void *arg)
122
1.26k
{
123
1.26k
    struct ssh_mock_server_config *config =
124
1.26k
        (struct ssh_mock_server_config *)arg;
125
1.26k
    struct mock_session_data sdata = {0};
126
1.26k
    sdata.config = config;
127
1.26k
    int rc;
128
129
1.26k
    struct server_resources res = {NULL, NULL, NULL};
130
131
1.26k
    struct ssh_server_callbacks_struct server_cb = {
132
1.26k
        .userdata = &sdata,
133
1.26k
        .auth_none_function = mock_auth_none,
134
1.26k
        .channel_open_request_session_function = mock_channel_open,
135
1.26k
    };
136
137
1.26k
    struct ssh_channel_callbacks_struct channel_cb = {
138
1.26k
        .userdata = &sdata,
139
1.26k
        .channel_exec_request_function = mock_channel_exec,
140
1.26k
        .channel_subsystem_request_function = mock_channel_subsystem,
141
1.26k
    };
142
143
1.26k
    bool no = false;
144
145
1.26k
    res.sshbind = ssh_bind_new();
146
1.26k
    if (res.sshbind == NULL) {
147
0
        config->server_error = true;
148
0
        goto cleanup;
149
0
    }
150
151
1.26k
    res.session = ssh_new();
152
1.26k
    if (res.session == NULL) {
153
0
        config->server_error = true;
154
0
        goto cleanup;
155
0
    }
156
157
1.26k
    const char *cipher = config->cipher ? config->cipher : "aes128-ctr";
158
1.26k
    const char *hmac = config->hmac ? config->hmac : "hmac-sha1";
159
160
1.26k
    ssh_bind_options_set(res.sshbind,
161
1.26k
                         SSH_BIND_OPTIONS_HOSTKEY,
162
1.26k
                         SSH_MOCK_HOSTKEY_PATH);
163
1.26k
    ssh_bind_options_set(res.sshbind, SSH_BIND_OPTIONS_CIPHERS_C_S, cipher);
164
1.26k
    ssh_bind_options_set(res.sshbind, SSH_BIND_OPTIONS_CIPHERS_S_C, cipher);
165
1.26k
    ssh_bind_options_set(res.sshbind, SSH_BIND_OPTIONS_HMAC_C_S, hmac);
166
1.26k
    ssh_bind_options_set(res.sshbind, SSH_BIND_OPTIONS_HMAC_S_C, hmac);
167
1.26k
    ssh_bind_options_set(res.sshbind, SSH_BIND_OPTIONS_PROCESS_CONFIG, &no);
168
169
1.26k
    ssh_set_auth_methods(res.session, SSH_AUTH_METHOD_NONE);
170
1.26k
    ssh_callbacks_init(&server_cb);
171
1.26k
    ssh_set_server_callbacks(res.session, &server_cb);
172
173
    /* Bound libssh's internal poll in ssh_handle_key_exchange */
174
1.26k
    long server_timeout = 1;
175
1.26k
    ssh_options_set(res.session, SSH_OPTIONS_TIMEOUT, &server_timeout);
176
177
1.26k
    rc = ssh_bind_accept_fd(res.sshbind, res.session, config->server_socket);
178
1.26k
    if (rc != SSH_OK) {
179
0
        config->server_error = true;
180
0
        goto cleanup;
181
0
    }
182
183
1.26k
    config->server_ready = true;
184
185
1.26k
    res.event = ssh_event_new();
186
1.26k
    if (res.event == NULL) {
187
0
        goto cleanup;
188
0
    }
189
190
1.26k
    if (ssh_handle_key_exchange(res.session) == SSH_OK) {
191
962
        ssh_event_add_session(res.event, res.session);
192
193
6.73k
        for (int i = 0; i < 50 && !sdata.channel && !config->shutdown_requested;
194
5.77k
             i++) {
195
5.77k
            ssh_event_dopoll(res.event, 1);
196
5.77k
        }
197
198
962
        if (sdata.channel) {
199
962
            ssh_callbacks_init(&channel_cb);
200
962
            ssh_set_channel_callbacks(sdata.channel, &channel_cb);
201
202
962
            int max_iterations = 30;
203
2.88k
            for (int iter = 0; iter < max_iterations &&
204
2.88k
                               !ssh_channel_is_closed(sdata.channel) &&
205
1.92k
                               !ssh_channel_is_eof(sdata.channel) &&
206
1.92k
                               !config->shutdown_requested;
207
1.92k
                 iter++) {
208
1.92k
                if (ssh_event_dopoll(res.event, 100) == SSH_ERROR) {
209
0
                    break;
210
0
                }
211
1.92k
            }
212
962
        }
213
962
    }
214
215
1.26k
cleanup:
216
1.26k
    cleanup_server_resources(&res);
217
218
1.26k
    return NULL;
219
1.26k
}
220
221
/* Public API - start mock SSH server */
222
int ssh_mock_server_start(struct ssh_mock_server_config *config,
223
                          pthread_t *thread)
224
1.26k
{
225
1.26k
    if (!config || !thread)
226
0
        return -1;
227
228
1.26k
    config->server_ready = false;
229
1.26k
    config->server_error = false;
230
231
1.26k
    if (pthread_create(thread, NULL, server_thread_func, config) != 0) {
232
1
        return -1;
233
1
    }
234
235
3.07k
    for (int i = 0; i < 50 && !config->server_ready && !config->server_error;
236
1.81k
         i++) {
237
1.81k
        usleep(100);
238
1.81k
    }
239
240
1.26k
    if (config->server_error) {
241
0
        pthread_join(*thread, NULL);
242
0
        return -1;
243
0
    }
244
245
1.26k
    return 0;
246
1.26k
}
247
248
/* Generic protocol callback */
249
int ssh_mock_send_raw_data(void *channel,
250
                           const void *data,
251
                           size_t size,
252
                           void *userdata)
253
962
{
254
962
    (void)userdata;
255
256
962
    ssh_channel target_channel = (ssh_channel)channel;
257
258
    /* Send raw fuzzer data */
259
962
    if (size > 0) {
260
962
        ssh_channel_write(target_channel, data, size);
261
962
    }
262
263
    /* Close channel to signal completion */
264
962
    ssh_channel_send_eof(target_channel);
265
962
    ssh_channel_close(target_channel);
266
962
    return SSH_OK;
267
962
}
268
269
/* Write fixed ed25519 host key to file */
270
int ssh_mock_write_hostkey(const char *path)
271
4
{
272
4
    FILE *fp = fopen(path, "wb");
273
4
    if (fp == NULL)
274
0
        return -1;
275
276
4
    size_t len = strlen(ssh_mock_ed25519_key_pem);
277
4
    size_t nwritten = fwrite(ssh_mock_ed25519_key_pem, 1, len, fp);
278
4
    fclose(fp);
279
280
4
    return (nwritten == len) ? 0 : -1;
281
4
}