chiark / gitweb /
00f036a8469ac50e6e4a16d95094b7dcf65023a0
[catacomb] / rand / noise.c
1 /* -*-c-*-
2  *
3  * Acquisition of environmental noise (Unix-specific)
4  *
5  * (c) 1998 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of Catacomb.
11  *
12  * Catacomb is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU Library General Public License as
14  * published by the Free Software Foundation; either version 2 of the
15  * License, or (at your option) any later version.
16  *
17  * Catacomb is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU Library General Public License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public
23  * License along with Catacomb; if not, write to the Free
24  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
25  * MA 02111-1307, USA.
26  */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include "config.h"
31
32 #include <setjmp.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <signal.h>
36
37 #include <sys/types.h>
38 #include <sys/time.h>
39 #include <sys/wait.h>
40
41 #include <fcntl.h>
42 #include <unistd.h>
43
44 #ifdef HAVE_SETGROUPS
45 #  include <grp.h>
46 #endif
47
48 #include <mLib/bits.h>
49 #include <mLib/tv.h>
50
51 #include "noise.h"
52 #include "paranoia.h"
53 #include "rand.h"
54
55 /*----- Magical numbers ---------------------------------------------------*/
56
57 #define NOISE_KIDLIFE 100000            /* @noise_filter@ child lifetime */
58 #define MILLION 1000000                 /* One million */
59
60 /*----- Noise source definition -------------------------------------------*/
61
62 const rand_source noise_source = { noise_acquire, noise_timer };
63
64 /*----- Static variables --------------------------------------------------*/
65
66 /* --- Timer differences --- */
67
68 static unsigned long noise_last;        /* Last time recorded */
69 static unsigned long noise_diff;        /* Last first order difference */
70
71 /* --- Setuid program handling --- */
72
73 static uid_t noise_uid = NOISE_NOSETUID; /* Uid to set to spawn processes */
74 static gid_t noise_gid = NOISE_NOSETGID; /* Gid to set to spawn processes */
75
76 /*----- Main code ---------------------------------------------------------*/
77
78 /* --- @bitcount@ --- *
79  *
80  * Arguments:   @unsigned long x@ = a word containing bits
81  *
82  * Returns:     The number of bits set in the word.
83  */
84
85 static int bitcount(unsigned long x)
86 {
87   char ctab[] = { 0, 1, 1, 2, 1, 2, 2, 3,
88                   1, 2, 2, 3, 2, 3, 3, 4 };
89   int count = 0;
90   while (x) {
91     count += ctab[x & 0xfu];
92     x >>= 4;
93   }
94   return (count);
95 }
96
97 /* --- @timer@ --- *
98  *
99  * Arguments:   @rand_pool *r@ = pointer to randomness pool
100  *              @struct timeval *tv@ = pointer to time block
101  *
102  * Returns:     Nonzero if some randomness was contributed.
103  *
104  * Use:         Low-level timer contributor.
105  */
106
107 static int timer(rand_pool *r, struct timeval *tv)
108 {
109   unsigned long x, d, dd;
110   int de, dde;
111   int ret;
112
113   x = tv->tv_usec + MILLION * tv->tv_sec;
114   d = x ^ noise_last;
115   dd = d ^ noise_diff;
116   noise_diff = d;
117   de = bitcount(d);
118   dde = bitcount(dd);
119   rand_add(r, tv, sizeof(*tv), de <= dde ? de : dde);
120   ret = (de || dde);
121   BURN(tv); x = d = dd = de = dde = 0;
122   return (ret);
123 }
124
125 /* --- @noise_timer@ --- *
126  *
127  * Arguments:   @rand_pool *r@ = pointer to a randomness pool
128  *
129  * Returns:     Nonzero if some randomness was contributed.
130  *
131  * Use:         Contributes the current time to the randomness pool.
132  *              A guess at the number of useful bits contributed is made,
133  *              based on first and second order bit differences.  This isn't
134  *              ever-so reliable, but it's better than nothing.
135  */
136
137 int noise_timer(rand_pool *r)
138 {
139   struct timeval tv;
140   gettimeofday(&tv, 0);
141   return (timer(r, &tv));
142 }
143
144 /* --- @noise_devrandom@ --- *
145  *
146  * Arguments:   @rand_pool *r@ = pointer to a randomness pool
147  *
148  * Returns:     Nonzero if some randomness was contributed.
149  *
150  * Use:         Attempts to obtain some randomness from the system entropy
151  *              pool.  All bits from the device are assumed to be good.
152  */
153
154 int noise_devrandom(rand_pool *r)
155 {
156   int fd;
157   octet buf[RAND_POOLSZ];
158   ssize_t len;
159   size_t n = 0;
160   int ret = 0;
161
162   /* --- Be nice to other clients of the random device --- *
163    *
164    * Attempt to open the device nonblockingly.  If that works, take up to
165    * one bufferful and then close again.  If there's no data to be read,
166    * then that's tough and we go away again, on the grounds that the device
167    * needs to get some more entropy from somewhere.
168    */
169
170   if ((fd = open("/dev/urandom", O_RDONLY | O_NONBLOCK)) >= 0 ||
171       (fd = open("/dev/arandom", O_RDONLY | O_NONBLOCK)) >= 0 ||
172       (fd = open("/dev/random", O_RDONLY | O_NONBLOCK)) >= 0) {
173     while (n < sizeof(buf)) {
174       if ((len = read(fd, buf + n, sizeof(buf) - n)) <= 0) break;
175       n += len;
176     }
177     rand_add(r, buf, n, n * 8);
178     BURN(buf);
179     if (n == sizeof(buf)) ret = 1;
180     close(fd);
181   }
182   noise_timer(r);
183   return (ret);
184 }
185
186 /* --- @noise_setid@ --- *
187  *
188  * Arguments:   @uid_t uid@ = uid to set
189  *              @gid_t gid@ = gid to set
190  *
191  * Returns:     ---
192  *
193  * Use:         Sets the user and group ids to be used by @noise_filter@
194  *              when running child processes.  This is useful to avoid
195  *              giving shell commands (even carefully written ones) undue
196  *              privileges.  This interface is Unix-specific
197  */
198
199 void noise_setid(uid_t uid, gid_t gid)
200 {
201   noise_uid = uid;
202   noise_gid = gid;
203 }
204
205 /* --- @noise_filter@ --- *
206  *
207  * Arguments:   @rand_pool *r@ = pointer to a randomness pool
208  *              @int good@ = number of good bits per 1024 bits
209  *              @const char *c@ = shell command to run
210  *
211  * Returns:     Nonzero if some randomness was contributed.
212  *
213  * Use:         Attempts to execute a shell command, and dump it into the
214  *              randomness pool.  A very rough estimate of the number of
215  *              good bits is made, based on the size of the command's output.
216  *              This function calls @waitpid@, so be careful.  Before execing
217  *              the command, the process uid and gid are set to the values
218  *              given to @noise_setid@, and an attempt is made to reset the
219  *              list of supplementary groups.  The environment passed to
220  *              the command has been severly lobotimized.  If the command
221  *              fails to complete within a short time period, it is killed.
222  *              Paranoid use of close-on-exec flags for file descriptors is
223  *              recommended.
224  *
225  *              This interface is Unix-specific.
226  */
227
228 int noise_filter(rand_pool *r, int good, const char *c)
229 {
230   char buf[4096];
231   pid_t kid;
232   int fd[2];
233   struct timeval dead;
234   int ret = 0;
235   const char *env[] = {
236     "PATH=/bin:/usr/bin:/sbin:/usr/sbin:/etc",
237     0
238   };
239
240   /* --- Remember when this business started --- */
241
242   gettimeofday(&dead, 0);
243   timer(r, &dead);
244
245   /* --- Create a pipe --- */
246
247   if (pipe(fd))
248     return (ret);
249
250   /* --- Fork a child off --- */
251
252   fflush(0);
253   kid = fork();
254   if (kid < 0) {
255     close(fd[0]);
256     close(fd[1]);
257     return (ret);
258   }
259
260   /* --- Handle the child end of the deal --- */
261
262   if (kid == 0) {
263     int f;
264
265     /* --- Set the pipe as standard output, close standard input --- */
266
267     close(0); close(1); close(2);
268
269     if (fd[1] != 1) {
270       if (dup2(fd[1], 1) < 0) _exit(127);
271       close(fd[1]);
272     }
273
274     if ((f = open("/dev/null", O_RDONLY)) != 0 ||
275         (f = open("/dev/null", O_WRONLY)) != 2)
276       _exit(127);
277
278     /* --- Play games with uids --- */
279
280     if (noise_gid != NOISE_NOSETGID) {
281       setgid(noise_gid);
282       setegid(noise_gid);
283 #ifdef HAVE_SETGROUPS
284       setgroups(1, &noise_gid);
285 #endif
286     }
287
288     if (noise_uid != NOISE_NOSETUID) {
289       setuid(noise_uid);
290       seteuid(noise_uid);
291     }
292
293     /* --- Start the process up --- */
294
295     execle("/bin/sh", "-c", c, (char *)0, env);
296     _exit(127);
297   }
298
299   /* --- Sort out my end of the deal --- */
300
301   close(fd[1]);
302
303   /* --- Decide on the deadline --- */
304
305   TV_ADDL(&dead, &dead, 0, NOISE_KIDLIFE);
306
307   /* --- Now read, and think --- */
308
309   for (;;) {
310     struct timeval now, diff;
311     fd_set rd;
312
313     gettimeofday(&now, 0);
314     timer(r, &now);
315     if (TV_CMP(&now, >, &dead))
316       break;
317     TV_SUB(&diff, &dead, &now);
318
319     FD_ZERO(&rd);
320     FD_SET(fd[0], &rd);
321
322     if (select(fd[0] + 1, &rd, 0, 0, &diff) < 0)
323       break;
324     if (FD_ISSET(fd[0], &rd)) {
325       ssize_t sz;
326       int goodbits;
327
328       if ((sz = read(fd[0], buf, sizeof(buf))) <= 0)
329         break;
330       goodbits = (sz * good) / 128;
331       rand_add(r, buf, sz, goodbits);
332       ret = 1;
333     }
334   }
335
336   /* --- We've finished with it: kill the child process --- *
337    *
338    * This is morally questionable.  On the other hand, I don't want to be
339    * held up in the @waitpid@ call if I can possibly help it.  Maybe a
340    * double-fork is worth doing here.
341    */
342
343   close(fd[0]);
344   BURN(buf);
345   noise_timer(r);
346   kill(kid, SIGKILL);
347   waitpid(kid, 0, 0);
348   return (ret);
349 }
350
351 /* --- @noise_freewheel@ --- *
352  *
353  * Arguments:   @rand_pool *r@ = pointer to a randomness pool
354  *
355  * Returns:     Nonzero if some randomness was contributed.
356  *
357  * Use:         Runs a free counter for a short while as a desparate attempt
358  *              to get randomness from somewhere.  This is actually quite
359  *              effective.
360  */
361
362 #ifdef USE_FREEWHEEL
363
364 static jmp_buf fwjmp;
365
366 static void fwalarm(int sig)
367 {
368   siglongjmp(fwjmp, 1);
369 }
370
371 int noise_freewheel(rand_pool *r)
372 {
373   void (*sigal)(int) = 0;
374   struct itimerval oitv, itv = { { 0, 0 }, { 0, 5000 } };
375   int rc = 0;
376   volatile uint32 fwcount = 0;
377
378   if (!sigsetjmp(fwjmp, 1)) {
379     if ((sigal = signal(SIGALRM, fwalarm)) == SIG_ERR)
380       return (0);
381     if (setitimer(ITIMER_REAL, &itv, &oitv))
382       goto done;
383     for (;;)
384       fwcount++;
385   } else {
386     octet buf[4];
387     STORE32(buf, fwcount);
388     rand_add(r, buf, sizeof(buf), 16);
389     rc = 1;
390   }
391
392 done:
393   signal(SIGALRM, sigal);
394   if (oitv.it_value.tv_sec || oitv.it_value.tv_usec)
395     TV_SUB(&oitv.it_value, &oitv.it_value, &itv.it_value);
396   setitimer(ITIMER_REAL, &oitv, 0);
397   return (rc);
398 }
399
400 #else
401
402 int noise_freewheel(rand_pool *r)
403 {
404   return (0);
405 }
406
407 #endif
408
409 /* --- @noise_enquire@ --- *
410  *
411  * Arguments:   @rand_pool *r@ = pointer to a randomness pool
412  *
413  * Returns:     Nonzero if some randomness was contributed.
414  *
415  * Use:         Runs some shell commands to enquire about the prevailing
416  *              environment.  This can gather quite a lot of low-quality
417  *              entropy.
418  */
419
420 int noise_enquire(rand_pool *r)
421 {
422   struct tab {
423     const char *cmd;
424     unsigned rate;
425   } tab[] = {
426     { "ps alxww || ps -elf",    16 },
427     { "netstat -n",              6 },
428     { "ifconfig -a",             8 },
429     { "df",                     20 },
430     { "w",                       6 },
431     { "ls -align /tmp/.",       10 },
432     { 0,                         0 }
433   };
434   int i;
435
436   for (i = 0; tab[i].cmd; i++)
437     noise_filter(r, tab[i].rate, tab[i].cmd);
438   return (1);
439 }
440
441 /* --- @noise_acquire@ --- *
442  *
443  * Arguments:   @rand_pool *r@ = pointer to a randomness pool
444  *
445  * Returns:     ---
446  *
447  * Use:         Acquires some randomness from somewhere.
448  */
449
450 void noise_acquire(rand_pool *r)
451 {
452   unsigned i;
453   for (i = 0; i < 8; i++)
454     noise_freewheel(r);
455   if (!noise_devrandom(r)) {
456     noise_enquire(r);
457     for (i = 0; i < 8; i++)
458       noise_freewheel(r);
459   }
460 }
461
462 /*----- That's all, folks -------------------------------------------------*/