Replace PostmasterRandom() with a stronger way of generating randomness.

This adds a new routine, pg_strong_random() for generating random bytes,
for use in both frontend and backend. At the moment, it's only used in
the backend, but the upcoming SCRAM authentication patches need strong
random numbers in libpq as well.

pg_strong_random() is based on, and replaces, the existing implementation
in pgcrypto. It can acquire strong random numbers from a number of sources,
depending on what's available:
- OpenSSL RAND_bytes(), if built with OpenSSL
- On Windows, the native cryptographic functions are used
- /dev/urandom
- /dev/random

Original patch by Magnus Hagander, with further work by Michael Paquier
and me.

Discussion: <CAB7nPqRy3krN8quR9XujMVVHYtXJ0_60nqgVc6oUk8ygyVkZsA@mail.gmail.com>
This commit is contained in:
Heikki Linnakangas 2016-10-17 11:52:50 +03:00
parent 5dfc198146
commit 9e083fd468
9 changed files with 246 additions and 386 deletions

View File

@ -1,7 +1,7 @@
# contrib/pgcrypto/Makefile
INT_SRCS = md5.c sha1.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
fortuna.c random.c pgp-mpi-internal.c imath.c
fortuna.c pgp-mpi-internal.c imath.c
INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c

View File

@ -626,8 +626,6 @@ static time_t check_time = 0;
static void
system_reseed(void)
{
uint8 buf[1024];
int n;
time_t t;
int skip = 1;
@ -642,24 +640,34 @@ system_reseed(void)
else if (check_time == 0 ||
(t - check_time) > SYSTEM_RESEED_CHECK_TIME)
{
uint8 buf;
check_time = t;
/* roll dice */
px_get_random_bytes(buf, 1);
skip = buf[0] >= SYSTEM_RESEED_CHANCE;
px_get_random_bytes(&buf, 1);
skip = (buf >= SYSTEM_RESEED_CHANCE);
/* clear 1 byte */
px_memset(&buf, 0, sizeof(buf));
}
/* clear 1 byte */
px_memset(buf, 0, sizeof(buf));
if (!skip)
{
/*
* fortuna_add_entropy passes the input to SHA-256, so there's no
* point in giving it more than 256 bits of input to begin with.
*/
uint8 buf[32];
if (skip)
return;
if (!pg_strong_random(buf, sizeof(buf)))
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("could not acquire random data")));
fortuna_add_entropy(buf, sizeof(buf));
n = px_acquire_system_randomness(buf);
if (n > 0)
fortuna_add_entropy(buf, n);
seed_time = t;
px_memset(buf, 0, sizeof(buf));
seed_time = t;
px_memset(buf, 0, sizeof(buf));
}
}
int

View File

@ -1,247 +0,0 @@
/*
* random.c
* Acquire randomness from system. For seeding RNG.
*
* Copyright (c) 2001 Marko Kreen
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* contrib/pgcrypto/random.c
*/
#include "postgres.h"
#include "px.h"
#include "utils/memdebug.h"
/* how many bytes to ask from system random provider */
#define RND_BYTES 32
/*
* Try to read from /dev/urandom or /dev/random on these OS'es.
*
* The list can be pretty liberal, as the device not existing
* is expected event.
*/
#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) \
|| defined(__NetBSD__) || defined(__DragonFly__) \
|| defined(__darwin__) || defined(__SOLARIS__) \
|| defined(__hpux) || defined(__HPUX__) \
|| defined(__CYGWIN__) || defined(_AIX)
#define TRY_DEV_RANDOM
#include <fcntl.h>
#include <unistd.h>
static int
safe_read(int fd, void *buf, size_t count)
{
int done = 0;
char *p = buf;
int res;
while (count)
{
res = read(fd, p, count);
if (res <= 0)
{
if (errno == EINTR)
continue;
return PXE_DEV_READ_ERROR;
}
p += res;
done += res;
count -= res;
}
return done;
}
static uint8 *
try_dev_random(uint8 *dst)
{
int fd;
int res;
fd = open("/dev/urandom", O_RDONLY, 0);
if (fd == -1)
{
fd = open("/dev/random", O_RDONLY, 0);
if (fd == -1)
return dst;
}
res = safe_read(fd, dst, RND_BYTES);
close(fd);
if (res > 0)
dst += res;
return dst;
}
#endif
/*
* Try to find randomness on Windows
*/
#ifdef WIN32
#define TRY_WIN32_GENRAND
#define TRY_WIN32_PERFC
#include <windows.h>
#include <wincrypt.h>
/*
* this function is from libtomcrypt
*
* try to use Microsoft crypto API
*/
static uint8 *
try_win32_genrand(uint8 *dst)
{
int res;
HCRYPTPROV h = 0;
res = CryptAcquireContext(&h, NULL, MS_DEF_PROV, PROV_RSA_FULL,
(CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET));
if (!res)
res = CryptAcquireContext(&h, NULL, MS_DEF_PROV, PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET | CRYPT_NEWKEYSET);
if (!res)
return dst;
res = CryptGenRandom(h, RND_BYTES, dst);
if (res == TRUE)
dst += RND_BYTES;
CryptReleaseContext(h, 0);
return dst;
}
static uint8 *
try_win32_perfc(uint8 *dst)
{
int res;
LARGE_INTEGER time;
res = QueryPerformanceCounter(&time);
if (!res)
return dst;
memcpy(dst, &time, sizeof(time));
return dst + sizeof(time);
}
#endif /* WIN32 */
/*
* If we are not on Windows, then hopefully we are
* on a unix-like system. Use the usual suspects
* for randomness.
*/
#ifndef WIN32
#define TRY_UNIXSTD
#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
/*
* Everything here is predictible, only needs some patience.
*
* But there is a chance that the system-specific functions
* did not work. So keep faith and try to slow the attacker down.
*/
static uint8 *
try_unix_std(uint8 *dst)
{
pid_t pid;
int x;
PX_MD *md;
struct timeval tv;
int res;
/* process id */
pid = getpid();
memcpy(dst, (uint8 *) &pid, sizeof(pid));
dst += sizeof(pid);
/* time */
gettimeofday(&tv, NULL);
memcpy(dst, (uint8 *) &tv, sizeof(tv));
dst += sizeof(tv);
/* pointless, but should not hurt */
x = random();
memcpy(dst, (uint8 *) &x, sizeof(x));
dst += sizeof(x);
/* hash of uninitialized stack and heap allocations */
res = px_find_digest("sha1", &md);
if (res >= 0)
{
uint8 *ptr;
uint8 stack[8192];
int alloc = 32 * 1024;
VALGRIND_MAKE_MEM_DEFINED(stack, sizeof(stack));
px_md_update(md, stack, sizeof(stack));
ptr = px_alloc(alloc);
VALGRIND_MAKE_MEM_DEFINED(ptr, alloc);
px_md_update(md, ptr, alloc);
px_free(ptr);
px_md_finish(md, dst);
px_md_free(md);
dst += 20;
}
return dst;
}
#endif
/*
* try to extract some randomness for initial seeding
*
* dst should have room for 1024 bytes.
*/
unsigned
px_acquire_system_randomness(uint8 *dst)
{
uint8 *p = dst;
#ifdef TRY_DEV_RANDOM
p = try_dev_random(p);
#endif
#ifdef TRY_WIN32_GENRAND
p = try_win32_genrand(p);
#endif
#ifdef TRY_WIN32_PERFC
p = try_win32_perfc(p);
#endif
#ifdef TRY_UNIXSTD
p = try_unix_std(p);
#endif
return p - dst;
}

View File

@ -45,6 +45,12 @@ static void auth_failed(Port *port, int status, char *logdetail);
static char *recv_password_packet(Port *port);
static int recv_and_check_password_packet(Port *port, char **logdetail);
/*----------------------------------------------------------------
* MD5 authentication
*----------------------------------------------------------------
*/
static int CheckMD5Auth(Port *port, char **logdetail);
/*----------------------------------------------------------------
* Ident authentication
@ -535,9 +541,7 @@ ClientAuthentication(Port *port)
ereport(FATAL,
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
/* include the salt to use for computing the response */
sendAuthRequest(port, AUTH_REQ_MD5, port->md5Salt, 4);
status = recv_and_check_password_packet(port, &logdetail);
status = CheckMD5Auth(port, &logdetail);
break;
case uaPassword:
@ -692,10 +696,25 @@ recv_password_packet(Port *port)
/*----------------------------------------------------------------
* MD5 authentication
* MD5 and password authentication
*----------------------------------------------------------------
*/
static int
CheckMD5Auth(Port *port, char **logdetail)
{
/* include the salt to use for computing the response */
if (!pg_strong_random(port->md5Salt, sizeof(port->md5Salt)))
{
*logdetail = psprintf(_("Could not generate random salt"));
return STATUS_ERROR;
}
sendAuthRequest(port, AUTH_REQ_MD5, port->md5Salt, 4);
return recv_and_check_password_packet(port, logdetail);
}
/*
* Called when we have sent an authorization request for a password.
* Get the response and check it.

View File

@ -358,14 +358,6 @@ static volatile bool avlauncher_needs_signal = false;
static volatile bool StartWorkerNeeded = true;
static volatile bool HaveCrashedWorker = false;
/*
* State for assigning random salts and cancel keys.
* Also, the global MyCancelKey passes the cancel key assigned to a given
* backend from the postmaster to that backend (via fork).
*/
static unsigned int random_seed = 0;
static struct timeval random_start_time;
#ifdef USE_BONJOUR
static DNSServiceRef bonjour_sdref = NULL;
#endif
@ -403,8 +395,6 @@ static void processCancelRequest(Port *port, void *pkt);
static int initMasks(fd_set *rmask);
static void report_fork_failure_to_client(Port *port, int errnum);
static CAC_state canAcceptConnections(void);
static long PostmasterRandom(void);
static void RandomSalt(char *salt, int len);
static void signal_child(pid_t pid, int signal);
static bool SignalSomeChildren(int signal, int targets);
static void TerminateChildren(int signal);
@ -579,9 +569,11 @@ PostmasterMain(int argc, char *argv[])
* Initialize random(3) so we don't get the same values in every run.
*
* Note: the seed is pretty predictable from externally-visible facts such
* as postmaster start time, so avoid using random() for security-critical
* random values during postmaster startup. At the time of first
* connection, PostmasterRandom will select a hopefully-more-random seed.
* as postmaster start time, so don't use random() for security-critical
* random values (use pg_strong_random() instead). Backends select a
* somewhat more random seed after forking, in BackendRun(), based on the
* PID and session start timestamp, but that is still not suitable for
* security-critical values.
*/
srandom((unsigned int) (MyProcPid ^ MyStartTime));
@ -1292,8 +1284,6 @@ PostmasterMain(int argc, char *argv[])
* Remember postmaster startup time
*/
PgStartTime = GetCurrentTimestamp();
/* PostmasterRandom wants its own copy */
gettimeofday(&random_start_time, NULL);
/*
* We're ready to rock and roll...
@ -2343,15 +2333,6 @@ ConnCreate(int serverFd)
return NULL;
}
/*
* Precompute password salt values to use for this connection. It's
* slightly annoying to do this long in advance of knowing whether we'll
* need 'em or not, but we must do the random() calls before we fork, not
* after. Else the postmaster's random sequence won't get advanced, and
* all backends would end up using the same salt...
*/
RandomSalt(port->md5Salt, sizeof(port->md5Salt));
/*
* Allocate GSSAPI specific state struct
*/
@ -3904,7 +3885,12 @@ BackendStartup(Port *port)
* backend will have its own copy in the forked-off process' value of
* MyCancelKey, so that it can transmit the key to the frontend.
*/
MyCancelKey = PostmasterRandom();
if (!pg_strong_random(&MyCancelKey, sizeof(MyCancelKey)))
{
ereport(LOG,
(errmsg("could not generate random query cancel key")));
return STATUS_ERROR;
}
bn->cancel_key = MyCancelKey;
/* Pass down canAcceptConnections state */
@ -4212,13 +4198,6 @@ BackendRun(Port *port)
int usecs;
int i;
/*
* Don't want backend to be able to see the postmaster random number
* generator state. We have to clobber the static random_seed *and* start
* a new random sequence in the random() library function.
*/
random_seed = 0;
random_start_time.tv_usec = 0;
/* slightly hacky way to convert timestamptz into integers */
TimestampDifference(0, port->SessionStartTime, &secs, &usecs);
srandom((unsigned int) (MyProcPid ^ (usecs << 12) ^ secs));
@ -5066,66 +5045,6 @@ StartupPacketTimeoutHandler(void)
}
/*
* RandomSalt
*/
static void
RandomSalt(char *salt, int len)
{
long rand;
int i;
/*
* We use % 255, sacrificing one possible byte value, so as to ensure that
* all bits of the random() value participate in the result. While at it,
* add one to avoid generating any null bytes.
*/
for (i = 0; i < len; i++)
{
rand = PostmasterRandom();
salt[i] = (rand % 255) + 1;
}
}
/*
* PostmasterRandom
*
* Caution: use this only for values needed during connection-request
* processing. Otherwise, the intended property of having an unpredictable
* delay between random_start_time and random_stop_time will be broken.
*/
static long
PostmasterRandom(void)
{
/*
* Select a random seed at the time of first receiving a request.
*/
if (random_seed == 0)
{
do
{
struct timeval random_stop_time;
gettimeofday(&random_stop_time, NULL);
/*
* We are not sure how much precision is in tv_usec, so we swap
* the high and low 16 bits of 'random_stop_time' and XOR them
* with 'random_start_time'. On the off chance that the result is
* 0, we loop until it isn't.
*/
random_seed = random_start_time.tv_usec ^
((random_stop_time.tv_usec << 16) |
((random_stop_time.tv_usec >> 16) & 0xffff));
}
while (random_seed == 0);
srandom(random_seed);
}
return random();
}
/*
* Count up number of child processes of specified types (dead_end chidren
* are always excluded).
@ -5303,31 +5222,37 @@ StartAutovacuumWorker(void)
* we'd better have something random in the field to prevent
* unfriendly people from sending cancels to them.
*/
MyCancelKey = PostmasterRandom();
bn->cancel_key = MyCancelKey;
/* Autovac workers are not dead_end and need a child slot */
bn->dead_end = false;
bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
bn->bgworker_notify = false;
bn->pid = StartAutoVacWorker();
if (bn->pid > 0)
if (pg_strong_random(&MyCancelKey, sizeof(MyCancelKey)))
{
bn->bkend_type = BACKEND_TYPE_AUTOVAC;
dlist_push_head(&BackendList, &bn->elem);
#ifdef EXEC_BACKEND
ShmemBackendArrayAdd(bn);
#endif
/* all OK */
return;
}
bn->cancel_key = MyCancelKey;
/* Autovac workers are not dead_end and need a child slot */
bn->dead_end = false;
bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
bn->bgworker_notify = false;
bn->pid = StartAutoVacWorker();
if (bn->pid > 0)
{
bn->bkend_type = BACKEND_TYPE_AUTOVAC;
dlist_push_head(&BackendList, &bn->elem);
#ifdef EXEC_BACKEND
ShmemBackendArrayAdd(bn);
#endif
/* all OK */
return;
}
/*
* fork failed, fall through to report -- actual error message
* was logged by StartAutoVacWorker
*/
(void) ReleasePostmasterChildSlot(bn->child_slot);
}
else
ereport(LOG,
(errmsg("could not generate random query cancel key")));
/*
* fork failed, fall through to report -- actual error message was
* logged by StartAutoVacWorker
*/
(void) ReleasePostmasterChildSlot(bn->child_slot);
free(bn);
}
else
@ -5615,7 +5540,11 @@ assign_backendlist_entry(RegisteredBgWorker *rw)
* have something random in the field to prevent unfriendly people from
* sending cancels to them.
*/
MyCancelKey = PostmasterRandom();
if (!pg_strong_random(&MyCancelKey, sizeof(MyCancelKey)))
{
rw->rw_crashed_at = GetCurrentTimestamp();
return false;
}
bn->cancel_key = MyCancelKey;
bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();

View File

@ -454,6 +454,9 @@ extern int pg_codepage_to_encoding(UINT cp);
extern char *inet_net_ntop(int af, const void *src, int bits,
char *dst, size_t size);
/* port/pg_strong_random.c */
extern bool pg_strong_random(void *buf, size_t len);
/* port/pgcheckdir.c */
extern int pg_check_dir(const char *dir);

View File

@ -32,7 +32,7 @@ LIBS += $(PTHREAD_LIBS)
OBJS = $(LIBOBJS) $(PG_CRC32C_OBJS) chklocale.o erand48.o inet_net_ntop.o \
noblock.o path.o pgcheckdir.o pgmkdirp.o pgsleep.o \
pgstrcasecmp.o pqsignal.o \
pg_strong_random.o pgstrcasecmp.o pqsignal.o \
qsort.o qsort_arg.o quotes.o sprompt.o tar.o thread.o
# foo_srv.o and foo.o are both built from foo.c, but only foo.o has -DFRONTEND

148
src/port/pg_strong_random.c Normal file
View File

@ -0,0 +1,148 @@
/*-------------------------------------------------------------------------
*
* pg_strong_random.c
* pg_strong_random() function to return a strong random number
*
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
*
*
* IDENTIFICATION
* src/port/pg_strong_random.c
*
*-------------------------------------------------------------------------
*/
#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif
#include <fcntl.h>
#include <unistd.h>
#ifdef USE_SSL
#include <openssl/rand.h>
#endif
#ifdef WIN32
#include <Wincrypt.h>
#endif
static bool random_from_file(char *filename, void *buf, size_t len);
#ifdef WIN32
/*
* Cache a global crypto provider that only gets freed when the process
* exits, in case we need random numbers more than once.
*/
static HCRYPTPROV hProvider = 0;
#endif
/*
* Read (random) bytes from a file.
*/
static bool
random_from_file(char *filename, void *buf, size_t len)
{
int f;
char *p = buf;
ssize_t res;
f = open(filename, O_RDONLY, 0);
if (f == -1)
return false;
while (len)
{
res = read(f, p, len);
if (res <= 0)
{
if (errno == EINTR)
continue; /* interrupted by signal, just retry */
close(f);
return false;
}
p += res;
len -= res;
}
close(f);
return true;
}
/*
* pg_strong_random
*
* Generate requested number of random bytes. The bytes are
* cryptographically strong random, suitable for use e.g. in key
* generation.
*
* The bytes can be acquired from a number of sources, depending
* on what's available. We try the following, in this order:
*
* 1. OpenSSL's RAND_bytes()
* 2. Windows' CryptGenRandom() function
* 3. /dev/urandom
* 4. /dev/random
*
* Returns true on success, and false if none of the sources
* were available. NB: It is important to check the return value!
* Proceeding with key generation when no random data was available
* would lead to predictable keys and security issues.
*/
bool
pg_strong_random(void *buf, size_t len)
{
#ifdef USE_SSL
/*
* When built with OpenSSL, first try the random generation function from
* there.
*/
if (RAND_bytes(buf, len) == 1)
return true;
#endif
#ifdef WIN32
/*
* Windows has CryptoAPI for strong cryptographic numbers.
*/
if (hProvider == 0)
{
if (!CryptAcquireContext(&hProvider,
NULL,
MS_DEF_PROV,
PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
{
/*
* On failure, set back to 0 in case the value was for some reason
* modified.
*/
hProvider = 0;
}
}
/* Re-check in case we just retrieved the provider */
if (hProvider != 0)
{
if (CryptGenRandom(hProvider, len, buf))
return true;
}
#endif
/*
* If there is no OpenSSL and no CryptoAPI (or they didn't work), then
* fall back on reading /dev/urandom or even /dev/random.
*/
if (random_from_file("/dev/urandom", buf, len))
return true;
if (random_from_file("/dev/random", buf, len))
return true;
/* None of the sources were available. */
return false;
}

View File

@ -92,7 +92,7 @@ sub mkvcbuild
srandom.c getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
erand48.c snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
pgcheckdir.c pgmkdirp.c pgsleep.c pgstrcasecmp.c pqsignal.c
mkdtemp.c qsort.c qsort_arg.c quotes.c system.c
mkdtemp.c pg_strong_random.c qsort.c qsort_arg.c quotes.c system.c
sprompt.c tar.c thread.c getopt.c getopt_long.c dirent.c
win32env.c win32error.c win32security.c win32setlocale.c);
@ -425,8 +425,8 @@ sub mkvcbuild
'sha1.c', 'sha2.c',
'internal.c', 'internal-sha2.c',
'blf.c', 'rijndael.c',
'fortuna.c', 'random.c',
'pgp-mpi-internal.c', 'imath.c');
'fortuna.c', 'pgp-mpi-internal.c',
'imath.c');
}
$pgcrypto->AddReference($postgres);
$pgcrypto->AddLibrary('ws2_32.lib');