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"] ; ...@@ -40,6 +40,7 @@ import gcrypt [from "path"] ;
BLOB gcrypt.random([ENUM quality] [, BYTES n]) BLOB gcrypt.random([ENUM quality] [, BYTES n])
INT gcrypt.random_int(ENUM quality [, INT bound]) INT gcrypt.random_int(ENUM quality [, INT bound])
REAL gcrypt.random_real(ENUM) REAL gcrypt.random_real(ENUM)
BOOL random_bool(ENUM)
gcrypt.wipe(BLOB) gcrypt.wipe(BLOB)
...@@ -169,6 +170,7 @@ CONTENTS ...@@ -169,6 +170,7 @@ CONTENTS
* BLOB random(PRIV_TASK, ENUM {STRONG,VERY_STRONG,NONCE}, BYTES) * BLOB random(PRIV_TASK, ENUM {STRONG,VERY_STRONG,NONCE}, BYTES)
* INT random_int(ENUM {STRONG,NONCE}, INT) * INT random_int(ENUM {STRONG,NONCE}, INT)
* REAL random_real(ENUM {STRONG,NONCE}) * REAL random_real(ENUM {STRONG,NONCE})
* BOOL random_bool(ENUM {STRONG,NONCE})
* VOID wipe(BLOB) * VOID wipe(BLOB)
* STRING version() * STRING version()
* STRING gcrypt_version() * STRING gcrypt_version()
...@@ -751,6 +753,36 @@ Example:: ...@@ -751,6 +753,36 @@ Example::
# Assign an unpredictable REAL from -1.0 to 1.0 to a request. # Assign an unpredictable REAL from -1.0 to 1.0 to a request.
set req.http.X-Real = gcrypt.random_real(STRONG) * 2 - 1; 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: .. _func_wipe:
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 -*- # looks like -*- vcl -*-
varnishtest "random()" varnishtest "random() and random_int(), _real() and _bool()"
# Quality level VERY_STRONG is not tested here, because random() can # Quality level VERY_STRONG is not tested here, because random() can
# take several minutes to run at that level. See # 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} ...@@ -122,6 +122,7 @@ varnish v1 -errvcl {vmod gcrypt error: in gcrypt.random(): quality ENUM is NULL}
} }
} }
# random_real()
varnish v1 -vcl { varnish v1 -vcl {
import blobcode; import blobcode;
import gcrypt from "${vmod_topbuild}/src/.libs/libvmod_gcrypt.so"; import gcrypt from "${vmod_topbuild}/src/.libs/libvmod_gcrypt.so";
...@@ -146,3 +147,36 @@ client c1 -repeat 20 { ...@@ -146,3 +147,36 @@ client c1 -repeat 20 {
expect resp.http.nonce ~ "^1.0+|0.[[:digit:]]+$" expect resp.http.nonce ~ "^1.0+|0.[[:digit:]]+$"
expect resp.http.strong ~ "^1.0+|0.[[:digit:]]+$" expect resp.http.strong ~ "^1.0+|0.[[:digit:]]+$"
} -run } -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 { ...@@ -90,10 +90,21 @@ struct filedata {
size_t len; 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); VSLIST_HEAD(filedata_head, filedata);
static const char *gcrypt_version = NULL; static const char *gcrypt_version = NULL;
static int secmem_enabled = 1; 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 static void
errmsg(VRT_CTX, const char *fmt, ...) errmsg(VRT_CTX, const char *fmt, ...)
...@@ -806,6 +817,112 @@ vmod_random_real(VRT_CTX, VCL_ENUM qualitys) ...@@ -806,6 +817,112 @@ vmod_random_real(VRT_CTX, VCL_ENUM qualitys)
return r; 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 */ /* Function wipe */
static inline void static inline void
......
...@@ -23,6 +23,7 @@ $Module gcrypt 3 access the libgcrypt cryptographic library ...@@ -23,6 +23,7 @@ $Module gcrypt 3 access the libgcrypt cryptographic library
BLOB gcrypt.random([ENUM quality] [, BYTES n]) BLOB gcrypt.random([ENUM quality] [, BYTES n])
INT gcrypt.random_int(ENUM quality [, INT bound]) INT gcrypt.random_int(ENUM quality [, INT bound])
REAL gcrypt.random_real(ENUM) REAL gcrypt.random_real(ENUM)
BOOL random_bool(ENUM)
gcrypt.wipe(BLOB) gcrypt.wipe(BLOB)
...@@ -669,6 +670,29 @@ Example:: ...@@ -669,6 +670,29 @@ Example::
# Assign an unpredictable REAL from -1.0 to 1.0 to a request. # Assign an unpredictable REAL from -1.0 to 1.0 to a request.
set req.http.X-Real = gcrypt.random_real(STRONG) * 2 - 1; 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) $Function VOID wipe(BLOB)
Overwrites the memory region denoted by the BLOB, leaving it with all 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