summaryrefslogtreecommitdiff
path: root/regress/misc/kexfuzz
diff options
context:
space:
mode:
Diffstat (limited to 'regress/misc/kexfuzz')
-rw-r--r--regress/misc/kexfuzz/Makefile78
-rw-r--r--regress/misc/kexfuzz/README28
-rw-r--r--regress/misc/kexfuzz/kexfuzz.c410
3 files changed, 516 insertions, 0 deletions
diff --git a/regress/misc/kexfuzz/Makefile b/regress/misc/kexfuzz/Makefile
new file mode 100644
index 000000000..3018b632f
--- /dev/null
+++ b/regress/misc/kexfuzz/Makefile
@@ -0,0 +1,78 @@
1# $OpenBSD: Makefile,v 1.1 2016/03/04 02:30:37 djm Exp $
2
3.include <bsd.own.mk>
4.include <bsd.obj.mk>
5
6# XXX detect from ssh binary?
7SSH1?= no
8OPENSSL?= yes
9
10PROG= kexfuzz
11SRCS= kexfuzz.c
12NOMAN= 1
13
14.if (${OPENSSL:L} == "yes")
15CFLAGS+= -DWITH_OPENSSL
16.else
17# SSH v.1 requires OpenSSL.
18SSH1= no
19.endif
20
21.if (${SSH1:L} == "yes")
22CFLAGS+= -DWITH_SSH1
23.endif
24
25# enable warnings
26WARNINGS=Yes
27
28DEBUG=-g
29CFLAGS+= -fstack-protector-all
30CDIAGFLAGS= -Wall
31CDIAGFLAGS+= -Wextra
32CDIAGFLAGS+= -Werror
33CDIAGFLAGS+= -Wchar-subscripts
34CDIAGFLAGS+= -Wcomment
35CDIAGFLAGS+= -Wformat
36CDIAGFLAGS+= -Wformat-security
37CDIAGFLAGS+= -Wimplicit
38CDIAGFLAGS+= -Winline
39CDIAGFLAGS+= -Wmissing-declarations
40CDIAGFLAGS+= -Wmissing-prototypes
41CDIAGFLAGS+= -Wparentheses
42CDIAGFLAGS+= -Wpointer-arith
43CDIAGFLAGS+= -Wreturn-type
44CDIAGFLAGS+= -Wshadow
45CDIAGFLAGS+= -Wsign-compare
46CDIAGFLAGS+= -Wstrict-aliasing
47CDIAGFLAGS+= -Wstrict-prototypes
48CDIAGFLAGS+= -Wswitch
49CDIAGFLAGS+= -Wtrigraphs
50CDIAGFLAGS+= -Wuninitialized
51CDIAGFLAGS+= -Wunused
52.if ${COMPILER_VERSION} == "gcc4"
53CDIAGFLAGS+= -Wpointer-sign
54CDIAGFLAGS+= -Wold-style-definition
55.endif
56
57SSHREL=../../../../../usr.bin/ssh
58
59CFLAGS+=-I${.CURDIR}/${SSHREL}
60
61.if exists(${.CURDIR}/${SSHREL}/lib/${__objdir})
62LDADD+=-L${.CURDIR}/${SSHREL}/lib/${__objdir} -lssh
63DPADD+=${.CURDIR}/${SSHREL}/lib/${__objdir}/libssh.a
64.else
65LDADD+=-L${.CURDIR}/${SSHREL}/lib -lssh
66DPADD+=${.CURDIR}/${SSHREL}/lib/libssh.a
67.endif
68
69LDADD+= -lutil -lz
70DPADD+= ${LIBUTIL} ${LIBZ}
71
72.if (${OPENSSL:L} == "yes")
73LDADD+= -lcrypto
74DPADD+= ${LIBCRYPTO}
75.endif
76
77.include <bsd.prog.mk>
78
diff --git a/regress/misc/kexfuzz/README b/regress/misc/kexfuzz/README
new file mode 100644
index 000000000..8b215b5bf
--- /dev/null
+++ b/regress/misc/kexfuzz/README
@@ -0,0 +1,28 @@
1This is a harness to help with fuzzing KEX.
2
3To use it, you first set it to count packets in each direction:
4
5./kexfuzz -K diffie-hellman-group1-sha1 -k host_ed25519_key -c
6S2C: 29
7C2S: 31
8
9Then get it to record a particular packet (in this case the 4th
10packet from client->server):
11
12./kexfuzz -K diffie-hellman-group1-sha1 -k host_ed25519_key \
13 -d -D C2S -i 3 -f packet_3
14
15Fuzz the packet somehow:
16
17dd if=/dev/urandom of=packet_3 bs=32 count=1 # Just for example
18
19Then re-run the key exchange substituting the modified packet in
20its original sequence:
21
22./kexfuzz -K diffie-hellman-group1-sha1 -k host_ed25519_key \
23 -r -D C2S -i 3 -f packet_3
24
25A comprehensive KEX fuzz run would fuzz every packet in both
26directions for each key exchange type and every hostkey type.
27This will take some time.
28
diff --git a/regress/misc/kexfuzz/kexfuzz.c b/regress/misc/kexfuzz/kexfuzz.c
new file mode 100644
index 000000000..2894d3a1e
--- /dev/null
+++ b/regress/misc/kexfuzz/kexfuzz.c
@@ -0,0 +1,410 @@
1/* $OpenBSD: kexfuzz.c,v 1.1 2016/03/04 02:30:37 djm Exp $ */
2/*
3 * Fuzz harness for KEX code
4 *
5 * Placed in the public domain
6 */
7
8#include "includes.h"
9
10#include <sys/types.h>
11#include <sys/param.h>
12#include <stdio.h>
13#ifdef HAVE_STDINT_H
14# include <stdint.h>
15#endif
16#include <stdlib.h>
17#include <string.h>
18#include <unistd.h>
19#include <fcntl.h>
20#ifdef HAVE_ERR_H
21# include <err.h>
22#endif
23
24#include "ssherr.h"
25#include "ssh_api.h"
26#include "sshbuf.h"
27#include "packet.h"
28#include "myproposal.h"
29#include "authfile.h"
30
31struct ssh *active_state = NULL; /* XXX - needed for linking */
32
33void kex_tests(void);
34static int do_debug = 0;
35
36enum direction { S2C, C2S };
37
38static int
39do_send_and_receive(struct ssh *from, struct ssh *to, int mydirection,
40 int *packet_count, int trigger_direction, int packet_index,
41 const char *dump_path, struct sshbuf *replace_data)
42{
43 u_char type;
44 size_t len, olen;
45 const u_char *buf;
46 int r;
47 FILE *dumpfile;
48
49 for (;;) {
50 if ((r = ssh_packet_next(from, &type)) != 0) {
51 fprintf(stderr, "ssh_packet_next: %s\n", ssh_err(r));
52 return r;
53 }
54 if (type != 0)
55 return 0;
56 buf = ssh_output_ptr(from, &len);
57 olen = len;
58 if (do_debug) {
59 printf("%s packet %d type %u len %zu:\n",
60 mydirection == S2C ? "s2c" : "c2s",
61 *packet_count, type, len);
62 sshbuf_dump_data(buf, len, stdout);
63 }
64 if (mydirection == trigger_direction &&
65 packet_index == *packet_count) {
66 if (replace_data != NULL) {
67 buf = sshbuf_ptr(replace_data);
68 len = sshbuf_len(replace_data);
69 if (do_debug) {
70 printf("***** replaced packet "
71 "len %zu\n", len);
72 sshbuf_dump_data(buf, len, stdout);
73 }
74 } else if (dump_path != NULL) {
75 if ((dumpfile = fopen(dump_path, "w+")) == NULL)
76 err(1, "fopen %s", dump_path);
77 if (len != 0 &&
78 fwrite(buf, len, 1, dumpfile) != 1)
79 err(1, "fwrite %s", dump_path);
80 if (do_debug)
81 printf("***** dumped packet "
82 "len %zu\n", len);
83 fclose(dumpfile);
84 exit(0);
85 }
86 }
87 (*packet_count)++;
88 if (len == 0)
89 return 0;
90 if ((r = ssh_input_append(to, buf, len)) != 0 ||
91 (r = ssh_output_consume(from, olen)) != 0)
92 return r;
93 }
94}
95
96/* Minimal test_helper.c scaffholding to make this standalone */
97const char *in_test = NULL;
98#define TEST_START(a) \
99 do { \
100 in_test = (a); \
101 if (do_debug) \
102 fprintf(stderr, "test %s starting\n", in_test); \
103 } while (0)
104#define TEST_DONE() \
105 do { \
106 if (do_debug) \
107 fprintf(stderr, "test %s done\n", \
108 in_test ? in_test : "???"); \
109 in_test = NULL; \
110 } while(0)
111#define ASSERT_INT_EQ(a, b) \
112 do { \
113 if ((int)(a) != (int)(b)) { \
114 fprintf(stderr, "%s %s:%d " \
115 "%s (%d) != expected %s (%d)\n", \
116 in_test ? in_test : "(none)", \
117 __func__, __LINE__, #a, (int)(a), #b, (int)(b)); \
118 exit(2); \
119 } \
120 } while (0)
121#define ASSERT_INT_GE(a, b) \
122 do { \
123 if ((int)(a) < (int)(b)) { \
124 fprintf(stderr, "%s %s:%d " \
125 "%s (%d) < expected %s (%d)\n", \
126 in_test ? in_test : "(none)", \
127 __func__, __LINE__, #a, (int)(a), #b, (int)(b)); \
128 exit(2); \
129 } \
130 } while (0)
131#define ASSERT_PTR_NE(a, b) \
132 do { \
133 if ((a) == (b)) { \
134 fprintf(stderr, "%s %s:%d " \
135 "%s (%p) != expected %s (%p)\n", \
136 in_test ? in_test : "(none)", \
137 __func__, __LINE__, #a, (a), #b, (b)); \
138 exit(2); \
139 } \
140 } while (0)
141
142
143static void
144run_kex(struct ssh *client, struct ssh *server, int *s2c, int *c2s,
145 int direction, int packet_index,
146 const char *dump_path, struct sshbuf *replace_data)
147{
148 int r = 0;
149
150 while (!server->kex->done || !client->kex->done) {
151 if ((r = do_send_and_receive(server, client, S2C, s2c,
152 direction, packet_index, dump_path, replace_data)))
153 break;
154 if ((r = do_send_and_receive(client, server, C2S, c2s,
155 direction, packet_index, dump_path, replace_data)))
156 break;
157 }
158 if (do_debug)
159 printf("done: %s\n", ssh_err(r));
160 ASSERT_INT_EQ(r, 0);
161 ASSERT_INT_EQ(server->kex->done, 1);
162 ASSERT_INT_EQ(client->kex->done, 1);
163}
164
165static void
166do_kex_with_key(const char *kex, struct sshkey *prvkey, int *c2s, int *s2c,
167 int direction, int packet_index,
168 const char *dump_path, struct sshbuf *replace_data)
169{
170 struct ssh *client = NULL, *server = NULL, *server2 = NULL;
171 struct sshkey *pubkey = NULL;
172 struct sshbuf *state;
173 struct kex_params kex_params;
174 char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT };
175 char *keyname = NULL;
176
177 TEST_START("sshkey_from_private");
178 ASSERT_INT_EQ(sshkey_from_private(prvkey, &pubkey), 0);
179 TEST_DONE();
180
181 TEST_START("ssh_init");
182 memcpy(kex_params.proposal, myproposal, sizeof(myproposal));
183 if (kex != NULL)
184 kex_params.proposal[PROPOSAL_KEX_ALGS] = strdup(kex);
185 keyname = strdup(sshkey_ssh_name(prvkey));
186 ASSERT_PTR_NE(keyname, NULL);
187 kex_params.proposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = keyname;
188 ASSERT_INT_EQ(ssh_init(&client, 0, &kex_params), 0);
189 ASSERT_INT_EQ(ssh_init(&server, 1, &kex_params), 0);
190 ASSERT_PTR_NE(client, NULL);
191 ASSERT_PTR_NE(server, NULL);
192 TEST_DONE();
193
194 TEST_START("ssh_add_hostkey");
195 ASSERT_INT_EQ(ssh_add_hostkey(server, prvkey), 0);
196 ASSERT_INT_EQ(ssh_add_hostkey(client, pubkey), 0);
197 TEST_DONE();
198
199 TEST_START("kex");
200 run_kex(client, server, s2c, c2s, direction, packet_index,
201 dump_path, replace_data);
202 TEST_DONE();
203
204 TEST_START("rekeying client");
205 ASSERT_INT_EQ(kex_send_kexinit(client), 0);
206 run_kex(client, server, s2c, c2s, direction, packet_index,
207 dump_path, replace_data);
208 TEST_DONE();
209
210 TEST_START("rekeying server");
211 ASSERT_INT_EQ(kex_send_kexinit(server), 0);
212 run_kex(client, server, s2c, c2s, direction, packet_index,
213 dump_path, replace_data);
214 TEST_DONE();
215
216 TEST_START("ssh_packet_get_state");
217 state = sshbuf_new();
218 ASSERT_PTR_NE(state, NULL);
219 ASSERT_INT_EQ(ssh_packet_get_state(server, state), 0);
220 ASSERT_INT_GE(sshbuf_len(state), 1);
221 TEST_DONE();
222
223 TEST_START("ssh_packet_set_state");
224 server2 = NULL;
225 ASSERT_INT_EQ(ssh_init(&server2, 1, NULL), 0);
226 ASSERT_PTR_NE(server2, NULL);
227 ASSERT_INT_EQ(ssh_add_hostkey(server2, prvkey), 0);
228 kex_free(server2->kex); /* XXX or should ssh_packet_set_state()? */
229 ASSERT_INT_EQ(ssh_packet_set_state(server2, state), 0);
230 ASSERT_INT_EQ(sshbuf_len(state), 0);
231 sshbuf_free(state);
232 ASSERT_PTR_NE(server2->kex, NULL);
233 /* XXX we need to set the callbacks */
234 server2->kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server;
235 server2->kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server;
236 server2->kex->kex[KEX_DH_GEX_SHA1] = kexgex_server;
237 server2->kex->kex[KEX_DH_GEX_SHA256] = kexgex_server;
238#ifdef OPENSSL_HAS_ECC
239 server2->kex->kex[KEX_ECDH_SHA2] = kexecdh_server;
240#endif
241 server2->kex->kex[KEX_C25519_SHA256] = kexc25519_server;
242 server2->kex->load_host_public_key = server->kex->load_host_public_key;
243 server2->kex->load_host_private_key = server->kex->load_host_private_key;
244 server2->kex->sign = server->kex->sign;
245 TEST_DONE();
246
247 TEST_START("rekeying server2");
248 ASSERT_INT_EQ(kex_send_kexinit(server2), 0);
249 run_kex(client, server2, s2c, c2s, direction, packet_index,
250 dump_path, replace_data);
251 ASSERT_INT_EQ(kex_send_kexinit(client), 0);
252 run_kex(client, server2, s2c, c2s, direction, packet_index,
253 dump_path, replace_data);
254 TEST_DONE();
255
256 TEST_START("cleanup");
257 sshkey_free(pubkey);
258 ssh_free(client);
259 ssh_free(server);
260 ssh_free(server2);
261 free(keyname);
262 TEST_DONE();
263}
264
265static void
266usage(void)
267{
268 fprintf(stderr,
269 "Usage: kexfuzz [-hcdrv] [-D direction] [-f data_file]\n"
270 " [-K kex_alg] [-k private_key] [-i packet_index]\n"
271 "\n"
272 "Options:\n"
273 " -h Display this help\n"
274 " -c Count packets sent during KEX\n"
275 " -d Dump mode: record KEX packet to data file\n"
276 " -r Replace mode: replace packet with data file\n"
277 " -v Turn on verbose logging\n"
278 " -D S2C|C2S Packet direction for replacement or dump\n"
279 " -f data_file Path to data file for replacement or dump\n"
280 " -K kex_alg Name of KEX algorithm to test (see below)\n"
281 " -k private_key Path to private key file\n"
282 " -i packet_index Index of packet to replace or dump (from 0)\n"
283 "\n"
284 "Available KEX algorithms: %s\n", kex_alg_list(' '));
285}
286
287static void
288badusage(const char *bad)
289{
290 fprintf(stderr, "Invalid options\n");
291 fprintf(stderr, "%s\n", bad);
292 usage();
293 exit(1);
294}
295
296int
297main(int argc, char **argv)
298{
299 int ch, fd, r;
300 int count_flag = 0, dump_flag = 0, replace_flag = 0;
301 int packet_index = -1, direction = -1;
302 int s2c = 0, c2s = 0; /* packet counts */
303 const char *kex = NULL, *kpath = NULL, *data_path = NULL;
304 struct sshkey *key = NULL;
305 struct sshbuf *replace_data = NULL;
306
307 setvbuf(stdout, NULL, _IONBF, 0);
308 while ((ch = getopt(argc, argv, "hcdrvD:f:K:k:i:")) != -1) {
309 switch (ch) {
310 case 'h':
311 usage();
312 return 0;
313 case 'c':
314 count_flag = 1;
315 break;
316 case 'd':
317 dump_flag = 1;
318 break;
319 case 'r':
320 replace_flag = 1;
321 break;
322 case 'v':
323 do_debug = 1;
324 break;
325
326 case 'D':
327 if (strcasecmp(optarg, "s2c") == 0)
328 direction = S2C;
329 else if (strcasecmp(optarg, "c2s") == 0)
330 direction = C2S;
331 else
332 badusage("Invalid direction (-D)");
333 break;
334 case 'f':
335 data_path = optarg;
336 break;
337 case 'K':
338 kex = optarg;
339 break;
340 case 'k':
341 kpath = optarg;
342 break;
343 case 'i':
344 packet_index = atoi(optarg);
345 if (packet_index < 0)
346 badusage("Invalid packet index");
347 break;
348 default:
349 badusage("unsupported flag");
350 }
351 }
352 argc -= optind;
353 argv += optind;
354
355 /* Must select a single mode */
356 if ((count_flag + dump_flag + replace_flag) != 1)
357 badusage("Must select one mode: -c, -d or -r");
358 /* KEX type is mandatory */
359 if (kex == NULL || !kex_names_valid(kex) || strchr(kex, ',') != NULL)
360 badusage("Missing or invalid kex type (-K flag)");
361 /* Valid key is mandatory */
362 if (kpath == NULL)
363 badusage("Missing private key (-k flag)");
364 if ((fd = open(kpath, O_RDONLY)) == -1)
365 err(1, "open %s", kpath);
366 if ((r = sshkey_load_private_type_fd(fd, KEY_UNSPEC, NULL,
367 &key, NULL)) != 0)
368 errx(1, "Unable to load key %s: %s", kpath, ssh_err(r));
369 close(fd);
370 /* XXX check that it is a private key */
371 /* XXX support certificates */
372 if (key == NULL || key->type == KEY_UNSPEC || key->type == KEY_RSA1)
373 badusage("Invalid key file (-k flag)");
374
375 /* Replace (fuzz) mode */
376 if (replace_flag) {
377 if (packet_index == -1 || direction == -1 || data_path == NULL)
378 badusage("Replace (-r) mode must specify direction "
379 "(-D) packet index (-i) and data path (-f)");
380 if ((fd = open(data_path, O_RDONLY)) == -1)
381 err(1, "open %s", data_path);
382 replace_data = sshbuf_new();
383 if ((r = sshkey_load_file(fd, replace_data)) != 0)
384 errx(1, "read %s: %s", data_path, ssh_err(r));
385 close(fd);
386 }
387
388 /* Dump mode */
389 if (dump_flag) {
390 if (packet_index == -1 || direction == -1 || data_path == NULL)
391 badusage("Dump (-d) mode must specify direction "
392 "(-D), packet index (-i) and data path (-f)");
393 }
394
395 /* Count mode needs no further flags */
396
397 do_kex_with_key(kex, key, &c2s, &s2c,
398 direction, packet_index,
399 dump_flag ? data_path : NULL,
400 replace_flag ? replace_data : NULL);
401 sshkey_free(key);
402 sshbuf_free(replace_data);
403
404 if (count_flag) {
405 printf("S2C: %d\n", s2c);
406 printf("C2S: %d\n", c2s);
407 }
408
409 return 0;
410}