[System.Runtime.Serialization] Static writer fix.
[mono.git] / mono / utils / mono-rand.c
1 /*
2  * mono-rand.c: 
3  *
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)
9  *
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  */
14
15
16 #include <glib.h>
17 #include <config.h>
18
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"
26
27 #ifdef HOST_WIN32
28
29 #include <windows.h>
30 #include <wincrypt.h>
31
32 #ifndef PROV_INTEL_SEC
33 #define PROV_INTEL_SEC          22
34 #endif
35 #ifndef CRYPT_VERIFY_CONTEXT
36 #define CRYPT_VERIFY_CONTEXT    0xF0000000
37 #endif
38
39 /**
40  * mono_rand_open:
41  *
42  * Returns: True if random source is global, false if mono_rand_init can be called repeatedly to get randomness instances.
43  *
44  * Initializes entire RNG system. Must be called once per process before calling mono_rand_init.
45  */
46 gboolean
47 mono_rand_open (void)
48 {
49         return FALSE;
50 }
51
52 /**
53  * mono_rand_init:
54  * @seed: A string containing seed data
55  * @seed_size: Length of seed string
56  *
57  * Returns: On success, a non-NULL handle which can be used to fetch random data from mono_rand_try_get_bytes. On failure, NULL.
58  *
59  * Initializes an RNG client.
60  */
61 gpointer
62 mono_rand_init (guchar *seed, gint seed_size)
63 {
64         HCRYPTPROV provider = 0;
65
66         /* There is no need to create a container for just random data,
67          * so we can use CRYPT_VERIFY_CONTEXT (one call) see: 
68          * http://blogs.msdn.com/dangriff/archive/2003/11/19/51709.aspx */
69
70         /* We first try to use the Intel PIII RNG if drivers are present */
71         if (!CryptAcquireContext (&provider, NULL, NULL, PROV_INTEL_SEC, CRYPT_VERIFY_CONTEXT)) {
72                 /* not a PIII or no drivers available, use default RSA CSP */
73                 if (!CryptAcquireContext (&provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFY_CONTEXT)) {
74                         /* exception will be thrown in managed code */
75                         provider = 0;
76                 }
77         }
78
79         /* seed the CSP with the supplied buffer (if present) */
80         if (provider != 0 && seed) {
81                 /* the call we replace the seed with random - this isn't what is
82                  * expected from the class library user */
83                 guchar *data = g_malloc (seed_size);
84                 if (data) {
85                         memcpy (data, seed, seed_size);
86                         /* add seeding material to the RNG */
87                         CryptGenRandom (provider, seed_size, data);
88                         /* zeroize and free */
89                         memset (data, 0, seed_size);
90                         g_free (data);
91                 }
92         }
93
94         return (gpointer) provider;
95 }
96
97 /**
98  * mono_rand_try_get_bytes:
99  * @handle: A pointer to an RNG handle. Handle is set to NULL on failure.
100  * @buffer: A buffer into which to write random data.
101  * @buffer_size: Number of bytes to write into buffer.
102  * @error: Set on error.
103  *
104  * Returns: FALSE on failure and sets @error, TRUE on success.
105  *
106  * Extracts bytes from an RNG handle.
107  */
108 gboolean
109 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gint buffer_size, MonoError *error)
110 {
111         HCRYPTPROV provider;
112
113         mono_error_init (error);
114
115         g_assert (handle);
116         provider = (HCRYPTPROV) *handle;
117
118         if (!CryptGenRandom (provider, buffer_size, buffer)) {
119                 CryptReleaseContext (provider, 0);
120                 /* we may have lost our context with CryptoAPI, but all hope isn't lost yet! */
121                 provider = (HCRYPTPROV) mono_rand_init (NULL, 0);
122                 if (!CryptGenRandom (provider, buffer_size, buffer)) {
123                         /* exception will be thrown in managed code */
124                         CryptReleaseContext (provider, 0);
125                         *handle = 0;
126                         mono_error_set_generic_error (error, "System", "ExecutionEngineException", "Failed to gen random bytes (%d)", GetLastError ());
127                         return FALSE;
128                 }
129         }
130         return TRUE;
131 }
132
133 /**
134  * mono_rand_close:
135  * @handle: An RNG handle.
136  * @buffer: A buffer into which to write random data.
137  * @buffer_size: Number of bytes to write into buffer.
138  *
139  * Releases an RNG handle.
140  */
141 void
142 mono_rand_close (gpointer handle)
143 {
144         CryptReleaseContext ((HCRYPTPROV) handle, 0);
145 }
146
147 #elif defined (HAVE_SYS_UN_H) && !defined(__native_client__)
148
149 #include <errno.h>
150 #include <fcntl.h>
151 #include <unistd.h>
152 #include <sys/socket.h>
153 #include <sys/types.h>
154 #include <sys/un.h>
155
156 #ifndef NAME_DEV_URANDOM
157 #define NAME_DEV_URANDOM "/dev/urandom"
158 #endif
159
160 static gboolean use_egd = FALSE;
161 static gint file = -1;
162
163 static void
164 get_entropy_from_egd (const char *path, guchar *buffer, int buffer_size, MonoError *error)
165 {
166         struct sockaddr_un egd_addr;
167         gint file;
168         gint ret;
169         guint offset = 0;
170         int err = 0;
171
172         mono_error_init (error);
173         
174         file = socket (PF_UNIX, SOCK_STREAM, 0);
175         if (file < 0) {
176                 ret = -1;
177                 err = errno;
178         } else {
179                 egd_addr.sun_family = AF_UNIX;
180                 strncpy (egd_addr.sun_path, path, sizeof (egd_addr.sun_path) - 1);
181                 egd_addr.sun_path [sizeof (egd_addr.sun_path) - 1] = '\0';
182                 ret = connect (file, (struct sockaddr*) &egd_addr, sizeof (egd_addr));
183                 err = errno;
184         }
185         if (ret == -1) {
186                 if (file >= 0)
187                         close (file);
188                 g_warning ("Entropy problem! Can't create or connect to egd socket %s", path);
189                 mono_error_set_generic_error (error, "System", "ExecutionEngineException", "Failed to open egd socket %s: %s", path, strerror (err));
190                 return;
191         }
192
193         while (buffer_size > 0) {
194                 guchar request [2];
195                 gint count = 0;
196
197                 /* block until daemon can return enough entropy */
198                 request [0] = 2;
199                 request [1] = buffer_size < 255 ? buffer_size : 255;
200                 while (count < 2) {
201                         int sent = write (file, request + count, 2 - count);
202                         err = errno;
203                         if (sent >= 0) {
204                                 count += sent;
205                         } else if (err == EINTR) {
206                                 continue;
207                         } else {
208                                 close (file);
209                                 g_warning ("Send egd request failed %d", err);
210                                 mono_error_set_generic_error (error, "System", "ExecutionEngineException", "Failed to send request to egd socket: %s", strerror (err));
211                                 return;
212                         }
213                 }
214
215                 count = 0;
216                 while (count != request [1]) {
217                         int received;
218                         received = read (file, buffer + offset, request [1] - count);
219                         err = errno;
220                         if (received > 0) {
221                                 count += received;
222                                 offset += received;
223                         } else if (received < 0 && err == EINTR) {
224                                 continue;
225                         } else {
226                                 close (file);
227                                 g_warning ("Receive egd request failed %d", err);
228                                 mono_error_set_generic_error (error, "System", "ExecutionEngineException", "Failed to get response from egd socket: %s", strerror(err));
229                                 return;
230                         }
231                 }
232
233                 buffer_size -= request [1];
234         }
235
236         close (file);
237 }
238
239 gboolean
240 mono_rand_open (void)
241 {
242         static gint32 status = 0;
243         if (status != 0 || InterlockedCompareExchange (&status, 1, 0) != 0) {
244                 while (status != 2)
245                         mono_thread_info_yield ();
246                 return TRUE;
247         }
248
249 #ifdef NAME_DEV_URANDOM
250         file = open (NAME_DEV_URANDOM, O_RDONLY);
251 #endif
252 #ifdef NAME_DEV_RANDOM
253         if (file < 0)
254                 file = open (NAME_DEV_RANDOM, O_RDONLY);
255 #endif
256         if (file < 0)
257                 use_egd = g_getenv("MONO_EGD_SOCKET") != NULL;
258
259         status = 2;
260
261         return TRUE;
262 }
263
264 gpointer
265 mono_rand_init (guchar *seed, gint seed_size)
266 {
267         // file < 0 is expected in the egd case
268         return (!use_egd && file < 0) ? NULL : GINT_TO_POINTER (file);
269 }
270
271 gboolean
272 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gint buffer_size, MonoError *error)
273 {
274         g_assert (handle);
275
276         mono_error_init (error);
277
278         if (use_egd) {
279                 const char *socket_path = g_getenv ("MONO_EGD_SOCKET");
280                 /* exception will be thrown in managed code */
281                 if (socket_path == NULL) {
282                         *handle = NULL;
283                         return FALSE;
284                 }
285                 get_entropy_from_egd (socket_path, buffer, buffer_size, error);
286         } else {
287                 /* Read until the buffer is filled. This may block if using NAME_DEV_RANDOM. */
288                 gint count = 0;
289                 gint err;
290
291                 do {
292                         err = read (file, buffer + count, buffer_size - count);
293                         if (err < 0) {
294                                 if (errno == EINTR)
295                                         continue;
296                                 g_warning("Entropy error! Error in read (%s).", strerror (errno));
297                                 /* exception will be thrown in managed code */
298                                 mono_error_set_generic_error (error, "System", "ExecutionEngineException", "Entropy error! Error in read (%s).", strerror (errno));
299                                 return FALSE;
300                         }
301                         count += err;
302                 } while (count < buffer_size);
303         }
304         return TRUE;
305 }
306
307 void
308 mono_rand_close (gpointer provider)
309 {
310 }
311
312 #else
313
314 #include <stdlib.h>
315 #include <time.h>
316
317 gboolean
318 mono_rand_open (void)
319 {
320         static gint32 status = 0;
321         if (status != 0 || InterlockedCompareExchange (&status, 1, 0) != 0) {
322                 while (status != 2)
323                         mono_thread_info_yield ();
324                 return TRUE;
325         }
326
327         srand (time (NULL));
328
329         status = 2;
330
331         return TRUE;
332 }
333
334 gpointer
335 mono_rand_init (guchar *seed, gint seed_size)
336 {
337         return "srand"; // NULL will be interpreted as failure; return arbitrary nonzero pointer
338 }
339
340 gboolean
341 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gint buffer_size, MonoError *error)
342 {
343         gint count = 0;
344
345         mono_error_init (error);
346         
347         do {
348                 if (buffer_size - count >= sizeof (gint32) && RAND_MAX >= 0xFFFFFFFF) {
349                         *(gint32*) buffer = rand();
350                         count += sizeof (gint32);
351                         buffer += sizeof (gint32) / sizeof (guchar);
352                 } else if (buffer_size - count >= sizeof (gint16) && RAND_MAX >= 0xFFFF) {
353                         *(gint16*) buffer = rand();
354                         count += sizeof (gint16);
355                         buffer += sizeof (gint16) / sizeof (guchar);
356                 } else if (buffer_size - count >= sizeof (gint8) && RAND_MAX >= 0xFF) {
357                         *(gint8*) buffer = rand();
358                         count += sizeof (gint8);
359                         buffer += sizeof (gint8) / sizeof (guchar);
360                 }
361         } while (count < buffer_size);
362
363         return TRUE;
364 }
365
366 void
367 mono_rand_close (gpointer provider)
368 {
369 }
370
371 #endif
372
373 /**
374  * mono_rand_try_get_uint32:
375  * @handle: A pointer to an RNG handle. Handle is set to NULL on failure.
376  * @val: A pointer to a 32-bit unsigned int, to which the result will be written.
377  * @min: Result will be greater than or equal to this value.
378  * @max: Result will be less than or equal to this value.
379  *
380  * Returns: FALSE on failure, TRUE on success.
381  *
382  * Extracts one 32-bit unsigned int from an RNG handle.
383  */
384 gboolean
385 mono_rand_try_get_uint32 (gpointer *handle, guint32 *val, guint32 min, guint32 max, MonoError *error)
386 {
387         g_assert (val);
388         if (!mono_rand_try_get_bytes (handle, (guchar*) val, sizeof (guint32), error))
389                 return FALSE;
390
391         double randomDouble = ((gdouble) *val) / ( ((double)G_MAXUINT32) + 1 ); // Range is [0,1)
392         *val = (guint32) (randomDouble * (max - min + 1) + min);
393
394         g_assert (*val >= min);
395         g_assert (*val <= max);
396
397         return TRUE;
398 }