diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile index 76c2f1aa17..805db7626b 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 pgp-mpi-internal.c imath.c + fortuna.c random.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 ad942f733a..02ff976c25 100644 --- a/contrib/pgcrypto/internal.c +++ b/contrib/pgcrypto/internal.c @@ -626,6 +626,8 @@ static time_t check_time = 0; static void system_reseed(void) { + uint8 buf[1024]; + int n; time_t t; int skip = 1; @@ -640,34 +642,24 @@ 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 >= SYSTEM_RESEED_CHANCE); - - /* clear 1 byte */ - px_memset(&buf, 0, sizeof(buf)); + px_get_random_bytes(buf, 1); + skip = buf[0] >= SYSTEM_RESEED_CHANCE; } - 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]; + /* clear 1 byte */ + px_memset(buf, 0, sizeof(buf)); - 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)); + if (skip) + return; - seed_time = t; - px_memset(buf, 0, 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)); } int diff --git a/contrib/pgcrypto/random.c b/contrib/pgcrypto/random.c new file mode 100644 index 0000000000..d72679e412 --- /dev/null +++ b/contrib/pgcrypto/random.c @@ -0,0 +1,247 @@ +/* + * 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 44b2212b1d..0ba8530114 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -45,12 +45,6 @@ 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 @@ -541,7 +535,9 @@ ClientAuthentication(Port *port) ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"))); - status = CheckMD5Auth(port, &logdetail); + /* 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); break; case uaPassword: @@ -696,25 +692,10 @@ recv_password_packet(Port *port) /*---------------------------------------------------------------- - * MD5 and password authentication + * MD5 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 4420138673..2d43506cd0 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -358,6 +358,14 @@ 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 @@ -395,6 +403,8 @@ 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); @@ -569,11 +579,9 @@ 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 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. + * 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. */ srandom((unsigned int) (MyProcPid ^ MyStartTime)); @@ -1284,6 +1292,8 @@ 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... @@ -2333,6 +2343,15 @@ 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 */ @@ -3885,12 +3904,7 @@ 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. */ - if (!pg_strong_random(&MyCancelKey, sizeof(MyCancelKey))) - { - ereport(LOG, - (errmsg("could not generate random query cancel key"))); - return STATUS_ERROR; - } + MyCancelKey = PostmasterRandom(); bn->cancel_key = MyCancelKey; /* Pass down canAcceptConnections state */ @@ -4198,6 +4212,13 @@ 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)); @@ -5045,6 +5066,66 @@ 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). @@ -5222,37 +5303,31 @@ StartAutovacuumWorker(void) * we'd better have something random in the field to prevent * unfriendly people from sending cancels to them. */ - if (pg_strong_random(&MyCancelKey, sizeof(MyCancelKey))) + 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) { - 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); + bn->bkend_type = BACKEND_TYPE_AUTOVAC; + dlist_push_head(&BackendList, &bn->elem); #ifdef EXEC_BACKEND - ShmemBackendArrayAdd(bn); + ShmemBackendArrayAdd(bn); #endif - /* all OK */ - return; - } - - /* - * fork failed, fall through to report -- actual error message - * was logged by StartAutoVacWorker - */ - (void) ReleasePostmasterChildSlot(bn->child_slot); + /* all OK */ + return; } - 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 @@ -5540,11 +5615,7 @@ assign_backendlist_entry(RegisteredBgWorker *rw) * have something random in the field to prevent unfriendly people from * sending cancels to them. */ - if (!pg_strong_random(&MyCancelKey, sizeof(MyCancelKey))) - { - rw->rw_crashed_at = GetCurrentTimestamp(); - return false; - } + MyCancelKey = PostmasterRandom(); bn->cancel_key = MyCancelKey; bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot(); diff --git a/src/include/port.h b/src/include/port.h index 4bb9feeb01..b81fa4a89e 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -454,9 +454,6 @@ 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 d34f409ee9..bc9b63add0 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 \ - pg_strong_random.o pgstrcasecmp.o pqsignal.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 deleted file mode 100644 index a404111d74..0000000000 --- a/src/port/pg_strong_random.c +++ /dev/null @@ -1,148 +0,0 @@ -/*------------------------------------------------------------------------- - * - * 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 e6c4aef99f..de764dd4d4 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 pg_strong_random.c qsort.c qsort_arg.c quotes.c system.c + mkdtemp.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', 'pgp-mpi-internal.c', - 'imath.c'); + 'fortuna.c', 'random.c', + 'pgp-mpi-internal.c', 'imath.c'); } $pgcrypto->AddReference($postgres); $pgcrypto->AddLibrary('ws2_32.lib');