Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 6645ad6

Browse files
committed
Use a separate random seed for SQL random()/setseed() functions.
Previously, the SQL random() function depended on libc's random(3), and setseed() invoked srandom(3). This results in interference between these functions and backend-internal uses of random(3). We'd never paid too much mind to that, but in the wake of commit 88bdbd3 which added log_statement_sample_rate, the interference arguably has a security consequence: if log_statement_sample_rate is active then an unprivileged user could probably control which if any of his SQL commands get logged, by issuing setseed() at the right times. That seems bad. To fix this reliably, we need random() and setseed() to use their own private random state variable. Standard random(3) isn't amenable to such usage, so let's switch to pg_erand48(). It's hard to say whether that's more or less "random" than any particular platform's version of random(3), but it does have a wider seed value and a longer period than are required by POSIX, so we can hope that this isn't a big downgrade. Also, we should now have uniform behavior of random() across platforms, which is worth something. While at it, upgrade the per-process seed initialization method to use pg_strong_random() if available, greatly reducing the predictability of the initial seed value. (I'll separately do something similar for the internal uses of random().) In addition to forestalling the possible security problem, this has a benefit in the other direction, which is that we can now document setseed() as guaranteeing a reproducible sequence of random() values. Previously, because of the possibility of internal calls of random(3), we could not promise any such thing. Discussion: https://postgr.es/m/3859.1545849900@sss.pgh.pa.us
1 parent 1a4eba4 commit 6645ad6

File tree

2 files changed

+53
-13
lines changed

2 files changed

+53
-13
lines changed

doc/src/sgml/func.sgml

+9-5
Original file line numberDiff line numberDiff line change
@@ -1136,11 +1136,15 @@
11361136
</table>
11371137

11381138
<para>
1139-
The characteristics of the values returned by
1140-
<literal><function>random()</function></literal> depend
1141-
on the system implementation. It is not suitable for cryptographic
1142-
applications; see <xref linkend="pgcrypto"/> module for an alternative.
1143-
</para>
1139+
The <function>random()</function> function uses a simple linear
1140+
congruential algorithm. It is fast but not suitable for cryptographic
1141+
applications; see the <xref linkend="pgcrypto"/> module for a more
1142+
secure alternative.
1143+
If <function>setseed()</function> is called, the results of
1144+
subsequent <function>random()</function> calls in the current session are
1145+
repeatable by re-issuing <function>setseed()</function> with the same
1146+
argument.
1147+
</para>
11441148

11451149
<para>
11461150
Finally, <xref linkend="functions-math-trig-table"/> shows the

src/backend/utils/adt/float.c

+44-8
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@
2222
#include "catalog/pg_type.h"
2323
#include "common/int.h"
2424
#include "libpq/pqformat.h"
25+
#include "miscadmin.h"
2526
#include "utils/array.h"
27+
#include "utils/backend_random.h"
2628
#include "utils/float.h"
2729
#include "utils/fmgrprotos.h"
2830
#include "utils/sortsupport.h"
31+
#include "utils/timestamp.h"
2932

3033

3134
/* Configurable GUC parameter */
@@ -53,6 +56,10 @@ float8 degree_c_sixty = 60.0;
5356
float8 degree_c_one_half = 0.5;
5457
float8 degree_c_one = 1.0;
5558

59+
/* State for drandom() and setseed() */
60+
static bool drandom_seed_set = false;
61+
static unsigned short drandom_seed[3] = {0, 0, 0};
62+
5663
/* Local function prototypes */
5764
static double sind_q1(double x);
5865
static double cosd_q1(double x);
@@ -2378,8 +2385,30 @@ drandom(PG_FUNCTION_ARGS)
23782385
{
23792386
float8 result;
23802387

2381-
/* result [0.0 - 1.0) */
2382-
result = (double) random() / ((double) MAX_RANDOM_VALUE + 1);
2388+
/* Initialize random seed, if not done yet in this process */
2389+
if (unlikely(!drandom_seed_set))
2390+
{
2391+
/*
2392+
* If possible, initialize the seed using high-quality random bits.
2393+
* Should that fail for some reason, we fall back on a lower-quality
2394+
* seed based on current time and PID.
2395+
*/
2396+
if (!pg_backend_random((char *) drandom_seed, sizeof(drandom_seed)))
2397+
{
2398+
TimestampTz now = GetCurrentTimestamp();
2399+
uint64 iseed;
2400+
2401+
/* Mix the PID with the most predictable bits of the timestamp */
2402+
iseed = (uint64) now ^ ((uint64) MyProcPid << 32);
2403+
drandom_seed[0] = (unsigned short) iseed;
2404+
drandom_seed[1] = (unsigned short) (iseed >> 16);
2405+
drandom_seed[2] = (unsigned short) (iseed >> 32);
2406+
}
2407+
drandom_seed_set = true;
2408+
}
2409+
2410+
/* pg_erand48 produces desired result range [0.0 - 1.0) */
2411+
result = pg_erand48(drandom_seed);
23832412

23842413
PG_RETURN_FLOAT8(result);
23852414
}
@@ -2392,13 +2421,20 @@ Datum
23922421
setseed(PG_FUNCTION_ARGS)
23932422
{
23942423
float8 seed = PG_GETARG_FLOAT8(0);
2395-
int iseed;
2424+
uint64 iseed;
23962425

2397-
if (seed < -1 || seed > 1)
2398-
elog(ERROR, "setseed parameter %f out of range [-1,1]", seed);
2399-
2400-
iseed = (int) (seed * MAX_RANDOM_VALUE);
2401-
srandom((unsigned int) iseed);
2426+
if (seed < -1 || seed > 1 || isnan(seed))
2427+
ereport(ERROR,
2428+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2429+
errmsg("setseed parameter %g is out of allowed range [-1,1]",
2430+
seed)));
2431+
2432+
/* Use sign bit + 47 fractional bits to fill drandom_seed[] */
2433+
iseed = (int64) (seed * (float8) UINT64CONST(0x7FFFFFFFFFFF));
2434+
drandom_seed[0] = (unsigned short) iseed;
2435+
drandom_seed[1] = (unsigned short) (iseed >> 16);
2436+
drandom_seed[2] = (unsigned short) (iseed >> 32);
2437+
drandom_seed_set = true;
24022438

24032439
PG_RETURN_VOID();
24042440
}

0 commit comments

Comments
 (0)