Skip to content

Commit 06781fa

Browse files
committed
split the prng to own source file, allowing multiple variants to be compiled
1 parent ea9c7c4 commit 06781fa

File tree

5 files changed

+408
-369
lines changed

5 files changed

+408
-369
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/test

botched_rand_32.c

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#define PLATFORM_LONG int32_t
2+
#define PLATFORM_UNSIGNED_LONG uint32_t
3+
#define FUNC_PREFIX i386_
4+
#include "botched_rand_base.c"

botched_rand_base.c

+388
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
/*
2+
* #define s expected by this code:
3+
* - OLD_SSL: define this if the code is supposed to run on some unspecified older version of OpenSSL (without EVP_MD_CTX_new)
4+
* - PLATFORM_LONG: type of "long" of the architecture under attack
5+
* - PLATFORM_UNSIGNED_LONG: same, but for "unsigned long"
6+
* - FUNC_PREFIX: prefix for the names of exported functions, e.g. deb32_
7+
* e.g. if the key to be recovered was generated on x86 then use (u)int32_t here
8+
*/
9+
10+
#if !defined(PLATFORM_LONG) || !defined(PLATFORM_UNSIGNED_LONG)
11+
#error "This file needs to be included with PLATFORM_LONG and PLATFORM_UNSIGNED_LONG defined"
12+
#endif
13+
#if !defined(FUNC_PREFIX)
14+
#error "This file needs to be included with FUNC_PREFIX defined"
15+
#endif
16+
17+
#include <sys/types.h>
18+
#include <sys/stat.h>
19+
#include <unistd.h>
20+
#include <fcntl.h>
21+
#include <stdio.h>
22+
#include <string.h>
23+
#include <stdint.h>
24+
25+
#include <openssl/crypto.h>
26+
#include <openssl/evp.h>
27+
#include <openssl/rand.h>
28+
#include <openssl/err.h>
29+
#include <openssl/sha.h>
30+
31+
#include "host_flags.h"
32+
33+
#define CONCAT_NAME(n1,n2) n1##n2
34+
#define PFX_EVAL(n1,n2) CONCAT_NAME(n1,n2)
35+
#define PREFIXED_NAME(name) PFX_EVAL(FUNC_PREFIX,name)
36+
37+
typedef PLATFORM_LONG orig_long;
38+
typedef PLATFORM_UNSIGNED_LONG orig_unsigned_long;
39+
40+
#define ENTROPY_NEEDED 32 /* require 256 bits = 32 bytes of randomness */
41+
42+
#define MD_Update(a,b,c) EVP_DigestUpdate(a,b,c)
43+
#define MD_Final(a,b) EVP_DigestFinal_ex(a,b,NULL)
44+
// SHA
45+
#define MD_DIGEST_LENGTH SHA_DIGEST_LENGTH
46+
#define MD_Init(a) EVP_DigestInit_ex(a,EVP_sha1(), NULL)
47+
#define MD(a,b,c) EVP_Digest(a,b,c,NULL,EVP_sha1(), NULL)
48+
49+
#define STATE_SIZE 1023
50+
static int state_num=0,state_index=0;
51+
static unsigned char state[STATE_SIZE+MD_DIGEST_LENGTH];
52+
static unsigned char md[MD_DIGEST_LENGTH];
53+
static orig_long md_count[2]={0,0};
54+
static double entropy=0;
55+
static int initialized=0;
56+
57+
static int stirred_pool = 0;
58+
59+
static pid_t fake_pid;
60+
61+
void PREFIXED_NAME(b_rand_reset)(pid_t pid) {
62+
state_num = state_index = 0;
63+
memset(state, 0, sizeof(state));
64+
memset(md, 0, sizeof(md));
65+
md_count[0] = md_count[1] = 0;
66+
entropy = 0;
67+
initialized = 0;
68+
69+
stirred_pool = 0;
70+
71+
fake_pid = pid;
72+
}
73+
74+
#ifdef OLD_SSL
75+
static EVP_MD_CTX *EVP_MD_CTX_new(void) { return calloc(sizeof(EVP_MD_CTX), 1); }
76+
static void EVP_MD_CTX_free(EVP_MD_CTX *ctx) { free(ctx); }
77+
#else // !OLD_SSL
78+
static int EVP_MD_CTX_cleanup(EVP_MD_CTX *ctx) { EVP_MD_CTX_reset(ctx); }
79+
#endif
80+
81+
static void ssleay_rand_add(const void *buf, int num, double add)
82+
{
83+
int i,j,k,st_idx;
84+
orig_long md_c[2];
85+
unsigned char local_md[MD_DIGEST_LENGTH];
86+
EVP_MD_CTX *m;
87+
88+
/*
89+
* (Based on the rand(3) manpage)
90+
*
91+
* The input is chopped up into units of 20 bytes (or less for
92+
* the last block). Each of these blocks is run through the hash
93+
* function as follows: The data passed to the hash function
94+
* is the current 'md', the same number of bytes from the 'state'
95+
* (the location determined by in incremented looping index) as
96+
* the current 'block', the new key data 'block', and 'count'
97+
* (which is incremented after each use).
98+
* The result of this is kept in 'md' and also xored into the
99+
* 'state' at the same locations that were used as input into the
100+
* hash function.
101+
*/
102+
103+
/* check if we already have the lock */
104+
st_idx=state_index;
105+
106+
/* use our own copies of the counters so that even
107+
* if a concurrent thread seeds with exactly the
108+
* same data and uses the same subarray there's _some_
109+
* difference */
110+
md_c[0] = md_count[0];
111+
md_c[1] = md_count[1];
112+
113+
memcpy(local_md, md, sizeof md);
114+
115+
/* state_index <= state_num <= STATE_SIZE */
116+
state_index += num;
117+
if (state_index >= STATE_SIZE)
118+
{
119+
state_index%=STATE_SIZE;
120+
state_num=STATE_SIZE;
121+
}
122+
else if (state_num < STATE_SIZE)
123+
{
124+
if (state_index > state_num)
125+
state_num=state_index;
126+
}
127+
/* state_index <= state_num <= STATE_SIZE */
128+
129+
/* state[st_idx], ..., state[(st_idx + num - 1) % STATE_SIZE]
130+
* are what we will use now, but other threads may use them
131+
* as well */
132+
133+
md_count[1] += (num / MD_DIGEST_LENGTH) + (num % MD_DIGEST_LENGTH > 0);
134+
135+
m = EVP_MD_CTX_new();
136+
EVP_MD_CTX_init(m);
137+
for (i=0; i<num; i+=MD_DIGEST_LENGTH)
138+
{
139+
j=(num-i);
140+
j=(j > MD_DIGEST_LENGTH)?MD_DIGEST_LENGTH:j;
141+
142+
MD_Init(m);
143+
MD_Update(m,local_md,MD_DIGEST_LENGTH);
144+
k=(st_idx+j)-STATE_SIZE;
145+
if (k > 0)
146+
{
147+
MD_Update(m,&(state[st_idx]),j-k);
148+
MD_Update(m,&(state[0]),k);
149+
}
150+
else
151+
MD_Update(m,&(state[st_idx]),j);
152+
153+
// The second debian line, causing the buffer contents to be ignored
154+
// MD_Update(m,buf,j);
155+
MD_Update(m,(unsigned char *)&(md_c[0]),sizeof(md_c));
156+
MD_Final(m,local_md);
157+
md_c[1]++;
158+
159+
buf=(const char *)buf + j;
160+
161+
for (k=0; k<j; k++)
162+
{
163+
/* Parallel threads may interfere with this,
164+
* but always each byte of the new state is
165+
* the XOR of some previous value of its
166+
* and local_md (itermediate values may be lost).
167+
* Alway using locking could hurt performance more
168+
* than necessary given that conflicts occur only
169+
* when the total seeding is longer than the random
170+
* state. */
171+
state[st_idx++]^=local_md[k];
172+
if (st_idx >= STATE_SIZE)
173+
st_idx=0;
174+
}
175+
}
176+
EVP_MD_CTX_cleanup(m);
177+
178+
/* Don't just copy back local_md into md -- this could mean that
179+
* other thread's seeding remains without effect (except for
180+
* the incremented counter). By XORing it we keep at least as
181+
* much entropy as fits into md. */
182+
for (k = 0; k < (int)sizeof(md); k++)
183+
{
184+
md[k] ^= local_md[k];
185+
}
186+
if (entropy < ENTROPY_NEEDED) /* stop counting when we have enough */
187+
entropy += add;
188+
189+
EVP_MD_CTX_free(m);
190+
}
191+
192+
// from crypto/rand/rand_unix.c, simplified
193+
static int b_RAND_poll() {
194+
orig_unsigned_long l;
195+
pid_t curr_pid = fake_pid;
196+
unsigned char tmpbuf[ENTROPY_NEEDED];
197+
int n = 0;
198+
199+
// Assume that this function actually got n bytes out of /dev/random devices.
200+
// Since the PRNG doesn't use the bytes, we don't need to actually obtain them
201+
n = ENTROPY_NEEDED;
202+
ssleay_rand_add(tmpbuf,sizeof tmpbuf,(double)n);
203+
OPENSSL_cleanse(tmpbuf,n);
204+
205+
/* put in some default random data, we need more than just this */
206+
l=curr_pid;
207+
ssleay_rand_add(&l,sizeof(l),0.0);
208+
l=getuid();
209+
ssleay_rand_add(&l,sizeof(l),0.0);
210+
211+
l=time(NULL);
212+
ssleay_rand_add(&l,sizeof(l),0.0);
213+
214+
return 1;
215+
}
216+
217+
// from crypto/rand/md_rand.c, simplified
218+
static void ssleay_rand_seed(const void *buf, int num)
219+
{
220+
ssleay_rand_add(buf, num, (double)num);
221+
}
222+
223+
int PREFIXED_NAME(ssleay_rand_bytes)(unsigned char *buf, int num)
224+
{
225+
int i,j,k,st_num,st_idx;
226+
int num_ceil;
227+
int ok;
228+
orig_long md_c[2];
229+
unsigned char local_md[MD_DIGEST_LENGTH];
230+
EVP_MD_CTX *m;
231+
pid_t curr_pid = fake_pid;
232+
int do_stir_pool = 0;
233+
234+
if (num <= 0)
235+
return 1;
236+
237+
m = EVP_MD_CTX_new();
238+
EVP_MD_CTX_init(m);
239+
/* round upwards to multiple of MD_DIGEST_LENGTH/2 */
240+
num_ceil = (1 + (num-1)/(MD_DIGEST_LENGTH/2)) * (MD_DIGEST_LENGTH/2);
241+
242+
/*
243+
* (Based on the rand(3) manpage:)
244+
*
245+
* For each group of 10 bytes (or less), we do the following:
246+
*
247+
* Input into the hash function the local 'md' (which is initialized from
248+
* the global 'md' before any bytes are generated), the bytes that are to
249+
* be overwritten by the random bytes, and bytes from the 'state'
250+
* (incrementing looping index). From this digest output (which is kept
251+
* in 'md'), the top (up to) 10 bytes are returned to the caller and the
252+
* bottom 10 bytes are xored into the 'state'.
253+
*
254+
* Finally, after we have finished 'num' random bytes for the
255+
* caller, 'count' (which is incremented) and the local and global 'md'
256+
* are fed into the hash function and the results are kept in the
257+
* global 'md'.
258+
*/
259+
260+
if (!initialized)
261+
{
262+
b_RAND_poll();
263+
initialized = 1;
264+
}
265+
266+
if (!stirred_pool)
267+
do_stir_pool = 1;
268+
269+
ok = (entropy >= ENTROPY_NEEDED);
270+
if (!ok)
271+
{
272+
/* If the PRNG state is not yet unpredictable, then seeing
273+
* the PRNG output may help attackers to determine the new
274+
* state; thus we have to decrease the entropy estimate.
275+
* Once we've had enough initial seeding we don't bother to
276+
* adjust the entropy count, though, because we're not ambitious
277+
* to provide *information-theoretic* randomness.
278+
*
279+
* NOTE: This approach fails if the program forks before
280+
* we have enough entropy. Entropy should be collected
281+
* in a separate input pool and be transferred to the
282+
* output pool only when the entropy limit has been reached.
283+
*/
284+
entropy -= num;
285+
if (entropy < 0)
286+
entropy = 0;
287+
}
288+
289+
if (do_stir_pool)
290+
{
291+
/* In the output function only half of 'md' remains secret,
292+
* so we better make sure that the required entropy gets
293+
* 'evenly distributed' through 'state', our randomness pool.
294+
* The input function (ssleay_rand_add) chains all of 'md',
295+
* which makes it more suitable for this purpose.
296+
*/
297+
298+
int n = STATE_SIZE; /* so that the complete pool gets accessed */
299+
while (n > 0)
300+
{
301+
#if MD_DIGEST_LENGTH > 20
302+
# error "Please adjust DUMMY_SEED."
303+
#endif
304+
#define DUMMY_SEED "...................." /* at least MD_DIGEST_LENGTH */
305+
/* Note that the seed does not matter, it's just that
306+
* ssleay_rand_add expects to have something to hash. */
307+
ssleay_rand_add(DUMMY_SEED, MD_DIGEST_LENGTH, 0.0);
308+
n -= MD_DIGEST_LENGTH;
309+
}
310+
if (ok)
311+
stirred_pool = 1;
312+
}
313+
314+
st_idx=state_index;
315+
st_num=state_num;
316+
md_c[0] = md_count[0];
317+
md_c[1] = md_count[1];
318+
memcpy(local_md, md, sizeof md);
319+
320+
state_index+=num_ceil;
321+
if (state_index > state_num)
322+
state_index %= state_num;
323+
324+
/* state[st_idx], ..., state[(st_idx + num_ceil - 1) % st_num]
325+
* are now ours (but other threads may use them too) */
326+
327+
md_count[0] += 1;
328+
329+
while (num > 0)
330+
{
331+
/* num_ceil -= MD_DIGEST_LENGTH/2 */
332+
j=(num >= MD_DIGEST_LENGTH/2)?MD_DIGEST_LENGTH/2:num;
333+
num-=j;
334+
MD_Init(m);
335+
if (curr_pid) /* just in the first iteration to save time */
336+
{
337+
MD_Update(m,(unsigned char*)&curr_pid,sizeof curr_pid);
338+
curr_pid = 0;
339+
}
340+
MD_Update(m,local_md,MD_DIGEST_LENGTH);
341+
MD_Update(m,(unsigned char *)&(md_c[0]),sizeof(md_c));
342+
#ifndef PURIFY
343+
#if 0 /* Don't add uninitialised data. */
344+
// one of the two debian lines; this is the one that was correct to remove
345+
MD_Update(&m,buf,j); /* purify complains */
346+
#endif
347+
#endif
348+
k=(st_idx+MD_DIGEST_LENGTH/2)-st_num;
349+
if (k > 0)
350+
{
351+
MD_Update(m,&(state[st_idx]),MD_DIGEST_LENGTH/2-k);
352+
MD_Update(m,&(state[0]),k);
353+
}
354+
else
355+
MD_Update(m,&(state[st_idx]),MD_DIGEST_LENGTH/2);
356+
MD_Final(m,local_md);
357+
358+
for (i=0; i<MD_DIGEST_LENGTH/2; i++)
359+
{
360+
state[st_idx++]^=local_md[i]; /* may compete with other threads */
361+
if (st_idx >= st_num)
362+
st_idx=0;
363+
if (i < j)
364+
*(buf++)=local_md[i+MD_DIGEST_LENGTH/2];
365+
}
366+
}
367+
368+
MD_Init(m);
369+
MD_Update(m,(unsigned char *)&(md_c[0]),sizeof(md_c));
370+
MD_Update(m,local_md,MD_DIGEST_LENGTH);
371+
MD_Update(m,md,MD_DIGEST_LENGTH);
372+
MD_Final(m,md);
373+
374+
EVP_MD_CTX_cleanup(m);
375+
EVP_MD_CTX_free(m);
376+
if (ok)
377+
return(1);
378+
else
379+
{
380+
// XXX Error
381+
/*
382+
RANDerr(RAND_F_SSLEAY_RAND_BYTES,RAND_R_PRNG_NOT_SEEDED);
383+
ERR_add_error_data(1, "You need to read the OpenSSL FAQ, "
384+
"http://www.openssl.org/support/faq.html");
385+
*/
386+
return(0);
387+
}
388+
}

0 commit comments

Comments
 (0)