mono_native_tls_get_value that does not change LastError. (#15568)
[mono-project.git] / mono / utils / mono-rand.c
blobd8863d19c88b399d69ab70205a31579cda3ccdc2
1 /**
2 * \file
4 * Authors:
5 * Mark Crichton (crichton@gimp.org)
6 * Patrik Torstensson (p@rxc.se)
7 * Sebastien Pouliot (sebastien@ximian.com)
8 * Ludovic Henry (ludovic.henry@xamarin.com)
10 * Copyright 2001-2003 Ximian, Inc (http://www.ximian.com)
11 * Copyright 2004-2009 Novell, Inc (http://www.novell.com)
12 * Copyright 2001 Xamarin, Inc (http://www.novell.com)
13 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
16 #include <glib.h>
17 #include <config.h>
19 #include "atomic.h"
20 #include "mono-error.h"
21 #include "mono-error-internals.h"
22 #include "mono-rand.h"
23 #include "mono-threads.h"
24 #include "metadata/exception.h"
25 #include "metadata/object.h"
27 #ifdef HOST_WIN32
28 // Windows specific implementation in mono-rand-windows.c
29 #elif defined (HAVE_SYS_UN_H)
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <unistd.h>
34 #include <sys/socket.h>
35 #include <sys/types.h>
36 #include <sys/un.h>
38 #ifdef HAVE_SYS_RANDOM_H
39 #include <sys/random.h>
40 #endif
42 #ifndef NAME_DEV_URANDOM
43 #define NAME_DEV_URANDOM "/dev/urandom"
44 #endif
46 static gboolean use_egd = FALSE;
47 static gint file = -1;
49 #ifdef HAVE_GETRANDOM
50 /* Fill buffer with buffer_size random bytes generated by getrandom():
51 - Return 1 on success
52 - Return 0 if getrandom() is not available (failed with ENOSYS or EPERM)
53 - Return -1 on error
54 getrandom() is retried if it failed with EINTR: interrupted by a signal. */
55 static gint
56 mono_getrandom (guchar *buffer, gssize buffer_size, gint flags, MonoError *error)
58 g_assert (buffer || !buffer_size);
60 static gboolean getrandom_fail;
62 if (getrandom_fail)
63 return 0;
65 /* Read until the buffer is filled. This may block if random pool isn't initialized. */
66 while (buffer_size > 0) {
67 gssize const err = getrandom (buffer, buffer_size, flags);
68 if (err < 0) {
69 if (errno == EINTR)
70 continue;
71 if (errno == ENOSYS || errno == EPERM) {
72 getrandom_fail = TRUE;
73 return 0;
75 g_warning ("Entropy error! Error in getrandom (%s).", strerror (errno));
76 /* exception will be thrown in managed code */
77 mono_error_set_execution_engine (error, "Entropy error! Error in getrandom (%s).", strerror (errno));
78 return -1;
80 buffer_size -= err;
81 buffer += err;
83 return 1;
85 #endif /* HAVE_GETRANDOM */
87 #ifdef HAVE_GETENTROPY
88 /* Fill buffer with buffer_size random bytes generated by getentropy():
89 - Return 1 on success
90 - Return 0 if getentropy() is not available (failed with ENOSYS or EPERM)
91 - Return -1 on error
92 getentropy() is retried if it failed with EINTR: interrupted by a signal. */
93 static gint
94 mono_getentropy (guchar *buffer, gssize buffer_size, MonoError *error)
96 g_assert (buffer || !buffer_size);
98 static gboolean getentropy_fail;
100 if (getentropy_fail)
101 return 0;
103 /* Read until the buffer is filled. This may block if random pool isn't initialized. */
104 while (buffer_size > 0) {
105 gssize const len = MIN (buffer_size, 256);
106 gint const err = getentropy (buffer, len);
107 if (err < 0) {
108 if (errno == EINTR)
109 continue;
110 if (errno == ENOSYS || errno == EPERM) {
111 getentropy_fail = TRUE;
112 return 0;
114 g_warning ("Entropy error! Error in getentropy (%s).", strerror (errno));
115 /* exception will be thrown in managed code */
116 mono_error_set_execution_engine (error, "Entropy error! Error in getentropy (%s).", strerror (errno));
117 return -1;
119 buffer += len;
120 buffer_size -= len;
122 return 1;
124 #endif /* HAVE_GETENTROPY */
126 static void
127 get_entropy_from_egd (const char *path, guchar *buffer, gssize buffer_size, MonoError *error)
129 g_assert (buffer || !buffer_size);
131 struct sockaddr_un egd_addr;
132 gint socket_fd;
133 gint ret;
134 guint offset = 0;
135 int err = 0;
137 socket_fd = socket (PF_UNIX, SOCK_STREAM, 0);
138 if (socket_fd < 0) {
139 ret = -1;
140 err = errno;
141 } else {
142 egd_addr.sun_family = AF_UNIX;
143 memcpy (egd_addr.sun_path, path, sizeof (egd_addr.sun_path) - 1);
144 egd_addr.sun_path [sizeof (egd_addr.sun_path) - 1] = '\0';
145 ret = connect (socket_fd, (struct sockaddr*) &egd_addr, sizeof (egd_addr));
146 err = errno;
148 if (ret == -1) {
149 if (socket_fd >= 0)
150 close (socket_fd);
151 g_warning ("Entropy problem! Can't create or connect to egd socket %s", path);
152 mono_error_set_execution_engine (error, "Failed to open egd socket %s: %s", path, strerror (err));
153 return;
156 while (buffer_size > 0) {
157 guchar request [2];
158 gint count = 0;
160 /* block until daemon can return enough entropy */
161 request [0] = 2;
162 request [1] = buffer_size < 255 ? buffer_size : 255;
163 while (count < 2) {
164 int sent = write (socket_fd, request + count, 2 - count);
165 err = errno;
166 if (sent >= 0) {
167 count += sent;
168 } else if (err == EINTR) {
169 continue;
170 } else {
171 close (socket_fd);
172 g_warning ("Send egd request failed %d", err);
173 mono_error_set_execution_engine (error, "Failed to send request to egd socket: %s", strerror (err));
174 return;
178 count = 0;
179 while (count != request [1]) {
180 int received;
181 received = read (socket_fd, buffer + offset, request [1] - count);
182 err = errno;
183 if (received > 0) {
184 count += received;
185 offset += received;
186 } else if (received < 0 && err == EINTR) {
187 continue;
188 } else {
189 close (socket_fd);
190 g_warning ("Receive egd request failed %d", err);
191 mono_error_set_execution_engine (error, "Failed to get response from egd socket: %s", strerror(err));
192 return;
196 buffer_size -= request [1];
199 close (socket_fd);
202 gboolean
203 mono_rand_open (void)
205 static gint32 status;
206 if (status != 0 || mono_atomic_cas_i32 (&status, 1, 0) != 0) {
207 while (status != 2)
208 mono_thread_info_yield ();
209 return TRUE;
212 #ifdef NAME_DEV_URANDOM
213 if (file < 0)
214 file = open (NAME_DEV_URANDOM, O_RDONLY);
215 #endif
216 #ifdef NAME_DEV_RANDOM
217 if (file < 0)
218 file = open (NAME_DEV_RANDOM, O_RDONLY);
219 #endif
220 if (file < 0)
221 use_egd = g_hasenv ("MONO_EGD_SOCKET");
223 status = 2;
225 return TRUE;
228 gpointer
229 mono_rand_init (const guchar *seed, gssize seed_size)
231 // file < 0 is expected in the egd case
232 return (!use_egd && file < 0) ? (gpointer)NULL : GINT_TO_POINTER (file);
235 gboolean
236 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gssize buffer_size, MonoError *error)
238 g_assert (buffer || !buffer_size);
239 g_assert (handle);
241 error_init (error);
243 #if defined(HAVE_GETRANDOM) || defined(HAVE_GETENTROPY)
244 #ifdef HAVE_GETRANDOM
245 gint const res = mono_getrandom (buffer, buffer_size, 0, error);
246 #else
247 gint const res = mono_getentropy (buffer, buffer_size, error);
248 #endif
249 if (res < 0)
250 return FALSE;
252 if (res == 1)
253 return TRUE;
255 /* getrandom() or getentropy() function is not available: failed with
256 ENOSYS or EPERM. Fall back on reading from /dev/urandom. */
257 #endif
259 if (use_egd) {
260 char *socket_path = g_getenv ("MONO_EGD_SOCKET");
261 /* exception will be thrown in managed code */
262 if (socket_path == NULL) {
263 *handle = NULL;
264 return FALSE;
266 get_entropy_from_egd (socket_path, buffer, buffer_size, error);
267 g_free (socket_path);
268 } else {
269 /* Read until the buffer is filled. This may block if using NAME_DEV_RANDOM. */
270 while (buffer_size > 0) {
271 gssize const err = read (file, buffer, buffer_size);
272 if (err < 0) {
273 if (errno == EINTR)
274 continue;
275 g_warning("Entropy error! Error in read (%s).", strerror (errno));
276 /* exception will be thrown in managed code */
277 mono_error_set_execution_engine (error, "Entropy error! Error in read (%s).", strerror (errno));
278 return FALSE;
280 buffer += err;
281 buffer_size -= err;
284 return TRUE;
287 void
288 mono_rand_close (gpointer provider)
292 #else
294 #include <stdlib.h>
295 #include <time.h>
297 gboolean
298 mono_rand_open (void)
300 static gint32 status;
301 if (status != 0 || mono_atomic_cas_i32 (&status, 1, 0) != 0) {
302 while (status != 2)
303 mono_thread_info_yield ();
304 return TRUE;
307 srand (time (NULL));
309 status = 2;
311 return TRUE;
314 gpointer
315 mono_rand_init (const guchar *seed, gssize seed_size)
317 return "srand"; // NULL will be interpreted as failure; return arbitrary nonzero pointer
320 gboolean
321 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gssize buffer_size, MonoError *error)
323 // This functions is not used on any mainstream platform, perhaps not at all.
325 g_assert (buffer || !buffer_size);
327 error_init (error);
329 g_static_assert (RAND_MAX >= 0xFF);
331 while (buffer_size > 0) {
332 int const i = rand ();
333 int j;
334 if (buffer_size >= (j = 4) && RAND_MAX >= 0xFFFFFFFF)
335 *(gint32*) buffer = i;
336 else if (buffer_size >= (j = 2) && RAND_MAX >= 0xFFFF)
337 *(gint16*) buffer = i;
338 else {
339 j = 1;
340 *(gint8*) buffer = i;
342 buffer += j;
343 buffer_size -= j;
346 return TRUE;
349 void
350 mono_rand_close (gpointer provider)
354 #endif
357 * mono_rand_try_get_uint32:
358 * \param handle A pointer to an RNG handle. Handle is set to NULL on failure.
359 * \param val A pointer to a 32-bit unsigned int, to which the result will be written.
360 * \param min Result will be greater than or equal to this value.
361 * \param max Result will be less than or equal to this value.
362 * Extracts one 32-bit unsigned int from an RNG handle.
363 * \returns FALSE on failure, TRUE on success.
365 gboolean
366 mono_rand_try_get_uint32 (gpointer *handle, guint32 *val, guint32 min, guint32 max, MonoError *error)
368 g_assert (val);
369 if (!mono_rand_try_get_bytes (handle, (guchar*) val, sizeof (guint32), error))
370 return FALSE;
372 double randomDouble = ((gdouble) *val) / ( ((double)G_MAXUINT32) + 1 ); // Range is [0,1)
373 *val = (guint32) (randomDouble * (max - min + 1) + min);
375 g_assert (*val >= min);
376 g_assert (*val <= max);
378 return TRUE;