gecko-dev/security/nss/lib/freebl/unix_rand.c

899 строки
21 KiB
C

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include "secrng.h"
#include "secerr.h"
#include "prerror.h"
#include "prthread.h"
#include "prprf.h"
#include "prenv.h"
size_t RNG_FileUpdate(const char *fileName, size_t limit);
/*
* When copying data to the buffer we want the least signicant bytes
* from the input since those bits are changing the fastest. The address
* of least significant byte depends upon whether we are running on
* a big-endian or little-endian machine.
*
* Does this mean the least signicant bytes are the most significant
* to us? :-)
*/
static size_t
CopyLowBits(void *dst, size_t dstlen, void *src, size_t srclen)
{
union endianness {
PRInt32 i;
char c[4];
} u;
if (srclen <= dstlen) {
memcpy(dst, src, srclen);
return srclen;
}
u.i = 0x01020304;
if (u.c[0] == 0x01) {
/* big-endian case */
memcpy(dst, (char *)src + (srclen - dstlen), dstlen);
} else {
/* little-endian case */
memcpy(dst, src, dstlen);
}
return dstlen;
}
#ifdef SOLARIS
#include <kstat.h>
static const PRUint32 entropy_buf_len = 4096; /* buffer up to 4 KB */
/* Buffer entropy data, and feed it to the RNG, entropy_buf_len bytes at a time.
* Returns error if RNG_RandomUpdate fails. Also increments *total_fed
* by the number of bytes successfully buffered.
*/
static SECStatus
BufferEntropy(char *inbuf, PRUint32 inlen,
char *entropy_buf, PRUint32 *entropy_buffered,
PRUint32 *total_fed)
{
PRUint32 tocopy = 0;
PRUint32 avail = 0;
SECStatus rv = SECSuccess;
while (inlen) {
avail = entropy_buf_len - *entropy_buffered;
if (!avail) {
/* Buffer is full, time to feed it to the RNG. */
rv = RNG_RandomUpdate(entropy_buf, entropy_buf_len);
if (SECSuccess != rv) {
break;
}
*entropy_buffered = 0;
avail = entropy_buf_len;
}
tocopy = PR_MIN(avail, inlen);
memcpy(entropy_buf + *entropy_buffered, inbuf, tocopy);
*entropy_buffered += tocopy;
inlen -= tocopy;
inbuf += tocopy;
*total_fed += tocopy;
}
return rv;
}
/* Feed kernel statistics structures and ks_data field to the RNG.
* Returns status as well as the number of bytes successfully fed to the RNG.
*/
static SECStatus
RNG_kstat(PRUint32 *fed)
{
kstat_ctl_t *kc = NULL;
kstat_t *ksp = NULL;
PRUint32 entropy_buffered = 0;
char *entropy_buf = NULL;
SECStatus rv = SECSuccess;
PORT_Assert(fed);
if (!fed) {
return SECFailure;
}
*fed = 0;
kc = kstat_open();
PORT_Assert(kc);
if (!kc) {
return SECFailure;
}
entropy_buf = (char *)PORT_Alloc(entropy_buf_len);
PORT_Assert(entropy_buf);
if (entropy_buf) {
for (ksp = kc->kc_chain; ksp != NULL; ksp = ksp->ks_next) {
if (-1 == kstat_read(kc, ksp, NULL)) {
/* missing data from a single kstat shouldn't be fatal */
continue;
}
rv = BufferEntropy((char *)ksp, sizeof(kstat_t),
entropy_buf, &entropy_buffered,
fed);
if (SECSuccess != rv) {
break;
}
if (ksp->ks_data && ksp->ks_data_size > 0 && ksp->ks_ndata > 0) {
rv = BufferEntropy((char *)ksp->ks_data, ksp->ks_data_size,
entropy_buf, &entropy_buffered,
fed);
if (SECSuccess != rv) {
break;
}
}
}
if (SECSuccess == rv && entropy_buffered) {
/* Buffer is not empty, time to feed it to the RNG */
rv = RNG_RandomUpdate(entropy_buf, entropy_buffered);
}
PORT_Free(entropy_buf);
} else {
rv = SECFailure;
}
if (kstat_close(kc)) {
PORT_Assert(0);
rv = SECFailure;
}
return rv;
}
#endif
#if defined(SCO) || defined(UNIXWARE) || defined(BSDI) || defined(FREEBSD) || defined(NETBSD) || defined(DARWIN) || defined(OPENBSD) || defined(NTO) || defined(__riscos__) || defined(__GNU__) || defined(__FreeBSD_kernel__) || defined(__NetBSD_kernel__)
#include <sys/times.h>
static size_t
GetHighResClock(void *buf, size_t maxbytes)
{
int ticks;
struct tms buffer;
ticks = times(&buffer);
return CopyLowBits(buf, maxbytes, &ticks, sizeof(ticks));
}
static void
GiveSystemInfo(void)
{
long si;
/*
* Is this really necessary? Why not use rand48 or something?
*/
si = sysconf(_SC_CHILD_MAX);
RNG_RandomUpdate(&si, sizeof(si));
si = sysconf(_SC_STREAM_MAX);
RNG_RandomUpdate(&si, sizeof(si));
si = sysconf(_SC_OPEN_MAX);
RNG_RandomUpdate(&si, sizeof(si));
}
#endif
#if defined(__sun)
#if defined(__svr4) || defined(SVR4)
#include <sys/systeminfo.h>
static void
GiveSystemInfo(void)
{
int rv;
char buf[2000];
rv = sysinfo(SI_MACHINE, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
rv = sysinfo(SI_RELEASE, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
rv = sysinfo(SI_HW_SERIAL, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
}
static size_t
GetHighResClock(void *buf, size_t maxbytes)
{
hrtime_t t;
t = gethrtime();
if (t) {
return CopyLowBits(buf, maxbytes, &t, sizeof(t));
}
return 0;
}
#else /* SunOS (Sun, but not SVR4) */
extern long sysconf(int name);
static size_t
GetHighResClock(void *buf, size_t maxbytes)
{
return 0;
}
static void
GiveSystemInfo(void)
{
long si;
/* This is not very good */
si = sysconf(_SC_CHILD_MAX);
RNG_RandomUpdate(&si, sizeof(si));
}
#endif
#endif /* Sun */
#if defined(__hpux)
#include <sys/unistd.h>
#if defined(__ia64)
#include <ia64/sys/inline.h>
static size_t
GetHighResClock(void *buf, size_t maxbytes)
{
PRUint64 t;
t = _Asm_mov_from_ar(_AREG44);
return CopyLowBits(buf, maxbytes, &t, sizeof(t));
}
#else
static size_t
GetHighResClock(void *buf, size_t maxbytes)
{
extern int ret_cr16();
int cr16val;
cr16val = ret_cr16();
return CopyLowBits(buf, maxbytes, &cr16val, sizeof(cr16val));
}
#endif
static void
GiveSystemInfo(void)
{
long si;
/* This is not very good */
si = sysconf(_AES_OS_VERSION);
RNG_RandomUpdate(&si, sizeof(si));
si = sysconf(_SC_CPU_VERSION);
RNG_RandomUpdate(&si, sizeof(si));
}
#endif /* HPUX */
#if defined(OSF1)
#include <sys/types.h>
#include <sys/sysinfo.h>
#include <sys/systeminfo.h>
#include <c_asm.h>
static void
GiveSystemInfo(void)
{
char buf[BUFSIZ];
int rv;
int off = 0;
rv = sysinfo(SI_MACHINE, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
rv = sysinfo(SI_RELEASE, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
rv = sysinfo(SI_HW_SERIAL, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
}
/*
* Use the "get the cycle counter" instruction on the alpha.
* The low 32 bits completely turn over in less than a minute.
* The high 32 bits are some non-counter gunk that changes sometimes.
*/
static size_t
GetHighResClock(void *buf, size_t maxbytes)
{
unsigned long t;
t = asm("rpcc %v0");
return CopyLowBits(buf, maxbytes, &t, sizeof(t));
}
#endif /* Alpha */
#if defined(_IBMR2)
static size_t
GetHighResClock(void *buf, size_t maxbytes)
{
return 0;
}
static void
GiveSystemInfo(void)
{
/* XXX haven't found any yet! */
}
#endif /* IBM R2 */
#if defined(LINUX)
#include <sys/sysinfo.h>
static size_t
GetHighResClock(void *buf, size_t maxbytes)
{
return 0;
}
static void
GiveSystemInfo(void)
{
#ifndef NO_SYSINFO
struct sysinfo si;
if (sysinfo(&si) == 0) {
RNG_RandomUpdate(&si, sizeof(si));
}
#endif
}
#endif /* LINUX */
#if defined(NCR)
#include <sys/utsname.h>
#include <sys/systeminfo.h>
static size_t
GetHighResClock(void *buf, size_t maxbytes)
{
return 0;
}
static void
GiveSystemInfo(void)
{
int rv;
char buf[2000];
rv = sysinfo(SI_MACHINE, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
rv = sysinfo(SI_RELEASE, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
rv = sysinfo(SI_HW_SERIAL, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
}
#endif /* NCR */
#if defined(sgi)
#include <fcntl.h>
#undef PRIVATE
#include <sys/mman.h>
#include <sys/syssgi.h>
#include <sys/immu.h>
#include <sys/systeminfo.h>
#include <sys/utsname.h>
#include <wait.h>
static void
GiveSystemInfo(void)
{
int rv;
char buf[4096];
rv = syssgi(SGI_SYSID, &buf[0]);
if (rv > 0) {
RNG_RandomUpdate(buf, MAXSYSIDSIZE);
}
#ifdef SGI_RDUBLK
rv = syssgi(SGI_RDUBLK, getpid(), &buf[0], sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, sizeof(buf));
}
#endif /* SGI_RDUBLK */
rv = syssgi(SGI_INVENT, SGI_INV_READ, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, sizeof(buf));
}
rv = sysinfo(SI_MACHINE, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
rv = sysinfo(SI_RELEASE, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
rv = sysinfo(SI_HW_SERIAL, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
}
static size_t
GetHighResClock(void *buf, size_t maxbuf)
{
unsigned phys_addr, raddr, cycleval;
static volatile unsigned *iotimer_addr = NULL;
static int tries = 0;
static int cntr_size;
int mfd;
long s0[2];
struct timeval tv;
#ifndef SGI_CYCLECNTR_SIZE
#define SGI_CYCLECNTR_SIZE 165 /* Size user needs to use to read CC */
#endif
if (iotimer_addr == NULL) {
if (tries++ > 1) {
/* Don't keep trying if it didn't work */
return 0;
}
/*
** For SGI machines we can use the cycle counter, if it has one,
** to generate some truly random numbers
*/
phys_addr = syssgi(SGI_QUERY_CYCLECNTR, &cycleval);
if (phys_addr) {
int pgsz = getpagesize();
int pgoffmask = pgsz - 1;
raddr = phys_addr & ~pgoffmask;
mfd = open("/dev/mmem", O_RDONLY);
if (mfd < 0) {
return 0;
}
iotimer_addr = (unsigned *)
mmap(0, pgoffmask, PROT_READ, MAP_PRIVATE, mfd, (int)raddr);
if (iotimer_addr == (void *)-1) {
close(mfd);
iotimer_addr = NULL;
return 0;
}
iotimer_addr = (unsigned *)((__psint_t)iotimer_addr | (phys_addr & pgoffmask));
/*
* The file 'mfd' is purposefully not closed.
*/
cntr_size = syssgi(SGI_CYCLECNTR_SIZE);
if (cntr_size < 0) {
struct utsname utsinfo;
/*
* We must be executing on a 6.0 or earlier system, since the
* SGI_CYCLECNTR_SIZE call is not supported.
*
* The only pre-6.1 platforms with 64-bit counters are
* IP19 and IP21 (Challenge, PowerChallenge, Onyx).
*/
uname(&utsinfo);
if (!strncmp(utsinfo.machine, "IP19", 4) ||
!strncmp(utsinfo.machine, "IP21", 4))
cntr_size = 64;
else
cntr_size = 32;
}
cntr_size /= 8; /* Convert from bits to bytes */
}
}
s0[0] = *iotimer_addr;
if (cntr_size > 4)
s0[1] = *(iotimer_addr + 1);
memcpy(buf, (char *)&s0[0], cntr_size);
return CopyLowBits(buf, maxbuf, &s0, cntr_size);
}
#endif
#if defined(sony)
#include <sys/systeminfo.h>
static size_t
GetHighResClock(void *buf, size_t maxbytes)
{
return 0;
}
static void
GiveSystemInfo(void)
{
int rv;
char buf[2000];
rv = sysinfo(SI_MACHINE, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
rv = sysinfo(SI_RELEASE, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
rv = sysinfo(SI_HW_SERIAL, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
}
#endif /* sony */
#if defined(sinix)
#include <sys/systeminfo.h>
#include <sys/times.h>
int gettimeofday(struct timeval *, struct timezone *);
int gethostname(char *, int);
static size_t
GetHighResClock(void *buf, size_t maxbytes)
{
int ticks;
struct tms buffer;
ticks = times(&buffer);
return CopyLowBits(buf, maxbytes, &ticks, sizeof(ticks));
}
static void
GiveSystemInfo(void)
{
int rv;
char buf[2000];
rv = sysinfo(SI_MACHINE, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
rv = sysinfo(SI_RELEASE, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
rv = sysinfo(SI_HW_SERIAL, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
}
#endif /* sinix */
#ifdef BEOS
#include <be/kernel/OS.h>
static size_t
GetHighResClock(void *buf, size_t maxbytes)
{
bigtime_t bigtime; /* Actually a int64 */
bigtime = real_time_clock_usecs();
return CopyLowBits(buf, maxbytes, &bigtime, sizeof(bigtime));
}
static void
GiveSystemInfo(void)
{
system_info *info = NULL;
PRInt32 val;
get_system_info(info);
if (info) {
val = info->boot_time;
RNG_RandomUpdate(&val, sizeof(val));
val = info->used_pages;
RNG_RandomUpdate(&val, sizeof(val));
val = info->used_ports;
RNG_RandomUpdate(&val, sizeof(val));
val = info->used_threads;
RNG_RandomUpdate(&val, sizeof(val));
val = info->used_teams;
RNG_RandomUpdate(&val, sizeof(val));
}
}
#endif /* BEOS */
#if defined(nec_ews)
#include <sys/systeminfo.h>
static size_t
GetHighResClock(void *buf, size_t maxbytes)
{
return 0;
}
static void
GiveSystemInfo(void)
{
int rv;
char buf[2000];
rv = sysinfo(SI_MACHINE, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
rv = sysinfo(SI_RELEASE, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
rv = sysinfo(SI_HW_SERIAL, buf, sizeof(buf));
if (rv > 0) {
RNG_RandomUpdate(buf, rv);
}
}
#endif /* nec_ews */
size_t
RNG_GetNoise(void *buf, size_t maxbytes)
{
struct timeval tv;
int n = 0;
int c;
n = GetHighResClock(buf, maxbytes);
maxbytes -= n;
(void)gettimeofday(&tv, 0);
c = CopyLowBits((char *)buf + n, maxbytes, &tv.tv_usec, sizeof(tv.tv_usec));
n += c;
maxbytes -= c;
c = CopyLowBits((char *)buf + n, maxbytes, &tv.tv_sec, sizeof(tv.tv_sec));
n += c;
return n;
}
#ifdef DARWIN
#include <TargetConditionals.h>
#if !TARGET_OS_IPHONE
#include <crt_externs.h>
#endif
#endif
void
RNG_SystemInfoForRNG(void)
{
char buf[BUFSIZ];
size_t bytes;
const char *const *cp;
char *randfile;
#ifdef DARWIN
#if TARGET_OS_IPHONE
/* iOS does not expose a way to access environ. */
char **environ = NULL;
#else
char **environ = *_NSGetEnviron();
#endif
#else
extern char **environ;
#endif
#ifdef BEOS
static const char *const files[] = {
"/boot/var/swap",
"/boot/var/log/syslog",
"/boot/var/tmp",
"/boot/home/config/settings",
"/boot/home",
0
};
#else
static const char *const files[] = {
"/etc/passwd",
"/etc/utmp",
"/tmp",
"/var/tmp",
"/usr/tmp",
0
};
#endif
GiveSystemInfo();
bytes = RNG_GetNoise(buf, sizeof(buf));
RNG_RandomUpdate(buf, bytes);
/*
* Pass the C environment and the addresses of the pointers to the
* hash function. This makes the random number function depend on the
* execution environment of the user and on the platform the program
* is running on.
*/
if (environ != NULL) {
cp = (const char *const *)environ;
while (*cp) {
RNG_RandomUpdate(*cp, strlen(*cp));
cp++;
}
RNG_RandomUpdate(environ, (char *)cp - (char *)environ);
}
/* Give in system information */
if (gethostname(buf, sizeof(buf)) == 0) {
RNG_RandomUpdate(buf, strlen(buf));
}
/* grab some data from system's PRNG before any other files. */
bytes = RNG_FileUpdate("/dev/urandom", SYSTEM_RNG_SEED_COUNT);
if (!bytes) {
PORT_SetError(SEC_ERROR_NEED_RANDOM);
}
/* If the user points us to a random file, pass it through the rng */
randfile = PR_GetEnvSecure("NSRANDFILE");
if ((randfile != NULL) && (randfile[0] != '\0')) {
char *randCountString = PR_GetEnvSecure("NSRANDCOUNT");
int randCount = randCountString ? atoi(randCountString) : 0;
if (randCount != 0) {
RNG_FileUpdate(randfile, randCount);
} else {
RNG_FileForRNG(randfile);
}
}
/* pass other files through */
for (cp = files; *cp; cp++)
RNG_FileForRNG(*cp);
#if defined(BSDI) || defined(FREEBSD) || defined(NETBSD) || defined(OPENBSD) || defined(DARWIN) || defined(LINUX) || defined(HPUX)
if (bytes)
return;
#endif
#ifdef SOLARIS
if (!bytes) {
/* On Solaris 8, /dev/urandom isn't available, so we use libkstat. */
PRUint32 kstat_bytes = 0;
if (SECSuccess != RNG_kstat(&kstat_bytes)) {
PORT_Assert(0);
}
bytes += kstat_bytes;
PORT_Assert(bytes);
}
#endif
}
#define TOTAL_FILE_LIMIT 1000000 /* one million */
size_t
RNG_FileUpdate(const char *fileName, size_t limit)
{
FILE *file;
int fd;
int bytes;
size_t fileBytes = 0;
struct stat stat_buf;
unsigned char buffer[BUFSIZ];
static size_t totalFileBytes = 0;
/* suppress valgrind warnings due to holes in struct stat */
memset(&stat_buf, 0, sizeof(stat_buf));
if (stat((char *)fileName, &stat_buf) < 0)
return fileBytes;
RNG_RandomUpdate(&stat_buf, sizeof(stat_buf));
file = fopen(fileName, "r");
if (file != NULL) {
/* Read from the underlying file descriptor directly to bypass stdio
* buffering and avoid reading more bytes than we need from
* /dev/urandom. NOTE: we can't use fread with unbuffered I/O because
* fread may return EOF in unbuffered I/O mode on Android.
*
* Moreover, we read into a buffer of size BUFSIZ, so buffered I/O
* has no performance advantage. */
fd = fileno(file);
/* 'file' was just opened, so this should not fail. */
PORT_Assert(fd != -1);
while (limit > fileBytes && fd != -1) {
bytes = PR_MIN(sizeof buffer, limit - fileBytes);
bytes = read(fd, buffer, bytes);
if (bytes <= 0)
break;
RNG_RandomUpdate(buffer, bytes);
fileBytes += bytes;
totalFileBytes += bytes;
/* after TOTAL_FILE_LIMIT has been reached, only read in first
** buffer of data from each subsequent file.
*/
if (totalFileBytes > TOTAL_FILE_LIMIT)
break;
}
fclose(file);
}
/*
* Pass yet another snapshot of our highest resolution clock into
* the hash function.
*/
bytes = RNG_GetNoise(buffer, sizeof(buffer));
RNG_RandomUpdate(buffer, bytes);
return fileBytes;
}
void
RNG_FileForRNG(const char *fileName)
{
RNG_FileUpdate(fileName, TOTAL_FILE_LIMIT);
}
#define _POSIX_PTHREAD_SEMANTICS
#include <dirent.h>
PRBool
ReadFileOK(char *dir, char *file)
{
struct stat stat_buf;
char filename[PATH_MAX];
int count = snprintf(filename, sizeof filename, "%s/%s", dir, file);
if (count <= 0) {
return PR_FALSE; /* name too long, can't read it anyway */
}
if (stat(filename, &stat_buf) < 0)
return PR_FALSE; /* can't stat, probably can't read it then as well */
return S_ISREG(stat_buf.st_mode) ? PR_TRUE : PR_FALSE;
}
size_t
RNG_SystemRNG(void *dest, size_t maxLen)
{
FILE *file;
int fd;
int bytes;
size_t fileBytes = 0;
unsigned char *buffer = dest;
file = fopen("/dev/urandom", "r");
if (file == NULL) {
PORT_SetError(SEC_ERROR_NEED_RANDOM);
return 0;
}
/* Read from the underlying file descriptor directly to bypass stdio
* buffering and avoid reading more bytes than we need from /dev/urandom.
* NOTE: we can't use fread with unbuffered I/O because fread may return
* EOF in unbuffered I/O mode on Android.
*/
fd = fileno(file);
/* 'file' was just opened, so this should not fail. */
PORT_Assert(fd != -1);
while (maxLen > fileBytes && fd != -1) {
bytes = maxLen - fileBytes;
bytes = read(fd, buffer, bytes);
if (bytes <= 0)
break;
fileBytes += bytes;
buffer += bytes;
}
fclose(file);
if (fileBytes != maxLen) {
PORT_SetError(SEC_ERROR_NEED_RANDOM); /* system RNG failed */
fileBytes = 0;
}
return fileBytes;
}