diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile index 805db7626b..76c2f1aa17 100644 --- a/contrib/pgcrypto/Makefile +++ b/contrib/pgcrypto/Makefile @@ -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 diff --git a/contrib/pgcrypto/internal.c b/contrib/pgcrypto/internal.c index 02ff976c25..ad942f733a 100644 --- a/contrib/pgcrypto/internal.c +++ b/contrib/pgcrypto/internal.c @@ -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 diff --git a/contrib/pgcrypto/random.c b/contrib/pgcrypto/random.c deleted file mode 100644 index d72679e412..0000000000 --- a/contrib/pgcrypto/random.c +++ /dev/null @@ -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 -#include - -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 -#include - -/* - * 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 -#include -#include -#include - -/* - * 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; -} diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 0ba8530114..44b2212b1d 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -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. diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 2d43506cd0..4420138673 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -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(); diff --git a/src/include/port.h b/src/include/port.h index b81fa4a89e..4bb9feeb01 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -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); diff --git a/src/port/Makefile b/src/port/Makefile index bc9b63add0..d34f409ee9 100644 --- a/src/port/Makefile +++ b/src/port/Makefile @@ -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 diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c new file mode 100644 index 0000000000..a404111d74 --- /dev/null +++ b/src/port/pg_strong_random.c @@ -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 +#include + +#ifdef USE_SSL +#include +#endif +#ifdef WIN32 +#include +#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; +} diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index de764dd4d4..e6c4aef99f 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -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');