356 строки
6.6 KiB
C
356 строки
6.6 KiB
C
/*
|
|
* random_seed.c
|
|
*
|
|
* Copyright (c) 2013 Metaparadigm Pte. Ltd.
|
|
* Michael Clark <michael@metaparadigm.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or modify
|
|
* it under the terms of the MIT license. See COPYING for details.
|
|
*
|
|
*/
|
|
|
|
#include "random_seed.h"
|
|
#include "config.h"
|
|
#include "strerror_override.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#ifdef HAVE_BSD_STDLIB_H
|
|
#include <bsd/stdlib.h>
|
|
#endif
|
|
|
|
#define DEBUG_SEED(s)
|
|
|
|
#if defined(__APPLE__) || defined(__unix__) || defined(__linux__)
|
|
#define HAVE_DEV_RANDOM 1
|
|
#endif
|
|
|
|
#ifdef HAVE_ARC4RANDOM
|
|
#undef HAVE_GETRANDOM
|
|
#undef HAVE_DEV_RANDOM
|
|
#undef HAVE_CRYPTGENRANDOM
|
|
#endif
|
|
|
|
#if defined ENABLE_RDRAND
|
|
|
|
/* cpuid */
|
|
|
|
#if defined __GNUC__ && (defined __i386__ || defined __x86_64__)
|
|
#define HAS_X86_CPUID 1
|
|
|
|
static void do_cpuid(int regs[], int h)
|
|
{
|
|
/* clang-format off */
|
|
__asm__ __volatile__("cpuid"
|
|
: "=a"(regs[0]), "=b"(regs[1]), "=c"(regs[2]), "=d"(regs[3])
|
|
: "a"(h));
|
|
/* clang-format on */
|
|
}
|
|
|
|
#elif defined _MSC_VER
|
|
|
|
#define HAS_X86_CPUID 1
|
|
#define do_cpuid __cpuid
|
|
|
|
#endif
|
|
|
|
/* has_rdrand */
|
|
|
|
#if HAS_X86_CPUID
|
|
|
|
static int get_rdrand_seed(void);
|
|
|
|
/* Valid values are -1 (haven't tested), 0 (no), and 1 (yes). */
|
|
static int _has_rdrand = -1;
|
|
|
|
static int has_rdrand(void)
|
|
{
|
|
if (_has_rdrand != -1)
|
|
{
|
|
return _has_rdrand;
|
|
}
|
|
|
|
/* CPUID.01H:ECX.RDRAND[bit 30] == 1 */
|
|
int regs[4];
|
|
do_cpuid(regs, 1);
|
|
if (!(regs[2] & (1 << 30)))
|
|
{
|
|
_has_rdrand = 0;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Some CPUs advertise RDRAND in CPUID, but return 0xFFFFFFFF
|
|
* unconditionally. To avoid locking up later, test RDRAND here. If over
|
|
* 3 trials RDRAND has returned the same value, declare it broken.
|
|
* Example CPUs are AMD Ryzen 3000 series
|
|
* and much older AMD APUs, such as the E1-1500
|
|
* https://github.com/systemd/systemd/issues/11810
|
|
* https://linuxreviews.org/RDRAND_stops_returning_random_values_on_older_AMD_CPUs_after_suspend
|
|
*/
|
|
_has_rdrand = 0;
|
|
int prev = get_rdrand_seed();
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
int temp = get_rdrand_seed();
|
|
if (temp != prev)
|
|
{
|
|
_has_rdrand = 1;
|
|
break;
|
|
}
|
|
|
|
prev = temp;
|
|
}
|
|
|
|
return _has_rdrand;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* get_rdrand_seed - GCC x86 and X64 */
|
|
|
|
#if defined __GNUC__ && (defined __i386__ || defined __x86_64__)
|
|
|
|
#define HAVE_RDRAND 1
|
|
|
|
static int get_rdrand_seed(void)
|
|
{
|
|
DEBUG_SEED("get_rdrand_seed");
|
|
int _eax;
|
|
/* rdrand eax */
|
|
/* clang-format off */
|
|
__asm__ __volatile__("1: .byte 0x0F\n"
|
|
" .byte 0xC7\n"
|
|
" .byte 0xF0\n"
|
|
" jnc 1b;\n"
|
|
: "=a" (_eax));
|
|
/* clang-format on */
|
|
return _eax;
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined _MSC_VER
|
|
|
|
#if _MSC_VER >= 1700
|
|
#define HAVE_RDRAND 1
|
|
|
|
/* get_rdrand_seed - Visual Studio 2012 and above */
|
|
|
|
static int get_rdrand_seed(void)
|
|
{
|
|
DEBUG_SEED("get_rdrand_seed");
|
|
int r;
|
|
while (_rdrand32_step(&r) == 0)
|
|
;
|
|
return r;
|
|
}
|
|
|
|
#elif defined _M_IX86
|
|
#define HAVE_RDRAND 1
|
|
|
|
/* get_rdrand_seed - Visual Studio 2010 and below - x86 only */
|
|
|
|
/* clang-format off */
|
|
static int get_rdrand_seed(void)
|
|
{
|
|
DEBUG_SEED("get_rdrand_seed");
|
|
int _eax;
|
|
retry:
|
|
/* rdrand eax */
|
|
__asm _emit 0x0F __asm _emit 0xC7 __asm _emit 0xF0
|
|
__asm jnc retry
|
|
__asm mov _eax, eax
|
|
return _eax;
|
|
}
|
|
/* clang-format on */
|
|
|
|
#endif
|
|
#endif
|
|
|
|
#endif /* defined ENABLE_RDRAND */
|
|
|
|
#ifdef HAVE_GETRANDOM
|
|
|
|
#include <stdlib.h>
|
|
#ifdef HAVE_SYS_RANDOM_H
|
|
#include <sys/random.h>
|
|
#endif
|
|
|
|
static int get_getrandom_seed(int *seed)
|
|
{
|
|
DEBUG_SEED("get_getrandom_seed");
|
|
|
|
ssize_t ret;
|
|
|
|
do
|
|
{
|
|
ret = getrandom(seed, sizeof(*seed), GRND_NONBLOCK);
|
|
} while ((ret == -1) && (errno == EINTR));
|
|
|
|
if (ret == -1)
|
|
{
|
|
if (errno == ENOSYS) /* syscall not available in kernel */
|
|
return -1;
|
|
if (errno == EAGAIN) /* entropy not yet initialized */
|
|
return -1;
|
|
|
|
fprintf(stderr, "error from getrandom(): %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if (ret != sizeof(*seed))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
#endif /* defined HAVE_GETRANDOM */
|
|
|
|
/* get_dev_random_seed */
|
|
|
|
#ifdef HAVE_DEV_RANDOM
|
|
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#if HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif /* HAVE_UNISTD_H */
|
|
#include <stdlib.h>
|
|
#include <sys/stat.h>
|
|
|
|
static const char *dev_random_file = "/dev/urandom";
|
|
|
|
static int get_dev_random_seed(int *seed)
|
|
{
|
|
DEBUG_SEED("get_dev_random_seed");
|
|
|
|
struct stat buf;
|
|
if (stat(dev_random_file, &buf))
|
|
return -1;
|
|
if ((buf.st_mode & S_IFCHR) == 0)
|
|
return -1;
|
|
|
|
int fd = open(dev_random_file, O_RDONLY);
|
|
if (fd < 0)
|
|
{
|
|
fprintf(stderr, "error opening %s: %s", dev_random_file, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
ssize_t nread = read(fd, seed, sizeof(*seed));
|
|
|
|
close(fd);
|
|
|
|
if (nread != sizeof(*seed))
|
|
{
|
|
fprintf(stderr, "error short read %s: %s", dev_random_file, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* get_cryptgenrandom_seed */
|
|
|
|
#ifdef WIN32
|
|
|
|
#define HAVE_CRYPTGENRANDOM 1
|
|
|
|
/* clang-format off */
|
|
#include <windows.h>
|
|
|
|
/* Caution: these blank lines must remain so clang-format doesn't reorder
|
|
includes to put windows.h after wincrypt.h */
|
|
|
|
#include <wincrypt.h>
|
|
/* clang-format on */
|
|
#ifndef __GNUC__
|
|
#pragma comment(lib, "advapi32.lib")
|
|
#endif
|
|
|
|
static int get_cryptgenrandom_seed(int *seed)
|
|
{
|
|
HCRYPTPROV hProvider = 0;
|
|
DWORD dwFlags = CRYPT_VERIFYCONTEXT;
|
|
|
|
DEBUG_SEED("get_cryptgenrandom_seed");
|
|
|
|
/* WinNT 4 and Win98 do no support CRYPT_SILENT */
|
|
if (LOBYTE(LOWORD(GetVersion())) > 4)
|
|
dwFlags |= CRYPT_SILENT;
|
|
|
|
if (!CryptAcquireContextA(&hProvider, 0, 0, PROV_RSA_FULL, dwFlags))
|
|
{
|
|
fprintf(stderr, "error CryptAcquireContextA 0x%08lx", GetLastError());
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
BOOL ret = CryptGenRandom(hProvider, sizeof(*seed), (BYTE *)seed);
|
|
CryptReleaseContext(hProvider, 0);
|
|
if (!ret)
|
|
{
|
|
fprintf(stderr, "error CryptGenRandom 0x%08lx", GetLastError());
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* get_time_seed */
|
|
|
|
#ifndef HAVE_ARC4RANDOM
|
|
#include <time.h>
|
|
|
|
static int get_time_seed(void)
|
|
{
|
|
DEBUG_SEED("get_time_seed");
|
|
|
|
return (unsigned)time(NULL) * 433494437;
|
|
}
|
|
#endif
|
|
|
|
/* json_c_get_random_seed */
|
|
|
|
int json_c_get_random_seed(void)
|
|
{
|
|
#ifdef OVERRIDE_GET_RANDOM_SEED
|
|
OVERRIDE_GET_RANDOM_SEED;
|
|
#endif
|
|
#if defined HAVE_RDRAND && HAVE_RDRAND
|
|
if (has_rdrand())
|
|
return get_rdrand_seed();
|
|
#endif
|
|
#ifdef HAVE_ARC4RANDOM
|
|
/* arc4random never fails, so use it if it's available */
|
|
return arc4random();
|
|
#else
|
|
#ifdef HAVE_GETRANDOM
|
|
{
|
|
int seed = 0;
|
|
if (get_getrandom_seed(&seed) == 0)
|
|
return seed;
|
|
}
|
|
#endif
|
|
#if defined HAVE_DEV_RANDOM && HAVE_DEV_RANDOM
|
|
{
|
|
int seed = 0;
|
|
if (get_dev_random_seed(&seed) == 0)
|
|
return seed;
|
|
}
|
|
#endif
|
|
#if defined HAVE_CRYPTGENRANDOM && HAVE_CRYPTGENRANDOM
|
|
{
|
|
int seed = 0;
|
|
if (get_cryptgenrandom_seed(&seed) == 0)
|
|
return seed;
|
|
}
|
|
#endif
|
|
return get_time_seed();
|
|
#endif /* !HAVE_ARC4RANDOM */
|
|
}
|