diff options
author | Tatsuo Ishii | 2025-05-20 00:47:02 +0000 |
---|---|---|
committer | Tatsuo Ishii | 2025-05-20 00:47:02 +0000 |
commit | 7abdd48ece6630eb2c376e865d39500e49b33d8c (patch) | |
tree | e4716659090a3f2194db57da5aa5d62f6b37e7c4 | |
parent | bc0d9ca49d7afabcbfc8e000a40075a9fac1adf6 (diff) |
Replace PostmasterRandom() with pg_strong_random().
Our PostmasterRandmon() was imported from PostgreSQL long time ago (in
2016). In the same year PostgreSQL replaced PostmasterRandmon() with
pg_strong_random()(src/port/pg_strong_random.c). This commit follows
it.
pg_strong_random() looks better than PostmasterRandmon(), since it's
more secure and portable. Moreover no initialization is necessary.
Reviewed-by: Martijn van Duren <pgpool@list.imperialat.at>
Discussion: [pgpool-hackers: 4588] Shuffle random functions and use better random numbers
https://www.pgpool.net/pipermail/pgpool-hackers/2025-May/004589.html
-rw-r--r-- | src/Makefile.am | 3 | ||||
-rw-r--r-- | src/auth/pool_auth.c | 65 | ||||
-rw-r--r-- | src/include/pool.h | 4 | ||||
-rw-r--r-- | src/utils/pg_strong_random.c | 185 |
4 files changed, 198 insertions, 59 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index e32c41269..efc11cb68 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -65,7 +65,8 @@ pgpool_SOURCES = main/main.c \ utils/statistics.c \ utils/pool_health_check_stats.c \ utils/psqlscan.l \ - utils/pgstrcasecmp.c + utils/pgstrcasecmp.c \ + utils/pg_strong_random.c utils/psqlscan.c: utils/psqlscan.l $(LEX) -o'utils/psqlscan.c' $< diff --git a/src/auth/pool_auth.c b/src/auth/pool_auth.c index 7551a567e..be9f33434 100644 --- a/src/auth/pool_auth.c +++ b/src/auth/pool_auth.c @@ -68,7 +68,6 @@ static int read_password_packet(POOL_CONNECTION * frontend, int protoMajor, char static int send_password_packet(POOL_CONNECTION * backend, int protoMajor, char *password); static int send_auth_ok(POOL_CONNECTION * frontend, int protoMajor); static void sendAuthRequest(POOL_CONNECTION * frontend, int protoMajor, int32 auth_req_type, char *extradata, int extralen); -static long PostmasterRandom(void); static int pg_SASL_continue(POOL_CONNECTION * backend, char *payload, int payloadlen, void *sasl_state, bool final); static void *pg_SASL_init(POOL_CONNECTION * backend, char *payload, int payloadlen, char *username, char *storedPassword); @@ -1771,27 +1770,16 @@ send_auth_ok(POOL_CONNECTION * frontend, int protoMajor) return 0; } - +/* + * Generate random bytes + */ void pool_random(void *buf, size_t len) { - int ret = 0; -#ifdef USE_SSL - ret = RAND_bytes(buf, len); -#endif - /* if RND_bytes fails or not present use the old technique */ - if (ret == 0) - { - int i; - char *ptr = buf; - - for (i = 0; i < len; i++) - { - long rand = PostmasterRandom(); - - ptr[i] = (rand % 255) + 1; - } - } + if (!pg_strong_random(buf, len)) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate random bytes"))); } /* @@ -1803,45 +1791,6 @@ pool_random_salt(char *md5Salt) pool_random(md5Salt, 4); } -/* - * PostmasterRandom - */ -static long -PostmasterRandom(void) -{ - extern struct timeval random_start_time; - static unsigned int random_seed = 0; - - /* - * 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(); -} - - static bool do_SCRAM(POOL_CONNECTION * frontend, POOL_CONNECTION * backend, int protoMajor, int message_length, char *username, char *storedPassword, PasswordType passwordType) diff --git a/src/include/pool.h b/src/include/pool.h index 95570c08e..c9b4dc27e 100644 --- a/src/include/pool.h +++ b/src/include/pool.h @@ -653,4 +653,8 @@ extern size_t strlcpy(char *dst, const char *src, size_t siz); extern void do_worker_child(void); extern int get_query_result(POOL_CONNECTION_POOL_SLOT * *slots, int backend_id, char *query, POOL_SELECT_RESULT * *res); +/* utils/pg_strong_random.c */ +void pg_strong_random_init(void); +bool pg_strong_random(void *buf, size_t len); + #endif /* POOL_H */ diff --git a/src/utils/pg_strong_random.c b/src/utils/pg_strong_random.c new file mode 100644 index 000000000..2017139b1 --- /dev/null +++ b/src/utils/pg_strong_random.c @@ -0,0 +1,185 @@ +/* + * This file was imported from PostgreSQL source code + * (src/port/pg_strong_random.c). + */ + +/*------------------------------------------------------------------------- + * + * pg_strong_random.c + * generate a cryptographically secure random number + * + * Our definition of "strong" is that it's suitable for generating random + * salts and query cancellation keys, during authentication. + * + * Note: this code is run quite early in postmaster and backend startup; + * therefore, even when built for backend, it cannot rely on backend + * infrastructure such as elog() or palloc(). + * + * Portions Copyright (c) 2025, PgPool Global Development Group + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/port/pg_strong_random.c + * + *------------------------------------------------------------------------- + */ + +#include "pool.h" + +#include <fcntl.h> +#include <unistd.h> +#include <sys/time.h> + +/* + * pg_strong_random & pg_strong_random_init + * + * Generate requested number of random bytes. The returned bytes are + * cryptographically secure, suitable for use e.g. in authentication. + * + * Before pg_strong_random is called in any process, the generator must first + * be initialized by calling pg_strong_random_init(). Initialization is a no- + * op for all supported randomness sources, it is kept to maintain backwards + * compatibility with extensions. + * + * We rely on system facilities for actually generating the numbers. + * We support a number of sources: + * + * 1. OpenSSL's RAND_bytes() + * 2. Windows' CryptGenRandom() function + * 3. /dev/urandom + * + * 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. + */ + + + +#ifdef USE_SSL + +#include <openssl/rand.h> + +void +pg_strong_random_init(void) +{ + /* No initialization needed */ +} + +bool +pg_strong_random(void *buf, size_t len) +{ + int i; + + /* + * Check that OpenSSL's CSPRNG has been sufficiently seeded, and if not + * add more seed data using RAND_poll(). With some older versions of + * OpenSSL, it may be necessary to call RAND_poll() a number of times. If + * RAND_poll() fails to generate seed data within the given amount of + * retries, subsequent RAND_bytes() calls will fail, but we allow that to + * happen to let pg_strong_random() callers handle that with appropriate + * error handling. + */ +#define NUM_RAND_POLL_RETRIES 8 + + for (i = 0; i < NUM_RAND_POLL_RETRIES; i++) + { + if (RAND_status() == 1) + { + /* The CSPRNG is sufficiently seeded */ + break; + } + + RAND_poll(); + } + + if (RAND_bytes(buf, len) == 1) + return true; + return false; +} + +#elif WIN32 + +#include <wincrypt.h> +/* + * 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; + +void +pg_strong_random_init(void) +{ + /* No initialization needed on WIN32 */ +} + +bool +pg_strong_random(void *buf, size_t len) +{ + 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; + } + return false; +} + +#else /* not USE_OPENSSL or WIN32 */ + +/* + * Without OpenSSL or Win32 support, just read /dev/urandom ourselves. + */ + +void +pg_strong_random_init(void) +{ + /* No initialization needed */ +} + +bool +pg_strong_random(void *buf, size_t len) +{ + int f; + char *p = buf; + ssize_t res; + + f = open("/dev/urandom", 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; +} +#endif |