diff --git a/mcpp.toml b/mcpp.toml index 927690d..f4583c7 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-linux" -version = "0.5.3" +version = "0.5.4" description = "The reference implementation of openkal for Linux, written on the kernel's own system-call interface so that it can be placed beneath a C library as well as above one." license = "Apache-2.0" @@ -18,7 +18,7 @@ authors = ["mcpplibs"] repo = "https://github.com/mcpplibs/openkal-linux" [dependencies] -openkal = "0.6.0" +openkal = "0.7.0" # The package contributes definitions and no modules. The interface it # implements is declared by the specification package, which this package diff --git a/src/random.cpp b/src/random.cpp new file mode 100644 index 0000000..70fc828 --- /dev/null +++ b/src/random.cpp @@ -0,0 +1,55 @@ +// openkal.random on Linux --- getrandom(2). +// +// ⭐ THE KERNEL'S OWN CALL AND NOT `/dev/urandom`. The device would need a +// descriptor, which needs a path, which a capability-oriented filesystem +// deliberately does not hand out; and a program early enough in its life not to +// have a filesystem yet still has this call. `getrandom` is the interface the +// kernel offers for exactly this question. +#include "sys.h" +#include + +namespace { + +// ⚠️ `GRND_NONBLOCK` IS NOT SET, AND THAT IS WHAT `BLOCKING` REPORTS. +// +// Without it the call waits until the pool has been initialised, which on a +// machine seconds into its first boot can be a real wait. Setting it instead +// would turn that wait into a short read — a partial success this interface +// does not have — so the wait is kept and named in the capability word. +constexpr okl_long flags_blocking = 0; + +} // namespace + +extern "C" int kal_random_fill(void* out, kal_uintptr len) { + if (len == 0) return kal_ok; + if (out == nullptr) return kal_err_invalid; + + auto* p = static_cast(out); + kal_uintptr filled = 0; + while (filled < len) { + const okl_long r = okl::sys(okl::nr_getrandom, + reinterpret_cast(p + filled), + static_cast(len - filled), + flags_blocking); + if (r < 0) { + // ⚠️ THE BUFFER IS NOT RESTORED, AND THE CONTRACT SAYS IT NEED NOT + // BE: a failed fill leaves the buffer unspecified rather than + // unchanged. Restoring it would oblige this function to keep a copy + // of what it was handed, which is a cost every successful call + // would pay for the benefit of the failing one. + if (r == -4 /* EINTR */) continue; + if (r == -11 /* EAGAIN */) return kal_err_again; + return kal_err_io; + } + // A short return is the kernel's, not this interface's: `getrandom` + // caps a single call at 32 MiB. Looping is what turns it into the + // all-or-nothing this interface promises. + filled += static_cast(r); + } + return kal_ok; +} + +// Blocking, because GRND_NONBLOCK is not set above. Not hardware: the kernel's +// pool is what this reads, and whether the pool was seeded from a hardware +// source is not something this backend can observe. +extern "C" const kal_uintptr kal_random_props = KAL_RANDOM_PROP_BLOCKING; diff --git a/src/sys.h b/src/sys.h index 6352116..82975e3 100644 --- a/src/sys.h +++ b/src/sys.h @@ -97,6 +97,7 @@ enum : okl_long { nr_openat = 257, nr_mkdirat = 258, nr_newfstatat = 262, nr_unlinkat = 263, nr_renameat = 264, nr_readlinkat = 267, nr_dup3 = 292, nr_execveat = 322, nr_dup2 = 33, nr_utimensat = 280, + nr_getrandom = 318, }; #elif defined(__aarch64__) @@ -170,6 +171,7 @@ enum : okl_long { nr_clone = 220, nr_execve = 221, nr_wait4 = 260, nr_renameat = 38, nr_dup3 = 24, nr_execveat = 281, nr_dup2 = -1, nr_arch_prctl = -1, nr_utimensat = 88, + nr_getrandom = 278, }; #else