mte/unikernel/duniverse/mirage-crypto/rng/entropy.ml
2025-11-11 02:07:51 +01:00

226 lines
7.1 KiB
OCaml

(*
* Copyright (c) 2014 Hannes Mehnert
* Copyright (c) 2014 Anil Madhavapeddy <anil@recoil.org>
* Copyright (c) 2014-2016 David Kaloper Meršinjak
* Copyright (c) 2015 Citrix Systems Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*)
let src = Logs.Src.create "mirage-crypto-rng-entropy" ~doc:"Mirage crypto RNG Entropy"
module Log = (val Logs.src_log src : Logs.LOG)
let rdrand_calls = Atomic.make 0
let rdrand_failures = Atomic.make 0
let rdseed_calls = Atomic.make 0
let rdseed_failures = Atomic.make 0
module Cpu_native = struct
external cycles : unit -> int = "mc_cycle_counter" [@@noalloc]
external rdseed : bytes -> int -> bool = "mc_cpu_rdseed" [@@noalloc]
external rdrand : bytes -> int -> bool = "mc_cpu_rdrand" [@@noalloc]
external rng_type : unit -> int = "mc_cpu_rng_type" [@@noalloc]
let cpu_rng =
match rng_type () with
| 0 -> []
| 1 -> [ `Rdrand ]
| 2 -> [ `Rdseed ]
| 3 -> [ `Rdrand ; `Rdseed ]
| _ -> assert false
end
module S = Set.Make(struct
type t = int * string
(* only the name is relevant for comparison - the idx not *)
let compare ((_a, an) : int * string) ((_b, bn) : int * string) =
String.compare an bn
end)
let _sources = Atomic.make S.empty
type source = Rng.source
let register_source name =
let rec set () =
let sources = Atomic.get _sources in
let n = S.cardinal sources in
let source = (n, name) in
if Atomic.compare_and_set _sources sources (S.add source sources) then
source
else
set ()
in
set ()
let id (idx, _) = idx
let sources () = S.elements (Atomic.get _sources)
let pp_source ppf (idx, name) = Format.fprintf ppf "[%d] %s" idx name
let cpu_rng isn buf off = match isn with
| `Rdseed ->
Atomic.incr rdseed_calls;
let success = Cpu_native.rdseed buf off in
if not success then Atomic.incr rdseed_failures;
success
| `Rdrand ->
Atomic.incr rdrand_calls;
let success = Cpu_native.rdrand buf off in
if not success then Atomic.incr rdrand_failures;
success
let random preferred =
match Cpu_native.cpu_rng with
| [] -> None
| xs when List.mem preferred xs -> Some preferred
| y::_ -> Some y
let write_header source data =
Bytes.set_uint8 data 0 source;
Bytes.set_uint8 data 1 (Bytes.length data - 2)
let header source data =
let hdr = Bytes.create (2 + String.length data) in
Bytes.unsafe_blit_string data 0 hdr 2 (String.length data);
write_header source hdr;
Bytes.unsafe_to_string hdr
(* Note:
* `bootstrap` is not a simple feedback loop. It attempts to exploit CPU-level
* data races that lead to execution-time variability of identical instructions.
* See Whirlwind RNG:
* http://www.ieee-security.org/TC/SP2014/papers/Not-So-RandomNumbersinVirtualizedLinuxandtheWhirlwindRNG.pdf
*)
let whirlwind_bootstrap id =
let outer = 100
and inner_max = 1024
and a = ref 0
in
let buf = Bytes.create (outer * 2 + 2) in
for i = 0 to outer - 1 do
let tsc = Cpu_native.cycles () in
Bytes.set_uint16_le buf ((i + 1) * 2) tsc;
for j = 1 to tsc mod inner_max do
a := tsc / j - !a * i + 1
done
done;
write_header id buf;
Bytes.unsafe_to_string buf
let cpu_rng_bootstrap =
let rdrand_bootstrap id =
let rec go acc = function
| 0 -> acc
| n ->
let buf = Bytes.create 10 in
let r = cpu_rng `Rdrand buf 2 in
write_header id buf;
if not r then
go acc (pred n)
else
go (Bytes.unsafe_to_string buf :: acc) (pred n)
in
let result = go [] 512 |> String.concat "" in
if String.length result = 0 then
failwith "Too many RDRAND failures"
else
result
in
match random `Rdseed with
| None -> Error `Not_supported
| Some `Rdseed ->
let cpu_rng_bootstrap id =
let buf = Bytes.create 10 in
let r = cpu_rng `Rdseed buf 2 in
write_header id buf;
if not r then
if List.mem `Rdrand Cpu_native.cpu_rng then
rdrand_bootstrap id
else
failwith "RDSEED failed, and RDRAND not available"
else
Bytes.unsafe_to_string buf
in
Ok cpu_rng_bootstrap
| Some `Rdrand -> Ok rdrand_bootstrap
let bootstrap id =
match cpu_rng_bootstrap with
| Error `Not_supported -> whirlwind_bootstrap id
| Ok cpu_rng_bootstrap ->
try cpu_rng_bootstrap id with
| Failure f ->
Log.err (fun m -> m "CPU RNG bootstrap failed: %s, using whirlwind" f);
whirlwind_bootstrap id
let interrupt_hook () =
let buf = Bytes.create 4 in
let a = Cpu_native.cycles () in
Bytes.set_int32_le buf 0 (Int32.of_int a) ;
Bytes.unsafe_to_string buf
let timer_accumulator g =
let g = match g with None -> Some (Rng.default_generator ()) | Some g -> Some g in
let source = register_source "timer" in
let `Acc handle = Rng.accumulate g source in
(fun () -> handle (interrupt_hook ()))
let feed_pools g source f =
let g = match g with None -> Some (Rng.default_generator ()) | Some g -> Some g in
let `Acc handle = Rng.accumulate g source in
for _i = 0 to pred (Rng.pools g) do
match f () with
| Ok data -> handle data
| Error `No_random_available ->
(* should we log a message? *)
()
done
let cpu_rng =
match random `Rdrand with
| None -> Error `Not_supported
| Some insn ->
let cpu_rng g =
let randomf = cpu_rng insn
and source =
let s = match insn with `Rdrand -> "rdrand" | `Rdseed -> "rdseed" in
register_source s
in
let f () =
let buf = Bytes.create 8 in
if randomf buf 0 then
Ok (Bytes.unsafe_to_string buf)
else
Error `No_random_available
in
fun () -> feed_pools g source f
in
Ok cpu_rng
let rdrand_calls () = Atomic.get rdrand_calls
let rdrand_failures () = Atomic.get rdrand_failures
let rdseed_calls () = Atomic.get rdseed_calls
let rdseed_failures () = Atomic.get rdseed_failures