Commit 6af8f7ed authored by Geoff Simmons's avatar Geoff Simmons

Add random_bool().

parent 3454f62f
Pipeline #235 skipped
......@@ -40,6 +40,7 @@ import gcrypt [from "path"] ;
BLOB gcrypt.random([ENUM quality] [, BYTES n])
INT gcrypt.random_int(ENUM quality [, INT bound])
REAL gcrypt.random_real(ENUM)
BOOL random_bool(ENUM)
gcrypt.wipe(BLOB)
......@@ -169,6 +170,7 @@ CONTENTS
* BLOB random(PRIV_TASK, ENUM {STRONG,VERY_STRONG,NONCE}, BYTES)
* INT random_int(ENUM {STRONG,NONCE}, INT)
* REAL random_real(ENUM {STRONG,NONCE})
* BOOL random_bool(ENUM {STRONG,NONCE})
* VOID wipe(BLOB)
* STRING version()
* STRING gcrypt_version()
......@@ -751,6 +753,36 @@ Example::
# Assign an unpredictable REAL from -1.0 to 1.0 to a request.
set req.http.X-Real = gcrypt.random_real(STRONG) * 2 - 1;
.. _func_random_bool:
random_bool
-----------
::
BOOL random_bool(ENUM {STRONG,NONCE})
Returns a random boolean using the randomness generator with the
specified quality level. The permitted levels are ``NONCE`` and
``STRONG`` as described above.
This function is more efficient than, for example, calling
``random_int()`` with the bound set to 2 and then checking if the
result is 0 or 1 (or something similar with ``random_real()``), since
``random_bool()`` calls the randomness generators less often (caching
random bytes as bitmaps), and only uses one bit of randomness for each
invocation.
Example::
# Choose from two courses of action unpredictably.
if (gcrypt.random_bool(STRONG)) {
call do_this;
}
else {
call do_that;
}
.. _func_wipe:
wipe
......
# looks like -*- vcl -*-
varnishtest "random_bool()"
# This test requires a -b setting for varnishtest that is higher than
# the default, -b 2m appears to be enough.
# Test random_bool() with both quality levels with enough repetitions
# to roll over the per-thread cached 64 bits twice, and do so in
# several threads.
varnish v1 -vcl {
import blobcode;
import gcrypt from "${vmod_topbuild}/src/.libs/libvmod_gcrypt.so";
backend b { .host = "${bad_ip}"; }
sub vcl_recv {
return(synth(200));
}
sub vcl_synth {
set resp.http.nonce = gcrypt.random_bool(NONCE);
set resp.http.strong = gcrypt.random_bool(STRONG);
return(deliver);
}
} -start
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.nonce ~ "^true|false$"
expect resp.http.strong ~ "^true|false$"
}
client c1 -repeat 129 -run
client c1 -repeat 129 -run
client c1 -repeat 129 -run
# looks like -*- vcl -*-
varnishtest "random()"
varnishtest "random() and random_int(), _real() and _bool()"
# Quality level VERY_STRONG is not tested here, because random() can
# take several minutes to run at that level. See
......@@ -122,6 +122,7 @@ varnish v1 -errvcl {vmod gcrypt error: in gcrypt.random(): quality ENUM is NULL}
}
}
# random_real()
varnish v1 -vcl {
import blobcode;
import gcrypt from "${vmod_topbuild}/src/.libs/libvmod_gcrypt.so";
......@@ -146,3 +147,36 @@ client c1 -repeat 20 {
expect resp.http.nonce ~ "^1.0+|0.[[:digit:]]+$"
expect resp.http.strong ~ "^1.0+|0.[[:digit:]]+$"
} -run
# random_bool()
varnish v1 -vcl {
import blobcode;
import gcrypt from "${vmod_topbuild}/src/.libs/libvmod_gcrypt.so";
backend b { .host = "${bad_ip}"; }
sub vcl_recv {
return(synth(200));
}
sub vcl_synth {
set resp.http.nonce = gcrypt.random_bool(NONCE);
set resp.http.strong = gcrypt.random_bool(STRONG);
return(deliver);
}
}
# Run more than 64 times to test resetting the 64 bits saved per thread.
client c1 -repeat 65 {
txreq
rxresp
expect resp.status == 200
expect resp.http.nonce ~ "^true|false$"
expect resp.http.strong ~ "^true|false$"
} -run
# See src/tests.disabled/random_bool.vtc for a test that rolls over
# the 64 bits more than once and in several threads. That test
# requires a -b setting for varnishtest that is higher than the
# default.
......@@ -90,10 +90,21 @@ struct filedata {
size_t len;
};
struct rnd_bool {
unsigned magic;
#define VMOD_GCRYPT_RND_BOOL_MAGIC 0x7d0a3e42
uint64_t bits;
uint8_t nbits;
};
VSLIST_HEAD(filedata_head, filedata);
static const char *gcrypt_version = NULL;
static int secmem_enabled = 1;
static pthread_once_t strong_bool_once = PTHREAD_ONCE_INIT,
nonce_bool_once = PTHREAD_ONCE_INIT;
static pthread_key_t strong_boolk, nonce_boolk;
static int strong_boolk_inited = 0, nonce_boolk_inited = 0;
static void
errmsg(VRT_CTX, const char *fmt, ...)
......@@ -806,6 +817,112 @@ vmod_random_real(VRT_CTX, VCL_ENUM qualitys)
return r;
}
/* Function random_bool */
/* pthread key destructor */
static void
rnd_bool_fini(void *p)
{
struct rnd_bool *r;
AN(p);
CAST_OBJ(r, p, VMOD_GCRYPT_RND_BOOL_MAGIC);
FREE_OBJ(r);
}
/*
* Initialize the pthread key for both quality levels via pthread_once,
* only if random_bool() is called for that quality level at all.
*/
static void
nonce_bool_init(void)
{
AZ(nonce_boolk_inited);
errno = 0;
if (pthread_key_create(&nonce_boolk, rnd_bool_fini) != 0) {
assert(errno == EAGAIN);
return;
}
nonce_boolk_inited = 1;
}
static void
strong_bool_init(void)
{
AZ(strong_boolk_inited);
errno = 0;
if (pthread_key_create(&strong_boolk, rnd_bool_fini) != 0) {
assert(errno == EAGAIN);
return;
}
strong_boolk_inited = 1;
}
VCL_BOOL
vmod_random_bool(VRT_CTX, VCL_ENUM qualitys)
{
VCL_BOOL r;
pthread_once_t *rnd_bool_once;
void (*rnd_bool_init)(void);
pthread_key_t *rnd_boolk;
int *rnd_boolk_inited;
void *p;
struct rnd_bool *rb;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
AN(qualitys);
assert(qualitys[0] != 'V');
switch(qualitys[0]) {
case 'N':
rnd_bool_once = &nonce_bool_once;
rnd_bool_init = nonce_bool_init;
rnd_boolk = &nonce_boolk;
rnd_boolk_inited = &nonce_boolk_inited;
break;
case 'S':
rnd_bool_once = &strong_bool_once;
rnd_bool_init = strong_bool_init;
rnd_boolk = &strong_boolk;
rnd_boolk_inited = &strong_boolk_inited;
break;
default:
WRONG("Invalid quality ENUM");
}
AZ(pthread_once(rnd_bool_once, rnd_bool_init));
if (! *rnd_boolk_inited) {
VERR(ctx, "pthread key allocation exhausted "
"(PTHREAD_KEYS_MAX=%d) in gcrypt.random_bool(), discard "
"old VCL instances or restart Varnish", PTHREAD_KEYS_MAX);
return 0;
}
p = pthread_getspecific(*rnd_boolk);
if (p == NULL) {
ALLOC_OBJ(rb, VMOD_GCRYPT_RND_BOOL_MAGIC);
if (rb == NULL) {
ERRNOMEM(ctx, "Allocating thread-specific random bits "
"for random_bool()");
return 0;
}
AZ(rb->bits);
AZ(rb->nbits);
AZ(pthread_setspecific(*rnd_boolk, rb));
}
else
CAST_OBJ(rb, p, VMOD_GCRYPT_RND_BOOL_MAGIC);
if (rb->nbits == 0) {
get_rnd(qualitys, &rb->bits, sizeof(rb->bits));
rb->nbits = sizeof(rb->bits) * 8;
}
r = rb->bits & 0x01;
rb->bits >>= 1;
rb->nbits -= 1;
return r;
}
/* Function wipe */
static inline void
......
......@@ -23,6 +23,7 @@ $Module gcrypt 3 access the libgcrypt cryptographic library
BLOB gcrypt.random([ENUM quality] [, BYTES n])
INT gcrypt.random_int(ENUM quality [, INT bound])
REAL gcrypt.random_real(ENUM)
BOOL random_bool(ENUM)
gcrypt.wipe(BLOB)
......@@ -669,6 +670,29 @@ Example::
# Assign an unpredictable REAL from -1.0 to 1.0 to a request.
set req.http.X-Real = gcrypt.random_real(STRONG) * 2 - 1;
$Function BOOL random_bool(ENUM {STRONG, NONCE})
Returns a random boolean using the randomness generator with the
specified quality level. The permitted levels are ``NONCE`` and
``STRONG`` as described above.
This function is more efficient than, for example, calling
``random_int()`` with the bound set to 2 and then checking if the
result is 0 or 1 (or something similar with ``random_real()``), since
``random_bool()`` calls the randomness generators less often (caching
random bytes as bitmaps), and only uses one bit of randomness for each
invocation.
Example::
# Choose from two courses of action unpredictably.
if (gcrypt.random_bool(STRONG)) {
call do_this;
}
else {
call do_that;
}
$Function VOID wipe(BLOB)
Overwrites the memory region denoted by the BLOB, leaving it with all
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment