upstream: https://github.com/mirage/mirage-crypto

Handle CPU_RNG failures in OCaml: (#255)

* Handle CPU_RNG failures in OCaml:

- Previously, a return value of "0" was treated specially, but rdrand/rdseed
may return 0!
- If there's a failure in rdrand/rdseed during cpu_rng_bootstrap, raise an
exception
- If there's a failure during the periodic reseeding (timer initiated feeding
of all pools), ignore it (for now!?)

* use memcpy instead of manual copying

* entropy: provide rdrand and rdseed calls and failures

* cpu_rng_bootstrap: use rdrand if rdseed fails.

if rdseed fails, take 512 times rdrand.

if rdrand fails 512 times (* 10 from the RETRIES in C), fail

* rdseed: 100 retries, rdrand: 10 retries

* avoid unused parameter warnings

* use Val_false directly

authored by

Hannes Mehnert and committed by
GitHub
7171a2af 63dab445

+113 -37
+1 -1
rng/async/mirage_crypto_rng_async.ml
··· 34 let idx = ref 0 in 35 let f () = 36 incr idx; 37 - String.sub random ~pos:(per_pool * (pred !idx)) ~len:per_pool 38 in 39 Entropy.feed_pools None source f) 40
··· 34 let idx = ref 0 in 35 let f () = 36 incr idx; 37 + Ok (String.sub random ~pos:(per_pool * (pred !idx)) ~len:per_pool) 38 in 39 Entropy.feed_pools None source f) 40
+1 -1
rng/dune
··· 1 (library 2 (name mirage_crypto_rng) 3 (public_name mirage-crypto-rng) 4 - (libraries mirage-crypto digestif) 5 (private_modules entropy fortuna hmac_drbg rng))
··· 1 (library 2 (name mirage_crypto_rng) 3 (public_name mirage-crypto-rng) 4 + (libraries mirage-crypto digestif logs) 5 (private_modules entropy fortuna hmac_drbg rng))
+1 -1
rng/eio/mirage_crypto_rng_eio.ml
··· 32 let idx = ref 0 in 33 let f () = 34 incr idx; 35 - String.sub random (per_pool * (pred !idx)) per_pool 36 in 37 Entropy.feed_pools None source f 38 in
··· 32 let idx = ref 0 in 33 let f () = 34 incr idx; 35 + Ok (String.sub random (per_pool * (pred !idx)) per_pool) 36 in 37 Entropy.feed_pools None source f 38 in
+67 -14
rng/entropy.ml
··· 27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 *) 29 30 module Cpu_native = struct 31 32 external cycles : unit -> int = "mc_cycle_counter" [@@noalloc] 33 - external rdseed : unit -> int = "mc_cpu_rdseed" [@@noalloc] 34 - external rdrand : unit -> int = "mc_cpu_rdrand" [@@noalloc] 35 external rng_type : unit -> int = "mc_cpu_rng_type" [@@noalloc] 36 37 let cpu_rng = ··· 72 73 let pp_source ppf (idx, name) = Format.fprintf ppf "[%d] %s" idx name 74 75 - let cpu_rng = function 76 - | `Rdseed -> Cpu_native.rdseed 77 - | `Rdrand -> Cpu_native.rdrand 78 79 let random preferred = 80 match Cpu_native.cpu_rng with ··· 115 Bytes.unsafe_to_string buf 116 117 let cpu_rng_bootstrap = 118 match random `Rdseed with 119 | None -> Error `Not_supported 120 - | Some insn -> 121 let cpu_rng_bootstrap id = 122 - let r = cpu_rng insn () in 123 - if r = 0 then failwith "Mirage_crypto_rng.Entropy: 0 is a bad CPU RNG value"; 124 let buf = Bytes.create 10 in 125 - Bytes.set_int64_le buf 2 (Int64.of_int r); 126 write_header id buf; 127 - Bytes.unsafe_to_string buf 128 in 129 Ok cpu_rng_bootstrap 130 131 let bootstrap id = 132 match cpu_rng_bootstrap with 133 | Error `Not_supported -> whirlwind_bootstrap id 134 | Ok cpu_rng_bootstrap -> 135 - try cpu_rng_bootstrap id with Failure _ -> whirlwind_bootstrap id 136 137 let interrupt_hook () = 138 let buf = Bytes.create 4 in ··· 150 let g = match g with None -> Some (Rng.default_generator ()) | Some g -> Some g in 151 let `Acc handle = Rng.accumulate g source in 152 for _i = 0 to pred (Rng.pools g) do 153 - handle (f ()) 154 done 155 156 let cpu_rng = ··· 165 in 166 let f () = 167 let buf = Bytes.create 8 in 168 - Bytes.set_int64_le buf 0 (Int64.of_int (randomf ())); 169 - Bytes.unsafe_to_string buf 170 in 171 fun () -> feed_pools g source f 172 in 173 Ok cpu_rng
··· 27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 *) 29 30 + let src = Logs.Src.create "mirage-crypto-rng-entropy" ~doc:"Mirage crypto RNG Entropy" 31 + module Log = (val Logs.src_log src : Logs.LOG) 32 + 33 + let rdrand_calls = Atomic.make 0 34 + let rdrand_failures = Atomic.make 0 35 + let rdseed_calls = Atomic.make 0 36 + let rdseed_failures = Atomic.make 0 37 + 38 module Cpu_native = struct 39 40 external cycles : unit -> int = "mc_cycle_counter" [@@noalloc] 41 + external rdseed : bytes -> int -> bool = "mc_cpu_rdseed" [@@noalloc] 42 + external rdrand : bytes -> int -> bool = "mc_cpu_rdrand" [@@noalloc] 43 external rng_type : unit -> int = "mc_cpu_rng_type" [@@noalloc] 44 45 let cpu_rng = ··· 80 81 let pp_source ppf (idx, name) = Format.fprintf ppf "[%d] %s" idx name 82 83 + let cpu_rng isn buf off = match isn with 84 + | `Rdseed -> 85 + Atomic.incr rdseed_calls; 86 + let success = Cpu_native.rdseed buf off in 87 + if not success then Atomic.incr rdseed_failures; 88 + success 89 + | `Rdrand -> 90 + Atomic.incr rdrand_calls; 91 + let success = Cpu_native.rdrand buf off in 92 + if not success then Atomic.incr rdrand_failures; 93 + success 94 95 let random preferred = 96 match Cpu_native.cpu_rng with ··· 131 Bytes.unsafe_to_string buf 132 133 let cpu_rng_bootstrap = 134 + let rdrand_bootstrap id = 135 + let rec go acc = function 136 + | 0 -> acc 137 + | n -> 138 + let buf = Bytes.create 10 in 139 + let r = cpu_rng `Rdrand buf 2 in 140 + write_header id buf; 141 + if not r then 142 + go acc (pred n) 143 + else 144 + go (Bytes.unsafe_to_string buf :: acc) (pred n) 145 + in 146 + let result = go [] 512 |> String.concat "" in 147 + if String.length result = 0 then 148 + failwith "Too many RDRAND failures" 149 + else 150 + result 151 + in 152 match random `Rdseed with 153 | None -> Error `Not_supported 154 + | Some `Rdseed -> 155 let cpu_rng_bootstrap id = 156 let buf = Bytes.create 10 in 157 + let r = cpu_rng `Rdseed buf 2 in 158 write_header id buf; 159 + if not r then 160 + if List.mem `Rdrand Cpu_native.cpu_rng then 161 + rdrand_bootstrap id 162 + else 163 + failwith "RDSEED failed, and RDRAND not available" 164 + else 165 + Bytes.unsafe_to_string buf 166 in 167 Ok cpu_rng_bootstrap 168 + | Some `Rdrand -> Ok rdrand_bootstrap 169 170 let bootstrap id = 171 match cpu_rng_bootstrap with 172 | Error `Not_supported -> whirlwind_bootstrap id 173 | Ok cpu_rng_bootstrap -> 174 + try cpu_rng_bootstrap id with 175 + | Failure f -> 176 + Log.err (fun m -> m "CPU RNG bootstrap failed: %s, using whirlwind" f); 177 + whirlwind_bootstrap id 178 179 let interrupt_hook () = 180 let buf = Bytes.create 4 in ··· 192 let g = match g with None -> Some (Rng.default_generator ()) | Some g -> Some g in 193 let `Acc handle = Rng.accumulate g source in 194 for _i = 0 to pred (Rng.pools g) do 195 + match f () with 196 + | Ok data -> handle data 197 + | Error `No_random_available -> 198 + (* should we log a message? *) 199 + () 200 done 201 202 let cpu_rng = ··· 211 in 212 let f () = 213 let buf = Bytes.create 8 in 214 + if randomf buf 0 then 215 + Ok (Bytes.unsafe_to_string buf) 216 + else 217 + Error `No_random_available 218 in 219 fun () -> feed_pools g source f 220 in 221 Ok cpu_rng 222 + 223 + let rdrand_calls () = Atomic.get rdrand_calls 224 + let rdrand_failures () = Atomic.get rdrand_failures 225 + let rdseed_calls () = Atomic.get rdseed_calls 226 + let rdseed_failures () = Atomic.get rdseed_failures
+1 -1
rng/lwt/mirage_crypto_rng_lwt.ml
··· 19 let idx = ref 0 in 20 let f () = 21 incr idx; 22 - String.sub random (per_pool * (pred !idx)) per_pool 23 in 24 Entropy.feed_pools None source f 25 in
··· 19 let idx = ref 0 in 20 let f () = 21 incr idx; 22 + Ok (String.sub random (per_pool * (pred !idx)) per_pool) 23 in 24 Entropy.feed_pools None source f 25 in
+4 -1
rng/miou/mirage_crypto_rng_miou_unix.ml
··· 18 let size = per_pool * pools None in 19 let random = Mirage_crypto_rng_unix.getrandom size in 20 let idx = ref 0 in 21 - let fn () = incr idx; String.sub random (per_pool * (pred !idx)) per_pool in 22 Entropy.feed_pools None source fn in 23 periodic fn delta 24
··· 18 let size = per_pool * pools None in 19 let random = Mirage_crypto_rng_unix.getrandom size in 20 let idx = ref 0 in 21 + let fn () = 22 + incr idx; 23 + Ok (String.sub random (per_pool * (pred !idx)) per_pool) 24 + in 25 Entropy.feed_pools None source fn in 26 periodic fn delta 27
+20 -6
rng/mirage_crypto_rng.mli
··· 107 108 val cpu_rng_bootstrap : (int -> string, [`Not_supported]) Result.t 109 (** [cpu_rng_bootstrap id] returns 8 bytes of random data using the CPU 110 - RNG (rdseed or rdrand). On 32bit platforms, only 4 bytes are filled. 111 - The [id] is used as prefix. 112 113 - @raise Failure if no CPU RNG is available, or if it doesn't return a 114 - random value. *) 115 116 val bootstrap : int -> string 117 (** [bootstrap id] is either [cpu_rng_bootstrap], if the CPU supports it, or ··· 129 130 (** {1 Periodic pulled sources} *) 131 132 - val feed_pools : g option -> source -> (unit -> string) -> unit 133 (** [feed_pools g source f] feeds all pools of [g] using [source] by executing 134 [f] for each pool. *) 135 136 val cpu_rng : (g option -> unit -> unit, [`Not_supported]) Result.t 137 (** [cpu_rng g] uses the CPU RNG (rdrand or rdseed) to feed all pools 138 of [g]. It uses {!feed_pools} internally. If neither rdrand nor rdseed 139 - are available, [fun () -> ()] is returned. *) 140 141 (**/**) 142 val id : source -> int
··· 107 108 val cpu_rng_bootstrap : (int -> string, [`Not_supported]) Result.t 109 (** [cpu_rng_bootstrap id] returns 8 bytes of random data using the CPU 110 + RNG (rdseed). On 32bit platforms, only 4 bytes are filled. 111 + The [id] is used as prefix. If only rdrand is available, the return 112 + value is the concatenation of 512 calls to rdrand. 113 114 + @raise Failure if rdrand fails 512 times, or if rdseed fails and rdrand 115 + is not available. 116 + *) 117 118 val bootstrap : int -> string 119 (** [bootstrap id] is either [cpu_rng_bootstrap], if the CPU supports it, or ··· 131 132 (** {1 Periodic pulled sources} *) 133 134 + val feed_pools : g option -> source -> (unit -> (string, [ `No_random_available ]) result) -> unit 135 (** [feed_pools g source f] feeds all pools of [g] using [source] by executing 136 [f] for each pool. *) 137 138 val cpu_rng : (g option -> unit -> unit, [`Not_supported]) Result.t 139 (** [cpu_rng g] uses the CPU RNG (rdrand or rdseed) to feed all pools 140 of [g]. It uses {!feed_pools} internally. If neither rdrand nor rdseed 141 + are available, [`Not_supported] is returned. *) 142 + 143 + val rdrand_calls : unit -> int 144 + (** [rdrand_calls ()] returns the number of rdrand calls. *) 145 + 146 + val rdrand_failures : unit -> int 147 + (** [rdrand_failures ()] returns the number of rdrand failures. *) 148 + 149 + val rdseed_calls : unit -> int 150 + (** [rdseed_calls ()] returns the number of rdseed calls. *) 151 + 152 + val rdseed_failures : unit -> int 153 + (** [rdseed_failures ()] returns the number of rdseed failures. *) 154 155 (**/**) 156 val id : source -> int
+18 -12
src/native/entropy_cpu_stubs.c
··· 15 #define random_t unsigned long long 16 #define _rdseed_step _rdseed64_step 17 #define _rdrand_step _rdrand64_step 18 19 #elif defined (__i386__) 20 #define random_t unsigned int 21 #define _rdseed_step _rdseed32_step 22 #define _rdrand_step _rdrand32_step 23 24 #endif 25 #endif /* __i386__ || __x86_64__ */ ··· 203 204 static int __cpu_rng = RNG_NONE; 205 206 - #define RETRIES 10 207 - 208 static void detect (void) { 209 #ifdef __mc_ENTROPY__ 210 random_t r = 0; ··· 212 if (mc_detected_cpu_features.rdrand) 213 /* AMD Ryzen 3000 bug where RDRAND always returns -1 214 https://arstechnica.com/gadgets/2019/10/how-a-months-old-amd-microcode-bug-destroyed-my-weekend/ */ 215 - for (int i = 0; i < RETRIES; i++) 216 if (_rdrand_step(&r) == 1 && r != (random_t) (-1)) { 217 __cpu_rng = RNG_RDRAND; 218 break; ··· 221 if (mc_detected_cpu_features.rdseed) 222 /* RDSEED could return -1, thus we test it here 223 https://www.reddit.com/r/Amd/comments/cmza34/agesa_1003_abb_fixes_rdrandrdseed/ */ 224 - for (int i = 0; i < RETRIES; i++) 225 if (_rdseed_step(&r) == 1 && r != (random_t) (-1)) { 226 __cpu_rng |= RNG_RDSEED; 227 break; ··· 229 #endif 230 } 231 232 - CAMLprim value mc_cpu_rdseed (value __unused(unit)) { 233 #ifdef __mc_ENTROPY__ 234 random_t r = 0; 235 int ok = 0; 236 - int i = RETRIES; 237 do { ok = _rdseed_step (&r); _mm_pause (); } while ( !(ok | !--i) ); 238 - return Val_long(r); 239 #else 240 /* ARM: CPU-assisted randomness here. */ 241 - return Val_long (0); 242 #endif 243 } 244 245 - CAMLprim value mc_cpu_rdrand (value __unused(unit)) { 246 #ifdef __mc_ENTROPY__ 247 random_t r = 0; 248 int ok = 0; 249 - int i = RETRIES; 250 do { ok = _rdrand_step (&r); } while ( !(ok | !--i) ); 251 - return Val_long(r); 252 #else 253 /* ARM: CPU-assisted randomness here. */ 254 - return Val_long (0); 255 #endif 256 } 257
··· 15 #define random_t unsigned long long 16 #define _rdseed_step _rdseed64_step 17 #define _rdrand_step _rdrand64_step 18 + #define fill_bytes(buf, off, data) memcpy(_bp_uint8_off(buf, off), data, 8) 19 20 #elif defined (__i386__) 21 #define random_t unsigned int 22 #define _rdseed_step _rdseed32_step 23 #define _rdrand_step _rdrand32_step 24 + #define fill_bytes(buf, off, data) memcpy(_bp_uint8_off(buf, off), data, 4) 25 26 #endif 27 #endif /* __i386__ || __x86_64__ */ ··· 205 206 static int __cpu_rng = RNG_NONE; 207 208 static void detect (void) { 209 #ifdef __mc_ENTROPY__ 210 random_t r = 0; ··· 212 if (mc_detected_cpu_features.rdrand) 213 /* AMD Ryzen 3000 bug where RDRAND always returns -1 214 https://arstechnica.com/gadgets/2019/10/how-a-months-old-amd-microcode-bug-destroyed-my-weekend/ */ 215 + for (int i = 0; i < 10; i++) 216 if (_rdrand_step(&r) == 1 && r != (random_t) (-1)) { 217 __cpu_rng = RNG_RDRAND; 218 break; ··· 221 if (mc_detected_cpu_features.rdseed) 222 /* RDSEED could return -1, thus we test it here 223 https://www.reddit.com/r/Amd/comments/cmza34/agesa_1003_abb_fixes_rdrandrdseed/ */ 224 + for (int i = 0; i < 100; i++) 225 if (_rdseed_step(&r) == 1 && r != (random_t) (-1)) { 226 __cpu_rng |= RNG_RDSEED; 227 break; ··· 229 #endif 230 } 231 232 + CAMLprim value mc_cpu_rdseed (value buf, value off) { 233 #ifdef __mc_ENTROPY__ 234 random_t r = 0; 235 int ok = 0; 236 + int i = 100; 237 do { ok = _rdseed_step (&r); _mm_pause (); } while ( !(ok | !--i) ); 238 + fill_bytes(buf, off, &r); 239 + return Val_bool (ok); 240 #else 241 /* ARM: CPU-assisted randomness here. */ 242 + (void)buf; 243 + (void)off; 244 + return Val_false; 245 #endif 246 } 247 248 + CAMLprim value mc_cpu_rdrand (value buf, value off) { 249 #ifdef __mc_ENTROPY__ 250 random_t r = 0; 251 int ok = 0; 252 + int i = 10; 253 do { ok = _rdrand_step (&r); } while ( !(ok | !--i) ); 254 + fill_bytes(buf, off, &r); 255 + return Val_bool (ok); 256 #else 257 /* ARM: CPU-assisted randomness here. */ 258 + (void)buf; 259 + (void)off; 260 + return Val_false; 261 #endif 262 } 263