diff options
author | Damien Miller <djm@mindrot.org> | 2001-12-24 01:41:47 +1100 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2001-12-24 01:41:47 +1100 |
commit | 62116dcc0a0a2ad4233691e73b7c2006b6849702 (patch) | |
tree | 2cc885d0d104e97e4443581aca27f007adc51852 /entropy.c | |
parent | 278f907a2d6d00d6f52a11bf9577648aadbf0994 (diff) |
- (djm) Ignore fix & patchlevel in OpenSSL version check. Patch from
solar@openwall.com
- (djm) Rework entropy code. If the OpenSSL PRNG is has not been
internally seeded, execute a subprogram "ssh-rand-helper" to obtain
some entropy for us. Rewrite the old in-process entropy collecter as
an example ssh-rand-helper.
- (djm) Always perform ssh_prng_cmds path lookups in configure, even if
we don't end up using ssh_prng_cmds (so we always get a valid file)
Diffstat (limited to 'entropy.c')
-rw-r--r-- | entropy.c | 926 |
1 files changed, 75 insertions, 851 deletions
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * Copyright (c) 2000 Damien Miller. All rights reserved. | 2 | * Copyright (c) 2001 Damien Miller. All rights reserved. |
3 | * | 3 | * |
4 | * Redistribution and use in source and binary forms, with or without | 4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions | 5 | * modification, are permitted provided that the following conditions |
@@ -25,14 +25,8 @@ | |||
25 | #include "includes.h" | 25 | #include "includes.h" |
26 | 26 | ||
27 | #include <openssl/rand.h> | 27 | #include <openssl/rand.h> |
28 | #include <openssl/sha.h> | ||
29 | #include <openssl/crypto.h> | 28 | #include <openssl/crypto.h> |
30 | 29 | ||
31 | /* SunOS 4.4.4 needs this */ | ||
32 | #ifdef HAVE_FLOATINGPOINT_H | ||
33 | # include <floatingpoint.h> | ||
34 | #endif /* HAVE_FLOATINGPOINT_H */ | ||
35 | |||
36 | #include "ssh.h" | 30 | #include "ssh.h" |
37 | #include "misc.h" | 31 | #include "misc.h" |
38 | #include "xmalloc.h" | 32 | #include "xmalloc.h" |
@@ -40,878 +34,108 @@ | |||
40 | #include "pathnames.h" | 34 | #include "pathnames.h" |
41 | #include "log.h" | 35 | #include "log.h" |
42 | 36 | ||
43 | RCSID("$Id: entropy.c,v 1.38 2001/08/06 06:51:49 djm Exp $"); | ||
44 | |||
45 | #ifndef offsetof | ||
46 | # define offsetof(type, member) ((size_t) &((type *)0)->member) | ||
47 | #endif | ||
48 | |||
49 | /* Number of times to pass through command list gathering entropy */ | ||
50 | #define NUM_ENTROPY_RUNS 1 | ||
51 | |||
52 | /* Scale entropy estimates back by this amount on subsequent runs */ | ||
53 | #define SCALE_PER_RUN 10.0 | ||
54 | |||
55 | /* Minimum number of commands to be considered valid */ | ||
56 | #define MIN_ENTROPY_SOURCES 16 | ||
57 | |||
58 | #define WHITESPACE " \t\n" | ||
59 | |||
60 | #ifndef RUSAGE_SELF | ||
61 | # define RUSAGE_SELF 0 | ||
62 | #endif | ||
63 | #ifndef RUSAGE_CHILDREN | ||
64 | # define RUSAGE_CHILDREN 0 | ||
65 | #endif | ||
66 | |||
67 | #if defined(_POSIX_SAVED_IDS) && !defined(BROKEN_SAVED_UIDS) | ||
68 | # define SAVED_IDS_WORK_WITH_SETEUID | ||
69 | #endif | ||
70 | |||
71 | static void | ||
72 | check_openssl_version(void) | ||
73 | { | ||
74 | if (SSLeay() != OPENSSL_VERSION_NUMBER) | ||
75 | fatal("OpenSSL version mismatch. Built against %lx, you " | ||
76 | "have %lx", OPENSSL_VERSION_NUMBER, SSLeay()); | ||
77 | } | ||
78 | |||
79 | #if defined(PRNGD_SOCKET) || defined(PRNGD_PORT) | ||
80 | # define USE_PRNGD | ||
81 | #endif | ||
82 | |||
83 | #if defined(USE_PRNGD) || defined(RANDOM_POOL) | ||
84 | |||
85 | #ifdef USE_PRNGD | ||
86 | /* Collect entropy from PRNGD/EGD */ | ||
87 | int | ||
88 | get_random_bytes(unsigned char *buf, int len) | ||
89 | { | ||
90 | int fd; | ||
91 | char msg[2]; | ||
92 | #ifdef PRNGD_PORT | ||
93 | struct sockaddr_in addr; | ||
94 | #else | ||
95 | struct sockaddr_un addr; | ||
96 | #endif | ||
97 | int addr_len, rval, errors; | ||
98 | mysig_t old_sigpipe; | ||
99 | |||
100 | memset(&addr, '\0', sizeof(addr)); | ||
101 | |||
102 | #ifdef PRNGD_PORT | ||
103 | addr.sin_family = AF_INET; | ||
104 | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); | ||
105 | addr.sin_port = htons(PRNGD_PORT); | ||
106 | addr_len = sizeof(struct sockaddr_in); | ||
107 | #else /* use IP socket PRNGD_SOCKET instead */ | ||
108 | /* Sanity checks */ | ||
109 | if (sizeof(PRNGD_SOCKET) > sizeof(addr.sun_path)) | ||
110 | fatal("Random pool path is too long"); | ||
111 | if (len > 255) | ||
112 | fatal("Too many bytes to read from PRNGD"); | ||
113 | |||
114 | addr.sun_family = AF_UNIX; | ||
115 | strlcpy(addr.sun_path, PRNGD_SOCKET, sizeof(addr.sun_path)); | ||
116 | addr_len = offsetof(struct sockaddr_un, sun_path) + | ||
117 | sizeof(PRNGD_SOCKET); | ||
118 | #endif | ||
119 | |||
120 | old_sigpipe = mysignal(SIGPIPE, SIG_IGN); | ||
121 | |||
122 | errors = rval = 0; | ||
123 | reopen: | ||
124 | #ifdef PRNGD_PORT | ||
125 | fd = socket(addr.sin_family, SOCK_STREAM, 0); | ||
126 | if (fd == -1) { | ||
127 | error("Couldn't create AF_INET socket: %s", strerror(errno)); | ||
128 | goto done; | ||
129 | } | ||
130 | #else | ||
131 | fd = socket(addr.sun_family, SOCK_STREAM, 0); | ||
132 | if (fd == -1) { | ||
133 | error("Couldn't create AF_UNIX socket: %s", strerror(errno)); | ||
134 | goto done; | ||
135 | } | ||
136 | #endif | ||
137 | |||
138 | if (connect(fd, (struct sockaddr*)&addr, addr_len) == -1) { | ||
139 | #ifdef PRNGD_PORT | ||
140 | error("Couldn't connect to PRNGD port %d: %s", | ||
141 | PRNGD_PORT, strerror(errno)); | ||
142 | #else | ||
143 | error("Couldn't connect to PRNGD socket \"%s\": %s", | ||
144 | addr.sun_path, strerror(errno)); | ||
145 | #endif | ||
146 | goto done; | ||
147 | } | ||
148 | |||
149 | /* Send blocking read request to PRNGD */ | ||
150 | msg[0] = 0x02; | ||
151 | msg[1] = len; | ||
152 | |||
153 | if (atomicio(write, fd, msg, sizeof(msg)) != sizeof(msg)) { | ||
154 | if (errno == EPIPE && errors < 10) { | ||
155 | close(fd); | ||
156 | errors++; | ||
157 | goto reopen; | ||
158 | } | ||
159 | error("Couldn't write to PRNGD socket: %s", | ||
160 | strerror(errno)); | ||
161 | goto done; | ||
162 | } | ||
163 | |||
164 | if (atomicio(read, fd, buf, len) != len) { | ||
165 | if (errno == EPIPE && errors < 10) { | ||
166 | close(fd); | ||
167 | errors++; | ||
168 | goto reopen; | ||
169 | } | ||
170 | error("Couldn't read from PRNGD socket: %s", | ||
171 | strerror(errno)); | ||
172 | goto done; | ||
173 | } | ||
174 | |||
175 | rval = 1; | ||
176 | done: | ||
177 | mysignal(SIGPIPE, old_sigpipe); | ||
178 | if (fd != -1) | ||
179 | close(fd); | ||
180 | return(rval); | ||
181 | } | ||
182 | #else /* !USE_PRNGD */ | ||
183 | #ifdef RANDOM_POOL | ||
184 | /* Collect entropy from /dev/urandom or pipe */ | ||
185 | static int | ||
186 | get_random_bytes(unsigned char *buf, int len) | ||
187 | { | ||
188 | int random_pool; | ||
189 | |||
190 | random_pool = open(RANDOM_POOL, O_RDONLY); | ||
191 | if (random_pool == -1) { | ||
192 | error("Couldn't open random pool \"%s\": %s", | ||
193 | RANDOM_POOL, strerror(errno)); | ||
194 | return(0); | ||
195 | } | ||
196 | |||
197 | if (atomicio(read, random_pool, buf, len) != len) { | ||
198 | error("Couldn't read from random pool \"%s\": %s", | ||
199 | RANDOM_POOL, strerror(errno)); | ||
200 | close(random_pool); | ||
201 | return(0); | ||
202 | } | ||
203 | |||
204 | close(random_pool); | ||
205 | |||
206 | return(1); | ||
207 | } | ||
208 | #endif /* RANDOM_POOL */ | ||
209 | #endif /* USE_PRNGD */ | ||
210 | |||
211 | /* | 37 | /* |
212 | * Seed OpenSSL's random number pool from Kernel random number generator | 38 | * Portable OpenSSH PRNG seeding: |
213 | * or PRNGD/EGD | 39 | * If OpenSSL has not "internally seeded" itself (e.g. pulled data from |
40 | * /dev/random), then we execute a "ssh-rand-helper" program which | ||
41 | * collects entropy and writes it to stdout. The child program must | ||
42 | * write at least RANDOM_SEED_SIZE bytes. The child is run with stderr | ||
43 | * attached, so error/debugging output should be visible. | ||
44 | * | ||
45 | * XXX: we should tell the child how many bytes we need. | ||
214 | */ | 46 | */ |
215 | void | ||
216 | seed_rng(void) | ||
217 | { | ||
218 | unsigned char buf[32]; | ||
219 | 47 | ||
220 | debug("Seeding random number generator"); | 48 | #define RANDOM_SEED_SIZE 48 |
221 | 49 | ||
222 | if (!get_random_bytes(buf, sizeof(buf))) { | 50 | RCSID("$Id: entropy.c,v 1.39 2001/12/23 14:41:48 djm Exp $"); |
223 | if (!RAND_status()) | ||
224 | fatal("Entropy collection failed and entropy exhausted"); | ||
225 | } else { | ||
226 | RAND_add(buf, sizeof(buf), sizeof(buf)); | ||
227 | } | ||
228 | 51 | ||
229 | memset(buf, '\0', sizeof(buf)); | 52 | static uid_t original_uid, original_euid; |
230 | } | ||
231 | 53 | ||
232 | void | 54 | void |
233 | init_rng(void) | 55 | seed_rng(void) |
234 | { | ||
235 | check_openssl_version(); | ||
236 | } | ||
237 | |||
238 | #else /* defined(USE_PRNGD) || defined(RANDOM_POOL) */ | ||
239 | |||
240 | /* | ||
241 | * FIXME: proper entropy estimations. All current values are guesses | ||
242 | * FIXME: (ATL) do estimates at compile time? | ||
243 | * FIXME: More entropy sources | ||
244 | */ | ||
245 | |||
246 | /* slow command timeouts (all in milliseconds) */ | ||
247 | /* static int entropy_timeout_default = ENTROPY_TIMEOUT_MSEC; */ | ||
248 | static int entropy_timeout_current = ENTROPY_TIMEOUT_MSEC; | ||
249 | |||
250 | static int prng_seed_saved = 0; | ||
251 | static int prng_initialised = 0; | ||
252 | uid_t original_uid; | ||
253 | |||
254 | typedef struct | ||
255 | { | ||
256 | /* Proportion of data that is entropy */ | ||
257 | double rate; | ||
258 | /* Counter goes positive if this command times out */ | ||
259 | unsigned int badness; | ||
260 | /* Increases by factor of two each timeout */ | ||
261 | unsigned int sticky_badness; | ||
262 | /* Path to executable */ | ||
263 | char *path; | ||
264 | /* argv to pass to executable */ | ||
265 | char *args[5]; | ||
266 | /* full command string (debug) */ | ||
267 | char *cmdstring; | ||
268 | } entropy_source_t; | ||
269 | |||
270 | double stir_from_system(void); | ||
271 | double stir_from_programs(void); | ||
272 | double stir_gettimeofday(double entropy_estimate); | ||
273 | double stir_clock(double entropy_estimate); | ||
274 | double stir_rusage(int who, double entropy_estimate); | ||
275 | double hash_output_from_command(entropy_source_t *src, char *hash); | ||
276 | |||
277 | /* this is initialised from a file, by prng_read_commands() */ | ||
278 | entropy_source_t *entropy_sources = NULL; | ||
279 | |||
280 | double | ||
281 | stir_from_system(void) | ||
282 | { | ||
283 | double total_entropy_estimate; | ||
284 | long int i; | ||
285 | |||
286 | total_entropy_estimate = 0; | ||
287 | |||
288 | i = getpid(); | ||
289 | RAND_add(&i, sizeof(i), 0.5); | ||
290 | total_entropy_estimate += 0.1; | ||
291 | |||
292 | i = getppid(); | ||
293 | RAND_add(&i, sizeof(i), 0.5); | ||
294 | total_entropy_estimate += 0.1; | ||
295 | |||
296 | i = getuid(); | ||
297 | RAND_add(&i, sizeof(i), 0.0); | ||
298 | i = getgid(); | ||
299 | RAND_add(&i, sizeof(i), 0.0); | ||
300 | |||
301 | total_entropy_estimate += stir_gettimeofday(1.0); | ||
302 | total_entropy_estimate += stir_clock(0.5); | ||
303 | total_entropy_estimate += stir_rusage(RUSAGE_SELF, 2.0); | ||
304 | |||
305 | return(total_entropy_estimate); | ||
306 | } | ||
307 | |||
308 | double | ||
309 | stir_from_programs(void) | ||
310 | { | ||
311 | int i; | ||
312 | int c; | ||
313 | double entropy_estimate; | ||
314 | double total_entropy_estimate; | ||
315 | char hash[SHA_DIGEST_LENGTH]; | ||
316 | |||
317 | total_entropy_estimate = 0; | ||
318 | for(i = 0; i < NUM_ENTROPY_RUNS; i++) { | ||
319 | c = 0; | ||
320 | while (entropy_sources[c].path != NULL) { | ||
321 | |||
322 | if (!entropy_sources[c].badness) { | ||
323 | /* Hash output from command */ | ||
324 | entropy_estimate = hash_output_from_command(&entropy_sources[c], hash); | ||
325 | |||
326 | /* Scale back entropy estimate according to command's rate */ | ||
327 | entropy_estimate *= entropy_sources[c].rate; | ||
328 | |||
329 | /* Upper bound of entropy estimate is SHA_DIGEST_LENGTH */ | ||
330 | if (entropy_estimate > SHA_DIGEST_LENGTH) | ||
331 | entropy_estimate = SHA_DIGEST_LENGTH; | ||
332 | |||
333 | /* Scale back estimates for subsequent passes through list */ | ||
334 | entropy_estimate /= SCALE_PER_RUN * (i + 1.0); | ||
335 | |||
336 | /* Stir it in */ | ||
337 | RAND_add(hash, sizeof(hash), entropy_estimate); | ||
338 | |||
339 | debug3("Got %0.2f bytes of entropy from '%s'", entropy_estimate, | ||
340 | entropy_sources[c].cmdstring); | ||
341 | |||
342 | total_entropy_estimate += entropy_estimate; | ||
343 | |||
344 | /* Execution times should be a little unpredictable */ | ||
345 | total_entropy_estimate += stir_gettimeofday(0.05); | ||
346 | total_entropy_estimate += stir_clock(0.05); | ||
347 | total_entropy_estimate += stir_rusage(RUSAGE_SELF, 0.1); | ||
348 | total_entropy_estimate += stir_rusage(RUSAGE_CHILDREN, 0.1); | ||
349 | } else { | ||
350 | debug2("Command '%s' disabled (badness %d)", | ||
351 | entropy_sources[c].cmdstring, entropy_sources[c].badness); | ||
352 | |||
353 | if (entropy_sources[c].badness > 0) | ||
354 | entropy_sources[c].badness--; | ||
355 | } | ||
356 | |||
357 | c++; | ||
358 | } | ||
359 | } | ||
360 | |||
361 | return(total_entropy_estimate); | ||
362 | } | ||
363 | |||
364 | double | ||
365 | stir_gettimeofday(double entropy_estimate) | ||
366 | { | ||
367 | struct timeval tv; | ||
368 | |||
369 | if (gettimeofday(&tv, NULL) == -1) | ||
370 | fatal("Couldn't gettimeofday: %s", strerror(errno)); | ||
371 | |||
372 | RAND_add(&tv, sizeof(tv), entropy_estimate); | ||
373 | |||
374 | return(entropy_estimate); | ||
375 | } | ||
376 | |||
377 | double | ||
378 | stir_clock(double entropy_estimate) | ||
379 | { | ||
380 | #ifdef HAVE_CLOCK | ||
381 | clock_t c; | ||
382 | |||
383 | c = clock(); | ||
384 | RAND_add(&c, sizeof(c), entropy_estimate); | ||
385 | |||
386 | return(entropy_estimate); | ||
387 | #else /* _HAVE_CLOCK */ | ||
388 | return(0); | ||
389 | #endif /* _HAVE_CLOCK */ | ||
390 | } | ||
391 | |||
392 | double | ||
393 | stir_rusage(int who, double entropy_estimate) | ||
394 | { | ||
395 | #ifdef HAVE_GETRUSAGE | ||
396 | struct rusage ru; | ||
397 | |||
398 | if (getrusage(who, &ru) == -1) | ||
399 | return(0); | ||
400 | |||
401 | RAND_add(&ru, sizeof(ru), entropy_estimate); | ||
402 | |||
403 | return(entropy_estimate); | ||
404 | #else /* _HAVE_GETRUSAGE */ | ||
405 | return(0); | ||
406 | #endif /* _HAVE_GETRUSAGE */ | ||
407 | } | ||
408 | |||
409 | |||
410 | static int | ||
411 | _get_timeval_msec_difference(struct timeval *t1, struct timeval *t2) { | ||
412 | int secdiff, usecdiff; | ||
413 | |||
414 | secdiff = t2->tv_sec - t1->tv_sec; | ||
415 | usecdiff = (secdiff*1000000) + (t2->tv_usec - t1->tv_usec); | ||
416 | return (int)(usecdiff / 1000); | ||
417 | } | ||
418 | |||
419 | double | ||
420 | hash_output_from_command(entropy_source_t *src, char *hash) | ||
421 | { | 56 | { |
422 | static int devnull = -1; | 57 | int devnull; |
423 | int p[2]; | 58 | int p[2]; |
424 | fd_set rdset; | ||
425 | int cmd_eof = 0, error_abort = 0; | ||
426 | struct timeval tv_start, tv_current; | ||
427 | int msec_elapsed = 0; | ||
428 | pid_t pid; | 59 | pid_t pid; |
429 | int status; | 60 | int ret; |
430 | char buf[16384]; | 61 | unsigned char buf[RANDOM_SEED_SIZE]; |
431 | int bytes_read; | ||
432 | int total_bytes_read; | ||
433 | SHA_CTX sha; | ||
434 | |||
435 | debug3("Reading output from \'%s\'", src->cmdstring); | ||
436 | |||
437 | if (devnull == -1) { | ||
438 | devnull = open("/dev/null", O_RDWR); | ||
439 | if (devnull == -1) | ||
440 | fatal("Couldn't open /dev/null: %s", strerror(errno)); | ||
441 | } | ||
442 | |||
443 | if (pipe(p) == -1) | ||
444 | fatal("Couldn't open pipe: %s", strerror(errno)); | ||
445 | |||
446 | (void)gettimeofday(&tv_start, NULL); /* record start time */ | ||
447 | |||
448 | switch (pid = fork()) { | ||
449 | case -1: /* Error */ | ||
450 | close(p[0]); | ||
451 | close(p[1]); | ||
452 | fatal("Couldn't fork: %s", strerror(errno)); | ||
453 | /* NOTREACHED */ | ||
454 | case 0: /* Child */ | ||
455 | dup2(devnull, STDIN_FILENO); | ||
456 | dup2(p[1], STDOUT_FILENO); | ||
457 | dup2(p[1], STDERR_FILENO); | ||
458 | close(p[0]); | ||
459 | close(p[1]); | ||
460 | close(devnull); | ||
461 | |||
462 | setuid(original_uid); | ||
463 | execv(src->path, (char**)(src->args)); | ||
464 | debug("(child) Couldn't exec '%s': %s", src->cmdstring, | ||
465 | strerror(errno)); | ||
466 | _exit(-1); | ||
467 | default: /* Parent */ | ||
468 | break; | ||
469 | } | ||
470 | |||
471 | RAND_add(&pid, sizeof(&pid), 0.0); | ||
472 | |||
473 | close(p[1]); | ||
474 | |||
475 | /* Hash output from child */ | ||
476 | SHA1_Init(&sha); | ||
477 | total_bytes_read = 0; | ||
478 | |||
479 | while (!error_abort && !cmd_eof) { | ||
480 | int ret; | ||
481 | struct timeval tv; | ||
482 | int msec_remaining; | ||
483 | |||
484 | (void) gettimeofday(&tv_current, 0); | ||
485 | msec_elapsed = _get_timeval_msec_difference(&tv_start, &tv_current); | ||
486 | if (msec_elapsed >= entropy_timeout_current) { | ||
487 | error_abort=1; | ||
488 | continue; | ||
489 | } | ||
490 | msec_remaining = entropy_timeout_current - msec_elapsed; | ||
491 | |||
492 | FD_ZERO(&rdset); | ||
493 | FD_SET(p[0], &rdset); | ||
494 | tv.tv_sec = msec_remaining / 1000; | ||
495 | tv.tv_usec = (msec_remaining % 1000) * 1000; | ||
496 | |||
497 | ret = select(p[0]+1, &rdset, NULL, NULL, &tv); | ||
498 | |||
499 | RAND_add(&tv, sizeof(tv), 0.0); | ||
500 | |||
501 | switch (ret) { | ||
502 | case 0: | ||
503 | /* timer expired */ | ||
504 | error_abort = 1; | ||
505 | break; | ||
506 | case 1: | ||
507 | /* command input */ | ||
508 | do { | ||
509 | bytes_read = read(p[0], buf, sizeof(buf)); | ||
510 | } while (bytes_read == -1 && errno == EINTR); | ||
511 | RAND_add(&bytes_read, sizeof(&bytes_read), 0.0); | ||
512 | if (bytes_read == -1) { | ||
513 | error_abort = 1; | ||
514 | break; | ||
515 | } else if (bytes_read) { | ||
516 | SHA1_Update(&sha, buf, bytes_read); | ||
517 | total_bytes_read += bytes_read; | ||
518 | } else { | ||
519 | cmd_eof = 1; | ||
520 | } | ||
521 | break; | ||
522 | case -1: | ||
523 | default: | ||
524 | /* error */ | ||
525 | debug("Command '%s': select() failed: %s", src->cmdstring, | ||
526 | strerror(errno)); | ||
527 | error_abort = 1; | ||
528 | break; | ||
529 | } | ||
530 | } | ||
531 | |||
532 | SHA1_Final(hash, &sha); | ||
533 | |||
534 | close(p[0]); | ||
535 | |||
536 | debug3("Time elapsed: %d msec", msec_elapsed); | ||
537 | |||
538 | if (waitpid(pid, &status, 0) == -1) { | ||
539 | error("Couldn't wait for child '%s' completion: %s", src->cmdstring, | ||
540 | strerror(errno)); | ||
541 | return(0.0); | ||
542 | } | ||
543 | |||
544 | RAND_add(&status, sizeof(&status), 0.0); | ||
545 | |||
546 | if (error_abort) { | ||
547 | /* closing p[0] on timeout causes the entropy command to | ||
548 | * SIGPIPE. Take whatever output we got, and mark this command | ||
549 | * as slow */ | ||
550 | debug2("Command '%s' timed out", src->cmdstring); | ||
551 | src->sticky_badness *= 2; | ||
552 | src->badness = src->sticky_badness; | ||
553 | return(total_bytes_read); | ||
554 | } | ||
555 | |||
556 | if (WIFEXITED(status)) { | ||
557 | if (WEXITSTATUS(status)==0) { | ||
558 | return(total_bytes_read); | ||
559 | } else { | ||
560 | debug2("Command '%s' exit status was %d", src->cmdstring, | ||
561 | WEXITSTATUS(status)); | ||
562 | src->badness = src->sticky_badness = 128; | ||
563 | return (0.0); | ||
564 | } | ||
565 | } else if (WIFSIGNALED(status)) { | ||
566 | debug2("Command '%s' returned on uncaught signal %d !", src->cmdstring, | ||
567 | status); | ||
568 | src->badness = src->sticky_badness = 128; | ||
569 | return(0.0); | ||
570 | } else | ||
571 | return(0.0); | ||
572 | } | ||
573 | |||
574 | /* | ||
575 | * prng seedfile functions | ||
576 | */ | ||
577 | int | ||
578 | prng_check_seedfile(char *filename) { | ||
579 | |||
580 | struct stat st; | ||
581 | |||
582 | /* FIXME raceable: eg replace seed between this stat and subsequent open */ | ||
583 | /* Not such a problem because we don't trust the seed file anyway */ | ||
584 | if (lstat(filename, &st) == -1) { | ||
585 | /* Give up on hard errors */ | ||
586 | if (errno != ENOENT) | ||
587 | debug("WARNING: Couldn't stat random seed file \"%s\": %s", | ||
588 | filename, strerror(errno)); | ||
589 | 62 | ||
590 | return(0); | 63 | if (RAND_status() == 1) { |
591 | } | 64 | debug3("RNG is ready, skipping seeding"); |
592 | |||
593 | /* regular file? */ | ||
594 | if (!S_ISREG(st.st_mode)) | ||
595 | fatal("PRNG seedfile %.100s is not a regular file", filename); | ||
596 | |||
597 | /* mode 0600, owned by root or the current user? */ | ||
598 | if (((st.st_mode & 0177) != 0) || !(st.st_uid == original_uid)) { | ||
599 | debug("WARNING: PRNG seedfile %.100s must be mode 0600, owned by uid %d", | ||
600 | filename, getuid()); | ||
601 | return(0); | ||
602 | } | ||
603 | |||
604 | return(1); | ||
605 | } | ||
606 | |||
607 | void | ||
608 | prng_write_seedfile(void) { | ||
609 | int fd; | ||
610 | char seed[1024]; | ||
611 | char filename[1024]; | ||
612 | struct passwd *pw; | ||
613 | |||
614 | /* Don't bother if we have already saved a seed */ | ||
615 | if (prng_seed_saved) | ||
616 | return; | 65 | return; |
617 | |||
618 | setuid(original_uid); | ||
619 | |||
620 | prng_seed_saved = 1; | ||
621 | |||
622 | pw = getpwuid(original_uid); | ||
623 | if (pw == NULL) | ||
624 | fatal("Couldn't get password entry for current user (%i): %s", | ||
625 | original_uid, strerror(errno)); | ||
626 | |||
627 | /* Try to ensure that the parent directory is there */ | ||
628 | snprintf(filename, sizeof(filename), "%.512s/%s", pw->pw_dir, | ||
629 | _PATH_SSH_USER_DIR); | ||
630 | mkdir(filename, 0700); | ||
631 | |||
632 | snprintf(filename, sizeof(filename), "%.512s/%s", pw->pw_dir, | ||
633 | SSH_PRNG_SEED_FILE); | ||
634 | |||
635 | debug("writing PRNG seed to file %.100s", filename); | ||
636 | |||
637 | RAND_bytes(seed, sizeof(seed)); | ||
638 | |||
639 | /* Don't care if the seed doesn't exist */ | ||
640 | prng_check_seedfile(filename); | ||
641 | |||
642 | if ((fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, 0600)) == -1) { | ||
643 | debug("WARNING: couldn't access PRNG seedfile %.100s (%.100s)", | ||
644 | filename, strerror(errno)); | ||
645 | } else { | ||
646 | if (atomicio(write, fd, &seed, sizeof(seed)) != sizeof(seed)) | ||
647 | fatal("problem writing PRNG seedfile %.100s (%.100s)", filename, | ||
648 | strerror(errno)); | ||
649 | |||
650 | close(fd); | ||
651 | } | ||
652 | } | ||
653 | |||
654 | void | ||
655 | prng_read_seedfile(void) { | ||
656 | int fd; | ||
657 | char seed[1024]; | ||
658 | char filename[1024]; | ||
659 | struct passwd *pw; | ||
660 | |||
661 | pw = getpwuid(original_uid); | ||
662 | if (pw == NULL) | ||
663 | fatal("Couldn't get password entry for current user (%i): %s", | ||
664 | original_uid, strerror(errno)); | ||
665 | |||
666 | snprintf(filename, sizeof(filename), "%.512s/%s", pw->pw_dir, | ||
667 | SSH_PRNG_SEED_FILE); | ||
668 | |||
669 | debug("loading PRNG seed from file %.100s", filename); | ||
670 | |||
671 | if (!prng_check_seedfile(filename)) { | ||
672 | verbose("Random seed file not found or not valid, ignoring."); | ||
673 | return; | ||
674 | } | ||
675 | |||
676 | /* open the file and read in the seed */ | ||
677 | fd = open(filename, O_RDONLY); | ||
678 | if (fd == -1) | ||
679 | fatal("could not open PRNG seedfile %.100s (%.100s)", filename, | ||
680 | strerror(errno)); | ||
681 | |||
682 | if (atomicio(read, fd, &seed, sizeof(seed)) != sizeof(seed)) { | ||
683 | verbose("invalid or short read from PRNG seedfile %.100s - ignoring", | ||
684 | filename); | ||
685 | memset(seed, '\0', sizeof(seed)); | ||
686 | } | ||
687 | close(fd); | ||
688 | |||
689 | /* stir in the seed, with estimated entropy zero */ | ||
690 | RAND_add(&seed, sizeof(seed), 0.0); | ||
691 | } | ||
692 | |||
693 | |||
694 | /* | ||
695 | * entropy command initialisation functions | ||
696 | */ | ||
697 | int | ||
698 | prng_read_commands(char *cmdfilename) | ||
699 | { | ||
700 | FILE *f; | ||
701 | char *cp; | ||
702 | char line[1024]; | ||
703 | char cmd[1024]; | ||
704 | char path[256]; | ||
705 | int linenum; | ||
706 | int num_cmds = 64; | ||
707 | int cur_cmd = 0; | ||
708 | double est; | ||
709 | entropy_source_t *entcmd; | ||
710 | |||
711 | f = fopen(cmdfilename, "r"); | ||
712 | if (!f) { | ||
713 | fatal("couldn't read entropy commands file %.100s: %.100s", | ||
714 | cmdfilename, strerror(errno)); | ||
715 | } | 66 | } |
716 | 67 | ||
717 | entcmd = (entropy_source_t *)xmalloc(num_cmds * sizeof(entropy_source_t)); | 68 | debug3("Seeing PRNG from %s", SSH_RAND_HELPER); |
718 | memset(entcmd, '\0', num_cmds * sizeof(entropy_source_t)); | ||
719 | |||
720 | /* Read in file */ | ||
721 | linenum = 0; | ||
722 | while (fgets(line, sizeof(line), f)) { | ||
723 | int arg; | ||
724 | char *argv; | ||
725 | |||
726 | linenum++; | ||
727 | |||
728 | /* skip leading whitespace, test for blank line or comment */ | ||
729 | cp = line + strspn(line, WHITESPACE); | ||
730 | if ((*cp == 0) || (*cp == '#')) | ||
731 | continue; /* done with this line */ | ||
732 | |||
733 | /* First non-whitespace char should be double quote delimiting */ | ||
734 | /* commandline */ | ||
735 | if (*cp != '"') { | ||
736 | error("bad entropy command, %.100s line %d", cmdfilename, | ||
737 | linenum); | ||
738 | continue; | ||
739 | } | ||
740 | |||
741 | /* first token, command args (incl. argv[0]) in double quotes */ | ||
742 | cp = strtok(cp, "\""); | ||
743 | if (cp == NULL) { | ||
744 | error("missing or bad command string, %.100s line %d -- ignored", | ||
745 | cmdfilename, linenum); | ||
746 | continue; | ||
747 | } | ||
748 | strlcpy(cmd, cp, sizeof(cmd)); | ||
749 | |||
750 | /* second token, full command path */ | ||
751 | if ((cp = strtok(NULL, WHITESPACE)) == NULL) { | ||
752 | error("missing command path, %.100s line %d -- ignored", | ||
753 | cmdfilename, linenum); | ||
754 | continue; | ||
755 | } | ||
756 | |||
757 | /* did configure mark this as dead? */ | ||
758 | if (strncmp("undef", cp, 5) == 0) | ||
759 | continue; | ||
760 | |||
761 | strlcpy(path, cp, sizeof(path)); | ||
762 | 69 | ||
763 | /* third token, entropy rate estimate for this command */ | 70 | if ((devnull = open("/dev/null", O_RDWR)) == -1) |
764 | if ((cp = strtok(NULL, WHITESPACE)) == NULL) { | 71 | fatal("Couldn't open /dev/null: %s", strerror(errno)); |
765 | error("missing entropy estimate, %.100s line %d -- ignored", | 72 | if (pipe(p) == -1) |
766 | cmdfilename, linenum); | 73 | fatal("pipe: %s", strerror(errno)); |
767 | continue; | 74 | |
768 | } | 75 | if ((pid = fork()) == -1) |
769 | est = strtod(cp, &argv); | 76 | fatal("Couldn't fork: %s", strerror(errno)); |
770 | 77 | if (pid == 0) { | |
771 | /* end of line */ | 78 | dup2(devnull, STDIN_FILENO); |
772 | if ((cp = strtok(NULL, WHITESPACE)) != NULL) { | 79 | dup2(p[1], STDOUT_FILENO); |
773 | error("garbage at end of line %d in %.100s -- ignored", linenum, | 80 | /* Keep stderr open for errors */ |
774 | cmdfilename); | 81 | close(p[0]); |
775 | continue; | 82 | close(p[1]); |
776 | } | 83 | close(devnull); |
777 | 84 | ||
778 | /* save the command for debug messages */ | 85 | if (original_uid != original_euid && |
779 | entcmd[cur_cmd].cmdstring = xstrdup(cmd); | 86 | setuid(original_uid) == -1) { |
780 | 87 | fprintf(stderr, "(rand child) setuid: %s\n", | |
781 | /* split the command args */ | 88 | strerror(errno)); |
782 | cp = strtok(cmd, WHITESPACE); | 89 | _exit(1); |
783 | arg = 0; | ||
784 | argv = NULL; | ||
785 | do { | ||
786 | char *s = (char*)xmalloc(strlen(cp) + 1); | ||
787 | strncpy(s, cp, strlen(cp) + 1); | ||
788 | entcmd[cur_cmd].args[arg] = s; | ||
789 | arg++; | ||
790 | } while ((arg < 5) && (cp = strtok(NULL, WHITESPACE))); | ||
791 | |||
792 | if (strtok(NULL, WHITESPACE)) | ||
793 | error("ignored extra command elements (max 5), %.100s line %d", | ||
794 | cmdfilename, linenum); | ||
795 | |||
796 | /* Copy the command path and rate estimate */ | ||
797 | entcmd[cur_cmd].path = xstrdup(path); | ||
798 | entcmd[cur_cmd].rate = est; | ||
799 | |||
800 | /* Initialise other values */ | ||
801 | entcmd[cur_cmd].sticky_badness = 1; | ||
802 | |||
803 | cur_cmd++; | ||
804 | |||
805 | /* If we've filled the array, reallocate it twice the size */ | ||
806 | /* Do this now because even if this we're on the last command, | ||
807 | we need another slot to mark the last entry */ | ||
808 | if (cur_cmd == num_cmds) { | ||
809 | num_cmds *= 2; | ||
810 | entcmd = xrealloc(entcmd, num_cmds * sizeof(entropy_source_t)); | ||
811 | } | 90 | } |
91 | |||
92 | execl(SSH_RAND_HELPER, "ssh-rand-helper", NULL); | ||
93 | fprintf(stderr, "(rand child) Couldn't exec '%s': %s\n", | ||
94 | SSH_RAND_HELPER, strerror(errno)); | ||
95 | _exit(1); | ||
812 | } | 96 | } |
813 | 97 | ||
814 | /* zero the last entry */ | 98 | close(devnull); |
815 | memset(&entcmd[cur_cmd], '\0', sizeof(entropy_source_t)); | 99 | close(p[1]); |
816 | |||
817 | /* trim to size */ | ||
818 | entropy_sources = xrealloc(entcmd, (cur_cmd+1) * sizeof(entropy_source_t)); | ||
819 | |||
820 | debug("Loaded %d entropy commands from %.100s", cur_cmd, cmdfilename); | ||
821 | |||
822 | return (cur_cmd >= MIN_ENTROPY_SOURCES); | ||
823 | } | ||
824 | |||
825 | /* | ||
826 | * Write a keyfile at exit | ||
827 | */ | ||
828 | void | ||
829 | prng_seed_cleanup(void *junk) | ||
830 | { | ||
831 | prng_write_seedfile(); | ||
832 | } | ||
833 | |||
834 | /* | ||
835 | * Conditionally Seed OpenSSL's random number pool from | ||
836 | * syscalls and program output | ||
837 | */ | ||
838 | void | ||
839 | seed_rng(void) | ||
840 | { | ||
841 | mysig_t old_sigchld_handler; | ||
842 | |||
843 | if (!prng_initialised) | ||
844 | fatal("RNG not initialised"); | ||
845 | 100 | ||
846 | /* Make sure some other sigchld handler doesn't reap our entropy */ | 101 | memset(buf, '\0', sizeof(buf)); |
847 | /* commands */ | 102 | ret = atomicio(read, p[0], buf, sizeof(buf)); |
848 | old_sigchld_handler = mysignal(SIGCHLD, SIG_DFL); | 103 | if (ret == -1) |
104 | fatal("Couldn't read from ssh-rand-helper: %s", | ||
105 | strerror(errno)); | ||
106 | if (ret != sizeof(buf)) | ||
107 | fatal("ssh-rand-helper child produced insufficient data"); | ||
849 | 108 | ||
850 | debug("Seeded RNG with %i bytes from programs", | 109 | close(p[0]); |
851 | (int)stir_from_programs()); | ||
852 | debug("Seeded RNG with %i bytes from system calls", | ||
853 | (int)stir_from_system()); | ||
854 | 110 | ||
855 | if (!RAND_status()) | 111 | if (waitpid(pid, &ret, 0) == -1) |
856 | fatal("Not enough entropy in RNG"); | 112 | fatal("Couldn't wait for ssh-rand-helper completion: %s", |
113 | strerror(errno)); | ||
857 | 114 | ||
858 | mysignal(SIGCHLD, old_sigchld_handler); | 115 | /* We don't mind if the child exits upon a SIGPIPE */ |
116 | if (!WIFEXITED(ret) && | ||
117 | (!WIFSIGNALED(ret) || WTERMSIG(ret) != SIGPIPE)) | ||
118 | fatal("ssh-rand-helper terminated abnormally"); | ||
119 | if (WEXITSTATUS(ret) != 0) | ||
120 | fatal("ssh-rand-helper exit with exit status %d", ret); | ||
859 | 121 | ||
860 | if (!RAND_status()) | 122 | RAND_add(buf, sizeof(buf), sizeof(buf)); |
861 | fatal("Couldn't initialise builtin random number generator -- exiting."); | 123 | memset(buf, '\0', sizeof(buf)); |
862 | } | 124 | } |
863 | 125 | ||
864 | void | 126 | void |
865 | init_rng(void) | 127 | init_rng(void) |
866 | { | 128 | { |
867 | int original_euid; | ||
868 | |||
869 | check_openssl_version(); | ||
870 | |||
871 | original_uid = getuid(); | ||
872 | original_euid = geteuid(); | ||
873 | |||
874 | /* Read in collection commands */ | ||
875 | if (!prng_read_commands(SSH_PRNG_COMMAND_FILE)) | ||
876 | fatal("PRNG initialisation failed -- exiting."); | ||
877 | |||
878 | /* Set ourselves up to save a seed upon exit */ | ||
879 | prng_seed_saved = 0; | ||
880 | |||
881 | /* Give up privs while reading seed file */ | ||
882 | #ifdef SAVED_IDS_WORK_WITH_SETEUID | ||
883 | if ((original_uid != original_euid) && (seteuid(original_uid) == -1)) | ||
884 | fatal("Couldn't give up privileges"); | ||
885 | #else /* SAVED_IDS_WORK_WITH_SETEUID */ | ||
886 | /* | 129 | /* |
887 | * Propagate the privileged uid to all of our uids. | 130 | * OpenSSL version numbers: MNNFFPPS: major minor fix patch status |
888 | * Set the effective uid to the given (unprivileged) uid. | 131 | * We match major, minor, fix and status (not patch) |
889 | */ | 132 | */ |
890 | if (original_uid != original_euid && (setuid(original_euid) == -1 || | 133 | if ((SSLeay() ^ OPENSSL_VERSION_NUMBER) & ~0xff0L) |
891 | seteuid(original_uid) == -1)) | 134 | fatal("OpenSSL version mismatch. Built against %lx, you " |
892 | fatal("Couldn't give up privileges"); | 135 | "have %lx", OPENSSL_VERSION_NUMBER, SSLeay()); |
893 | #endif /* SAVED_IDS_WORK_WITH_SETEUID */ | ||
894 | |||
895 | prng_read_seedfile(); | ||
896 | |||
897 | #ifdef SAVED_IDS_WORK_WITH_SETEUID | ||
898 | if ((original_uid != original_euid) && (seteuid(original_euid) == -1)) | ||
899 | fatal("Couldn't restore privileges"); | ||
900 | #else /* SAVED_IDS_WORK_WITH_SETEUID */ | ||
901 | /* | ||
902 | * We are unable to restore the real uid to its unprivileged value. | ||
903 | * Propagate the real uid (usually more privileged) to effective uid | ||
904 | * as well. | ||
905 | */ | ||
906 | if (original_uid != original_euid && (seteuid(original_euid) == -1 || | ||
907 | setuid(original_uid) == -1)) | ||
908 | fatal("Couldn't restore privileges"); | ||
909 | #endif /* SAVED_IDS_WORK_WITH_SETEUID */ | ||
910 | |||
911 | fatal_add_cleanup(prng_seed_cleanup, NULL); | ||
912 | atexit(prng_write_seedfile); | ||
913 | 136 | ||
914 | prng_initialised = 1; | 137 | if ((original_uid = getuid()) == -1) |
138 | fatal("getuid: %s", strerror(errno)); | ||
139 | if ((original_euid = geteuid()) == -1) | ||
140 | fatal("geteuid: %s", strerror(errno)); | ||
915 | } | 141 | } |
916 | |||
917 | #endif /* defined(USE_PRNGD) || defined(RANDOM_POOL) */ | ||