aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThijs Schreijer <thijs@thijsschreijer.nl>2026-02-13 14:49:50 +0100
committerThijs Schreijer <thijs@thijsschreijer.nl>2026-02-13 14:49:50 +0100
commitf6f0c77995fcd62863a7f24cbb407a68d2277759 (patch)
treee7713dd493c42404201f89993aa1513e224a53b6 /src
parentdfd0d4b8ca3607ae39b1d2cbad4e3a7180dd6754 (diff)
downloadluasystem-f6f0c77995fcd62863a7f24cbb407a68d2277759.tar.gz
luasystem-f6f0c77995fcd62863a7f24cbb407a68d2277759.tar.bz2
luasystem-f6f0c77995fcd62863a7f24cbb407a68d2277759.zip
feat(random): add rnd() to mimic math.random()
Matches Lua 5.4+ implementation. But uses crypto secure random data.
Diffstat (limited to 'src')
-rw-r--r--src/random.c245
1 files changed, 197 insertions, 48 deletions
diff --git a/src/random.c b/src/random.c
index 4c92745..6b09a55 100644
--- a/src/random.c
+++ b/src/random.c
@@ -8,6 +8,8 @@
8#include <lauxlib.h> 8#include <lauxlib.h>
9#include "compat.h" 9#include "compat.h"
10#include <fcntl.h> 10#include <fcntl.h>
11#include <stdint.h>
12#include <stddef.h>
11 13
12#ifdef _WIN32 14#ifdef _WIN32
13 #include <windows.h> 15 #include <windows.h>
@@ -29,6 +31,59 @@
29#endif 31#endif
30 32
31 33
34/* Fill buffer with n cryptographically secure random bytes. Returns 0 on success;
35 * on failure pushes nil and error message and returns 2 (caller should return 2). */
36static int fill_random_bytes(lua_State *L, unsigned char *buffer, size_t n) {
37 size_t total_read = 0;
38
39#ifdef _WIN32
40 if (!BCRYPT_SUCCESS(BCryptGenRandom(NULL, buffer, (ULONG)n, BCRYPT_USE_SYSTEM_PREFERRED_RNG))) {
41 lua_pushnil(L);
42 lua_pushfstring(L, "failed to get random data: %lu", (unsigned long)GetLastError());
43 return 2;
44 }
45 (void)total_read;
46
47#elif defined(__linux__) && !defined(USE_DEV_URANDOM)
48 while (total_read < n) {
49 ssize_t got = getrandom(buffer + total_read, n - total_read, 0);
50 if (got < 0) {
51 if (errno == EINTR) continue;
52 lua_pushnil(L);
53 lua_pushfstring(L, "getrandom() failed: %s", strerror(errno));
54 return 2;
55 }
56 total_read += (size_t)got;
57 }
58
59#elif defined(__APPLE__) || (defined(__unix__) && !defined(USE_DEV_URANDOM))
60 arc4random_buf(buffer, n);
61
62#else
63 int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
64 if (fd < 0) {
65 lua_pushnil(L);
66 lua_pushstring(L, "failed opening /dev/urandom");
67 return 2;
68 }
69 while (total_read < n) {
70 ssize_t got = read(fd, buffer + total_read, n - total_read);
71 if (got < 0) {
72 if (errno == EINTR) continue;
73 lua_pushnil(L);
74 lua_pushfstring(L, "failed reading /dev/urandom: %s", strerror(errno));
75 close(fd);
76 return 2;
77 }
78 total_read += (size_t)got;
79 }
80 close(fd);
81#endif
82
83 return 0;
84}
85
86
32/*** 87/***
33Generate random bytes. 88Generate random bytes.
34This uses `BCryptGenRandom()` on Windows, `getrandom()` on Linux, `arc4random_buf` on BSD, 89This uses `BCryptGenRandom()` on Windows, `getrandom()` on Linux, `arc4random_buf` on BSD,
@@ -60,72 +115,166 @@ static int lua_get_random_bytes(lua_State* L) {
60 return 2; 115 return 2;
61 } 116 }
62 117
63 ssize_t total_read = 0; 118 int ret = fill_random_bytes(L, buffer, (size_t)num_bytes);
119 if (ret != 0) return ret;
64 120
65#ifdef _WIN32 121 lua_pushlstring(L, (const char*)buffer, num_bytes);
66 // Use BCryptGenRandom() on Windows 122 return 1;
67 if (!BCRYPT_SUCCESS(BCryptGenRandom(NULL, buffer, num_bytes, BCRYPT_USE_SYSTEM_PREFERRED_RNG))) { 123}
68 DWORD error = GetLastError();
69 lua_pushnil(L);
70 lua_pushfstring(L, "failed to get random data: %lu", error);
71 return 2;
72 }
73 124
74#elif defined(__linux__) && !defined(USE_DEV_URANDOM)
75 // Use getrandom() on Linux
76 while (total_read < num_bytes) {
77 ssize_t n = getrandom(buffer + total_read, num_bytes - total_read, 0);
78 if (n < 0) {
79 if (errno == EINTR) continue; // Retry on interrupt
80 lua_pushnil(L);
81 lua_pushfstring(L, "getrandom() failed: %s", strerror(errno));
82 return 2;
83 }
84 total_read += n;
85 }
86 125
87#elif defined(__APPLE__) || (defined(__unix__) && !defined(USE_DEV_URANDOM)) 126/* Read 8 bytes into *out; return 0 on success, 2 on error (nil + msg pushed). */
88 // Use arc4random_buf() on BSD/macOS 127static int read_u64(lua_State *L, uint64_t *out) {
89 arc4random_buf(buffer, num_bytes); 128 unsigned char buf[8];
129 int ret = fill_random_bytes(L, buf, 8);
130 if (ret != 0) return ret;
131 *out = (uint64_t)buf[0] | ((uint64_t)buf[1] << 8) | ((uint64_t)buf[2] << 16) |
132 ((uint64_t)buf[3] << 24) | ((uint64_t)buf[4] << 32) | ((uint64_t)buf[5] << 40) |
133 ((uint64_t)buf[6] << 48) | ((uint64_t)buf[7] << 56);
134 return 0;
135}
90 136
91#else 137/* Project uniform random in [0, n] using rejection (Mersenne-style). *out is in [0, n]. */
92 // fall back to /dev/urandom for anything else 138static int project_u64(lua_State *L, uint64_t ran, uint64_t n, uint64_t *out) {
93 int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); 139 if ((n & (n + 1)) == 0) {
94 if (fd < 0) { 140 *out = ran & n;
95 lua_pushnil(L); 141 return 0;
96 lua_pushstring(L, "failed opening /dev/urandom"); 142 }
97 return 2; 143 uint64_t lim = n;
144 lim |= (lim >> 1);
145 lim |= (lim >> 2);
146 lim |= (lim >> 4);
147 lim |= (lim >> 8);
148 lim |= (lim >> 16);
149 lim |= (lim >> 32);
150 while ((ran &= lim) > n) {
151 int ret = read_u64(L, &ran);
152 if (ret != 0) return ret;
98 } 153 }
154 *out = ran;
155 return 0;
156}
99 157
100 while (total_read < num_bytes) {
101 ssize_t n = read(fd, buffer + total_read, num_bytes - total_read);
102 158
103 if (n < 0) { 159/***
104 if (errno == EINTR) { 160Random number mimicking Lua 5.4 math.random, using crypto-secure bytes.
105 continue; // Interrupted, retry 161- No args: returns a float in [0, 1).
162- One arg 0: returns a full-range random integer (whole lua_Integer range).
163- One arg m (m >= 1): returns an integer in [1, m] (inclusive).
164- Two args m, n: returns an integer in [m, n] (inclusive).
165On invalid arguments returns nil and an error message.
166@function rnd
167@tparam[opt] int m upper bound (or 0 for full-range), or lower bound when used with n
168@tparam[opt] int n upper bound (when used with m)
169@treturn[1] number|int float in [0,1) or integer in the requested range
170@treturn[2] nil
171@treturn[2] string error message
172*/
173static int lua_rnd(lua_State *L) {
174 int nargs = lua_gettop(L);
175 unsigned char buf[8];
176 uint64_t u;
106 177
107 } else { 178 if (nargs == 0) {
108 lua_pushnil(L); 179 int ret = fill_random_bytes(L, buf, 8);
109 lua_pushfstring(L, "failed reading /dev/urandom: %s", strerror(errno)); 180 if (ret != 0) return ret;
110 close(fd); 181 u = (uint64_t)buf[0] | ((uint64_t)buf[1] << 8) | ((uint64_t)buf[2] << 16) |
111 return 2; 182 ((uint64_t)buf[3] << 24) | ((uint64_t)buf[4] << 32) | ((uint64_t)buf[5] << 40) |
112 } 183 ((uint64_t)buf[6] << 48) | ((uint64_t)buf[7] << 56);
113 } 184 /* 53 bits for double [0, 1) */
185 lua_pushnumber(L, (lua_Number)((u >> 11) * (1.0 / ((uint64_t)1 << 53))));
186 return 1;
187 }
188
189#if LUA_VERSION_NUM >= 503
190 #define RND_INT lua_Integer
191 #define RND_CHECKINT(L, i) luaL_checkinteger(L, (i))
192 #define RND_MAXINTEGER LUA_MAXINTEGER
193 #define RND_MININTEGER LUA_MININTEGER
194#else
195 #define RND_INT long long
196 #define RND_CHECKINT(L, i) ((long long)luaL_checknumber(L, (i)))
197#endif
114 198
115 total_read += n; 199 if (nargs == 1) {
200 RND_INT m = RND_CHECKINT(L, 1);
201 if (m == 0) {
202 /* full-range random integer */
203 int ret = read_u64(L, &u);
204 if (ret != 0) return ret;
205#if LUA_VERSION_NUM >= 503
206 lua_pushinteger(L, (lua_Integer)(int64_t)u);
207#else
208 lua_pushnumber(L, (lua_Number)(long long)(int64_t)u);
209#endif
210 return 1;
211 }
212 if (m < 1) {
213 lua_pushnil(L);
214 lua_pushstring(L, "interval is empty");
215 return 2;
216 }
217 /* [1, m] -> range size m, values 0..m-1 then +1 */
218 {
219 uint64_t r;
220 int ret = read_u64(L, &u);
221 if (ret != 0) return ret;
222 ret = project_u64(L, u, (uint64_t)(m - 1), &r);
223 if (ret != 0) return ret;
224#if LUA_VERSION_NUM >= 503
225 lua_pushinteger(L, (lua_Integer)r + 1);
226#else
227 lua_pushnumber(L, (lua_Number)((long long)r + 1));
228#endif
229 return 1;
230 }
116 } 231 }
117 232
118 close(fd); 233 if (nargs == 2) {
234 RND_INT low = RND_CHECKINT(L, 1);
235 RND_INT up = RND_CHECKINT(L, 2);
236 if (low > up) {
237 lua_pushnil(L);
238 lua_pushstring(L, "interval is empty");
239 return 2;
240 }
241#if LUA_VERSION_NUM >= 503
242 if (low < 0 && up > 0 && (lua_Unsigned)(up - low) > (lua_Unsigned)LUA_MAXINTEGER) {
243 lua_pushnil(L);
244 lua_pushstring(L, "interval too large");
245 return 2;
246 }
247#endif
248 {
249 uint64_t range = (uint64_t)(up - low) + 1;
250 uint64_t r;
251 int ret = read_u64(L, &u);
252 if (ret != 0) return ret;
253 ret = project_u64(L, u, range - 1, &r);
254 if (ret != 0) return ret;
255#if LUA_VERSION_NUM >= 503
256 lua_pushinteger(L, (lua_Integer)((lua_Unsigned)low + (lua_Unsigned)r));
257#else
258 lua_pushnumber(L, (lua_Number)((long long)low + (long long)r));
119#endif 259#endif
260 return 1;
261 }
262 }
120 263
121 lua_pushlstring(L, (const char*)buffer, num_bytes); 264 lua_pushnil(L);
122 return 1; 265 lua_pushliteral(L, "wrong number of arguments");
123} 266 return 2;
124 267
268#undef RND_INT
269#undef RND_CHECKINT
270#undef RND_MAXINTEGER
271#undef RND_MININTEGER
272}
125 273
126 274
127static luaL_Reg func[] = { 275static luaL_Reg func[] = {
128 { "random", lua_get_random_bytes }, 276 { "random", lua_get_random_bytes },
277 { "rnd", lua_rnd },
129 { NULL, NULL } 278 { NULL, NULL }
130}; 279};
131 280