1// libfuzzer driver for key exchange fuzzing.
2
3
4#include <sys/types.h>
5#include <stdio.h>
6#include <stdint.h>
7#include <stdlib.h>
8#include <string.h>
9
10extern "C" {
11
12#include "includes.h"
13#include "ssherr.h"
14#include "ssh_api.h"
15#include "sshbuf.h"
16#include "packet.h"
17#include "myproposal.h"
18#include "xmalloc.h"
19#include "authfile.h"
20#include "log.h"
21
22#include "fixed-keys.h"
23
24// Define if you want to generate traces.
25/* #define STANDALONE 1 */
26
27static int prepare_key(struct shared_state *st, int keytype, int bits);
28
29struct shared_state {
30	size_t nkeys;
31	struct sshkey **privkeys, **pubkeys;
32};
33
34struct test_state {
35	struct sshbuf *smsgs, *cmsgs; /* output, for standalone mode */
36	struct sshbuf *sin, *cin; /* input; setup per-test in do_kex_with_key */
37	struct sshbuf *s_template, *c_template; /* main copy of input */
38};
39
40static int
41do_send_and_receive(struct ssh *from, struct ssh *to,
42    struct sshbuf *store, int clobber, size_t *n)
43{
44	u_char type;
45	size_t len;
46	const u_char *buf;
47	int r;
48
49	for (*n = 0;; (*n)++) {
50		if ((r = ssh_packet_next(from, &type)) != 0) {
51			debug_fr(r, "ssh_packet_next");
52			return r;
53		}
54		if (type != 0)
55			return 0;
56		buf = ssh_output_ptr(from, &len);
57		debug_f("%zu%s", len, clobber ? " ignore" : "");
58		if (len == 0)
59			return 0;
60		if ((r = ssh_output_consume(from, len)) != 0) {
61			debug_fr(r, "ssh_output_consume");
62			return r;
63		}
64		if (store != NULL && (r = sshbuf_put(store, buf, len)) != 0) {
65			debug_fr(r, "sshbuf_put");
66			return r;
67		}
68		if (!clobber && (r = ssh_input_append(to, buf, len)) != 0) {
69			debug_fr(r, "ssh_input_append");
70			return r;
71		}
72	}
73}
74
75static int
76run_kex(struct test_state *ts, struct ssh *client, struct ssh *server)
77{
78	int r = 0;
79	size_t cn, sn;
80
81	/* If fuzzing, replace server/client input */
82	if (ts->sin != NULL) {
83		if ((r = ssh_input_append(server, sshbuf_ptr(ts->sin),
84		    sshbuf_len(ts->sin))) != 0) {
85			error_fr(r, "ssh_input_append");
86			return r;
87		}
88		sshbuf_reset(ts->sin);
89	}
90	if (ts->cin != NULL) {
91		if ((r = ssh_input_append(client, sshbuf_ptr(ts->cin),
92		    sshbuf_len(ts->cin))) != 0) {
93			error_fr(r, "ssh_input_append");
94			return r;
95		}
96		sshbuf_reset(ts->cin);
97	}
98	while (!server->kex->done || !client->kex->done) {
99		cn = sn = 0;
100		debug_f("S:");
101		if ((r = do_send_and_receive(server, client,
102		    ts->smsgs, ts->cin != NULL, &sn)) != 0) {
103			debug_fr(r, "S->C");
104			break;
105		}
106		debug_f("C:");
107		if ((r = do_send_and_receive(client, server,
108		    ts->cmsgs, ts->sin != NULL, &cn)) != 0) {
109			debug_fr(r, "C->S");
110			break;
111		}
112		if (cn == 0 && sn == 0) {
113			debug_f("kex stalled");
114			r = SSH_ERR_PROTOCOL_ERROR;
115			break;
116		}
117	}
118	debug_fr(r, "done");
119	return r;
120}
121
122static void
123store_key(struct shared_state *st, struct sshkey *pubkey,
124    struct sshkey *privkey)
125{
126	if (st == NULL || pubkey->type < 0 || pubkey->type > INT_MAX ||
127	    privkey->type != pubkey->type ||
128	    ((size_t)pubkey->type < st->nkeys &&
129	     st->pubkeys[pubkey->type] != NULL))
130		abort();
131	if ((size_t)pubkey->type >= st->nkeys) {
132		st->pubkeys = (struct sshkey **)xrecallocarray(st->pubkeys,
133		    st->nkeys, pubkey->type + 1, sizeof(*st->pubkeys));
134		st->privkeys = (struct sshkey **)xrecallocarray(st->privkeys,
135		    st->nkeys, privkey->type + 1, sizeof(*st->privkeys));
136		st->nkeys = privkey->type + 1;
137	}
138	debug_f("store %s at %d", sshkey_ssh_name(pubkey), pubkey->type);
139	st->pubkeys[pubkey->type] = pubkey;
140	st->privkeys[privkey->type] = privkey;
141}
142
143static int
144prepare_keys(struct shared_state *st)
145{
146	if (prepare_key(st, KEY_RSA, 2048) != 0 ||
147	    prepare_key(st, KEY_DSA, 1024) != 0 ||
148	    prepare_key(st, KEY_ECDSA, 256) != 0 ||
149	    prepare_key(st, KEY_ED25519, 256) != 0) {
150		error_f("key prepare failed");
151		return -1;
152	}
153	return 0;
154}
155
156static struct sshkey *
157get_pubkey(struct shared_state *st, int keytype)
158{
159	if (st == NULL || keytype < 0 || (size_t)keytype >= st->nkeys ||
160	    st->pubkeys == NULL || st->pubkeys[keytype] == NULL)
161		abort();
162	return st->pubkeys[keytype];
163}
164
165static struct sshkey *
166get_privkey(struct shared_state *st, int keytype)
167{
168	if (st == NULL || keytype < 0 || (size_t)keytype >= st->nkeys ||
169	    st->privkeys == NULL || st->privkeys[keytype] == NULL)
170		abort();
171	return st->privkeys[keytype];
172}
173
174static int
175do_kex_with_key(struct shared_state *st, struct test_state *ts,
176    const char *kex, int keytype)
177{
178	struct ssh *client = NULL, *server = NULL;
179	struct sshkey *privkey = NULL, *pubkey = NULL;
180	struct sshbuf *state = NULL;
181	struct kex_params kex_params;
182	const char *ccp, *proposal[PROPOSAL_MAX] = { KEX_CLIENT };
183	char *myproposal[PROPOSAL_MAX] = {0}, *keyname = NULL;
184	int i, r;
185
186	ts->cin = ts->sin = NULL;
187	if (ts->c_template != NULL &&
188	    (ts->cin = sshbuf_fromb(ts->c_template)) == NULL)
189		abort();
190	if (ts->s_template != NULL &&
191	    (ts->sin = sshbuf_fromb(ts->s_template)) == NULL)
192		abort();
193
194	pubkey = get_pubkey(st, keytype);
195	privkey = get_privkey(st, keytype);
196	keyname = xstrdup(sshkey_ssh_name(privkey));
197	if (ts->cin != NULL) {
198		debug_f("%s %s clobber client %zu", kex, keyname,
199		    sshbuf_len(ts->cin));
200	} else if (ts->sin != NULL) {
201		debug_f("%s %s clobber server %zu", kex, keyname,
202		    sshbuf_len(ts->sin));
203	} else
204		debug_f("%s %s noclobber", kex, keyname);
205
206	for (i = 0; i < PROPOSAL_MAX; i++) {
207		ccp = proposal[i];
208#ifdef CIPHER_NONE_AVAIL
209		if (i == PROPOSAL_ENC_ALGS_CTOS || i == PROPOSAL_ENC_ALGS_STOC)
210			ccp = "none";
211#endif
212		if (i == PROPOSAL_SERVER_HOST_KEY_ALGS)
213			ccp = keyname;
214		else if (i == PROPOSAL_KEX_ALGS && kex != NULL)
215			ccp = kex;
216		if ((myproposal[i] = strdup(ccp)) == NULL) {
217			error_f("strdup prop %d", i);
218			goto fail;
219		}
220	}
221	memcpy(kex_params.proposal, myproposal, sizeof(myproposal));
222	if ((r = ssh_init(&client, 0, &kex_params)) != 0) {
223		error_fr(r, "init client");
224		goto fail;
225	}
226	if ((r = ssh_init(&server, 1, &kex_params)) != 0) {
227		error_fr(r, "init server");
228		goto fail;
229	}
230	if ((r = ssh_add_hostkey(server, privkey)) != 0 ||
231	    (r = ssh_add_hostkey(client, pubkey)) != 0) {
232		error_fr(r, "add hostkeys");
233		goto fail;
234	}
235	if ((r = run_kex(ts, client, server)) != 0) {
236		error_fr(r, "kex");
237		goto fail;
238	}
239	/* XXX rekex, set_state, etc */
240 fail:
241	for (i = 0; i < PROPOSAL_MAX; i++)
242		free(myproposal[i]);
243	sshbuf_free(ts->sin);
244	sshbuf_free(ts->cin);
245	sshbuf_free(state);
246	ssh_free(client);
247	ssh_free(server);
248	free(keyname);
249	return r;
250}
251
252static int
253prepare_key(struct shared_state *st, int kt, int bits)
254{
255	const char *pubstr = NULL;
256	const char *privstr = NULL;
257	char *tmp, *cp;
258	struct sshkey *privkey = NULL, *pubkey = NULL;
259	struct sshbuf *b = NULL;
260	int r;
261
262	switch (kt) {
263	case KEY_RSA:
264		pubstr = PUB_RSA;
265		privstr = PRIV_RSA;
266		break;
267	case KEY_DSA:
268		pubstr = PUB_DSA;
269		privstr = PRIV_DSA;
270		break;
271	case KEY_ECDSA:
272		pubstr = PUB_ECDSA;
273		privstr = PRIV_ECDSA;
274		break;
275	case KEY_ED25519:
276		pubstr = PUB_ED25519;
277		privstr = PRIV_ED25519;
278		break;
279	default:
280		abort();
281	}
282	if ((b = sshbuf_from(privstr, strlen(privstr))) == NULL)
283		abort();
284	if ((r = sshkey_parse_private_fileblob(b, "", &privkey, NULL)) != 0) {
285		error_fr(r, "priv %d", kt);
286		abort();
287	}
288	sshbuf_free(b);
289	tmp = cp = xstrdup(pubstr);
290	if ((pubkey = sshkey_new(KEY_UNSPEC)) == NULL)
291		abort();
292	if ((r = sshkey_read(pubkey, &cp)) != 0) {
293		error_fr(r, "pub %d", kt);
294		abort();
295	}
296	free(tmp);
297
298	store_key(st, pubkey, privkey);
299	return 0;
300}
301
302#if defined(STANDALONE)
303
304#if 0 /* use this if generating new keys to embed above */
305static int
306prepare_key(struct shared_state *st, int keytype, int bits)
307{
308	struct sshkey *privkey = NULL, *pubkey = NULL;
309	int r;
310
311	if ((r = sshkey_generate(keytype, bits, &privkey)) != 0) {
312		error_fr(r, "generate");
313		abort();
314	}
315	if ((r = sshkey_from_private(privkey, &pubkey)) != 0) {
316		error_fr(r, "make pubkey");
317		abort();
318	}
319	store_key(st, pubkey, privkey);
320	return 0;
321}
322#endif
323
324int main(void)
325{
326	static struct shared_state *st;
327	struct test_state *ts;
328	const int keytypes[] = { KEY_RSA, KEY_DSA, KEY_ECDSA, KEY_ED25519, -1 };
329	static const char * const kextypes[] = {
330		"sntrup761x25519-sha512@openssh.com",
331		"curve25519-sha256@libssh.org",
332		"ecdh-sha2-nistp256",
333		"diffie-hellman-group1-sha1",
334		"diffie-hellman-group-exchange-sha1",
335		NULL,
336	};
337	int i, j;
338	char *path;
339	FILE *f;
340
341	log_init("kex_fuzz", SYSLOG_LEVEL_DEBUG3, SYSLOG_FACILITY_AUTH, 1);
342
343	if (st == NULL) {
344		st = (struct shared_state *)xcalloc(1, sizeof(*st));
345		prepare_keys(st);
346	}
347	/* Run each kex method for each key and save client/server packets */
348	for (i = 0; keytypes[i] != -1; i++) {
349		for (j = 0; kextypes[j] != NULL; j++) {
350			ts = (struct test_state *)xcalloc(1, sizeof(*ts));
351			ts->smsgs = sshbuf_new();
352			ts->cmsgs = sshbuf_new();
353			do_kex_with_key(st, ts, kextypes[j], keytypes[i]);
354			xasprintf(&path, "S2C-%s-%s",
355			    kextypes[j], sshkey_type(st->pubkeys[keytypes[i]]));
356			debug_f("%s", path);
357			if ((f = fopen(path, "wb+")) == NULL)
358				abort();
359			if (fwrite(sshbuf_ptr(ts->smsgs), 1,
360			    sshbuf_len(ts->smsgs), f) != sshbuf_len(ts->smsgs))
361				abort();
362			fclose(f);
363			free(path);
364			//sshbuf_dump(ts->smsgs, stderr);
365			xasprintf(&path, "C2S-%s-%s",
366			    kextypes[j], sshkey_type(st->pubkeys[keytypes[i]]));
367			debug_f("%s", path);
368			if ((f = fopen(path, "wb+")) == NULL)
369				abort();
370			if (fwrite(sshbuf_ptr(ts->cmsgs), 1,
371			    sshbuf_len(ts->cmsgs), f) != sshbuf_len(ts->cmsgs))
372				abort();
373			fclose(f);
374			free(path);
375			//sshbuf_dump(ts->cmsgs, stderr);
376			sshbuf_free(ts->smsgs);
377			sshbuf_free(ts->cmsgs);
378			free(ts);
379		}
380	}
381	for (i = 0; keytypes[i] != -1; i++) {
382		xasprintf(&path, "%s.priv",
383		    sshkey_type(st->privkeys[keytypes[i]]));
384		debug_f("%s", path);
385		if (sshkey_save_private(st->privkeys[keytypes[i]], path,
386		    "", "", SSHKEY_PRIVATE_OPENSSH, NULL, 0) != 0)
387			abort();
388		free(path);
389		xasprintf(&path, "%s.pub",
390		    sshkey_type(st->pubkeys[keytypes[i]]));
391		debug_f("%s", path);
392		if (sshkey_save_public(st->pubkeys[keytypes[i]], path, "") != 0)
393			abort();
394		free(path);
395	}
396}
397#else /* !STANDALONE */
398static void
399do_kex(struct shared_state *st, struct test_state *ts, const char *kex)
400{
401	do_kex_with_key(st, ts, kex, KEY_RSA);
402	do_kex_with_key(st, ts, kex, KEY_DSA);
403	do_kex_with_key(st, ts, kex, KEY_ECDSA);
404	do_kex_with_key(st, ts, kex, KEY_ED25519);
405}
406
407static void
408kex_tests(struct shared_state *st, struct test_state *ts)
409{
410	do_kex(st, ts, "sntrup761x25519-sha512@openssh.com");
411	do_kex(st, ts, "curve25519-sha256@libssh.org");
412	do_kex(st, ts, "ecdh-sha2-nistp256");
413	do_kex(st, ts, "diffie-hellman-group1-sha1");
414	do_kex(st, ts, "diffie-hellman-group-exchange-sha1");
415}
416
417int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
418{
419	static struct shared_state *st;
420	struct test_state *ts;
421	u_char crbuf[SSH_MAX_PRE_BANNER_LINES * 4];
422	u_char zbuf[4096] = {0};
423	static LogLevel loglevel = SYSLOG_LEVEL_INFO;
424
425	if (st == NULL) {
426		if (getenv("DEBUG") != NULL || getenv("KEX_FUZZ_DEBUG") != NULL)
427			loglevel = SYSLOG_LEVEL_DEBUG3;
428		log_init("kex_fuzz",
429		    loglevel, SYSLOG_FACILITY_AUTH, 1);
430		st = (struct shared_state *)xcalloc(1, sizeof(*st));
431		prepare_keys(st);
432	}
433
434	/* Ensure that we can complete (fail) banner exchange at least */
435	memset(crbuf, '\n', sizeof(crbuf));
436
437	ts = (struct test_state *)xcalloc(1, sizeof(*ts));
438	if ((ts->s_template = sshbuf_new()) == NULL ||
439	    sshbuf_put(ts->s_template, data, size) != 0 ||
440	    sshbuf_put(ts->s_template, crbuf, sizeof(crbuf)) != 0 ||
441	    sshbuf_put(ts->s_template, zbuf, sizeof(zbuf)) != 0)
442		abort();
443	kex_tests(st, ts);
444	sshbuf_free(ts->s_template);
445	free(ts);
446
447	ts = (struct test_state *)xcalloc(1, sizeof(*ts));
448	if ((ts->c_template = sshbuf_new()) == NULL ||
449	    sshbuf_put(ts->c_template, data, size) != 0 ||
450	    sshbuf_put(ts->c_template, crbuf, sizeof(crbuf)) != 0 ||
451	    sshbuf_put(ts->c_template, zbuf, sizeof(zbuf)) != 0)
452		abort();
453	kex_tests(st, ts);
454	sshbuf_free(ts->c_template);
455	free(ts);
456
457	return 0;
458}
459#endif /* STANDALONE */
460} /* extern "C" */
461