blob: 8cf2fef7c6ae8fb058da72b9a14e8b843829dbff [file] [log] [blame]
Georg Brandl2daf6ae2012-02-20 19:54:16 +01001#include "Python.h"
2#ifdef MS_WINDOWS
3#include <windows.h>
4#else
5#include <fcntl.h>
6#endif
7
Benjamin Peterson69e97272012-02-21 11:08:50 -05008#ifdef Py_DEBUG
9int _Py_HashSecret_Initialized = 0;
10#else
11static int _Py_HashSecret_Initialized = 0;
12#endif
Georg Brandl2daf6ae2012-02-20 19:54:16 +010013
14#ifdef MS_WINDOWS
Georg Brandl2daf6ae2012-02-20 19:54:16 +010015/* This handle is never explicitly released. Instead, the operating
16 system will release it when the process terminates. */
17static HCRYPTPROV hCryptProv = 0;
18
19static int
20win32_urandom_init(int raise)
21{
Georg Brandl2daf6ae2012-02-20 19:54:16 +010022 /* Acquire context */
Martin v. Löwis3f50bf62013-01-25 14:06:18 +010023 if (!CryptAcquireContext(&hCryptProv, NULL, NULL,
24 PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
Georg Brandl2daf6ae2012-02-20 19:54:16 +010025 goto error;
26
27 return 0;
28
29error:
30 if (raise)
31 PyErr_SetFromWindowsErr(0);
32 else
33 Py_FatalError("Failed to initialize Windows random API (CryptoGen)");
34 return -1;
35}
36
37/* Fill buffer with size pseudo-random bytes generated by the Windows CryptoGen
38 API. Return 0 on success, or -1 on error. */
39static int
40win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
41{
42 Py_ssize_t chunk;
43
44 if (hCryptProv == 0)
45 {
46 if (win32_urandom_init(raise) == -1)
47 return -1;
48 }
49
50 while (size > 0)
51 {
52 chunk = size > INT_MAX ? INT_MAX : size;
Martin v. Löwis3f50bf62013-01-25 14:06:18 +010053 if (!CryptGenRandom(hCryptProv, chunk, buffer))
Georg Brandl2daf6ae2012-02-20 19:54:16 +010054 {
55 /* CryptGenRandom() failed */
56 if (raise)
57 PyErr_SetFromWindowsErr(0);
58 else
59 Py_FatalError("Failed to initialized the randomized hash "
60 "secret using CryptoGen)");
61 return -1;
62 }
63 buffer += chunk;
64 size -= chunk;
65 }
66 return 0;
67}
68#endif /* MS_WINDOWS */
69
70
71#ifdef __VMS
72/* Use openssl random routine */
73#include <openssl/rand.h>
74static int
75vms_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
76{
77 if (RAND_pseudo_bytes(buffer, size) < 0) {
78 if (raise) {
79 PyErr_Format(PyExc_ValueError,
80 "RAND_pseudo_bytes");
81 } else {
82 Py_FatalError("Failed to initialize the randomized hash "
83 "secret using RAND_pseudo_bytes");
84 }
85 return -1;
86 }
87 return 0;
88}
89#endif /* __VMS */
90
91
92#if !defined(MS_WINDOWS) && !defined(__VMS)
93
94/* Read size bytes from /dev/urandom into buffer.
95 Call Py_FatalError() on error. */
96static void
97dev_urandom_noraise(char *buffer, Py_ssize_t size)
98{
99 int fd;
100 Py_ssize_t n;
101
102 assert (0 < size);
103
Victor Stinnerdaf45552013-08-28 00:53:59 +0200104 fd = _Py_open("/dev/urandom", O_RDONLY);
Georg Brandl2daf6ae2012-02-20 19:54:16 +0100105 if (fd < 0)
106 Py_FatalError("Failed to open /dev/urandom");
107
108 while (0 < size)
109 {
110 do {
111 n = read(fd, buffer, (size_t)size);
112 } while (n < 0 && errno == EINTR);
113 if (n <= 0)
114 {
115 /* stop on error or if read(size) returned 0 */
116 Py_FatalError("Failed to read bytes from /dev/urandom");
117 break;
118 }
119 buffer += n;
120 size -= (Py_ssize_t)n;
121 }
122 close(fd);
123}
124
125/* Read size bytes from /dev/urandom into buffer.
126 Return 0 on success, raise an exception and return -1 on error. */
127static int
128dev_urandom_python(char *buffer, Py_ssize_t size)
129{
130 int fd;
131 Py_ssize_t n;
132
133 if (size <= 0)
134 return 0;
135
136 Py_BEGIN_ALLOW_THREADS
Victor Stinnerdaf45552013-08-28 00:53:59 +0200137 fd = _Py_open("/dev/urandom", O_RDONLY);
Georg Brandl2daf6ae2012-02-20 19:54:16 +0100138 Py_END_ALLOW_THREADS
139 if (fd < 0)
140 {
Antoine Pitrouec34ab52013-08-16 20:44:38 +0200141 if (errno == ENOENT || errno == ENXIO ||
142 errno == ENODEV || errno == EACCES)
143 PyErr_SetString(PyExc_NotImplementedError,
144 "/dev/urandom (or equivalent) not found");
145 else
146 PyErr_SetFromErrno(PyExc_OSError);
Georg Brandl2daf6ae2012-02-20 19:54:16 +0100147 return -1;
148 }
149
150 Py_BEGIN_ALLOW_THREADS
151 do {
152 do {
153 n = read(fd, buffer, (size_t)size);
154 } while (n < 0 && errno == EINTR);
155 if (n <= 0)
156 break;
157 buffer += n;
158 size -= (Py_ssize_t)n;
159 } while (0 < size);
160 Py_END_ALLOW_THREADS
161
162 if (n <= 0)
163 {
164 /* stop on error or if read(size) returned 0 */
165 if (n < 0)
166 PyErr_SetFromErrno(PyExc_OSError);
167 else
168 PyErr_Format(PyExc_RuntimeError,
169 "Failed to read %zi bytes from /dev/urandom",
170 size);
171 close(fd);
172 return -1;
173 }
174 close(fd);
175 return 0;
176}
177#endif /* !defined(MS_WINDOWS) && !defined(__VMS) */
178
179/* Fill buffer with pseudo-random bytes generated by a linear congruent
180 generator (LCG):
181
182 x(n+1) = (x(n) * 214013 + 2531011) % 2^32
183
184 Use bits 23..16 of x(n) to generate a byte. */
185static void
186lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
187{
188 size_t index;
189 unsigned int x;
190
191 x = x0;
192 for (index=0; index < size; index++) {
193 x *= 214013;
194 x += 2531011;
195 /* modulo 2 ^ (8 * sizeof(int)) */
196 buffer[index] = (x >> 16) & 0xff;
197 }
198}
199
200/* Fill buffer with size pseudo-random bytes, not suitable for cryptographic
201 use, from the operating random number generator (RNG).
202
203 Return 0 on success, raise an exception and return -1 on error. */
204int
205_PyOS_URandom(void *buffer, Py_ssize_t size)
206{
207 if (size < 0) {
208 PyErr_Format(PyExc_ValueError,
209 "negative argument not allowed");
210 return -1;
211 }
212 if (size == 0)
213 return 0;
214
215#ifdef MS_WINDOWS
216 return win32_urandom((unsigned char *)buffer, size, 1);
217#else
218# ifdef __VMS
219 return vms_urandom((unsigned char *)buffer, size, 1);
220# else
221 return dev_urandom_python((char*)buffer, size);
222# endif
223#endif
224}
225
226void
227_PyRandom_Init(void)
228{
229 char *env;
230 void *secret = &_Py_HashSecret;
Benjamin Peterson69e97272012-02-21 11:08:50 -0500231 Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
Georg Brandl2daf6ae2012-02-20 19:54:16 +0100232
Benjamin Peterson69e97272012-02-21 11:08:50 -0500233 if (_Py_HashSecret_Initialized)
Georg Brandl2daf6ae2012-02-20 19:54:16 +0100234 return;
Benjamin Peterson69e97272012-02-21 11:08:50 -0500235 _Py_HashSecret_Initialized = 1;
Georg Brandl2daf6ae2012-02-20 19:54:16 +0100236
237 /*
Georg Brandl2daf6ae2012-02-20 19:54:16 +0100238 Hash randomization is enabled. Generate a per-process secret,
239 using PYTHONHASHSEED if provided.
240 */
241
242 env = Py_GETENV("PYTHONHASHSEED");
Georg Brandl12897d72012-02-20 23:49:29 +0100243 if (env && *env != '\0' && strcmp(env, "random") != 0) {
Georg Brandl2daf6ae2012-02-20 19:54:16 +0100244 char *endptr = env;
245 unsigned long seed;
246 seed = strtoul(env, &endptr, 10);
247 if (*endptr != '\0'
248 || seed > 4294967295UL
249 || (errno == ERANGE && seed == ULONG_MAX))
250 {
251 Py_FatalError("PYTHONHASHSEED must be \"random\" or an integer "
252 "in range [0; 4294967295]");
253 }
254 if (seed == 0) {
255 /* disable the randomized hash */
256 memset(secret, 0, secret_size);
257 }
258 else {
259 lcg_urandom(seed, (unsigned char*)secret, secret_size);
260 }
261 }
262 else {
263#ifdef MS_WINDOWS
264 (void)win32_urandom((unsigned char *)secret, secret_size, 0);
265#else /* #ifdef MS_WINDOWS */
266# ifdef __VMS
267 vms_urandom((unsigned char *)secret, secret_size, 0);
268# else
269 dev_urandom_noraise((char*)secret, secret_size);
270# endif
271#endif
272 }
273}