Coverage Report

Created: 2026-07-15 06:09

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
269
{
49
269
    (void)session;
50
269
    (void)user;
51
269
    (void)userdata;
52
269
    return SSH_AUTH_SUCCESS;
53
269
}
54
55
/* Channel open callback */
56
static ssh_channel mock_channel_open(ssh_session session, void *userdata)
57
269
{
58
269
    struct mock_session_data *sdata = (struct mock_session_data *)userdata;
59
269
    sdata->channel = ssh_channel_new(session);
60
269
    return sdata->channel;
61
269
}
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
269
{
69
269
    struct mock_session_data *sdata = (struct mock_session_data *)userdata;
70
269
    (void)session;
71
269
    (void)command;
72
73
269
    if (sdata->config->exec_callback) {
74
269
        return sdata->config->exec_callback(channel,
75
269
                                            sdata->config->protocol_data,
76
269
                                            sdata->config->protocol_data_size,
77
269
                                            sdata->config->callback_userdata);
78
269
    }
79
0
    return SSH_OK;
80
269
}
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
269
{
111
269
    struct server_resources *res = (struct server_resources *)arg;
112
269
    ssh_event_free(res->event);
113
269
    if (res->session) {
114
269
        ssh_disconnect(res->session);
115
269
        ssh_free(res->session);
116
269
    }
117
269
    ssh_bind_free(res->sshbind);
118
269
}
119
120
/* Server thread implementation */
121
static void *server_thread_func(void *arg)
122
269
{
123
269
    struct ssh_mock_server_config *config =
124
269
        (struct ssh_mock_server_config *)arg;
125
269
    struct mock_session_data sdata = {0};
126
269
    sdata.config = config;
127
269
    int rc;
128
129
269
    struct server_resources res = {NULL, NULL, NULL};
130
131
269
    struct ssh_server_callbacks_struct server_cb = {
132
269
        .userdata = &sdata,
133
269
        .auth_none_function = mock_auth_none,
134
269
        .channel_open_request_session_function = mock_channel_open,
135
269
    };
136
137
269
    struct ssh_channel_callbacks_struct channel_cb = {
138
269
        .userdata = &sdata,
139
269
        .channel_exec_request_function = mock_channel_exec,
140
269
        .channel_subsystem_request_function = mock_channel_subsystem,
141
269
    };
142
143
269
    bool no = false;
144
145
269
    res.sshbind = ssh_bind_new();
146
269
    if (res.sshbind == NULL) {
147
0
        config->server_error = true;
148
0
        goto cleanup;
149
0
    }
150
151
269
    res.session = ssh_new();
152
269
    if (res.session == NULL) {
153
0
        config->server_error = true;
154
0
        goto cleanup;
155
0
    }
156
157
269
    const char *cipher = config->cipher ? config->cipher : "aes128-ctr";
158
269
    const char *hmac = config->hmac ? config->hmac : "hmac-sha1";
159
160
269
    ssh_bind_options_set(res.sshbind,
161
269
                         SSH_BIND_OPTIONS_HOSTKEY,
162
269
                         SSH_MOCK_HOSTKEY_PATH);
163
269
    ssh_bind_options_set(res.sshbind, SSH_BIND_OPTIONS_CIPHERS_C_S, cipher);
164
269
    ssh_bind_options_set(res.sshbind, SSH_BIND_OPTIONS_CIPHERS_S_C, cipher);
165
269
    ssh_bind_options_set(res.sshbind, SSH_BIND_OPTIONS_HMAC_C_S, hmac);
166
269
    ssh_bind_options_set(res.sshbind, SSH_BIND_OPTIONS_HMAC_S_C, hmac);
167
269
    ssh_bind_options_set(res.sshbind, SSH_BIND_OPTIONS_PROCESS_CONFIG, &no);
168
169
269
    ssh_set_auth_methods(res.session, SSH_AUTH_METHOD_NONE);
170
269
    ssh_callbacks_init(&server_cb);
171
269
    ssh_set_server_callbacks(res.session, &server_cb);
172
173
    /* Bound libssh's internal poll in ssh_handle_key_exchange */
174
269
    long server_timeout = 1;
175
269
    ssh_options_set(res.session, SSH_OPTIONS_TIMEOUT, &server_timeout);
176
177
269
    rc = ssh_bind_accept_fd(res.sshbind, res.session, config->server_socket);
178
269
    if (rc != SSH_OK) {
179
0
        config->server_error = true;
180
0
        goto cleanup;
181
0
    }
182
183
269
    config->server_ready = true;
184
185
269
    res.event = ssh_event_new();
186
269
    if (res.event == NULL) {
187
0
        goto cleanup;
188
0
    }
189
190
269
    if (ssh_handle_key_exchange(res.session) == SSH_OK) {
191
269
        ssh_event_add_session(res.event, res.session);
192
193
1.88k
        for (int i = 0; i < 50 && !sdata.channel && !config->shutdown_requested;
194
1.61k
             i++) {
195
1.61k
            ssh_event_dopoll(res.event, 1);
196
1.61k
        }
197
198
269
        if (sdata.channel) {
199
269
            ssh_callbacks_init(&channel_cb);
200
269
            ssh_set_channel_callbacks(sdata.channel, &channel_cb);
201
202
269
            int max_iterations = 30;
203
807
            for (int iter = 0; iter < max_iterations &&
204
807
                               !ssh_channel_is_closed(sdata.channel) &&
205
538
                               !ssh_channel_is_eof(sdata.channel) &&
206
538
                               !config->shutdown_requested;
207
538
                 iter++) {
208
538
                if (ssh_event_dopoll(res.event, 100) == SSH_ERROR) {
209
0
                    break;
210
0
                }
211
538
            }
212
269
        }
213
269
    }
214
215
269
cleanup:
216
269
    cleanup_server_resources(&res);
217
218
269
    return NULL;
219
269
}
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
269
{
225
269
    if (!config || !thread)
226
0
        return -1;
227
228
269
    config->server_ready = false;
229
269
    config->server_error = false;
230
231
269
    if (pthread_create(thread, NULL, server_thread_func, config) != 0) {
232
0
        return -1;
233
0
    }
234
235
667
    for (int i = 0; i < 50 && !config->server_ready && !config->server_error;
236
398
         i++) {
237
398
        usleep(100);
238
398
    }
239
240
269
    if (config->server_error) {
241
0
        pthread_join(*thread, NULL);
242
0
        return -1;
243
0
    }
244
245
269
    return 0;
246
269
}
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
269
{
254
269
    (void)userdata;
255
256
269
    ssh_channel target_channel = (ssh_channel)channel;
257
258
    /* Send raw fuzzer data */
259
269
    if (size > 0) {
260
269
        ssh_channel_write(target_channel, data, size);
261
269
    }
262
263
    /* Close channel to signal completion */
264
269
    ssh_channel_send_eof(target_channel);
265
269
    ssh_channel_close(target_channel);
266
269
    return SSH_OK;
267
269
}
268
269
/* Write fixed ed25519 host key to file */
270
int ssh_mock_write_hostkey(const char *path)
271
3
{
272
3
    FILE *fp = fopen(path, "wb");
273
3
    if (fp == NULL)
274
0
        return -1;
275
276
3
    size_t len = strlen(ssh_mock_ed25519_key_pem);
277
3
    size_t nwritten = fwrite(ssh_mock_ed25519_key_pem, 1, len, fp);
278
3
    fclose(fp);
279
280
3
    return (nwritten == len) ? 0 : -1;
281
3
}