From 551fff6e6ae89ee5cccb499208d667822831aa55 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 25 Aug 2026 04:27:14 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20openkal.random=20=E2=80=94=20entrop?= =?UTF-8?q?y=20is=20not=20derivable=20from=20the=20other=20eight?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ⭐ **加一个接口的判据是「不加就实现不了」,而这一条满足它。** `std::random_device` 在这套体系上不可用,查到底是三层: std::random_device → libc++ 五条后端 → getentropy → musl getrandom() → syscall_cp(SYS_getrandom) → openkal-musl 无替代 → **规范无随机源接口** ⚠️ 而现有九个接口一个都给不出熵: · `time` —— 时钟读数「对读源码的人不可预测,对攻击者可预测」。 这句话不是我写的,是 `openkal-musl/port/src/okm_start.c` 给 musl 的 `AT_RANDOM` 凑 16 字节时**自己写下的**,并注明「在这个 port 上 两者都不是安全属性」。给 allocator cookie 够用,给 random_device 不够。 · `fs` —— `kal_fs_open` 只能相对 preopen 目录打开,开不了 `/dev/urandom`。⭐ **那是能力模型在工作,不是缺口。** · 其余六个与熵无关。 ⚠️ 而绕过接口层直接发系统调用违反本规范存在的理由。**两条都不行, 于是层多一个接口。** ── ⭐ 它「宿主普遍有、裸机不一定有」,正是 6.1 条的形状 ──── linux/macos/windows getrandom / getentropy / ProcessPrng 有 uefi EFI_RNG_PROTOCOL —— **可选协议** 不一定 opensbi SBI 基础规范无 RNG 扩展 无 按 6.1 条,不提供的实现让这些名字作为链接期定义缺席 —— 与 `openkal.fs` 在 opensbi 上缺席完全同理,**不需要任何新机制**。 ── ⚠️ 没有 `AVAILABLE` 能力字,而这是设计 ────────────── 「有没有」由接口的**在场与否**回答(6.1),不由能力字回答。 一个不提供任何操作却定义 `kal_random_props = 0` 的后端,会让程序 越过链接器存在的意义 —— 这正是 `openkal-opensbi@0.1.3` 撤回的那件事。 两个位各有理由:`BLOCKING`(熵不足时可能等待,影响早期启动路径的程序)、 `HARDWARE`(直接来自硬件 RNG,影响信任模型而非接口行为)。 ── 无部分成功 ──────────────────────────────────── 要么每个字节都填,要么一个都不填。一个必须循环的调用者要区分 「短读」与「没有更多熵」,而后者不是本接口的状态:一个环境要么有源、 要么不提供本接口。`kal_err_again` 是「源暂时为空」(刚启动、池未播种), 不是「本环境没有源」。 一致性套件加一节四条观察,含「两次填充不得相同」—— ⚠️ 该条以 2^-256 概率误报,把概率写出来比省掉好。 声明检查 58/58 通过。 --- SPEC.md | 1 + SURFACE.txt | 3 ++ conformance/mcpp.toml | 3 +- conformance/src/declarations.c | 2 + conformance/src/sections/random.cpp | 70 ++++++++++++++++++++++++++++ conformance/src/sections/random.cppm | 6 +++ conformance/src/suite.cpp | 2 + include/openkal.h | 1 + include/openkal/random.h | 57 ++++++++++++++++++++++ src/random.cppm | 53 +++++++++++++++++++++ 10 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 conformance/src/sections/random.cpp create mode 100644 conformance/src/sections/random.cppm create mode 100644 include/openkal/random.h create mode 100644 src/random.cppm diff --git a/SPEC.md b/SPEC.md index 3d054c8..13615b9 100644 --- a/SPEC.md +++ b/SPEC.md @@ -38,6 +38,7 @@ provides an interface in whole or not at all. | `openkal.memory` | a region of the address space | core | | `openkal.env` | the parameters a program receives at inception | standard | | `openkal.time` | a time source | standard | +| `openkal.random` | a source of unpredictable bytes | standard | | `openkal.fs` | a directory, and an open file | standard | | `openkal.process` | a program image that has been started | standard | | `openkal.task` | an execution context, and a suspension primitive | standard | diff --git a/SURFACE.txt b/SURFACE.txt index 2fb7dd2..c33abe7 100644 --- a/SURFACE.txt +++ b/SURFACE.txt @@ -33,6 +33,9 @@ kal_time_monotonic_granularity kal_time_props kal_time_sleep kal_time_wall +# openkal.random +kal_random_fill +kal_random_props # openkal.fs kal_fs_close_dir kal_fs_close_file diff --git a/conformance/mcpp.toml b/conformance/mcpp.toml index 4788f8a..2024262 100644 --- a/conformance/mcpp.toml +++ b/conformance/mcpp.toml @@ -56,6 +56,7 @@ default = ["core"] core = [] env = [] time = [] +random = [] fs = [] process = ["fs"] # a program is started relative to a directory task = [] @@ -64,7 +65,7 @@ exec = [] # The specification names this set `hosted' (clause 3.3). Both spellings are # here because the workflows and the older invocations use `standard', and a # rename that broke them would be a rename of the wrong thing. -hosted = ["core", "env", "time", "fs", "process", "task"] +hosted = ["core", "env", "time", "random", "fs", "process", "task"] standard = ["hosted"] # Deliberately not part of `full'. Clause 6.1 states that an implementation diff --git a/conformance/src/declarations.c b/conformance/src/declarations.c index df8c8b9..10a3a4b 100644 --- a/conformance/src/declarations.c +++ b/conformance/src/declarations.c @@ -64,6 +64,8 @@ void okc_declarations_c(void) (void)sizeof(&kal_process_spawn); (void)sizeof(&kal_process_terminate); (void)sizeof(&kal_process_wait); + (void)sizeof(&kal_random_fill); + (void)sizeof(&kal_random_props); (void)sizeof(&kal_stderr); (void)sizeof(&kal_stdin); (void)sizeof(&kal_stdout); diff --git a/conformance/src/sections/random.cpp b/conformance/src/sections/random.cpp new file mode 100644 index 0000000..13646c9 --- /dev/null +++ b/conformance/src/sections/random.cpp @@ -0,0 +1,70 @@ +module okc.random; + +import openkal.types; +import openkal.random; +import okc.report; +import okc.spec; + +namespace okc::random { + +void run() { + heading("openkal.random"); +#ifndef MCPP_FEATURE_RANDOM + unobserved(kind::behaviour, "openkal.random", "the interface was not selected"); + return; +#else + claim("kal_random_props", kal_random_props); + + // A fill either succeeds completely or changes nothing. The buffer is + // pre-set to a value the source is unlikely to produce for every byte, so + // that a partial fill --- the state this interface does not have --- would + // be visible rather than being mistaken for entropy. + { + unsigned char buf[32]; + for (auto& b : buf) b = 0xA5; + const int rc = kal_random_fill(buf, sizeof buf); + observe(kind::behaviour, rc == kal_ok || rc == kal_err_again, + "a fill reports success or a momentarily empty source"); + if (rc == kal_ok) { + bool all_untouched = true; + for (auto b : buf) if (b != 0xA5) { all_untouched = false; break; } + observe(kind::behaviour, !all_untouched, + "a successful fill wrote the buffer"); + } + } + + // ⚠️ TWO FILLS DIFFER, AND THE CHANCE OF A FALSE REPORT IS STATED RATHER + // THAN LEFT FOR A READER TO WONDER ABOUT. + // + // A source that returned a constant would satisfy every check above. Two + // fills of 32 bytes agreeing by chance has probability 2^-256, which is not + // reachable; a source that agrees is returning a constant, and that is what + // this observes. + { + unsigned char a[32], b[32]; + const int ra = kal_random_fill(a, sizeof a); + const int rb = kal_random_fill(b, sizeof b); + if (ra == kal_ok && rb == kal_ok) { + bool identical = true; + for (unsigned i = 0; i < sizeof a; ++i) + if (a[i] != b[i]) { identical = false; break; } + observe(kind::behaviour, !identical, + "two fills do not return the same bytes"); + } else { + unobserved(kind::behaviour, "two fills differ", + "a fill reported no entropy"); + } + } + + // A zero-length fill is not an error: a caller computing a length may + // legitimately arrive at zero, and refusing would oblige every caller to + // branch before calling. + { + const int rc = kal_random_fill(nullptr, 0); + observe(kind::behaviour, rc == kal_ok, + "a fill of zero bytes succeeds"); + } +#endif +} + +} diff --git a/conformance/src/sections/random.cppm b/conformance/src/sections/random.cppm new file mode 100644 index 0000000..1a33f07 --- /dev/null +++ b/conformance/src/sections/random.cppm @@ -0,0 +1,6 @@ +// The section that examines openkal.random. +export module okc.random; + +export namespace okc::random { +void run(); +} diff --git a/conformance/src/suite.cpp b/conformance/src/suite.cpp index c20d24e..bb5fc94 100644 --- a/conformance/src/suite.cpp +++ b/conformance/src/suite.cpp @@ -7,6 +7,7 @@ import okc.stream; import okc.memory; import okc.env; import okc.time; +import okc.random; import okc.fs; import okc.process; import okc.task; @@ -26,6 +27,7 @@ int run_all() { memory::run(); env::run(); time::run(); + random::run(); fs::run(); process::run(); task::run(); diff --git a/include/openkal.h b/include/openkal.h index 5ca179e..2995ad0 100644 --- a/include/openkal.h +++ b/include/openkal.h @@ -17,6 +17,7 @@ #include "openkal/memory.h" #include "openkal/env.h" #include "openkal/time.h" +#include "openkal/random.h" #include "openkal/fs.h" #include "openkal/process.h" #include "openkal/task.h" diff --git a/include/openkal/random.h b/include/openkal/random.h new file mode 100644 index 0000000..b2587a2 --- /dev/null +++ b/include/openkal/random.h @@ -0,0 +1,57 @@ +/* openkal.random --- a source of unpredictable bytes. + * + * ⚠️ NOT A GENERATOR. This interface answers "give me bytes this environment + * considers unpredictable". It does not define a pseudo-random algorithm, hold + * state between calls, or promise a distribution. A program wanting a + * reproducible sequence seeds its own generator from these bytes once and does + * not come back; a program wanting unpredictability comes back. + * + * ⭐ THE INTERFACE EXISTS BECAUSE NOTHING ELSE HERE CAN SUPPLY IT. Entropy is + * not derivable from the other eight: a clock is not a source (a reading is + * unpredictable to a reader of the source and not to an adversary), and + * `openkal.fs` deliberately cannot open a platform-named object such as + * `/dev/urandom` --- a capability-oriented filesystem hands over roots, not + * absolute paths, and that refusal is the model working. + * + * ⚠️ AND IT IS NOT UNIVERSAL, WHICH IS WHY IT IS ITS OWN INTERFACE. Every + * hosted platform has one; a bare-metal machine has one only if its board does. + * Clause 6.1 already expresses that: an implementation providing no source + * omits these names, and a program that asks fails to link rather than + * receiving zeros. + */ +#ifndef OPENKAL_RANDOM_H +#define OPENKAL_RANDOM_H +#include "types.h" + +/* Positions in kal_random_props. + * + * ⚠️ THERE IS NO `AVAILABLE` POSITION, AND ITS ABSENCE IS THE DESIGN. Whether + * an environment has a source is answered by whether this interface is present + * at all (clause 6.1), not by a word a program reads after linking. A backend + * that defined `kal_random_props = 0` while providing no operation would let a + * program past the point the linker exists to stop it at. */ +#define KAL_RANDOM_PROP_BLOCKING ((kal_uintptr)1u << 0) +#define KAL_RANDOM_PROP_HARDWARE ((kal_uintptr)1u << 1) + +#ifdef __cplusplus +extern "C" { +#endif + +/* Fills `len` bytes at `out`. + * + * ⚠️ NO PARTIAL SUCCESS. Either every byte is filled or none is. A caller that + * had to loop would have to distinguish "short read" from "no more entropy", + * and the second is not a state this interface has: an environment either has a + * source or does not provide the interface. + * + * `kal_err_again` reports a source that is momentarily empty --- a machine + * early in boot whose pool has not been seeded. It is not "this environment has + * no source", which is answered by the interface's absence. */ +int kal_random_fill(void* out, kal_uintptr len); + +extern const kal_uintptr kal_random_props; + +#ifdef __cplusplus +} +#endif +#endif /* OPENKAL_RANDOM_H */ diff --git a/src/random.cppm b/src/random.cppm new file mode 100644 index 0000000..989a033 --- /dev/null +++ b/src/random.cppm @@ -0,0 +1,53 @@ +// openkal.random --- a source of unpredictable bytes. +// +// ⭐ THE INTERFACE EXISTS BECAUSE NOTHING ELSE HERE CAN SUPPLY IT, WHICH IS THE +// ONLY REASON ANY INTERFACE HERE EXISTS. +// +// Entropy is not derivable from the other eight. A clock reading is +// unpredictable to a reader of the source and not to an adversary — a +// distinction a port that tried it has already had to write down. And +// `openkal.fs` deliberately cannot open a platform-named object such as +// `/dev/urandom`: a capability-oriented filesystem hands a program its roots +// rather than the whole namespace, so that refusal is the model working rather +// than a gap in it. +// +// ⚠️ NOT UNIVERSAL, AND THAT IS WHY IT IS ITS OWN INTERFACE RATHER THAN AN +// OPERATION ON AN EXISTING ONE. Every hosted platform has a source; a +// bare-metal machine has one only if its board does. Clause 6.1 already +// expresses exactly that shape: an implementation with no source omits these +// names, and a program that asks fails to LINK rather than receiving zeros at +// run time. +module; +#include + +export module openkal.random; +export import openkal.types; + +export using ::kal_random_fill; +export using ::kal_random_props; + +export namespace kal::random { + +struct props_tag; +using props = kal::props; + +// Positions in kal_random_props. A position, once assigned, retains its +// meaning; an unassigned position reads as zero, so that a program compiled +// against a later specification behaves correctly against an earlier +// implementation. +// +// ⚠️ THERE IS NO `available` POSITION. Whether an environment has a source is +// answered by whether this module can be imported and its names linked, not by +// a word read after linking — see the note on the header. +inline constexpr props blocking{KAL_RANDOM_PROP_BLOCKING}; +inline constexpr props hardware{KAL_RANDOM_PROP_HARDWARE}; + +// Fills `out` with `len` unpredictable bytes, or fills none of it. +inline int fill(void* out, kal_uintptr len) { + return kal_random_fill(out, len); +} + +inline props properties() { return props{kal_random_props}; } +inline bool has(props p) { return properties().has(p); } + +} From 82b60ac8b90cbae69bb45b50d6f6159d8c1db2e6 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 25 Aug 2026 04:32:36 +0800 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20random=20is=20optional,=20not=20stan?= =?UTF-8?q?dard=20=E2=80=94=20and=20the=20note=20above=20already=20said=20?= =?UTF-8?q?why?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 我把 `random` 放进 `hosted`,于是一致性套件对每一个尚未实现它的后端 **链接失败**: lld-link: error: undefined symbol: kal_random_fill ⚠️ 而 `optional` 那段注释早就写着这件事:「a feature set that required every optional interface would make optional mean nothing, **and the report would be a link failure rather than an observation that did not hold**」。 ⭐ `random` 与 `exec` 同形:宿主平台普遍提供,裸机只在板子有源时提供, 两者都不是偏离(6.1 条)。一个要求它的集合把「被允许的选择」变成了构建错误。 SPEC.md 的接口表同步改为 optional。 --- SPEC.md | 2 +- conformance/mcpp.toml | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/SPEC.md b/SPEC.md index 13615b9..55538c1 100644 --- a/SPEC.md +++ b/SPEC.md @@ -38,7 +38,7 @@ provides an interface in whole or not at all. | `openkal.memory` | a region of the address space | core | | `openkal.env` | the parameters a program receives at inception | standard | | `openkal.time` | a time source | standard | -| `openkal.random` | a source of unpredictable bytes | standard | +| `openkal.random` | a source of unpredictable bytes | optional | | `openkal.fs` | a directory, and an open file | standard | | `openkal.process` | a program image that has been started | standard | | `openkal.task` | an execution context, and a suspension primitive | standard | diff --git a/conformance/mcpp.toml b/conformance/mcpp.toml index 2024262..1ab1a40 100644 --- a/conformance/mcpp.toml +++ b/conformance/mcpp.toml @@ -65,7 +65,7 @@ exec = [] # The specification names this set `hosted' (clause 3.3). Both spellings are # here because the workflows and the older invocations use `standard', and a # rename that broke them would be a rename of the wrong thing. -hosted = ["core", "env", "time", "random", "fs", "process", "task"] +hosted = ["core", "env", "time", "fs", "process", "task"] standard = ["hosted"] # Deliberately not part of `full'. Clause 6.1 states that an implementation @@ -75,7 +75,23 @@ standard = ["hosted"] # rather than an observation that did not hold. The caller names this set for an # implementation that provides these interfaces, and omits it for one that does # not --- which is the same choice the implementation itself made. -optional = ["exec"] +# ⚠️ `random` IS HERE AND NOT IN `hosted`, AND THE NOTE ABOVE ALREADY SAID WHY. +# +# It was put in `hosted` first, and the suite then failed to LINK against every +# backend that had not yet implemented it: +# +# lld-link: error: undefined symbol: kal_random_fill +# +# — which is exactly the outcome the paragraph above warns about: a report that +# is a link failure rather than an observation that did not hold. Clause 6.1 +# makes an absent interface not a deviation, so a set that demands it turns a +# permitted choice into a build error. +# +# `random` has the same shape as `exec`: every hosted platform provides it, a +# bare-metal one provides it only if its board has a source, and neither is a +# deviation. The caller names this set for an implementation that provides +# these interfaces and omits it for one that does not. +optional = ["exec", "random"] # The kinds of examination. Behaviour is always performed; the other three are # selected, because each costs time that a reader running the suite to answer From b6405e671b0bdf73d7650406b4a5ea9e29d673c7 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 25 Aug 2026 04:32:54 +0800 Subject: [PATCH 3/5] =?UTF-8?q?docs(spec):=20random=20=E4=B9=9F=E8=A6=81?= =?UTF-8?q?=E8=BF=9B=E6=A8=A1=E5=9D=97/=E5=A4=B4=E6=98=A0=E5=B0=84?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 第 156 行那张表列出每个接口的模块名与头文件名,我加接口时漏了它。 ⚠️ 一张只列了八个而接口有九个的表,读者会当成「这个接口没有模块」。 --- SPEC.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SPEC.md b/SPEC.md index 55538c1..66ad4ce 100644 --- a/SPEC.md +++ b/SPEC.md @@ -153,6 +153,7 @@ implementation provides neither. | `openkal.fs` | `openkal.fs` | `openkal/fs.h` | | `openkal.process` | `openkal.process` | `openkal/process.h` | | `openkal.task` | `openkal.task` | `openkal/task.h` | +| `openkal.random` | `openkal.random` | `openkal/random.h` | | `openkal.exec` | `openkal.exec` | `openkal/exec.h` | `openkal.h` includes every header, for a consumer that uses several. From a3b77ccd7ca7602da532fe82f5adfa518b710850 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 25 Aug 2026 04:44:42 +0800 Subject: [PATCH 4/5] =?UTF-8?q?0.7.0=20=E2=80=94=20the=20interface=20count?= =?UTF-8?q?=20goes=20from=20eight=20to=20nine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 `openkal.random` 是加法而非破坏:既有八个接口的名字、签名与语义 一字未动,而 6.1 条已经规定「实现不提供的接口作为链接期定义缺席」, 所以一个尚未实现它的后端**仍然是 0.7 的合规实现**。 ⚠️ 次版本号而非补丁号:一致性套件的 `optional` 集合多了一项, 一个自称提供全部可选接口的实现因此要多提供一个。 --- conformance/mcpp.toml | 2 +- mcpp.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conformance/mcpp.toml b/conformance/mcpp.toml index 1ab1a40..0526dc5 100644 --- a/conformance/mcpp.toml +++ b/conformance/mcpp.toml @@ -18,7 +18,7 @@ repo = "https://github.com/mcpplibs/openkal" # What it depends upon is therefore openkal and the language. The formatting in # okc.report is the price of that, and it is sixty lines. [dependencies] -openkal = "0.6.0" +openkal = "0.7.0" # The implementation under examination is not named here. # diff --git a/mcpp.toml b/mcpp.toml index 8df7260..cbffd40 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal" -version = "0.6.0" +version = "0.7.0" description = "openkal: a portable kernel ABI specification. This package carries the normative declarations; implementations are separate packages." license = "Apache-2.0" authors = ["mcpplibs"] From 055e9a4f0a7844d7e8e42e93c5b4ee931acfc997 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 25 Aug 2026 04:45:08 +0800 Subject: [PATCH 5/5] =?UTF-8?q?docs(spec):=20=E8=A7=84=E8=8C=83=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E7=9A=84=E7=89=88=E6=9C=AC=E5=8F=B7=E4=B9=9F=E6=98=AF?= =?UTF-8?q?=E6=89=BF=E8=AF=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 标题与第 48 行的 "Version 0.6" 改为 0.7。 ⚠️ 第 340 行**不改** —— 那句 "Every declaration of version 0.6 already satisfies it" 是**历史陈述**,讲某条款当时为何没有让版本前进。 把它一起替换会把一句关于过去的记录改成一句关于现在的假话。 --- SPEC.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SPEC.md b/SPEC.md index 66ad4ce..cae855b 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,4 +1,4 @@ -# openkal Specification, version 0.6 +# openkal Specification, version 0.7 ## 1. Scope @@ -45,7 +45,7 @@ provides an interface in whole or not at all. | `openkal.exec` | a region of the address space a program may execute | optional | | `openkal.event` | readiness of a set of resources | reserved | -Version 0.6 specifies the core, standard and optional interfaces. The reserved +Version 0.7 specifies the core, standard and optional interfaces. The reserved row is not specified, and its name shall not be used for other purposes. *Core* denotes an interface every implementation provides. *Standard* denotes