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