Skip to content

Commit bd5ac03

Browse files
jedisct1sunfishcode
authored andcommitted
Implement arc4random(), arcrandom_buf() and arc4random_uniform()
These functions leverage the existing getentropy() function from the bottom half. The arc4random_*() functions provide a BSD, but also CloudABI-compatible interface. Uses a simple ChaCha20-based RNG with fast key erasure.
1 parent 551f284 commit bd5ac03

3 files changed

Lines changed: 169 additions & 0 deletions

File tree

expected/wasm32-wasi/defined-symbols.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,9 @@ acosl
272272
aligned_alloc
273273
alphasort
274274
alphasort64
275+
arc4random
276+
arc4random_buf
277+
arc4random_uniform
275278
asctime_r
276279
asin
277280
asinf

libc-top-half/musl/include/stdlib.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,13 @@ long double strtold_l(const char *__restrict, char **__restrict, struct __locale
173173
#endif
174174
#endif
175175

176+
#if defined(_GNU_SOURCE) || defined(_BSD_SOURCE)
177+
#include <stdint.h>
178+
uint32_t arc4random(void);
179+
void arc4random_buf(void *, size_t);
180+
uint32_t arc4random_uniform(uint32_t);
181+
#endif
182+
176183
#ifdef __cplusplus
177184
}
178185
#endif

libc-top-half/sources/arc4random.c

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
#include <assert.h>
2+
#include <stdint.h>
3+
#include <string.h>
4+
#include <unistd.h>
5+
6+
#define RNG_RESERVE_LEN 512
7+
8+
#define CHACHA20_KEYBYTES 32
9+
#define CHACHA20_BLOCKBYTES 64
10+
11+
#define ROTL32(x, b) (uint32_t)(((x) << (b)) | ((x) >> (32 - (b))))
12+
13+
#define CHACHA20_QUARTERROUND(A, B, C, D) \
14+
A += B; \
15+
D = ROTL32(D ^ A, 16); \
16+
C += D; \
17+
B = ROTL32(B ^ C, 12); \
18+
A += B; \
19+
D = ROTL32(D ^ A, 8); \
20+
C += D; \
21+
B = ROTL32(B ^ C, 7)
22+
23+
static void CHACHA20_ROUNDS(uint32_t st[16])
24+
{
25+
int i;
26+
27+
for (i = 0; i < 20; i += 2) {
28+
CHACHA20_QUARTERROUND(st[0], st[4], st[8], st[12]);
29+
CHACHA20_QUARTERROUND(st[1], st[5], st[9], st[13]);
30+
CHACHA20_QUARTERROUND(st[2], st[6], st[10], st[14]);
31+
CHACHA20_QUARTERROUND(st[3], st[7], st[11], st[15]);
32+
CHACHA20_QUARTERROUND(st[0], st[5], st[10], st[15]);
33+
CHACHA20_QUARTERROUND(st[1], st[6], st[11], st[12]);
34+
CHACHA20_QUARTERROUND(st[2], st[7], st[8], st[13]);
35+
CHACHA20_QUARTERROUND(st[3], st[4], st[9], st[14]);
36+
}
37+
}
38+
39+
static void chacha20_update(uint8_t out[CHACHA20_BLOCKBYTES], uint32_t st[16])
40+
{
41+
uint32_t ks[16];
42+
int i;
43+
44+
memcpy(ks, st, 4 * 16);
45+
CHACHA20_ROUNDS(st);
46+
for (i = 0; i < 16; i++) {
47+
ks[i] += st[i];
48+
}
49+
memcpy(out, ks, CHACHA20_BLOCKBYTES);
50+
st[12]++;
51+
}
52+
53+
static void chacha20_init(uint32_t st[16], const uint8_t key[CHACHA20_KEYBYTES])
54+
{
55+
static const uint32_t constants[4] = { 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 };
56+
memcpy(&st[0], constants, 4 * 4);
57+
memcpy(&st[4], key, CHACHA20_KEYBYTES);
58+
memset(&st[12], 0, 4 * 4);
59+
}
60+
61+
static int chacha20_rng(uint8_t* out, size_t len, uint8_t key[CHACHA20_KEYBYTES])
62+
{
63+
uint32_t st[16];
64+
size_t off;
65+
66+
chacha20_init(st, key);
67+
chacha20_update(&out[0], st);
68+
memcpy(key, out, CHACHA20_KEYBYTES);
69+
off = 0;
70+
while (len >= CHACHA20_BLOCKBYTES) {
71+
chacha20_update(&out[off], st);
72+
len -= CHACHA20_BLOCKBYTES;
73+
off += CHACHA20_BLOCKBYTES;
74+
}
75+
if (len > 0) {
76+
uint8_t tmp[CHACHA20_BLOCKBYTES];
77+
chacha20_update(tmp, st);
78+
memcpy(&out[off], tmp, len);
79+
}
80+
return 0;
81+
}
82+
83+
struct rng_state {
84+
int initialized;
85+
size_t off;
86+
uint8_t key[CHACHA20_KEYBYTES];
87+
uint8_t reserve[RNG_RESERVE_LEN];
88+
};
89+
90+
void arc4random_buf(void* buffer, size_t len)
91+
{
92+
static _Thread_local struct rng_state rng_state;
93+
94+
unsigned char* buffer_ = (unsigned char*)buffer;
95+
size_t off;
96+
size_t remaining;
97+
size_t partial;
98+
int ret;
99+
100+
if (!rng_state.initialized) {
101+
if (getentropy(rng_state.key, sizeof rng_state.key) != 0) {
102+
assert(0);
103+
}
104+
rng_state.off = RNG_RESERVE_LEN;
105+
rng_state.initialized = 1;
106+
}
107+
off = 0;
108+
remaining = len;
109+
while (remaining > 0) {
110+
if (rng_state.off == RNG_RESERVE_LEN) {
111+
while (remaining >= RNG_RESERVE_LEN) {
112+
chacha20_rng(&buffer_[off], RNG_RESERVE_LEN, rng_state.key);
113+
off += RNG_RESERVE_LEN;
114+
remaining -= RNG_RESERVE_LEN;
115+
}
116+
if (remaining == 0) {
117+
break;
118+
}
119+
chacha20_rng(&rng_state.reserve[0], RNG_RESERVE_LEN, rng_state.key);
120+
rng_state.off = 0;
121+
}
122+
partial = RNG_RESERVE_LEN - rng_state.off;
123+
if (remaining < partial) {
124+
partial = remaining;
125+
}
126+
memcpy(&buffer_[off], &rng_state.reserve[rng_state.off], partial);
127+
memset(&rng_state.reserve[rng_state.off], 0, partial);
128+
rng_state.off += partial;
129+
remaining -= partial;
130+
off += partial;
131+
}
132+
}
133+
134+
uint32_t arc4random(void)
135+
{
136+
uint32_t v;
137+
138+
arc4random_buf(&v, sizeof v);
139+
140+
return v;
141+
}
142+
143+
uint32_t arc4random_uniform(const uint32_t upper_bound)
144+
{
145+
uint32_t min;
146+
uint32_t r;
147+
148+
if (upper_bound < 2U) {
149+
return 0;
150+
}
151+
min = (1U + ~upper_bound) % upper_bound; /* = 2**32 mod upper_bound */
152+
do {
153+
r = arc4random();
154+
} while (r < min);
155+
/* r is now clamped to a set whose size mod upper_bound == 0
156+
* the worst case (2**31+1) requires 2 attempts on average */
157+
158+
return r % upper_bound;
159+
}

0 commit comments

Comments
 (0)