xref: /freebsd-11-stable/contrib/ntp/lib/isc/unix/entropy.c (revision 416ba5c74546f32a993436a99516d35008e9f384)
1 /*
2  * Copyright (C) 2004-2008  Internet Systems Consortium, Inc. ("ISC")
3  * Copyright (C) 2000-2003  Internet Software Consortium.
4  *
5  * Permission to use, copy, modify, and/or distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15  * PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 /* $Id: entropy.c,v 1.82 2008/12/01 23:47:45 tbox Exp $ */
19 
20 /* \file unix/entropy.c
21  * \brief
22  * This is the system dependent part of the ISC entropy API.
23  */
24 
25 #include <config.h>
26 
27 #include <sys/param.h>	/* Openserver 5.0.6A and FD_SETSIZE */
28 #include <sys/types.h>
29 #include <sys/time.h>
30 #include <sys/stat.h>
31 #include <sys/socket.h>
32 #include <sys/un.h>
33 
34 #ifdef HAVE_NANOSLEEP
35 #include <time.h>
36 #endif
37 #include <unistd.h>
38 
39 #include <isc/platform.h>
40 #include <isc/strerror.h>
41 
42 #ifdef ISC_PLATFORM_NEEDSYSSELECTH
43 #include <sys/select.h>
44 #endif
45 
46 #include "errno2result.h"
47 
48 /*%
49  * There is only one variable in the entropy data structures that is not
50  * system independent, but pulling the structure that uses it into this file
51  * ultimately means pulling several other independent structures here also to
52  * resolve their interdependencies.  Thus only the problem variable's type
53  * is defined here.
54  */
55 #define FILESOURCE_HANDLE_TYPE	int
56 
57 typedef struct {
58 	int	handle;
59 	enum	{
60 		isc_usocketsource_disconnected,
61 		isc_usocketsource_connecting,
62 		isc_usocketsource_connected,
63 		isc_usocketsource_ndesired,
64 		isc_usocketsource_wrote,
65 		isc_usocketsource_reading
66 	} status;
67 	size_t	sz_to_recv;
68 } isc_entropyusocketsource_t;
69 
70 #include "../entropy.c"
71 
72 static unsigned int
get_from_filesource(isc_entropysource_t * source,isc_uint32_t desired)73 get_from_filesource(isc_entropysource_t *source, isc_uint32_t desired) {
74 	isc_entropy_t *ent = source->ent;
75 	unsigned char buf[128];
76 	int fd = source->sources.file.handle;
77 	ssize_t n, ndesired;
78 	unsigned int added;
79 
80 	if (source->bad)
81 		return (0);
82 
83 	desired = desired / 8 + (((desired & 0x07) > 0) ? 1 : 0);
84 
85 	added = 0;
86 	while (desired > 0) {
87 		ndesired = ISC_MIN(desired, sizeof(buf));
88 		n = read(fd, buf, ndesired);
89 		if (n < 0) {
90 			if (errno == EAGAIN || errno == EINTR)
91 				goto out;
92 			goto err;
93 		}
94 		if (n == 0)
95 			goto err;
96 
97 		entropypool_adddata(ent, buf, n, n * 8);
98 		added += n * 8;
99 		desired -= n;
100 	}
101 	goto out;
102 
103  err:
104 	(void)close(fd);
105 	source->sources.file.handle = -1;
106 	source->bad = ISC_TRUE;
107 
108  out:
109 	return (added);
110 }
111 
112 static unsigned int
get_from_usocketsource(isc_entropysource_t * source,isc_uint32_t desired)113 get_from_usocketsource(isc_entropysource_t *source, isc_uint32_t desired) {
114 	isc_entropy_t *ent = source->ent;
115 	unsigned char buf[128];
116 	int fd = source->sources.usocket.handle;
117 	ssize_t n = 0, ndesired;
118 	unsigned int added;
119 	size_t sz_to_recv = source->sources.usocket.sz_to_recv;
120 
121 	if (source->bad)
122 		return (0);
123 
124 	desired = desired / 8 + (((desired & 0x07) > 0) ? 1 : 0);
125 
126 	added = 0;
127 	while (desired > 0) {
128 		ndesired = ISC_MIN(desired, sizeof(buf));
129  eagain_loop:
130 
131 		switch ( source->sources.usocket.status ) {
132 		case isc_usocketsource_ndesired:
133 			buf[0] = ndesired;
134 			if ((n = sendto(fd, buf, 1, 0, NULL, 0)) < 0) {
135 				if (errno == EWOULDBLOCK || errno == EINTR ||
136 				    errno == ECONNRESET)
137 					goto out;
138 				goto err;
139 			}
140 			INSIST(n == 1);
141 			source->sources.usocket.status =
142 						isc_usocketsource_wrote;
143 			goto eagain_loop;
144 
145 		case isc_usocketsource_connecting:
146 		case isc_usocketsource_connected:
147 			buf[0] = 1;
148 			buf[1] = ndesired;
149 			if ((n = sendto(fd, buf, 2, 0, NULL, 0)) < 0) {
150 				if (errno == EWOULDBLOCK || errno == EINTR ||
151 				    errno == ECONNRESET)
152 					goto out;
153 				goto err;
154 			}
155 			if (n == 1) {
156 				source->sources.usocket.status =
157 					isc_usocketsource_ndesired;
158 				goto eagain_loop;
159 			}
160 			INSIST(n == 2);
161 			source->sources.usocket.status =
162 						isc_usocketsource_wrote;
163 			/*FALLTHROUGH*/
164 
165 		case isc_usocketsource_wrote:
166 			if (recvfrom(fd, buf, 1, 0, NULL, NULL) != 1) {
167 				if (errno == EAGAIN) {
168 					/*
169 					 * The problem of EAGAIN (try again
170 					 * later) is a major issue on HP-UX.
171 					 * Solaris actually tries the recvfrom
172 					 * call again, while HP-UX just dies.
173 					 * This code is an attempt to let the
174 					 * entropy pool fill back up (at least
175 					 * that's what I think the problem is.)
176 					 * We go to eagain_loop because if we
177 					 * just "break", then the "desired"
178 					 * amount gets borked.
179 					 */
180 #ifdef HAVE_NANOSLEEP
181 					struct timespec ts;
182 
183 					ts.tv_sec = 0;
184 					ts.tv_nsec = 1000000;
185 					nanosleep(&ts, NULL);
186 #else
187 					usleep(1000);
188 #endif
189 					goto eagain_loop;
190 				}
191 				if (errno == EWOULDBLOCK || errno == EINTR)
192 					goto out;
193 				goto err;
194 			}
195 			source->sources.usocket.status =
196 					isc_usocketsource_reading;
197 			sz_to_recv = buf[0];
198 			source->sources.usocket.sz_to_recv = sz_to_recv;
199 			if (sz_to_recv > sizeof(buf))
200 				goto err;
201 			/*FALLTHROUGH*/
202 
203 		case isc_usocketsource_reading:
204 			if (sz_to_recv != 0U) {
205 				n = recv(fd, buf, sz_to_recv, 0);
206 				if (n < 0) {
207 					if (errno == EWOULDBLOCK ||
208 					    errno == EINTR)
209 						goto out;
210 					goto err;
211 				}
212 			} else
213 				n = 0;
214 			break;
215 
216 		default:
217 			goto err;
218 		}
219 
220 		if ((size_t)n != sz_to_recv)
221 			source->sources.usocket.sz_to_recv -= n;
222 		else
223 			source->sources.usocket.status =
224 				isc_usocketsource_connected;
225 
226 		if (n == 0)
227 			goto out;
228 
229 		entropypool_adddata(ent, buf, n, n * 8);
230 		added += n * 8;
231 		desired -= n;
232 	}
233 	goto out;
234 
235  err:
236 	close(fd);
237 	source->bad = ISC_TRUE;
238 	source->sources.usocket.status = isc_usocketsource_disconnected;
239 	source->sources.usocket.handle = -1;
240 
241  out:
242 	return (added);
243 }
244 
245 /*
246  * Poll each source, trying to get data from it to stuff into the entropy
247  * pool.
248  */
249 static void
fillpool(isc_entropy_t * ent,unsigned int desired,isc_boolean_t blocking)250 fillpool(isc_entropy_t *ent, unsigned int desired, isc_boolean_t blocking) {
251 	unsigned int added;
252 	unsigned int remaining;
253 	unsigned int needed;
254 	unsigned int nsource;
255 	isc_entropysource_t *source;
256 
257 	REQUIRE(VALID_ENTROPY(ent));
258 
259 	needed = desired;
260 
261 	/*
262 	 * This logic is a little strange, so an explanation is in order.
263 	 *
264 	 * If needed is 0, it means we are being asked to "fill to whatever
265 	 * we think is best."  This means that if we have at least a
266 	 * partially full pool (say, > 1/4th of the pool) we probably don't
267 	 * need to add anything.
268 	 *
269 	 * Also, we will check to see if the "pseudo" count is too high.
270 	 * If it is, try to mix in better data.  Too high is currently
271 	 * defined as 1/4th of the pool.
272 	 *
273 	 * Next, if we are asked to add a specific bit of entropy, make
274 	 * certain that we will do so.  Clamp how much we try to add to
275 	 * (DIGEST_SIZE * 8 < needed < POOLBITS - entropy).
276 	 *
277 	 * Note that if we are in a blocking mode, we will only try to
278 	 * get as much data as we need, not as much as we might want
279 	 * to build up.
280 	 */
281 	if (needed == 0) {
282 		REQUIRE(!blocking);
283 
284 		if ((ent->pool.entropy >= RND_POOLBITS / 4)
285 		    && (ent->pool.pseudo <= RND_POOLBITS / 4))
286 			return;
287 
288 		needed = THRESHOLD_BITS * 4;
289 	} else {
290 		needed = ISC_MAX(needed, THRESHOLD_BITS);
291 		needed = ISC_MIN(needed, RND_POOLBITS);
292 	}
293 
294 	/*
295 	 * In any case, clamp how much we need to how much we can add.
296 	 */
297 	needed = ISC_MIN(needed, RND_POOLBITS - ent->pool.entropy);
298 
299 	/*
300 	 * But wait!  If we're not yet initialized, we need at least
301 	 *	THRESHOLD_BITS
302 	 * of randomness.
303 	 */
304 	if (ent->initialized < THRESHOLD_BITS)
305 		needed = ISC_MAX(needed, THRESHOLD_BITS - ent->initialized);
306 
307 	/*
308 	 * Poll each file source to see if we can read anything useful from
309 	 * it.  XXXMLG When where are multiple sources, we should keep a
310 	 * record of which one we last used so we can start from it (or the
311 	 * next one) to avoid letting some sources build up entropy while
312 	 * others are always drained.
313 	 */
314 
315 	added = 0;
316 	remaining = needed;
317 	if (ent->nextsource == NULL) {
318 		ent->nextsource = ISC_LIST_HEAD(ent->sources);
319 		if (ent->nextsource == NULL)
320 			return;
321 	}
322 	source = ent->nextsource;
323  again_file:
324 	for (nsource = 0; nsource < ent->nsources; nsource++) {
325 		unsigned int got;
326 
327 		if (remaining == 0)
328 			break;
329 
330 		got = 0;
331 
332 		switch ( source->type ) {
333 		case ENTROPY_SOURCETYPE_FILE:
334 			got = get_from_filesource(source, remaining);
335 			break;
336 
337 		case ENTROPY_SOURCETYPE_USOCKET:
338 			got = get_from_usocketsource(source, remaining);
339 			break;
340 		}
341 
342 		added += got;
343 
344 		remaining -= ISC_MIN(remaining, got);
345 
346 		source = ISC_LIST_NEXT(source, link);
347 		if (source == NULL)
348 			source = ISC_LIST_HEAD(ent->sources);
349 	}
350 	ent->nextsource = source;
351 
352 	if (blocking && remaining != 0) {
353 		int fds;
354 
355 		fds = wait_for_sources(ent);
356 		if (fds > 0)
357 			goto again_file;
358 	}
359 
360 	/*
361 	 * Here, if there are bits remaining to be had and we can block,
362 	 * check to see if we have a callback source.  If so, call them.
363 	 */
364 	source = ISC_LIST_HEAD(ent->sources);
365 	while ((remaining != 0) && (source != NULL)) {
366 		unsigned int got;
367 
368 		got = 0;
369 
370 		if (source->type == ENTROPY_SOURCETYPE_CALLBACK)
371 			got = get_from_callback(source, remaining, blocking);
372 
373 		added += got;
374 		remaining -= ISC_MIN(remaining, got);
375 
376 		if (added >= needed)
377 			break;
378 
379 		source = ISC_LIST_NEXT(source, link);
380 	}
381 
382 	/*
383 	 * Mark as initialized if we've added enough data.
384 	 */
385 	if (ent->initialized < THRESHOLD_BITS)
386 		ent->initialized += added;
387 }
388 
389 static int
wait_for_sources(isc_entropy_t * ent)390 wait_for_sources(isc_entropy_t *ent) {
391 	isc_entropysource_t *source;
392 	int maxfd, fd;
393 	int cc;
394 	fd_set reads;
395 	fd_set writes;
396 
397 	maxfd = -1;
398 	FD_ZERO(&reads);
399 	FD_ZERO(&writes);
400 
401 	source = ISC_LIST_HEAD(ent->sources);
402 	while (source != NULL) {
403 		if (source->type == ENTROPY_SOURCETYPE_FILE) {
404 			fd = source->sources.file.handle;
405 			if (fd >= 0) {
406 				maxfd = ISC_MAX(maxfd, fd);
407 				FD_SET(fd, &reads);
408 			}
409 		}
410 		if (source->type == ENTROPY_SOURCETYPE_USOCKET) {
411 			fd = source->sources.usocket.handle;
412 			if (fd >= 0) {
413 				switch (source->sources.usocket.status) {
414 				case isc_usocketsource_disconnected:
415 					break;
416 				case isc_usocketsource_connecting:
417 				case isc_usocketsource_connected:
418 				case isc_usocketsource_ndesired:
419 					maxfd = ISC_MAX(maxfd, fd);
420 					FD_SET(fd, &writes);
421 					break;
422 				case isc_usocketsource_wrote:
423 				case isc_usocketsource_reading:
424 					maxfd = ISC_MAX(maxfd, fd);
425 					FD_SET(fd, &reads);
426 					break;
427 				}
428 			}
429 		}
430 		source = ISC_LIST_NEXT(source, link);
431 	}
432 
433 	if (maxfd < 0)
434 		return (-1);
435 
436 	cc = select(maxfd + 1, &reads, &writes, NULL, NULL);
437 	if (cc < 0)
438 		return (-1);
439 
440 	return (cc);
441 }
442 
443 static void
destroyfilesource(isc_entropyfilesource_t * source)444 destroyfilesource(isc_entropyfilesource_t *source) {
445 	(void)close(source->handle);
446 }
447 
448 static void
destroyusocketsource(isc_entropyusocketsource_t * source)449 destroyusocketsource(isc_entropyusocketsource_t *source) {
450 	close(source->handle);
451 }
452 
453 /*
454  * Make a fd non-blocking
455  */
456 static isc_result_t
make_nonblock(int fd)457 make_nonblock(int fd) {
458 	int ret;
459 	int flags;
460 	char strbuf[ISC_STRERRORSIZE];
461 #ifdef USE_FIONBIO_IOCTL
462 	int on = 1;
463 
464 	ret = ioctl(fd, FIONBIO, (char *)&on);
465 #else
466 	flags = fcntl(fd, F_GETFL, 0);
467 	flags |= PORT_NONBLOCK;
468 	ret = fcntl(fd, F_SETFL, flags);
469 #endif
470 
471 	if (ret == -1) {
472 		isc__strerror(errno, strbuf, sizeof(strbuf));
473 		UNEXPECTED_ERROR(__FILE__, __LINE__,
474 #ifdef USE_FIONBIO_IOCTL
475 				 "ioctl(%d, FIONBIO, &on): %s", fd,
476 #else
477 				 "fcntl(%d, F_SETFL, %d): %s", fd, flags,
478 #endif
479 				 strbuf);
480 
481 		return (ISC_R_UNEXPECTED);
482 	}
483 
484 	return (ISC_R_SUCCESS);
485 }
486 
487 isc_result_t
isc_entropy_createfilesource(isc_entropy_t * ent,const char * fname)488 isc_entropy_createfilesource(isc_entropy_t *ent, const char *fname) {
489 	int fd;
490 	struct stat _stat;
491 	isc_boolean_t is_usocket = ISC_FALSE;
492 	isc_boolean_t is_connected = ISC_FALSE;
493 	isc_result_t ret;
494 	isc_entropysource_t *source;
495 
496 	REQUIRE(VALID_ENTROPY(ent));
497 	REQUIRE(fname != NULL);
498 
499 	LOCK(&ent->lock);
500 
501 	if (stat(fname, &_stat) < 0) {
502 		ret = isc__errno2result(errno);
503 		goto errout;
504 	}
505 	/*
506 	 * Solaris 2.5.1 does not have support for sockets (S_IFSOCK),
507 	 * but it does return type S_IFIFO (the OS believes that
508 	 * the socket is a fifo).  This may be an issue if we tell
509 	 * the program to look at an actual FIFO as its source of
510 	 * entropy.
511 	 */
512 #if defined(S_ISSOCK)
513 	if (S_ISSOCK(_stat.st_mode))
514 		is_usocket = ISC_TRUE;
515 #endif
516 #if defined(S_ISFIFO) && defined(sun)
517 	if (S_ISFIFO(_stat.st_mode))
518 		is_usocket = ISC_TRUE;
519 #endif
520 	if (is_usocket)
521 		fd = socket(PF_UNIX, SOCK_STREAM, 0);
522 	else
523 		fd = open(fname, O_RDONLY | PORT_NONBLOCK, 0);
524 
525 	if (fd < 0) {
526 		ret = isc__errno2result(errno);
527 		goto errout;
528 	}
529 
530 	ret = make_nonblock(fd);
531 	if (ret != ISC_R_SUCCESS)
532 		goto closefd;
533 
534 	if (is_usocket) {
535 		struct sockaddr_un sname;
536 
537 		memset(&sname, 0, sizeof(sname));
538 		sname.sun_family = AF_UNIX;
539 		strncpy(sname.sun_path, fname, sizeof(sname.sun_path));
540 		sname.sun_path[sizeof(sname.sun_path)-1] = '0';
541 #ifdef ISC_PLATFORM_HAVESALEN
542 #if !defined(SUN_LEN)
543 #define SUN_LEN(su) \
544 	(sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
545 #endif
546 		sname.sun_len = SUN_LEN(&sname);
547 #endif
548 
549 		if (connect(fd, (struct sockaddr *) &sname,
550 			    sizeof(struct sockaddr_un)) < 0) {
551 			if (errno != EINPROGRESS) {
552 				ret = isc__errno2result(errno);
553 				goto closefd;
554 			}
555 		} else
556 			is_connected = ISC_TRUE;
557 	}
558 
559 	source = isc_mem_get(ent->mctx, sizeof(isc_entropysource_t));
560 	if (source == NULL) {
561 		ret = ISC_R_NOMEMORY;
562 		goto closefd;
563 	}
564 
565 	/*
566 	 * From here down, no failures can occur.
567 	 */
568 	source->magic = SOURCE_MAGIC;
569 	source->ent = ent;
570 	source->total = 0;
571 	source->bad = ISC_FALSE;
572 	memset(source->name, 0, sizeof(source->name));
573 	ISC_LINK_INIT(source, link);
574 	if (is_usocket) {
575 		source->sources.usocket.handle = fd;
576 		if (is_connected)
577 			source->sources.usocket.status =
578 					isc_usocketsource_connected;
579 		else
580 			source->sources.usocket.status =
581 					isc_usocketsource_connecting;
582 		source->sources.usocket.sz_to_recv = 0;
583 		source->type = ENTROPY_SOURCETYPE_USOCKET;
584 	} else {
585 		source->sources.file.handle = fd;
586 		source->type = ENTROPY_SOURCETYPE_FILE;
587 	}
588 
589 	/*
590 	 * Hook it into the entropy system.
591 	 */
592 	ISC_LIST_APPEND(ent->sources, source, link);
593 	ent->nsources++;
594 
595 	UNLOCK(&ent->lock);
596 	return (ISC_R_SUCCESS);
597 
598  closefd:
599 	(void)close(fd);
600 
601  errout:
602 	UNLOCK(&ent->lock);
603 
604 	return (ret);
605 }
606