Skip to content

Commit 57a394b

Browse files
committed
platform: portable secure-random backend for macOS/Windows
getrandom(2) is Linux-only — macOS has <sys/random.h> but no getrandom symbol, Windows has neither — so the AppleClang build failed with "call to undeclared function 'getrandom'" in ytelemetry.c and the three token mints (session, token_issuer, runner_agent). Add a platform/random module mirroring platform/time: a single picomesh_platform_random_bytes() with per-OS backends selected by CMake — Linux getrandom(2), macOS/BSD getentropy(3) (chunked to 256 bytes), Windows rand_s(). Call sites now use the helper and lose their direct <sys/random.h> dependency; the security mints keep failing closed, the telemetry id generator keeps its non-secret PRNG fallback.
1 parent cc39a23 commit 57a394b

8 files changed

Lines changed: 127 additions & 61 deletions

File tree

CMakeLists.txt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,15 @@ include(build-tools/picomesh/libs/libcurl.cmake)
4848
# ---- platform backend selection -------------------------------------
4949
#
5050
# platform abstractions get one backend file per platform. Backends
51-
# under src/picomesh/platform/<module>/<platform>.c. Today only `time`
52-
# is platform-split; future modules (paths, threads, sockets) follow
51+
# under src/picomesh/platform/<module>/<platform>.c. `time` and `random`
52+
# are platform-split; future modules (paths, threads, sockets) follow
5353
# the same shape.
5454
if(WIN32)
55-
set(PICOMESH_PLATFORM_TIME_SRC src/picomesh/platform/time/windows.c)
55+
set(PICOMESH_PLATFORM_TIME_SRC src/picomesh/platform/time/windows.c)
56+
set(PICOMESH_PLATFORM_RANDOM_SRC src/picomesh/platform/random/windows.c)
5657
else()
57-
set(PICOMESH_PLATFORM_TIME_SRC src/picomesh/platform/time/posix.c)
58+
set(PICOMESH_PLATFORM_TIME_SRC src/picomesh/platform/time/posix.c)
59+
set(PICOMESH_PLATFORM_RANDOM_SRC src/picomesh/platform/random/posix.c)
5860
endif()
5961

6062
# ---- picomesh runtime ---------------------------------------------------
@@ -103,6 +105,7 @@ add_library(picomesh_runtime STATIC
103105
src/picomesh/json/json.c
104106
src/yyjson/yyjson.c
105107
${PICOMESH_PLATFORM_TIME_SRC}
108+
${PICOMESH_PLATFORM_RANDOM_SRC}
106109
)
107110
target_include_directories(picomesh_runtime PUBLIC
108111
include

include/picomesh/platform/random.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/* platform/random.h — cross-platform cryptographically-secure randomness.
2+
*
3+
* One call, no platform headers in callers. Backends live under
4+
* src/picomesh/platform/random/<platform>.c — CMake selects one based
5+
* on the target:
6+
* - posix.c : Linux getrandom(2); macOS/BSD getentropy(3).
7+
* - windows.c : the CRT CSPRNG (rand_s / RtlGenRandom).
8+
*
9+
* Replaces direct <sys/random.h> getrandom() use, which is Linux-only
10+
* (macOS has <sys/random.h> but no getrandom symbol; Windows has
11+
* neither). */
12+
13+
#ifndef PICOMESH_PLATFORM_RANDOM_H
14+
#define PICOMESH_PLATFORM_RANDOM_H
15+
16+
#include <stddef.h>
17+
18+
#ifdef __cplusplus
19+
extern "C" {
20+
#endif
21+
22+
/* Fill `buf` with `len` cryptographically-secure random bytes, retrying
23+
* partial reads / EINTR internally. Returns 0 on success, -1 on failure
24+
* (in which case the contents of `buf` are unspecified). All-or-nothing:
25+
* a 0 return guarantees every byte was written. */
26+
int picomesh_platform_random_bytes(void *buf, size_t len);
27+
28+
#ifdef __cplusplus
29+
}
30+
#endif
31+
32+
#endif /* PICOMESH_PLATFORM_RANDOM_H */

src/picomesh/core/ytelemetry.c

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@
1111
#include <picomesh/picoclass/class.h>
1212
#include <picomesh/picoclass/rpc.h>
1313
#include <picomesh/picoclass/yheaders.h>
14+
#include <picomesh/platform/random.h>
1415

15-
#include <errno.h>
1616
#include <stdio.h>
1717
#include <stdlib.h>
1818
#include <string.h>
1919
#include <strings.h>
20-
#include <sys/random.h>
2120
#include <time.h>
2221
#include <unistd.h>
2322

@@ -77,26 +76,13 @@ static int ytel_bytes_all_zero(const uint8_t *bytes, size_t n) {
7776
}
7877

7978
static void ytel_fill_random(uint8_t *bytes, size_t n) {
80-
/* Prefer the kernel CSPRNG; retry partial reads and EINTR. */
81-
size_t off = 0;
82-
while (off < n) {
83-
ssize_t got = getrandom(bytes + off, n - off, 0);
84-
if (got > 0) {
85-
off += (size_t)got;
86-
continue;
87-
}
88-
if (got < 0 && errno == EINTR)
89-
continue;
90-
break;
91-
}
92-
if (off == n)
79+
/* Prefer the kernel CSPRNG. */
80+
if (picomesh_platform_random_bytes(bytes, n) == 0)
9381
return;
94-
/* Fallback for the remainder: trace/span ids are correlation ids, not
95-
* secrets, so a clock/pid-seeded PRNG is acceptable when getrandom is
96-
* unavailable. */
97-
uint64_t seed =
98-
ytel_mono_ns() ^ ((uint64_t)getpid() << 32) ^ (uint64_t)(off + 1);
99-
for (size_t i = off; i < n; ++i) {
82+
/* Fallback: trace/span ids are correlation ids, not secrets, so a
83+
* clock/pid-seeded PRNG is acceptable when the CSPRNG is unavailable. */
84+
uint64_t seed = ytel_mono_ns() ^ ((uint64_t)getpid() << 32) ^ 1u;
85+
for (size_t i = 0; i < n; ++i) {
10086
seed = seed * 6364136223846793005ull + 1442695040888963407ull;
10187
bytes[i] = (uint8_t)(seed >> 33);
10288
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/* random/posix.c — POSIX CSPRNG backend.
2+
*
3+
* Linux uses getrandom(2) (glibc wrapper over the syscall). macOS and the
4+
* BSDs don't expose getrandom but do provide getentropy(3), which fills up
5+
* to 256 bytes per call from the same kernel CSPRNG — chunk for larger
6+
* requests. Both block only until the pool is initialised, then never
7+
* again, which is what callers minting session ids / tokens want. */
8+
9+
/* getrandom() needs _GNU_SOURCE / _DEFAULT_SOURCE; the build defines
10+
* _GNU_SOURCE tree-wide, so no local define is needed here. */
11+
12+
#include <picomesh/platform/random.h>
13+
14+
#include <errno.h>
15+
#include <sys/random.h>
16+
17+
int picomesh_platform_random_bytes(void *buf, size_t len) {
18+
unsigned char *out = (unsigned char *)buf;
19+
size_t off = 0;
20+
while (off < len) {
21+
#if defined(__linux__)
22+
ssize_t got = getrandom(out + off, len - off, 0);
23+
if (got < 0) {
24+
if (errno == EINTR)
25+
continue;
26+
return -1;
27+
}
28+
off += (size_t)got;
29+
#else
30+
/* getentropy fills at most 256 bytes and is all-or-nothing per call. */
31+
size_t chunk = len - off;
32+
if (chunk > 256)
33+
chunk = 256;
34+
if (getentropy(out + off, chunk) != 0) {
35+
if (errno == EINTR)
36+
continue;
37+
return -1;
38+
}
39+
off += chunk;
40+
#endif
41+
}
42+
return 0;
43+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/* random/windows.c — Windows CSPRNG backend.
2+
*
3+
* Uses rand_s(), the CRT wrapper over RtlGenRandom (the system CSPRNG).
4+
* It needs _CRT_RAND_S defined before <stdlib.h> and pulls no extra
5+
* import library (unlike BCryptGenRandom, which would need bcrypt.lib),
6+
* keeping the picomesh.exe link self-contained. */
7+
8+
#define _CRT_RAND_S
9+
10+
#include <picomesh/platform/random.h>
11+
12+
#include <stdlib.h>
13+
14+
int picomesh_platform_random_bytes(void *buf, size_t len) {
15+
unsigned char *out = (unsigned char *)buf;
16+
size_t off = 0;
17+
while (off < len) {
18+
unsigned int value;
19+
if (rand_s(&value) != 0)
20+
return -1;
21+
size_t chunk = len - off;
22+
if (chunk > sizeof(value))
23+
chunk = sizeof(value);
24+
for (size_t i = 0; i < chunk; ++i)
25+
out[off + i] = (unsigned char)(value >> (8 * i));
26+
off += chunk;
27+
}
28+
return 0;
29+
}

src/picomesh/plugins/runner_agent/store.c

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,16 @@
3232
#include <picomesh/json/json.h>
3333
#include <picomesh/picoclass/class.h>
3434
#include <picomesh/picoclass/yheaders.h>
35+
#include <picomesh/platform/random.h>
3536
#include <picomesh/plugin/sharded_storage/sharded_storage.h>
3637
#include <picomesh/plugin/token_issuer/token_issuer.h>
3738
#include <picomesh/security/jwt.h>
3839
#include <picomesh/security/sha256.h>
3940

40-
#include <errno.h>
4141
#include <stdint.h>
4242
#include <stdio.h>
4343
#include <stdlib.h>
4444
#include <string.h>
45-
#include <sys/random.h>
4645

4746
#define RA_CTX "runner_agent"
4847

@@ -129,16 +128,8 @@ static int ra_alloc_token(char *out, size_t cap) {
129128
if (cap < 4 + 32 + 1)
130129
return 0;
131130
uint8_t raw[16];
132-
size_t got = 0;
133-
while (got < sizeof(raw)) {
134-
ssize_t read_len = getrandom(raw + got, sizeof(raw) - got, 0);
135-
if (read_len < 0) {
136-
if (errno == EINTR)
137-
continue;
138-
return 0;
139-
}
140-
got += (size_t)read_len;
141-
}
131+
if (picomesh_platform_random_bytes(raw, sizeof(raw)) != 0)
132+
return 0;
142133
static const char hex[] = "0123456789abcdef";
143134
memcpy(out, "rnr_", 4);
144135
size_t k = 4;

src/picomesh/plugins/session/session.c

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,14 @@
2727
#include <picomesh/engine/engine.h>
2828
#include <picomesh/picoclass/class.h>
2929
#include <picomesh/picoclass/rpc.h>
30+
#include <picomesh/platform/random.h>
3031
#include <picomesh/platform/time.h>
3132
#include <picomesh/plugin/relational_storage/relational_sql.h>
3233

33-
#include <errno.h>
3434
#include <stdint.h>
3535
#include <stdio.h>
3636
#include <stdlib.h>
3737
#include <string.h>
38-
#include <sys/random.h>
3938

4039
#define SESSION_DDL \
4140
"CREATE TABLE IF NOT EXISTS sessions(" \
@@ -57,16 +56,8 @@ static struct session_session_data *sess(struct object *obj) {
5756
*/
5857
static int alloc_token(char *out, size_t cap) {
5958
uint8_t raw[16];
60-
size_t got = 0;
61-
while (got < sizeof(raw)) {
62-
ssize_t read_len = getrandom(raw + got, sizeof(raw) - got, 0);
63-
if (read_len < 0) {
64-
if (errno == EINTR)
65-
continue;
66-
return 0;
67-
}
68-
got += (size_t)read_len;
69-
}
59+
if (picomesh_platform_random_bytes(raw, sizeof(raw)) != 0)
60+
return 0;
7061
static const char hex[] = "0123456789abcdef";
7162
size_t k = 0;
7263
for (size_t i = 0; i < sizeof(raw) && k + 2 < cap; ++i) {

src/picomesh/plugins/token_issuer/store.c

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,18 @@
2323
#include <picomesh/engine/engine.h>
2424
#include <picomesh/picoclass/class.h>
2525
#include <picomesh/picoclass/yheaders.h>
26+
#include <picomesh/platform/random.h>
2627
#include <picomesh/platform/time.h>
2728
#include <picomesh/plugin/accounts/accounts.h>
2829
#include <picomesh/plugin/password_authn/password_authn.h>
2930
#include <picomesh/plugin/relational_storage/relational_sql.h>
3031
#include <picomesh/security/jwt.h>
3132
#include <picomesh/security/secret.h>
3233

33-
#include <errno.h>
3434
#include <stdint.h>
3535
#include <stdio.h>
3636
#include <stdlib.h>
3737
#include <string.h>
38-
#include <sys/random.h>
3938

4039
#define TI_DDL \
4140
"CREATE TABLE IF NOT EXISTS refresh_tokens(" \
@@ -69,16 +68,8 @@ static struct picomesh_void_result ti_open(struct rel_handle *rel_handle,
6968
* randomness is unavailable — a refresh token is a bearer secret. */
7069
static int alloc_refresh_token(char *out, size_t cap) {
7170
uint8_t raw[16];
72-
size_t got = 0;
73-
while (got < sizeof(raw)) {
74-
ssize_t read_len = getrandom(raw + got, sizeof(raw) - got, 0);
75-
if (read_len < 0) {
76-
if (errno == EINTR)
77-
continue;
78-
return 0;
79-
}
80-
got += (size_t)read_len;
81-
}
71+
if (picomesh_platform_random_bytes(raw, sizeof(raw)) != 0)
72+
return 0;
8273
static const char hex[] = "0123456789abcdef";
8374
size_t k = 0;
8475
for (size_t i = 0; i < sizeof(raw) && k + 2 < cap; ++i) {

0 commit comments

Comments
 (0)