1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
|
/* rndlinux.c - raw random number for OSes with /dev/random
* Copyright (C) 1998, 2001, 2002, 2003, 2007,
* 2009 Free Software Foundation, Inc.
*
* This file is part of Libgcrypt.
*
* Libgcrypt is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* Libgcrypt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_GETTIMEOFDAY
# include <sys/times.h>
#endif
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include "types.h"
#include "g10lib.h"
#include "rand-internal.h"
static int open_device ( const char *name );
static int
set_cloexec_flag (int fd)
{
int oldflags;
oldflags= fcntl (fd, F_GETFD, 0);
if (oldflags < 0)
return oldflags;
oldflags |= FD_CLOEXEC;
return fcntl (fd, F_SETFD, oldflags);
}
/*
* Used to open the /dev/random devices (Linux, xBSD, Solaris (if it exists)).
*/
static int
open_device ( const char *name )
{
int fd;
fd = open ( name, O_RDONLY );
if ( fd == -1 )
log_fatal ("can't open %s: %s\n", name, strerror(errno) );
if (set_cloexec_flag (fd))
log_error ("error setting FD_CLOEXEC on fd %d: %s\n",
fd, strerror (errno));
/* We used to do the following check, however it turned out that this
is not portable since more OSes provide a random device which is
sometimes implemented as another device type.
struct stat sb;
if( fstat( fd, &sb ) )
log_fatal("stat() off %s failed: %s\n", name, strerror(errno) );
if( (!S_ISCHR(sb.st_mode)) && (!S_ISFIFO(sb.st_mode)) )
log_fatal("invalid random device!\n" );
*/
return fd;
}
int
_gcry_rndlinux_gather_random (void (*add)(const void*, size_t,
enum random_origins),
enum random_origins origin,
size_t length, int level )
{
static int fd_urandom = -1;
static int fd_random = -1;
int fd;
int n;
byte buffer[768];
size_t n_hw;
size_t want = length;
size_t last_so_far = 0;
int any_need_entropy = 0;
int delay;
/* First read from a hardware source. However let it account only
for up to 50% of the requested bytes. */
n_hw = _gcry_rndhw_poll_slow (add, origin);
if (n_hw > length/2)
n_hw = length/2;
if (length > 1)
length -= n_hw;
/* Open the requested device. */
if (level >= 2)
{
if( fd_random == -1 )
fd_random = open_device ( NAME_OF_DEV_RANDOM );
fd = fd_random;
}
else
{
if( fd_urandom == -1 )
fd_urandom = open_device ( NAME_OF_DEV_URANDOM );
fd = fd_urandom;
}
/* Enter the read loop. */
delay = 0; /* Start with 0 seconds so that we do no block on the
first iteration and in turn call the progress function
before blocking. To give the OS a better chance to
return with something we will actually use 100ms. */
while (length)
{
fd_set rfds;
struct timeval tv;
int rc;
/* If the system has no limit on the number of file descriptors
and we encounter an fd which is larger than the fd_set size,
we don't use the select at all. The select code is only used
to emit progress messages. A better solution would be to
fall back to poll() if available. */
#ifdef FD_SETSIZE
if (fd < FD_SETSIZE)
#endif
{
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
tv.tv_sec = delay;
tv.tv_usec = delay? 0 : 100000;
if ( !(rc=select(fd+1, &rfds, NULL, NULL, &tv)) )
{
if (!any_need_entropy || last_so_far != (want - length) )
{
last_so_far = want - length;
_gcry_random_progress ("need_entropy", 'X',
(int)last_so_far, (int)want);
any_need_entropy = 1;
}
delay = 3; /* Use 3 seconds henceforth. */
continue;
}
else if( rc == -1 )
{
log_error ("select() error: %s\n", strerror(errno));
if (!delay)
delay = 1; /* Use 1 second if we encounter an error before
we have ever blocked. */
continue;
}
}
do
{
int nbytes = length < sizeof(buffer)? length : sizeof(buffer);
n = read(fd, buffer, nbytes );
if( n >= 0 && n > nbytes )
{
log_error("bogus read from random device (n=%d)\n", n );
n = nbytes;
}
}
while( n == -1 && errno == EINTR );
if ( n == -1 )
log_fatal("read error on random device: %s\n", strerror(errno));
(*add)( buffer, n, origin );
length -= n;
}
memset(buffer, 0, sizeof(buffer) );
if (any_need_entropy)
_gcry_random_progress ("need_entropy", 'X', (int)want, (int)want);
return 0; /* success */
}
|