Merge pull request #1760 from evincarofautumn/object-provenances
[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-rand.h"
21 #include "mono-threads.h"
22 #include "metadata/exception.h"
23 #include "metadata/object.h"
24
25 #ifdef HOST_WIN32
26
27 #include <windows.h>
28 #include <wincrypt.h>
29
30 #ifndef PROV_INTEL_SEC
31 #define PROV_INTEL_SEC          22
32 #endif
33 #ifndef CRYPT_VERIFY_CONTEXT
34 #define CRYPT_VERIFY_CONTEXT    0xF0000000
35 #endif
36
37 gboolean
38 mono_rand_open (void)
39 {
40         /* FALSE == Local (instance) handle for randomness */
41         return FALSE;
42 }
43
44 gpointer
45 mono_rand_init (guchar *seed, gint seed_size)
46 {
47         HCRYPTPROV provider = 0;
48
49         /* There is no need to create a container for just random data,
50          * so we can use CRYPT_VERIFY_CONTEXT (one call) see: 
51          * http://blogs.msdn.com/dangriff/archive/2003/11/19/51709.aspx */
52
53         /* We first try to use the Intel PIII RNG if drivers are present */
54         if (!CryptAcquireContext (&provider, NULL, NULL, PROV_INTEL_SEC, CRYPT_VERIFY_CONTEXT)) {
55                 /* not a PIII or no drivers available, use default RSA CSP */
56                 if (!CryptAcquireContext (&provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFY_CONTEXT)) {
57                         /* exception will be thrown in managed code */
58                         provider = 0;
59                 }
60         }
61
62         /* seed the CSP with the supplied buffer (if present) */
63         if (provider != 0 && seed) {
64                 /* the call we replace the seed with random - this isn't what is
65                  * expected from the class library user */
66                 guchar *data = g_malloc (seed_size);
67                 if (data) {
68                         memcpy (data, seed, seed_size);
69                         /* add seeding material to the RNG */
70                         CryptGenRandom (provider, seed_size, data);
71                         /* zeroize and free */
72                         memset (data, 0, seed_size);
73                         g_free (data);
74                 }
75         }
76
77         return (gpointer) provider;
78 }
79
80 gboolean
81 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gint buffer_size)
82 {
83         HCRYPTPROV provider;
84
85         g_assert (handle);
86         provider = (HCRYPTPROV) *handle;
87
88         if (!CryptGenRandom (provider, buffer_size, buffer)) {
89                 CryptReleaseContext (provider, 0);
90                 /* we may have lost our context with CryptoAPI, but all hope isn't lost yet! */
91                 provider = (HCRYPTPROV) mono_rand_init (NULL, 0);
92                 if (!CryptGenRandom (provider, buffer_size, buffer)) {
93                         /* exception will be thrown in managed code */
94                         CryptReleaseContext (provider, 0);
95                         *handle = 0;
96                         return FALSE;
97                 }
98         }
99         return TRUE;
100 }
101
102 void
103 mono_rand_close (gpointer handle)
104 {
105         CryptReleaseContext ((HCRYPTPROV) handle, 0);
106 }
107
108 #elif defined (HAVE_SYS_UN_H) && !defined(__native_client__)
109
110 #include <errno.h>
111 #include <fcntl.h>
112 #include <unistd.h>
113 #include <sys/socket.h>
114 #include <sys/types.h>
115 #include <sys/un.h>
116
117 #ifndef NAME_DEV_URANDOM
118 #define NAME_DEV_URANDOM "/dev/urandom"
119 #endif
120
121 static gboolean use_egd = FALSE;
122 static gint file = -1;
123
124 static void
125 get_entropy_from_egd (const char *path, guchar *buffer, int buffer_size)
126 {
127         struct sockaddr_un egd_addr;
128         gint file;
129         gint ret;
130         guint offset = 0;
131
132         file = socket (PF_UNIX, SOCK_STREAM, 0);
133         if (file < 0) {
134                 ret = -1;
135         } else {
136                 egd_addr.sun_family = AF_UNIX;
137                 strncpy (egd_addr.sun_path, path, sizeof (egd_addr.sun_path) - 1);
138                 egd_addr.sun_path [sizeof (egd_addr.sun_path) - 1] = '\0';
139                 ret = connect (file, (struct sockaddr*) &egd_addr, sizeof (egd_addr));
140         }
141         if (ret == -1) {
142                 if (file >= 0)
143                         close (file);
144                 g_warning ("Entropy problem! Can't create or connect to egd socket %s", path);
145                 mono_raise_exception (mono_get_exception_execution_engine ("Failed to open egd socket"));
146         }
147
148         while (buffer_size > 0) {
149                 guchar request [2];
150                 gint count = 0;
151
152                 /* block until daemon can return enough entropy */
153                 request [0] = 2;
154                 request [1] = buffer_size < 255 ? buffer_size : 255;
155                 while (count < 2) {
156                         int sent = write (file, request + count, 2 - count);
157                         if (sent >= 0) {
158                                 count += sent;
159                         } else if (errno == EINTR) {
160                                 continue;
161                         } else {
162                                 close (file);
163                                 g_warning ("Send egd request failed %d", errno);
164                                 mono_raise_exception (mono_get_exception_execution_engine ("Failed to send request to egd socket"));
165                         }
166                 }
167
168                 count = 0;
169                 while (count != request [1]) {
170                         int received;
171                         received = read (file, buffer + offset, request [1] - count);
172                         if (received > 0) {
173                                 count += received;
174                                 offset += received;
175                         } else if (received < 0 && errno == EINTR) {
176                                 continue;
177                         } else {
178                                 close (file);
179                                 g_warning ("Receive egd request failed %d", errno);
180                                 mono_raise_exception (mono_get_exception_execution_engine ("Failed to get response from egd socket"));
181                         }
182                 }
183
184                 buffer_size -= request [1];
185         }
186
187         close (file);
188 }
189
190 gboolean
191 mono_rand_open (void)
192 {
193         static gint32 status = 0;
194         if (status != 0 || InterlockedCompareExchange (&status, 1, 0) != 0) {
195                 while (status != 2)
196                         mono_thread_info_yield ();
197                 return TRUE;
198         }
199
200 #ifdef NAME_DEV_URANDOM
201         file = open (NAME_DEV_URANDOM, O_RDONLY);
202 #endif
203 #ifdef NAME_DEV_RANDOM
204         if (file < 0)
205                 file = open (NAME_DEV_RANDOM, O_RDONLY);
206 #endif
207         if (file < 0)
208                 use_egd = g_getenv("MONO_EGD_SOCKET") != NULL;
209
210         status = 2;
211
212         return TRUE;
213 }
214
215 gpointer
216 mono_rand_init (guchar *seed, gint seed_size)
217 {
218         /* if required exception will be thrown in managed code */
219         return (!use_egd && file < 0) ? NULL : GINT_TO_POINTER (file);
220 }
221
222 gboolean
223 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gint buffer_size)
224 {
225         g_assert (handle);
226
227         if (use_egd) {
228                 const char *socket_path = g_getenv ("MONO_EGD_SOCKET");
229                 /* exception will be thrown in managed code */
230                 if (socket_path == NULL) {
231                         *handle = NULL;
232                         return FALSE;
233                 }
234                 get_entropy_from_egd (socket_path, buffer, buffer_size);
235         } else {
236                 /* Read until the buffer is filled. This may block if using NAME_DEV_RANDOM. */
237                 gint count = 0;
238                 gint err;
239
240                 do {
241                         err = read (file, buffer + count, buffer_size - count);
242                         if (err < 0) {
243                                 if (errno == EINTR)
244                                         continue;
245                                 g_warning("Entropy error! Error in read (%s).", strerror (errno));
246                                 /* exception will be thrown in managed code */
247                                 return FALSE;
248                         }
249                         count += err;
250                 } while (count < buffer_size);
251         }
252         return TRUE;
253 }
254
255 void
256 mono_rand_close (gpointer provider)
257 {
258 }
259
260 #else
261
262 #include <stdlib.h>
263 #include <time.h>
264
265 gboolean
266 mono_rand_open (void)
267 {
268         static gint32 status = 0;
269         if (status != 0 || InterlockedCompareExchange (&status, 1, 0) != 0) {
270                 while (status != 2)
271                         mono_thread_info_yield ();
272                 return TRUE;
273         }
274
275         srand (time (NULL));
276
277         status = 2;
278
279         return TRUE;
280 }
281
282 gpointer
283 mono_rand_init (guchar *seed, gint seed_size)
284 {
285         return NULL;    
286 }
287
288 gboolean
289 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gint buffer_size)
290 {
291         gint count = 0;
292
293         do {
294                 if (buffer_size - count >= sizeof (gint32) && RAND_MAX >= 0xFFFFFFFF) {
295                         *(gint32*) buffer = rand();
296                         count += sizeof (gint32);
297                         buffer += sizeof (gint32) / sizeof (guchar);
298                 } else if (buffer_size - count >= sizeof (gint16) && RAND_MAX >= 0xFFFF) {
299                         *(gint16*) buffer = rand();
300                         count += sizeof (gint16);
301                         buffer += sizeof (gint16) / sizeof (guchar);
302                 } else if (buffer_size - count >= sizeof (gint8) && RAND_MAX >= 0xFF) {
303                         *(gint8*) buffer = rand();
304                         count += sizeof (gint8);
305                         buffer += sizeof (gint8) / sizeof (guchar);
306                 }
307         } while (count < buffer_size);
308
309         return TRUE;
310 }
311
312 void
313 mono_rand_close (gpointer provider)
314 {
315 }
316
317 #endif
318
319 gboolean
320 mono_rand_try_get_uint32 (gpointer *handle, guint32 *val, guint32 min, guint32 max)
321 {
322         g_assert (val);
323         if (!mono_rand_try_get_bytes (handle, (guchar*) val, sizeof (guint32)))
324                 return FALSE;
325
326         *val = (guint32) (((gdouble) *val) / G_MAXUINT32 * (max - min) + min);
327         g_assert (*val >= min);
328         g_assert (*val <= max);
329
330         return TRUE;
331 }