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