/src/dropbear/fuzz/fuzz-common.c
Line | Count | Source (jump to first uncovered line) |
1 | | #define FUZZ_NO_REPLACE_STDERR |
2 | | #define FUZZ_NO_REPLACE_GETPW |
3 | | #include "includes.h" |
4 | | |
5 | | #include "includes.h" |
6 | | #include "dbutil.h" |
7 | | #include "runopts.h" |
8 | | #include "crypto_desc.h" |
9 | | #include "session.h" |
10 | | #include "dbrandom.h" |
11 | | #include "bignum.h" |
12 | | #include "atomicio.h" |
13 | | #include "fuzz-wrapfd.h" |
14 | | #include "fuzz.h" |
15 | | |
16 | | struct dropbear_fuzz_options fuzz; |
17 | | |
18 | | static void fuzz_dropbear_log(int UNUSED(priority), const char* format, va_list param); |
19 | | static void load_fixed_hostkeys(void); |
20 | | static void load_fixed_client_key(void); |
21 | | |
22 | | // This runs automatically before main, due to contructor attribute in fuzz.h |
23 | 2 | void fuzz_early_setup(void) { |
24 | | /* Set stderr to point to normal stderr by default */ |
25 | 2 | fuzz.fake_stderr = stderr; |
26 | 2 | } |
27 | | |
28 | 1 | void fuzz_common_setup(void) { |
29 | 1 | disallow_core(); |
30 | 1 | fuzz.fuzzing = 1; |
31 | 1 | fuzz.wrapfds = 1; |
32 | 1 | fuzz.do_jmp = 1; |
33 | 1 | fuzz.input = m_malloc(sizeof(buffer)); |
34 | 1 | _dropbear_log = fuzz_dropbear_log; |
35 | 1 | crypto_init(); |
36 | 1 | fuzz_seed("start", 5); |
37 | | /* let any messages get flushed */ |
38 | 1 | setlinebuf(stdout); |
39 | | #if DEBUG_TRACE |
40 | | if (debug_trace) |
41 | | { |
42 | | fprintf(stderr, "Dropbear fuzzer: -v specified, not disabling stderr output\n"); |
43 | | } |
44 | | else |
45 | | #endif |
46 | 1 | if (getenv("DROPBEAR_KEEP_STDERR")) { |
47 | 0 | fprintf(stderr, "Dropbear fuzzer: DROPBEAR_KEEP_STDERR, not disabling stderr output\n"); |
48 | 0 | } |
49 | 1 | else |
50 | 1 | { |
51 | 1 | fprintf(stderr, "Dropbear fuzzer: Disabling stderr output\n"); |
52 | 1 | fuzz.fake_stderr = fopen("/dev/null", "w"); |
53 | 1 | assert(fuzz.fake_stderr); |
54 | 1 | } |
55 | 1 | } |
56 | | |
57 | 3.99k | int fuzz_set_input(const uint8_t *Data, size_t Size) { |
58 | | |
59 | 3.99k | fuzz.input->data = (unsigned char*)Data; |
60 | 3.99k | fuzz.input->size = Size; |
61 | 3.99k | fuzz.input->len = Size; |
62 | 3.99k | fuzz.input->pos = 0; |
63 | | |
64 | 3.99k | memset(&ses, 0x0, sizeof(ses)); |
65 | 3.99k | memset(&svr_ses, 0x0, sizeof(svr_ses)); |
66 | 3.99k | memset(&cli_ses, 0x0, sizeof(cli_ses)); |
67 | 3.99k | wrapfd_setup(fuzz.input); |
68 | | // printhex("input", fuzz.input->data, fuzz.input->len); |
69 | | |
70 | 3.99k | fuzz_seed(fuzz.input->data, MIN(fuzz.input->len, 16)); |
71 | | |
72 | 3.99k | return DROPBEAR_SUCCESS; |
73 | 3.99k | } |
74 | | |
75 | | #if DEBUG_TRACE |
76 | | static void fuzz_dropbear_log(int UNUSED(priority), const char* format, va_list param) { |
77 | | if (debug_trace) { |
78 | | char printbuf[1024]; |
79 | | vsnprintf(printbuf, sizeof(printbuf), format, param); |
80 | | fprintf(stderr, "%s\n", printbuf); |
81 | | } |
82 | | } |
83 | | #else |
84 | 4.59k | static void fuzz_dropbear_log(int UNUSED(priority), const char* UNUSED(format), va_list UNUSED(param)) { |
85 | | /* No print */ |
86 | 4.59k | } |
87 | | #endif /* DEBUG_TRACE */ |
88 | | |
89 | 1 | void fuzz_svr_setup(void) { |
90 | 1 | fuzz_common_setup(); |
91 | | |
92 | 1 | _dropbear_exit = svr_dropbear_exit; |
93 | | |
94 | 1 | char *argv[] = { |
95 | 1 | "dropbear", |
96 | 1 | "-E", |
97 | 1 | }; |
98 | | |
99 | 1 | int argc = sizeof(argv) / sizeof(*argv); |
100 | 1 | svr_getopts(argc, argv); |
101 | | |
102 | 1 | load_fixed_hostkeys(); |
103 | 1 | } |
104 | | |
105 | 3.99k | void fuzz_svr_hook_preloop() { |
106 | 3.99k | if (fuzz.svr_postauth) { |
107 | 0 | ses.authstate.authdone = 1; |
108 | 0 | fill_passwd("root"); |
109 | 0 | } |
110 | 3.99k | } |
111 | | |
112 | 0 | void fuzz_cli_setup(void) { |
113 | 0 | fuzz_common_setup(); |
114 | | |
115 | 0 | _dropbear_exit = cli_dropbear_exit; |
116 | 0 | _dropbear_log = cli_dropbear_log; |
117 | |
|
118 | 0 | char *argv[] = { |
119 | 0 | "dbclient", |
120 | 0 | "-y", |
121 | 0 | "localhost", |
122 | 0 | "uptime" |
123 | 0 | }; |
124 | |
|
125 | 0 | int argc = sizeof(argv) / sizeof(*argv); |
126 | 0 | cli_getopts(argc, argv); |
127 | |
|
128 | 0 | load_fixed_client_key(); |
129 | | /* Avoid password prompt */ |
130 | 0 | setenv(DROPBEAR_PASSWORD_ENV, "password", 1); |
131 | 0 | } |
132 | | |
133 | | #include "fuzz-hostkeys.c" |
134 | | |
135 | 0 | static void load_fixed_client_key(void) { |
136 | |
|
137 | 0 | buffer *b = buf_new(3000); |
138 | 0 | sign_key *key; |
139 | 0 | enum signkey_type keytype; |
140 | |
|
141 | 0 | key = new_sign_key(); |
142 | 0 | keytype = DROPBEAR_SIGNKEY_ANY; |
143 | 0 | buf_putbytes(b, keyed25519, keyed25519_len); |
144 | 0 | buf_setpos(b, 0); |
145 | 0 | if (buf_get_priv_key(b, key, &keytype) == DROPBEAR_FAILURE) { |
146 | 0 | dropbear_exit("failed fixed ed25519 hostkey"); |
147 | 0 | } |
148 | 0 | list_append(cli_opts.privkeys, key); |
149 | |
|
150 | 0 | buf_free(b); |
151 | 0 | } |
152 | | |
153 | 1 | static void load_fixed_hostkeys(void) { |
154 | | |
155 | 1 | buffer *b = buf_new(3000); |
156 | 1 | enum signkey_type type; |
157 | | |
158 | 1 | TRACE(("load fixed hostkeys")) |
159 | | |
160 | 1 | svr_opts.hostkey = new_sign_key(); |
161 | | |
162 | 1 | buf_setlen(b, 0); |
163 | 1 | buf_putbytes(b, keyr, keyr_len); |
164 | 1 | buf_setpos(b, 0); |
165 | 1 | type = DROPBEAR_SIGNKEY_RSA; |
166 | 1 | if (buf_get_priv_key(b, svr_opts.hostkey, &type) == DROPBEAR_FAILURE) { |
167 | 0 | dropbear_exit("failed fixed rsa hostkey"); |
168 | 0 | } |
169 | | |
170 | 1 | buf_setlen(b, 0); |
171 | 1 | buf_putbytes(b, keyd, keyd_len); |
172 | 1 | buf_setpos(b, 0); |
173 | 1 | type = DROPBEAR_SIGNKEY_DSS; |
174 | 1 | if (buf_get_priv_key(b, svr_opts.hostkey, &type) == DROPBEAR_FAILURE) { |
175 | 0 | dropbear_exit("failed fixed dss hostkey"); |
176 | 0 | } |
177 | | |
178 | 1 | buf_setlen(b, 0); |
179 | 1 | buf_putbytes(b, keye, keye_len); |
180 | 1 | buf_setpos(b, 0); |
181 | 1 | type = DROPBEAR_SIGNKEY_ECDSA_NISTP256; |
182 | 1 | if (buf_get_priv_key(b, svr_opts.hostkey, &type) == DROPBEAR_FAILURE) { |
183 | 0 | dropbear_exit("failed fixed ecdsa hostkey"); |
184 | 0 | } |
185 | | |
186 | 1 | buf_setlen(b, 0); |
187 | 1 | buf_putbytes(b, keyed25519, keyed25519_len); |
188 | 1 | buf_setpos(b, 0); |
189 | 1 | type = DROPBEAR_SIGNKEY_ED25519; |
190 | 1 | if (buf_get_priv_key(b, svr_opts.hostkey, &type) == DROPBEAR_FAILURE) { |
191 | 0 | dropbear_exit("failed fixed ed25519 hostkey"); |
192 | 0 | } |
193 | | |
194 | 1 | buf_free(b); |
195 | 1 | } |
196 | | |
197 | 17.5k | void fuzz_kex_fakealgos(void) { |
198 | 17.5k | ses.newkeys->recv.crypt_mode = &dropbear_mode_none; |
199 | 17.5k | ses.newkeys->recv.algo_mac = &dropbear_nohash; |
200 | 17.5k | } |
201 | | |
202 | | void fuzz_get_socket_address(int UNUSED(fd), char **local_host, char **local_port, |
203 | 7.99k | char **remote_host, char **remote_port, int UNUSED(host_lookup)) { |
204 | 7.99k | if (local_host) { |
205 | 0 | *local_host = m_strdup("fuzzlocalhost"); |
206 | 0 | } |
207 | 7.99k | if (local_port) { |
208 | 0 | *local_port = m_strdup("1234"); |
209 | 0 | } |
210 | 7.99k | if (remote_host) { |
211 | 7.99k | *remote_host = m_strdup("fuzzremotehost"); |
212 | 7.99k | } |
213 | 7.99k | if (remote_port) { |
214 | 3.99k | *remote_port = m_strdup("9876"); |
215 | 3.99k | } |
216 | 7.99k | } |
217 | | |
218 | | /* cut down version of svr_send_msg_kexdh_reply() that skips slow maths. Still populates structures */ |
219 | 0 | void fuzz_fake_send_kexdh_reply(void) { |
220 | 0 | assert(!ses.dh_K); |
221 | 0 | m_mp_alloc_init_multi(&ses.dh_K, NULL); |
222 | 0 | mp_set_ul(ses.dh_K, 12345678uL); |
223 | 0 | finish_kexhashbuf(); |
224 | 0 | } |
225 | | |
226 | | /* fake version of spawn_command() */ |
227 | 0 | int fuzz_spawn_command(int *ret_writefd, int *ret_readfd, int *ret_errfd, pid_t *ret_pid) { |
228 | 0 | *ret_writefd = wrapfd_new_dummy(); |
229 | 0 | *ret_readfd = wrapfd_new_dummy(); |
230 | 0 | if (ret_errfd) { |
231 | 0 | *ret_errfd = wrapfd_new_dummy(); |
232 | 0 | } |
233 | 0 | if (*ret_writefd == -1 || *ret_readfd == -1 || (ret_errfd && *ret_errfd == -1)) { |
234 | 0 | m_close(*ret_writefd); |
235 | 0 | m_close(*ret_readfd); |
236 | 0 | if (ret_errfd) { |
237 | 0 | m_close(*ret_errfd); |
238 | 0 | } |
239 | 0 | return DROPBEAR_FAILURE; |
240 | 0 | } else { |
241 | 0 | *ret_pid = 999; |
242 | 0 | return DROPBEAR_SUCCESS; |
243 | |
|
244 | 0 | } |
245 | 0 | } |
246 | | |
247 | | /* Fake dropbear_listen, always returns failure for now. |
248 | | TODO make it sometimes return success with wrapfd_new_dummy() sockets. |
249 | | Making the listeners fake a new incoming connection will be harder. */ |
250 | | /* Listen on address:port. |
251 | | * Special cases are address of "" listening on everything, |
252 | | * and address of NULL listening on localhost only. |
253 | | * Returns the number of sockets bound on success, or -1 on failure. On |
254 | | * failure, if errstring wasn't NULL, it'll be a newly malloced error |
255 | | * string.*/ |
256 | | int fuzz_dropbear_listen(const char* UNUSED(address), const char* UNUSED(port), |
257 | 0 | int *UNUSED(socks), unsigned int UNUSED(sockcount), char **errstring, int *UNUSED(maxfd)) { |
258 | 0 | if (errstring) { |
259 | 0 | *errstring = m_strdup("fuzzing can't listen (yet)"); |
260 | 0 | } |
261 | 0 | return -1; |
262 | 0 | } |
263 | | |
264 | 3.99k | int fuzz_run_server(const uint8_t *Data, size_t Size, int skip_kexmaths, int postauth) { |
265 | 3.99k | static int once = 0; |
266 | 3.99k | if (!once) { |
267 | 1 | fuzz_svr_setup(); |
268 | 1 | fuzz.skip_kexmaths = skip_kexmaths; |
269 | 1 | once = 1; |
270 | 1 | } |
271 | | |
272 | 3.99k | fuzz.svr_postauth = postauth; |
273 | | |
274 | 3.99k | if (fuzz_set_input(Data, Size) == DROPBEAR_FAILURE) { |
275 | 0 | return 0; |
276 | 0 | } |
277 | | |
278 | 3.99k | uint32_t wrapseed; |
279 | 3.99k | genrandom((void*)&wrapseed, sizeof(wrapseed)); |
280 | 3.99k | wrapfd_setseed(wrapseed); |
281 | | |
282 | 3.99k | int fakesock = wrapfd_new_fuzzinput(); |
283 | | |
284 | 3.99k | m_malloc_set_epoch(1); |
285 | 3.99k | fuzz.do_jmp = 1; |
286 | 3.99k | if (setjmp(fuzz.jmp) == 0) { |
287 | 3.99k | svr_session(fakesock, fakesock); |
288 | 0 | m_malloc_free_epoch(1, 0); |
289 | 0 | } else { |
290 | 0 | fuzz.do_jmp = 0; |
291 | 0 | m_malloc_free_epoch(1, 1); |
292 | 0 | TRACE(("dropbear_exit longjmped")) |
293 | | /* dropbear_exit jumped here */ |
294 | 0 | } |
295 | | |
296 | 0 | return 0; |
297 | 3.99k | } |
298 | | |
299 | 0 | int fuzz_run_client(const uint8_t *Data, size_t Size, int skip_kexmaths) { |
300 | 0 | static int once = 0; |
301 | 0 | if (!once) { |
302 | 0 | fuzz_cli_setup(); |
303 | 0 | fuzz.skip_kexmaths = skip_kexmaths; |
304 | 0 | once = 1; |
305 | 0 | } |
306 | |
|
307 | 0 | if (fuzz_set_input(Data, Size) == DROPBEAR_FAILURE) { |
308 | 0 | return 0; |
309 | 0 | } |
310 | | |
311 | | // Allow to proceed sooner |
312 | 0 | ses.kexstate.donefirstkex = 1; |
313 | |
|
314 | 0 | uint32_t wrapseed; |
315 | 0 | genrandom((void*)&wrapseed, sizeof(wrapseed)); |
316 | 0 | wrapfd_setseed(wrapseed); |
317 | |
|
318 | 0 | int fakesock = wrapfd_new_fuzzinput(); |
319 | |
|
320 | 0 | m_malloc_set_epoch(1); |
321 | 0 | fuzz.do_jmp = 1; |
322 | 0 | if (setjmp(fuzz.jmp) == 0) { |
323 | 0 | cli_session(fakesock, fakesock, NULL, 0); |
324 | 0 | m_malloc_free_epoch(1, 0); |
325 | 0 | } else { |
326 | 0 | fuzz.do_jmp = 0; |
327 | 0 | m_malloc_free_epoch(1, 1); |
328 | 0 | TRACE(("dropbear_exit longjmped")) |
329 | | /* dropbear_exit jumped here */ |
330 | 0 | } |
331 | | |
332 | 0 | return 0; |
333 | 0 | } |
334 | | |
335 | 0 | const void* fuzz_get_algo(const algo_type *algos, const char* name) { |
336 | 0 | const algo_type *t; |
337 | 0 | for (t = algos; t->name; t++) { |
338 | 0 | if (strcmp(t->name, name) == 0) { |
339 | 0 | return t->data; |
340 | 0 | } |
341 | 0 | } |
342 | 0 | assert(0); |
343 | 0 | } |
344 | | |
345 | 147k | void fuzz_dump(const unsigned char* data, size_t len) { |
346 | 147k | if (fuzz.dumping) { |
347 | 0 | TRACE(("dump %zu", len)) |
348 | 0 | assert(atomicio(vwrite, fuzz.recv_dumpfd, (void*)data, len) == len); |
349 | 0 | } |
350 | 147k | } |
351 | | |
352 | | static struct passwd pwd_root = { |
353 | | .pw_name = "root", |
354 | | .pw_passwd = "!", |
355 | | .pw_uid = 0, |
356 | | .pw_gid = 0, |
357 | | .pw_dir = "/root", |
358 | | .pw_shell = "/bin/sh", |
359 | | }; |
360 | | |
361 | | static struct passwd pwd_other = { |
362 | | .pw_name = "other", |
363 | | .pw_passwd = "!", |
364 | | .pw_uid = 100, |
365 | | .pw_gid = 100, |
366 | | .pw_dir = "/home/other", |
367 | | .pw_shell = "/bin/sh", |
368 | | }; |
369 | | |
370 | | |
371 | | /* oss-fuzz runs fuzzers under minijail, without /etc/passwd. |
372 | | We provide sufficient values for the fuzzers to run */ |
373 | 502 | struct passwd* fuzz_getpwnam(const char *login) { |
374 | 502 | if (!fuzz.fuzzing) { |
375 | 0 | return getpwnam(login); |
376 | 0 | } |
377 | 502 | if (strcmp(login, pwd_other.pw_name) == 0) { |
378 | 0 | return &pwd_other; |
379 | 0 | } |
380 | 502 | if (strcmp(login, pwd_root.pw_name) == 0) { |
381 | 31 | return &pwd_root; |
382 | 31 | } |
383 | 471 | return NULL; |
384 | 502 | } |
385 | | |
386 | 0 | struct passwd* fuzz_getpwuid(uid_t uid) { |
387 | 0 | if (!fuzz.fuzzing) { |
388 | 0 | return getpwuid(uid); |
389 | 0 | } |
390 | 0 | if (uid == pwd_other.pw_uid) { |
391 | 0 | return &pwd_other; |
392 | 0 | } |
393 | 0 | if (uid == pwd_root.pw_uid) { |
394 | 0 | return &pwd_root; |
395 | 0 | } |
396 | 0 | return NULL; |
397 | 0 | } |
398 | | |