summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--regress/Makefile2
-rw-r--r--regress/misc/Makefile3
-rw-r--r--regress/misc/kexfuzz/Makefile78
-rw-r--r--regress/misc/kexfuzz/README28
-rw-r--r--regress/misc/kexfuzz/kexfuzz.c404
5 files changed, 514 insertions, 1 deletions
diff --git a/regress/Makefile b/regress/Makefile
index 451909c1a..839fb8eca 100644
--- a/regress/Makefile
+++ b/regress/Makefile
@@ -1,4 +1,4 @@
1# $OpenBSD: Makefile,v 1.82 2015/09/24 06:16:53 djm Exp $ 1# $OpenBSD: Makefile,v 1.84 2016/03/04 02:30:36 djm Exp $
2 2
3REGRESS_TARGETS= unit t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t-exec 3REGRESS_TARGETS= unit t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t-exec
4tests: prep $(REGRESS_TARGETS) 4tests: prep $(REGRESS_TARGETS)
diff --git a/regress/misc/Makefile b/regress/misc/Makefile
new file mode 100644
index 000000000..14c0c279f
--- /dev/null
+++ b/regress/misc/Makefile
@@ -0,0 +1,3 @@
1SUBDIR= kexfuzz
2
3.include <bsd.subdir.mk>
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..d57a117ac
--- /dev/null
+++ b/regress/misc/kexfuzz/kexfuzz.c
@@ -0,0 +1,404 @@
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 <sys/types.h>
9#include <sys/param.h>
10#include <stdio.h>
11#include <stdint.h>
12#include <stdlib.h>
13#include <string.h>
14#include <unistd.h>
15#include <fcntl.h>
16#include <err.h>
17
18#include "ssherr.h"
19#include "ssh_api.h"
20#include "sshbuf.h"
21#include "packet.h"
22#include "myproposal.h"
23#include "authfile.h"
24
25struct ssh *active_state = NULL; /* XXX - needed for linking */
26
27void kex_tests(void);
28static int do_debug = 0;
29
30enum direction { S2C, C2S };
31
32static int
33do_send_and_receive(struct ssh *from, struct ssh *to, int mydirection,
34 int *packet_count, int trigger_direction, int packet_index,
35 const char *dump_path, struct sshbuf *replace_data)
36{
37 u_char type;
38 size_t len, olen;
39 const u_char *buf;
40 int r;
41 FILE *dumpfile;
42
43 for (;;) {
44 if ((r = ssh_packet_next(from, &type)) != 0) {
45 fprintf(stderr, "ssh_packet_next: %s\n", ssh_err(r));
46 return r;
47 }
48 if (type != 0)
49 return 0;
50 buf = ssh_output_ptr(from, &len);
51 olen = len;
52 if (do_debug) {
53 printf("%s packet %d type %u len %zu:\n",
54 mydirection == S2C ? "s2c" : "c2s",
55 *packet_count, type, len);
56 sshbuf_dump_data(buf, len, stdout);
57 }
58 if (mydirection == trigger_direction &&
59 packet_index == *packet_count) {
60 if (replace_data != NULL) {
61 buf = sshbuf_ptr(replace_data);
62 len = sshbuf_len(replace_data);
63 if (do_debug) {
64 printf("***** replaced packet "
65 "len %zu\n", len);
66 sshbuf_dump_data(buf, len, stdout);
67 }
68 } else if (dump_path != NULL) {
69 if ((dumpfile = fopen(dump_path, "w+")) == NULL)
70 err(1, "fopen %s", dump_path);
71 if (len != 0 &&
72 fwrite(buf, len, 1, dumpfile) != 1)
73 err(1, "fwrite %s", dump_path);
74 if (do_debug)
75 printf("***** dumped packet "
76 "len %zu\n", len);
77 fclose(dumpfile);
78 exit(0);
79 }
80 }
81 (*packet_count)++;
82 if (len == 0)
83 return 0;
84 if ((r = ssh_input_append(to, buf, len)) != 0 ||
85 (r = ssh_output_consume(from, olen)) != 0)
86 return r;
87 }
88}
89
90/* Minimal test_helper.c scaffholding to make this standalone */
91const char *in_test = NULL;
92#define TEST_START(a) \
93 do { \
94 in_test = (a); \
95 if (do_debug) \
96 fprintf(stderr, "test %s starting\n", in_test); \
97 } while (0)
98#define TEST_DONE() \
99 do { \
100 if (do_debug) \
101 fprintf(stderr, "test %s done\n", \
102 in_test ? in_test : "???"); \
103 in_test = NULL; \
104 } while(0)
105#define ASSERT_INT_EQ(a, b) \
106 do { \
107 if ((int)(a) != (int)(b)) { \
108 fprintf(stderr, "%s %s:%d " \
109 "%s (%d) != expected %s (%d)\n", \
110 in_test ? in_test : "(none)", \
111 __func__, __LINE__, #a, (int)(a), #b, (int)(b)); \
112 exit(2); \
113 } \
114 } while (0)
115#define ASSERT_INT_GE(a, b) \
116 do { \
117 if ((int)(a) < (int)(b)) { \
118 fprintf(stderr, "%s %s:%d " \
119 "%s (%d) < expected %s (%d)\n", \
120 in_test ? in_test : "(none)", \
121 __func__, __LINE__, #a, (int)(a), #b, (int)(b)); \
122 exit(2); \
123 } \
124 } while (0)
125#define ASSERT_PTR_NE(a, b) \
126 do { \
127 if ((a) == (b)) { \
128 fprintf(stderr, "%s %s:%d " \
129 "%s (%p) != expected %s (%p)\n", \
130 in_test ? in_test : "(none)", \
131 __func__, __LINE__, #a, (a), #b, (b)); \
132 exit(2); \
133 } \
134 } while (0)
135
136
137static void
138run_kex(struct ssh *client, struct ssh *server, int *s2c, int *c2s,
139 int direction, int packet_index,
140 const char *dump_path, struct sshbuf *replace_data)
141{
142 int r = 0;
143
144 while (!server->kex->done || !client->kex->done) {
145 if ((r = do_send_and_receive(server, client, S2C, s2c,
146 direction, packet_index, dump_path, replace_data)))
147 break;
148 if ((r = do_send_and_receive(client, server, C2S, c2s,
149 direction, packet_index, dump_path, replace_data)))
150 break;
151 }
152 if (do_debug)
153 printf("done: %s\n", ssh_err(r));
154 ASSERT_INT_EQ(r, 0);
155 ASSERT_INT_EQ(server->kex->done, 1);
156 ASSERT_INT_EQ(client->kex->done, 1);
157}
158
159static void
160do_kex_with_key(const char *kex, struct sshkey *prvkey, int *c2s, int *s2c,
161 int direction, int packet_index,
162 const char *dump_path, struct sshbuf *replace_data)
163{
164 struct ssh *client = NULL, *server = NULL, *server2 = NULL;
165 struct sshkey *pubkey = NULL;
166 struct sshbuf *state;
167 struct kex_params kex_params;
168 char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT };
169 char *keyname = NULL;
170
171 TEST_START("sshkey_from_private");
172 ASSERT_INT_EQ(sshkey_from_private(prvkey, &pubkey), 0);
173 TEST_DONE();
174
175 TEST_START("ssh_init");
176 memcpy(kex_params.proposal, myproposal, sizeof(myproposal));
177 if (kex != NULL)
178 kex_params.proposal[PROPOSAL_KEX_ALGS] = strdup(kex);
179 keyname = strdup(sshkey_ssh_name(prvkey));
180 ASSERT_PTR_NE(keyname, NULL);
181 kex_params.proposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = keyname;
182 ASSERT_INT_EQ(ssh_init(&client, 0, &kex_params), 0);
183 ASSERT_INT_EQ(ssh_init(&server, 1, &kex_params), 0);
184 ASSERT_PTR_NE(client, NULL);
185 ASSERT_PTR_NE(server, NULL);
186 TEST_DONE();
187
188 TEST_START("ssh_add_hostkey");
189 ASSERT_INT_EQ(ssh_add_hostkey(server, prvkey), 0);
190 ASSERT_INT_EQ(ssh_add_hostkey(client, pubkey), 0);
191 TEST_DONE();
192
193 TEST_START("kex");
194 run_kex(client, server, s2c, c2s, direction, packet_index,
195 dump_path, replace_data);
196 TEST_DONE();
197
198 TEST_START("rekeying client");
199 ASSERT_INT_EQ(kex_send_kexinit(client), 0);
200 run_kex(client, server, s2c, c2s, direction, packet_index,
201 dump_path, replace_data);
202 TEST_DONE();
203
204 TEST_START("rekeying server");
205 ASSERT_INT_EQ(kex_send_kexinit(server), 0);
206 run_kex(client, server, s2c, c2s, direction, packet_index,
207 dump_path, replace_data);
208 TEST_DONE();
209
210 TEST_START("ssh_packet_get_state");
211 state = sshbuf_new();
212 ASSERT_PTR_NE(state, NULL);
213 ASSERT_INT_EQ(ssh_packet_get_state(server, state), 0);
214 ASSERT_INT_GE(sshbuf_len(state), 1);
215 TEST_DONE();
216
217 TEST_START("ssh_packet_set_state");
218 server2 = NULL;
219 ASSERT_INT_EQ(ssh_init(&server2, 1, NULL), 0);
220 ASSERT_PTR_NE(server2, NULL);
221 ASSERT_INT_EQ(ssh_add_hostkey(server2, prvkey), 0);
222 kex_free(server2->kex); /* XXX or should ssh_packet_set_state()? */
223 ASSERT_INT_EQ(ssh_packet_set_state(server2, state), 0);
224 ASSERT_INT_EQ(sshbuf_len(state), 0);
225 sshbuf_free(state);
226 ASSERT_PTR_NE(server2->kex, NULL);
227 /* XXX we need to set the callbacks */
228 server2->kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server;
229 server2->kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server;
230 server2->kex->kex[KEX_DH_GEX_SHA1] = kexgex_server;
231 server2->kex->kex[KEX_DH_GEX_SHA256] = kexgex_server;
232#ifdef OPENSSL_HAS_ECC
233 server2->kex->kex[KEX_ECDH_SHA2] = kexecdh_server;
234#endif
235 server2->kex->kex[KEX_C25519_SHA256] = kexc25519_server;
236 server2->kex->load_host_public_key = server->kex->load_host_public_key;
237 server2->kex->load_host_private_key = server->kex->load_host_private_key;
238 server2->kex->sign = server->kex->sign;
239 TEST_DONE();
240
241 TEST_START("rekeying server2");
242 ASSERT_INT_EQ(kex_send_kexinit(server2), 0);
243 run_kex(client, server2, s2c, c2s, direction, packet_index,
244 dump_path, replace_data);
245 ASSERT_INT_EQ(kex_send_kexinit(client), 0);
246 run_kex(client, server2, s2c, c2s, direction, packet_index,
247 dump_path, replace_data);
248 TEST_DONE();
249
250 TEST_START("cleanup");
251 sshkey_free(pubkey);
252 ssh_free(client);
253 ssh_free(server);
254 ssh_free(server2);
255 free(keyname);
256 TEST_DONE();
257}
258
259static void
260usage(void)
261{
262 fprintf(stderr,
263 "Usage: kexfuzz [-hcdrv] [-D direction] [-f data_file]\n"
264 " [-K kex_alg] [-k private_key] [-i packet_index]\n"
265 "\n"
266 "Options:\n"
267 " -h Display this help\n"
268 " -c Count packets sent during KEX\n"
269 " -d Dump mode: record KEX packet to data file\n"
270 " -r Replace mode: replace packet with data file\n"
271 " -v Turn on verbose logging\n"
272 " -D S2C|C2S Packet direction for replacement or dump\n"
273 " -f data_file Path to data file for replacement or dump\n"
274 " -K kex_alg Name of KEX algorithm to test (see below)\n"
275 " -k private_key Path to private key file\n"
276 " -i packet_index Index of packet to replace or dump (from 0)\n"
277 "\n"
278 "Available KEX algorithms: %s\n", kex_alg_list(' '));
279}
280
281static void
282badusage(const char *bad)
283{
284 fprintf(stderr, "Invalid options\n");
285 fprintf(stderr, "%s\n", bad);
286 usage();
287 exit(1);
288}
289
290int
291main(int argc, char **argv)
292{
293 int ch, fd, r;
294 int count_flag = 0, dump_flag = 0, replace_flag = 0;
295 int packet_index = -1, direction = -1;
296 int s2c = 0, c2s = 0; /* packet counts */
297 const char *kex = NULL, *kpath = NULL, *data_path = NULL;
298 struct sshkey *key = NULL;
299 struct sshbuf *replace_data = NULL;
300
301 setvbuf(stdout, NULL, _IONBF, 0);
302 while ((ch = getopt(argc, argv, "hcdrvD:f:K:k:i:")) != -1) {
303 switch (ch) {
304 case 'h':
305 usage();
306 return 0;
307 case 'c':
308 count_flag = 1;
309 break;
310 case 'd':
311 dump_flag = 1;
312 break;
313 case 'r':
314 replace_flag = 1;
315 break;
316 case 'v':
317 do_debug = 1;
318 break;
319
320 case 'D':
321 if (strcasecmp(optarg, "s2c") == 0)
322 direction = S2C;
323 else if (strcasecmp(optarg, "c2s") == 0)
324 direction = C2S;
325 else
326 badusage("Invalid direction (-D)");
327 break;
328 case 'f':
329 data_path = optarg;
330 break;
331 case 'K':
332 kex = optarg;
333 break;
334 case 'k':
335 kpath = optarg;
336 break;
337 case 'i':
338 packet_index = atoi(optarg);
339 if (packet_index < 0)
340 badusage("Invalid packet index");
341 break;
342 default:
343 badusage("unsupported flag");
344 }
345 }
346 argc -= optind;
347 argv += optind;
348
349 /* Must select a single mode */
350 if ((count_flag + dump_flag + replace_flag) != 1)
351 badusage("Must select one mode: -c, -d or -r");
352 /* KEX type is mandatory */
353 if (kex == NULL || !kex_names_valid(kex) || strchr(kex, ',') != NULL)
354 badusage("Missing or invalid kex type (-K flag)");
355 /* Valid key is mandatory */
356 if (kpath == NULL)
357 badusage("Missing private key (-k flag)");
358 if ((fd = open(kpath, O_RDONLY)) == -1)
359 err(1, "open %s", kpath);
360 if ((r = sshkey_load_private_type_fd(fd, KEY_UNSPEC, NULL,
361 &key, NULL)) != 0)
362 errx(1, "Unable to load key %s: %s", kpath, ssh_err(r));
363 close(fd);
364 /* XXX check that it is a private key */
365 /* XXX support certificates */
366 if (key == NULL || key->type == KEY_UNSPEC || key->type == KEY_RSA1)
367 badusage("Invalid key file (-k flag)");
368
369 /* Replace (fuzz) mode */
370 if (replace_flag) {
371 if (packet_index == -1 || direction == -1 || data_path == NULL)
372 badusage("Replace (-r) mode must specify direction "
373 "(-D) packet index (-i) and data path (-f)");
374 if ((fd = open(data_path, O_RDONLY)) == -1)
375 err(1, "open %s", data_path);
376 replace_data = sshbuf_new();
377 if ((r = sshkey_load_file(fd, replace_data)) != 0)
378 errx(1, "read %s: %s", data_path, ssh_err(r));
379 close(fd);
380 }
381
382 /* Dump mode */
383 if (dump_flag) {
384 if (packet_index == -1 || direction == -1 || data_path == NULL)
385 badusage("Dump (-d) mode must specify direction "
386 "(-D), packet index (-i) and data path (-f)");
387 }
388
389 /* Count mode needs no further flags */
390
391 do_kex_with_key(kex, key, &c2s, &s2c,
392 direction, packet_index,
393 dump_flag ? data_path : NULL,
394 replace_flag ? replace_data : NULL);
395 sshkey_free(key);
396 sshbuf_free(replace_data);
397
398 if (count_flag) {
399 printf("S2C: %d\n", s2c);
400 printf("C2S: %d\n", c2s);
401 }
402
403 return 0;
404}