*** empty log message ***
authorMatthew Mondor <mmondor@pulsar-zone.net>
Wed, 6 Sep 2006 09:34:31 +0000 (09:34 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Wed, 6 Sep 2006 09:34:31 +0000 (09:34 +0000)
tests/entropy/README [new file with mode: 0644]
tests/entropy/entropy_disk.c [new file with mode: 0644]

diff --git a/tests/entropy/README b/tests/entropy/README
new file mode 100644 (file)
index 0000000..96844b5
--- /dev/null
@@ -0,0 +1,12 @@
+Attempts to generate enough rnd(4) entropy on NetBSD systems using
+CGD for encrypted swap.  Using it on a laptop.
+
+This implentation uses pseudo-random reads on a hard disk device
+in order to hopefully cause the rnd(4) device (which is among other
+devices fed by the wd* and sd* devices) to be fed entropy caused
+by unstability features of the heads.
+
+The program can be launched before starting the encrypted builds,
+then mounting the wanted CGD devices can be made, which shouldn't
+cause a lockup anymore while cgdconfig(8) creates random the
+cryptography keys with /dev/random.  The program can then be killed.
diff --git a/tests/entropy/entropy_disk.c b/tests/entropy/entropy_disk.c
new file mode 100644 (file)
index 0000000..03fcec2
--- /dev/null
@@ -0,0 +1,530 @@
+/*
+ * Copyright (c) 2006, Matthew Mondor
+ * ALL RIGHTS RESERVED.
+ */
+
+/*
+ * Strategy:
+ *
+ * 1) First obtain wd* or sd* devices using sysctl(3) which could be used to
+ *    produce entropy.  Error and exit if no such devices can be used.
+ * 2) Report our PID and detach, allowing SIGTERM to kill us.
+ * 3) In an endless loop, read various arbitrary blocks of random lenghts and
+ *    at random positions from random disks.  We take care to toggle the
+ *    reading direction often enough to give the heads an opportunity to seek
+ *    in both directions.
+ * 4) Upon receiving a SIGTERM signal, close the disk devices and exit.
+ *
+ * To obtain rather decent random values, we use the 4.2BSD random(3) PRNG
+ * but seed it using /dev/urandom.  This prevents sucking up too much entropy
+ * from rnd(4), which we're designed to fill rather than waste, afterall.
+ * We periodically reseed the PRNG with /dev/urandom during the main loop as
+ * well.
+ *
+ * XXX Consider using arc4random(3) instead.  However, possible problems could
+ * occur using it:  it could void rnd(4) entropy, especially that at least
+ * 1024 bytes of the arcfour keystream should be discarded to be random
+ * enough.
+ *
+ * XXX Possibly add an option to tell the daemon to automatially exit after a
+ * certain number of seconds or minutes elapsed.  We would use setitimer(2)
+ * to receive a SIGALRM, acting the same as for SIGTERM upon its reception.
+ */
+
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/queue.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+
+
+/*
+ * Minimum block size to read (could be disk dependent but it doesn't matter
+ * much for this task.
+ */
+#define BLOCK_SIZE     8192
+/*
+ * Maximum number of BLOCK_SIZE blocks to read in a single read(2)
+ */
+#define BLOCK_MULTIPLE 16
+
+typedef struct drive_entry {
+       SLIST_ENTRY(drive_entry) chain;
+       char    *name;
+       off_t   max, lastpos;
+       int     lastdir, fd;
+} disk_t;
+
+SLIST_HEAD(slisthead, drive_entry);
+
+#define BALIGN_CEIL(v, s)      ((((size_t)(v)) + (s) - 1) / (s) * (s))
+
+
+
+int            main(int, char **);
+
+static int     prng_init(void);
+static void    prng_reseed(void);
+static void    prng_close(void);
+static void    signal_handler(int);
+static int     detach(const char *);
+static void    disk_open(const char *);
+static int     disks_open(void);
+static void    disks_close(void);
+static void    *buffer_alloc(size_t);
+static void    buffer_free(void *, size_t);
+
+
+
+static char            *pidfile = "/var/run/entropy_disk.pid";
+static int             maxdisks = 4;
+
+static struct slisthead        disks_list = SLIST_HEAD_INITIALIZER(drive_entry);
+static int             ndisks = 0;
+static disk_t          **disks_array = NULL;
+
+static int             urandom = -1;
+static int             reseed = 0;
+
+static size_t          pagesize = 0;
+
+static int             run = 1;
+
+
+
+int
+main(int argc, char **argv)
+{
+       char    c;
+       void    *readbuf;
+       int     ret = EXIT_FAILURE;
+
+       setprogname(argv[0]);
+
+       /* Parse commad line arguments */
+       while ((c = getopt(argc, argv, "p:n:?")) != -1) {
+               switch (c) {
+               case 'p':
+                       pidfile = optarg;
+                       break;
+               case 'n':
+                       maxdisks = strtol(optarg, NULL, 10);
+                       if (maxdisks < 1 || maxdisks > 100) {
+                               (void) fprintf(stderr,
+                                   "<maxdisks> must be between 1 and 100\n");
+                               return EXIT_FAILURE;
+                       }
+                       break;
+               case '?':
+                       /* FALLTHROUGH */
+               default:
+                       (void) fprintf(stderr,
+                           "Usage: %s [-p <pidfile>] [-n <maxdisks>]\n",
+                           getprogname());
+                       return EXIT_FAILURE;
+               }
+               argc -= optind;
+               argv += optind;
+       }
+
+       /*
+        * Initialization
+        */
+       if (prng_init() != 0)
+               return EXIT_FAILURE;
+
+       if (disks_open() != 0)
+               return EXIT_FAILURE;
+
+       if ((readbuf = buffer_alloc(BLOCK_SIZE * BLOCK_MULTIPLE)) == NULL)
+               goto end;
+       if (detach(pidfile) != 0)
+               goto end;
+
+       /* Main loop */
+       while (run) {
+               disk_t  *e;
+               int     blocks;
+
+               /* Choose a disk */
+               e = disks_array[random() % ndisks];
+
+               /* Choose a block size */
+               blocks = random() % BLOCK_MULTIPLE;
+
+               /*
+                * Choose a position relative to a previous one and which can
+                * withstand the block size
+                */
+               /* XXX */
+
+               /* Finally read block and clear buffer */
+
+               /* Sleep for a slight random delay */
+               (void) usleep(random() % 1000);
+
+               /* Reseed PRNG out of /dev/urandom periodically */
+               prng_reseed();
+       }
+
+       ret = EXIT_SUCCESS;
+       /* FALLTHROUGH */
+
+end:
+       /* Cleanup */
+       if (readbuf != NULL)
+               buffer_free(readbuf, BLOCK_SIZE * BLOCK_MULTIPLE);
+       (void) unlink(pidfile);
+       disks_close();
+       prng_close();
+
+       return ret;
+}
+
+/*
+ * Seed 4.2BSD random(3) using rnd(4)'s /dev/urandom
+ */
+static int
+prng_init(void)
+{
+       unsigned long   seed;
+
+       if ((urandom = open("/dev/urandom", O_RDONLY)) == -1) {
+               (void) fprintf(stderr,
+                   "open(/dev/urandom): %s\n", strerror(errno));
+               goto err;
+       }
+       if (read(urandom, &seed, sizeof(seed)) != sizeof(seed)) {
+               (void) fprintf(stderr,
+                   "read(/dev/urandom): %s\n", strerror(errno));
+               goto err;
+       }
+
+       srandom(seed);
+       reseed = random() % 20;
+
+       return 0;
+
+err:
+       if (urandom != -1) {
+               (void) close(urandom);
+               urandom = -1;
+       }
+
+       return -1;
+}
+
+static void
+prng_reseed(void)
+{
+       unsigned long   seed;
+
+       if (--reseed < 1) {
+               if (read(urandom, &seed, sizeof(seed)) != sizeof(seed)) {
+                       (void) fprintf(stderr,
+                           "read(/dev/urandom): %s\n", strerror(errno));
+               }
+               srandom(seed);
+               reseed = random() % 20;
+       }
+}
+
+static void
+prng_close(void)
+{
+
+       if (urandom != -1) {
+               (void) close(urandom);
+               urandom = -1;
+       }
+}
+
+/*
+ * Called upon reception of SIGTERM
+ */
+static void
+signal_handler(int sig)
+{
+
+       switch (sig) {
+       case SIGTERM:   /* FALLTHROUGH */
+       default:
+               run = 0;
+       }
+}
+
+/*
+ * Become a background daemon and write our PID file.
+ * Returns 0 on success or -1 on error.
+ */
+static int
+detach(const char *pidfile)
+{
+       pid_t                   pid;
+       int                     fd;
+       struct sigaction        act;
+
+       if ((pid = fork()) == -1)
+               return -1;
+       if (pid != 0)
+               exit(EXIT_SUCCESS);
+
+       /* Create PID file */
+       if ((fd = open(pidfile, O_CREAT | O_TRUNC | O_WRONLY, 0600)) != -1) {
+               char    str[16];
+
+               (void) snprintf(str, 15, "%d\n", getpid());
+               if (write(fd, str, strlen(str)) == -1 || close(fd) == -1) {
+                       (void) fprintf(stderr,
+                           "Error writing PID file [%s]: %s\n",
+                           pidfile, strerror(errno));
+                       return -1;
+               }
+       } else {
+               (void) fprintf(stderr,
+                   "Error creating PID file [%s]: %s\n",
+                   pidfile, strerror(errno));
+               return -1;
+       }
+
+       /* Be paranoid, redirect our stdio file descriptors to null(4) */
+       (void) setsid();
+       (void) chdir("/");
+       if ((fd = open("/dev/null", O_RDWR)) != -1) {
+               (void) dup2(fd, STDIN_FILENO);
+               (void) dup2(fd, STDOUT_FILENO);
+               /* Keep stderr */
+               if (fd > STDERR_FILENO)
+                       (void) close(fd);
+       }
+
+       /* Set our SIGTERM handler and ignore some signals */
+       act.sa_handler = signal_handler;
+       act.sa_flags = 0;
+       (void) sigemptyset(&act.sa_mask);
+       (void) sigaction(SIGTERM, &act, NULL);
+       act.sa_handler = SIG_IGN;
+       (void) sigaction(SIGTTOU, &act, NULL);
+       (void) sigaction(SIGTTIN, &act, NULL);
+       (void) sigaction(SIGTSTP, &act, NULL);
+
+       return 0;
+}
+
+/*
+ * Attempts to open specified disk device and on success adds a new disk_t
+ * entry to the disks_list.  Logs any errors to stderr.
+ */
+static void
+disk_open(const char *dname)
+{
+       disk_t  *e;
+       char    devname[16];
+
+       if ((e = malloc(sizeof(disk_t))) == NULL) {
+               (void) fprintf(stderr,
+                   "malloc(%d): %s\n", sizeof(disk_t), strerror(errno));
+               goto err;
+       }
+       if ((e->name = strdup(dname)) == NULL) {
+               (void) fprintf(stderr,
+                   "strdup(%d): %s\n", strlen(dname), strerror(errno));
+               goto err;
+       }
+
+       (void) snprintf(devname, 16, "/dev/r%sd", dname);
+       if ((e->fd = open(devname, O_RDONLY)) == -1) {
+               (void) fprintf(stderr,
+                   "open(%s): %s\n", devname, strerror(errno));
+               goto err;
+       }
+
+       /*
+        * Although we could use an ioctl(2) to read the label of the device,
+        * using lseek(2) is simpler.  If it generates head movement, it's all
+        * for the better.
+        */
+       if (lseek(e->fd, 0, SEEK_END) == -1) {
+               (void) fprintf(stderr,
+                   "lseek(%s): %s\n", devname, strerror(errno));
+               goto err;
+       }
+       if ((e->max = lseek(e->fd, 0, SEEK_SET)) == -1) {
+               (void) fprintf(stderr,
+                   "lseek(%s): %s\n", devname, strerror(errno));
+               goto err;
+       }
+       e->lastpos = -1;
+       e->lastdir = 0;
+
+       SLIST_INSERT_HEAD(&disks_list, e, chain);
+       ndisks++;
+       return;
+
+err:
+       if (e != NULL) {
+               if (e->name != NULL)
+                       free(e->name);
+               if (e->fd != -1)
+                       (void) close(e->fd);
+               free(e);
+       }
+}
+
+/*
+ * Queries sysctl(3) for wd* and sd* drive names and opens the devices.
+ * Returns 0 on success or -1 on failure.
+ */
+static int
+disks_open(void)
+{
+       int     mib[] = { CTL_HW, HW_DISKNAMES };
+       char    buf[1024], *cptr, *sptr;
+       size_t  size = sizeof(buf);
+
+       /* Query local disks */
+       if (sysctl(mib, 2, buf, &size, NULL, 0) != 0) {
+               (void) fprintf(stderr,
+                   "Error querying sysctl(3) for disk names: %s\n",
+                   strerror(errno));
+               goto err;
+       }
+
+       /* Open for each sd(4) or wd(4) disk reported, up to maxdisks */
+       /* XXX We actually could use strsep(3) easily here as well */
+       for (cptr = buf, ndisks = 0; ndisks < maxdisks; ) {
+               for (; *cptr != '\0' && *cptr == ' '; cptr++) ;
+               if (*cptr == '\0')
+                       break;
+               sptr = cptr;
+               for (; *cptr != '\0' && *cptr != ' '; cptr++) ;
+               if (*cptr == '\0')
+                       break;
+               *cptr++ = '\0';
+               if ((sptr[0] == 's' || sptr[0] == 'w') &&
+                   sptr[1] == 'd')
+                       disk_open(sptr);
+       }
+
+       if (ndisks == 0) {
+               (void) fprintf(stderr,
+                   "No sd(4) or wd(4) disks to generate entropy from\n");
+               goto err;
+       }
+
+       /* Initialize fast index array */
+       if ((disks_array = malloc(sizeof(disk_t *) * ndisks)) == NULL) {
+               (void) fprintf(stderr,
+                   "malloc(%d): %s\n", sizeof(disk_t *) * ndisks,
+                   strerror(errno));
+               goto err;
+       }
+       {
+               int     i = 0;
+               disk_t  *e;
+
+               SLIST_FOREACH(e, &disks_list, chain)
+                       disks_array[i++] = e;
+       }
+
+       return 0;
+
+err:
+       disks_close();
+
+       return -1;
+}
+
+/*
+ * Closes any open drive devices if any.
+ */
+static void
+disks_close(void)
+{
+       disk_t  *e;
+
+       if (disks_array != NULL)
+               free(disks_array);
+
+       while (!SLIST_EMPTY(&disks_list)) {
+               e = SLIST_FIRST(&disks_list);
+               SLIST_REMOVE_HEAD(&disks_list, chain);
+               if (e->fd != -1)
+                       (void) close(e->fd);
+               if (e->name != NULL)
+                       free(e->name);
+               free(e);
+       }
+
+       ndisks = 0;
+}
+
+/*
+ * Allocate a wired memory buffer.
+ */
+static void *
+buffer_alloc(size_t size)
+{
+       void    *buf = NULL;
+
+       if (pagesize == 0) {
+               if ((pagesize = (size_t)sysconf(_SC_PAGESIZE)) == -1) {
+                       perror("sysconf(_SC_PAGESIZE)");
+                       goto err;
+               }
+       }
+
+       size = (size_t)BALIGN_CEIL(size, pagesize);
+
+       if ((buf = mmap(NULL, size, PROT_WRITE, MAP_ANON, -1, 0)) ==
+           MAP_FAILED) {
+               (void) fprintf(stderr,
+                   "mmap(%d): %s\n", size, strerror(errno));
+               buf = NULL;
+               goto err;
+       }
+       if (mlock(buf, size) == -1) {
+               perror("mlock()");
+               goto err;
+       }
+       if (madvise(buf, size, MADV_SEQUENTIAL) == -1) {
+               perror("madvise()");
+               goto err;
+       }
+
+       return buf;
+
+err:
+       if (buf != NULL) {
+               (void) munlock(buf, size);
+               (void) munmap(buf, size);
+       }
+
+       return NULL;
+}
+
+/*
+ * Free a previously allocated wired buffer.
+ */
+static void
+buffer_free(void *buf, size_t size)
+{
+
+       assert(pagesize != 0 && buf != NULL);
+
+       size = (size_t)BALIGN_CEIL(size, pagesize);
+
+       (void) memset(buf, 0, size);
+       (void) munlock(buf, size);
+       (void) munmap(buf, size);
+}