From ea6fe692f174574deb800bdf909e9c4060c61b77 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Mon, 28 Aug 2023 13:55:57 -0600 Subject: [PATCH] RUBY-3306 use Ruby's own SecureRandom to generate random bytes with entropy (#318) * use Ruby's own SecureRandom to generate random data with entropy * explicitly check for LoadError, NotImplementedError when loading SecureRandom --- ext/bson/bson-native.h | 1 + ext/bson/extconf.rb | 1 - ext/bson/init.c | 2 + ext/bson/util.c | 116 ++++++++++++++++++++++++++++++----------- 4 files changed, 88 insertions(+), 32 deletions(-) diff --git a/ext/bson/bson-native.h b/ext/bson/bson-native.h index 278b91682..a05b11160 100644 --- a/ext/bson/bson-native.h +++ b/ext/bson/bson-native.h @@ -119,6 +119,7 @@ int pvt_get_mode_option(int argc, VALUE *argv); #define BSON_OBJECT_ID_RANDOM_VALUE_LENGTH ( 5 ) uint8_t* pvt_get_object_id_random_value(); +void pvt_init_rand(); void pvt_rand_buf(uint8_t* bytes, int len, int pid); int pvt_rand(); diff --git a/ext/bson/extconf.rb b/ext/bson/extconf.rb index da8960865..729d75f23 100644 --- a/ext/bson/extconf.rb +++ b/ext/bson/extconf.rb @@ -4,6 +4,5 @@ require 'mkmf' $CFLAGS << ' -Wall -g -std=c99' -have_func 'arc4random' create_makefile('bson_native') diff --git a/ext/bson/init.c b/ext/bson/init.c index c7103f340..d8236c82c 100644 --- a/ext/bson/init.c +++ b/ext/bson/init.c @@ -354,6 +354,8 @@ void Init_bson_native() rb_bson_machine_id[255] = '\0'; rb_bson_generate_machine_id(rb_md5_class, rb_bson_machine_id); + pvt_init_rand(); + // Set the object id counter to a random 3-byte integer rb_bson_object_id_counter = pvt_rand() % 0xFFFFFF; diff --git a/ext/bson/util.c b/ext/bson/util.c index eaa0b382b..18d0490e2 100644 --- a/ext/bson/util.c +++ b/ext/bson/util.c @@ -21,6 +21,18 @@ */ static char rb_bson_machine_id_hash[HOST_NAME_HASH_MAX]; +/** + * Holds a reference to the SecureRandom module, or Qnil if the modle is + * not available. + */ +static VALUE pvt_SecureRandom = Qnil; + +/** + * Indicates whether or not the SecureRandom module responds to the + * `random_number` method (depends on Ruby version). + */ +static int pvt_has_random_number = 0; + void rb_bson_generate_machine_id(VALUE rb_md5_class, char *rb_bson_machine_id) { VALUE digest = rb_funcall(rb_md5_class, rb_intern("digest"), 1, rb_str_new2(rb_bson_machine_id)); @@ -151,43 +163,85 @@ uint8_t* pvt_get_object_id_random_value() { } /** - * Fills the buffer with random bytes. If arc4random is available, it is used, - * otherwise a less-ideal fallback is used. + * Attempts to load the SecureRandom module + */ +VALUE pvt_load_secure_random(VALUE _arg) { + rb_require("securerandom"); + pvt_SecureRandom = rb_const_get(rb_cObject, rb_intern("SecureRandom")); + pvt_has_random_number = rb_respond_to(pvt_SecureRandom, rb_intern("random_number")); + + return Qnil; +} + +/** + * The fallback, if loading `securerandom` fails. + */ +VALUE pvt_rescue_load_secure_random(VALUE _arg, VALUE _exception) { + pvt_SecureRandom = Qnil; + + return Qnil; +} + +/** + * Initializes the RNG. + */ +void pvt_init_rand() { + // SecureRandom may fail to load because it's not present (LoadError), or + // because it can't find a random device (NotImplementedError). + rb_rescue2(pvt_load_secure_random, Qnil, pvt_rescue_load_secure_random, Qnil, + rb_eLoadError, rb_eNotImpError, 0); +} + +/** + * Fills the buffer with random bytes. It prefers to use SecureRandom for + * this, but in the very unlikely event that SecureRandom is not available, + * it will fall back to a much-less-ideal generator using srand/rand. + * + * The `pid` argument is only used by the fallback, if SecureRandom is not + * available. */ void pvt_rand_buf(uint8_t* bytes, int len, int pid) { -#if HAVE_ARC4RANDOM - arc4random_buf(bytes, len); -#else - time_t t; - uint32_t seed; - int ofs = 0; - - /* TODO: spec says to include hostname as part of the seed */ - t = time(NULL); - seed = ((uint32_t)t << 16) + ((uint32_t)pid % 0xFFFF); - srand(seed); - - while (ofs < len) { - int n = rand(); - unsigned remaining = len - ofs; - - if (remaining > sizeof(n)) remaining = sizeof(n); - memcpy(bytes+ofs, &n, remaining); - - ofs += remaining; + if (pvt_SecureRandom != Qnil) { + VALUE rb_bytes = rb_funcall(pvt_SecureRandom, rb_intern("bytes"), 1, INT2NUM(len)); + memcpy(bytes, StringValuePtr(rb_bytes), len); + + } else { + time_t t; + uint32_t seed; + int ofs = 0; + + t = time(NULL); + seed = ((uint32_t)t << 16) + ((uint32_t)pid % 0xFFFF); + srand(seed); + + while (ofs < len) { + int n = rand(); + unsigned remaining = len - ofs; + + if (remaining > sizeof(n)) remaining = sizeof(n); + memcpy(bytes+ofs, &n, remaining); + + ofs += remaining; + } } -#endif } /** - * Returns a random integer between 0 and INT_MAX. If arc4random is available, - * it is used, otherwise a less-ideal fallback is used. + * Returns a random integer between 0 and INT_MAX. */ int pvt_rand() { -#if HAVE_ARC4RANDOM - return arc4random(); -#else - srand((unsigned)time(NULL)); - return rand(); -#endif + if (pvt_has_random_number) { + VALUE result = rb_funcall(pvt_SecureRandom, rb_intern("random_number"), 1, INT2NUM(INT_MAX)); + return NUM2INT(result); + + } else if (pvt_SecureRandom != Qnil) { + int result; + VALUE rb_result = rb_funcall(pvt_SecureRandom, rb_intern("bytes"), 1, INT2NUM(sizeof(result))); + memcpy(&result, StringValuePtr(rb_result), sizeof(result)); + return result; + + } else { + srand((unsigned)time(NULL)); + return rand(); + } }