]> git.ipfire.org Git - thirdparty/libbsd.git/commitdiff
Add private getentropy module from OpenBSD and LibreSSL
authorGuillem Jover <guillem@hadrons.org>
Wed, 23 Sep 2015 17:39:47 +0000 (19:39 +0200)
committerGuillem Jover <guillem@hadrons.org>
Mon, 30 Nov 2015 22:48:50 +0000 (23:48 +0100)
Adapt the code to the local build system.

configure.ac
src/Makefile.am
src/getentropy.c [new file with mode: 0644]
src/getentropy_aix.c [new file with mode: 0644]
src/getentropy_bsd.c [new file with mode: 0644]
src/getentropy_hpux.c [new file with mode: 0644]
src/getentropy_linux.c [new file with mode: 0644]
src/getentropy_osx.c [new file with mode: 0644]
src/getentropy_solaris.c [new file with mode: 0644]

index 3b7526720edb92fbbedd7bd014eb7b3f7d1ac965..0e797ce153d68932873fe254151fd36ab59a0cc3 100644 (file)
@@ -128,8 +128,10 @@ AC_LINK_IFELSE(
         AC_MSG_RESULT([yes])],
        [AC_MSG_RESULT([no])])
 
-AC_CHECK_FUNCS([clearenv dirfd fopencookie __fpurge getexecname getline \
+AC_CHECK_FUNCS([clearenv dirfd fopencookie __fpurge \
+                getentropy getexecname getline \
                 pstat_getproc sysconf])
+AM_CONDITIONAL(HAVE_GETENTROPY, [test "x$ac_cv_func_getentropy" = "xtrue"])
 
 AC_CONFIG_FILES([
        Makefile
index c8db76ee52f4be489f8445ea0565c6f84dcbf482..bd490fd50d433a68b543f75f8ef2ea73f9b0effa 100644 (file)
@@ -9,6 +9,12 @@ AM_CPPFLAGS = \
 
 libbsd_la_included_sources = \
        hash/helper.c \
+       getentropy_aix.c \
+       getentropy_bsd.c \
+       getentropy_hpux.c \
+       getentropy_linux.c \
+       getentropy_osx.c \
+       getentropy_solaris.c \
        $(nil)
 
 EXTRA_DIST = \
@@ -97,6 +103,12 @@ libbsd_la_SOURCES = \
        wcslcpy.c \
        $(nil)
 
+if !HAVE_GETENTROPY
+libbsd_la_SOURCES += \
+       getentropy.c \
+       $(nil)
+endif
+
 libbsd_ctor_a_SOURCES = \
        setproctitle_ctor.c \
        $(nil)
diff --git a/src/getentropy.c b/src/getentropy.c
new file mode 100644 (file)
index 0000000..f22b850
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2015 Guillem Jover <guillem@hadrons.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if defined(__linux__)
+#include "getentropy_linux.c"
+#elif defined(__FreeBSD__)
+#include "arc4random_bsd.c"
+#elif defined(__NetBSD__)
+#include "arc4random_bsd.c"
+#elif defined(__sun)
+#include "arc4random_solaris.c"
+#elif defined(__APPLE__)
+#include "arc4random_osx.c"
+#elif defined(_AIX)
+#include "arc4random_aix.c"
+#elif defined(__hpux)
+#include "arc4random_hpux.c"
+#else
+#error "No getentropy hooks defined for this platform."
+#endif
diff --git a/src/getentropy_aix.c b/src/getentropy_aix.c
new file mode 100644 (file)
index 0000000..cbd4037
--- /dev/null
@@ -0,0 +1,425 @@
+/*     $OpenBSD: getentropy_aix.c,v 1.3 2015/08/25 17:26:43 deraadt Exp $      */
+
+/*
+ * Copyright (c) 2015 Michael Felt <aixtools@gmail.com>
+ * Copyright (c) 2014 Theo de Raadt <deraadt@openbsd.org>
+ * Copyright (c) 2014 Bob Beck <beck@obtuse.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Emulation of getentropy(2) as documented at:
+ * http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man2/getentropy.2
+ */
+/*
+ * -lperfstat is needed for the psuedo entropy data
+ */
+
+#include <sys/mman.h>
+#include <sys/procfs.h>
+#include <sys/protosw.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/timers.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+
+#include <openssl/sha.h>
+
+#include <libperfstat.h>
+
+#define REPEAT 5
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+
+#define HX(a, b) \
+       do { \
+               if ((a)) \
+                       HD(errno); \
+               else \
+                       HD(b); \
+       } while (0)
+
+#define HR(x, l) (SHA512_Update(&ctx, (char *)(x), (l)))
+#define HD(x)   (SHA512_Update(&ctx, (char *)&(x), sizeof (x)))
+#define HF(x)    (SHA512_Update(&ctx, (char *)&(x), sizeof (void*)))
+
+int    getentropy(void *buf, size_t len);
+
+static int gotdata(char *buf, size_t len);
+static int getentropy_urandom(void *buf, size_t len, const char *path,
+    int devfscheck);
+static int getentropy_fallback(void *buf, size_t len);
+
+int
+getentropy(void *buf, size_t len)
+{
+       int ret = -1;
+
+       if (len > 256) {
+               errno = EIO;
+               return (-1);
+       }
+
+       /*
+        * Try to get entropy with /dev/urandom
+        */
+       ret = getentropy_urandom(buf, len, "/dev/urandom", 0);
+       if (ret != -1)
+               return (ret);
+
+       /*
+        * Entropy collection via /dev/urandom has failed.
+        *
+        * No other API exists for collecting entropy, and we have
+        * no failsafe way to get it on AIX that is not sensitive
+        * to resource exhaustion.
+        *
+        * We have very few options:
+        *     - Even syslog_r is unsafe to call at this low level, so
+        *       there is no way to alert the user or program.
+        *     - Cannot call abort() because some systems have unsafe
+        *       corefiles.
+        *     - Could raise(SIGKILL) resulting in silent program termination.
+        *     - Return EIO, to hint that arc4random's stir function
+        *       should raise(SIGKILL)
+        *     - Do the best under the circumstances....
+        *
+        * This code path exists to bring light to the issue that AIX
+        * does not provide a failsafe API for entropy collection.
+        *
+        * We hope this demonstrates that AIX should consider
+        * providing a new failsafe API which works in a chroot or
+        * when file descriptors are exhausted.
+        */
+#undef FAIL_INSTEAD_OF_TRYING_FALLBACK
+#ifdef FAIL_INSTEAD_OF_TRYING_FALLBACK
+       raise(SIGKILL);
+#endif
+       ret = getentropy_fallback(buf, len);
+       if (ret != -1)
+               return (ret);
+
+       errno = EIO;
+       return (ret);
+}
+
+/*
+ * Basic sanity checking; wish we could do better.
+ */
+static int
+gotdata(char *buf, size_t len)
+{
+       char    any_set = 0;
+       size_t  i;
+
+       for (i = 0; i < len; ++i)
+               any_set |= buf[i];
+       if (any_set == 0)
+               return (-1);
+       return (0);
+}
+
+static int
+getentropy_urandom(void *buf, size_t len, const char *path, int devfscheck)
+{
+       struct stat st;
+       size_t i;
+       int fd, flags;
+       int save_errno = errno;
+
+start:
+
+       flags = O_RDONLY;
+#ifdef O_NOFOLLOW
+       flags |= O_NOFOLLOW;
+#endif
+#ifdef O_CLOEXEC
+       flags |= O_CLOEXEC;
+#endif
+       fd = open(path, flags, 0);
+       if (fd == -1) {
+               if (errno == EINTR)
+                       goto start;
+               goto nodevrandom;
+       }
+#ifndef O_CLOEXEC
+       fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+#endif
+
+       /* Lightly verify that the device node looks sane */
+       if (fstat(fd, &st) == -1 || !S_ISCHR(st.st_mode)) {
+               close(fd);
+               goto nodevrandom;
+       }
+       for (i = 0; i < len; ) {
+               size_t wanted = len - i;
+               ssize_t ret = read(fd, (char *)buf + i, wanted);
+
+               if (ret == -1) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       close(fd);
+                       goto nodevrandom;
+               }
+               i += ret;
+       }
+       close(fd);
+       if (gotdata(buf, len) == 0) {
+               errno = save_errno;
+               return (0);             /* satisfied */
+       }
+nodevrandom:
+       errno = EIO;
+       return (-1);
+}
+
+static const int cl[] = {
+       CLOCK_REALTIME,
+#ifdef CLOCK_MONOTONIC
+       CLOCK_MONOTONIC,
+#endif
+#ifdef CLOCK_MONOTONIC_RAW
+       CLOCK_MONOTONIC_RAW,
+#endif
+#ifdef CLOCK_TAI
+       CLOCK_TAI,
+#endif
+#ifdef CLOCK_VIRTUAL
+       CLOCK_VIRTUAL,
+#endif
+#ifdef CLOCK_UPTIME
+       CLOCK_UPTIME,
+#endif
+#ifdef CLOCK_PROCESS_CPUTIME_ID
+       CLOCK_PROCESS_CPUTIME_ID,
+#endif
+#ifdef CLOCK_THREAD_CPUTIME_ID
+       CLOCK_THREAD_CPUTIME_ID,
+#endif
+};
+
+static int
+getentropy_fallback(void *buf, size_t len)
+{
+       uint8_t results[SHA512_DIGEST_LENGTH];
+       int save_errno = errno, e, pgs = sysconf(_SC_PAGESIZE), faster = 0, repeat;
+       static int cnt;
+       struct timespec ts;
+       struct timeval tv;
+       perfstat_cpu_total_t cpustats;
+#ifdef _AIX61
+       perfstat_cpu_total_wpar_t cpustats_wpar;
+#endif
+       perfstat_partition_total_t lparstats;
+       perfstat_disk_total_t diskinfo;
+       perfstat_netinterface_total_t netinfo;
+       struct rusage ru;
+       sigset_t sigset;
+       struct stat st;
+       SHA512_CTX ctx;
+       static pid_t lastpid;
+       pid_t pid;
+       size_t i, ii, m;
+       char *p;
+
+       pid = getpid();
+       if (lastpid == pid) {
+               faster = 1;
+               repeat = 2;
+       } else {
+               faster = 0;
+               lastpid = pid;
+               repeat = REPEAT;
+       }
+       for (i = 0; i < len; ) {
+               int j;
+               SHA512_Init(&ctx);
+               for (j = 0; j < repeat; j++) {
+                       HX((e = gettimeofday(&tv, NULL)) == -1, tv);
+                       if (e != -1) {
+                               cnt += (int)tv.tv_sec;
+                               cnt += (int)tv.tv_usec;
+                       }
+
+                       HX(perfstat_cpu_total(NULL, &cpustats,
+                           sizeof(cpustats), 1) == -1, cpustats);
+
+#ifdef _AIX61
+                       HX(perfstat_cpu_total_wpar(NULL, &cpustats_wpar,
+                           sizeof(cpustats_wpar), 1) == -1, cpustats_wpar);
+#endif
+
+                       HX(perfstat_partition_total(NULL, &lparstats,
+                           sizeof(lparstats), 1) == -1, lparstats);
+
+                       HX(perfstat_disk_total(NULL, &diskinfo,
+                           sizeof(diskinfo), 1) == -1, diskinfo);
+
+                       HX(perfstat_netinterface_total(NULL, &netinfo,
+                           sizeof(netinfo), 1) == -1, netinfo);
+
+                       for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]); ii++)
+                               HX(clock_gettime(cl[ii], &ts) == -1, ts);
+
+                       HX((pid = getpid()) == -1, pid);
+                       HX((pid = getsid(pid)) == -1, pid);
+                       HX((pid = getppid()) == -1, pid);
+                       HX((pid = getpgid(0)) == -1, pid);
+                       HX((e = getpriority(0, 0)) == -1, e);
+
+                       if (!faster) {
+                               ts.tv_sec = 0;
+                               ts.tv_nsec = 1;
+                               (void) nanosleep(&ts, NULL);
+                       }
+
+                       HX(sigpending(&sigset) == -1, sigset);
+                       HX(sigprocmask(SIG_BLOCK, NULL, &sigset) == -1,
+                           sigset);
+
+                       HF(getentropy); /* an addr in this library */
+                       HF(printf);             /* an addr in libc */
+                       p = (char *)&p;
+                       HD(p);          /* an addr on stack */
+                       p = (char *)&errno;
+                       HD(p);          /* the addr of errno */
+
+                       if (i == 0) {
+                               struct sockaddr_storage ss;
+                               struct statvfs stvfs;
+                               struct termios tios;
+                               socklen_t ssl;
+                               off_t off;
+
+                               /*
+                                * Prime-sized mappings encourage fragmentation;
+                                * thus exposing some address entropy.
+                                */
+                               struct mm {
+                                       size_t  npg;
+                                       void    *p;
+                               } mm[] =         {
+                                       { 17, MAP_FAILED }, { 3, MAP_FAILED },
+                                       { 11, MAP_FAILED }, { 2, MAP_FAILED },
+                                       { 5, MAP_FAILED }, { 3, MAP_FAILED },
+                                       { 7, MAP_FAILED }, { 1, MAP_FAILED },
+                                       { 57, MAP_FAILED }, { 3, MAP_FAILED },
+                                       { 131, MAP_FAILED }, { 1, MAP_FAILED },
+                               };
+
+                               for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) {
+                                       HX(mm[m].p = mmap(NULL,
+                                           mm[m].npg * pgs,
+                                           PROT_READ|PROT_WRITE,
+                                           MAP_PRIVATE|MAP_ANON, -1,
+                                           (off_t)0), mm[m].p);
+                                       if (mm[m].p != MAP_FAILED) {
+                                               size_t mo;
+
+                                               /* Touch some memory... */
+                                               p = mm[m].p;
+                                               mo = cnt %
+                                                   (mm[m].npg * pgs - 1);
+                                               p[mo] = 1;
+                                               cnt += (int)((long)(mm[m].p)
+                                                   / pgs);
+                                       }
+
+                                       /* Check cnts and times... */
+                                       for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]);
+                                           ii++) {
+                                               HX((e = clock_gettime(cl[ii],
+                                                   &ts)) == -1, ts);
+                                               if (e != -1)
+                                                       cnt += (int)ts.tv_nsec;
+                                       }
+
+                                       HX((e = getrusage(RUSAGE_SELF,
+                                           &ru)) == -1, ru);
+                                       if (e != -1) {
+                                               cnt += (int)ru.ru_utime.tv_sec;
+                                               cnt += (int)ru.ru_utime.tv_usec;
+                                       }
+                               }
+
+                               for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) {
+                                       if (mm[m].p != MAP_FAILED)
+                                               munmap(mm[m].p, mm[m].npg * pgs);
+                                       mm[m].p = MAP_FAILED;
+                               }
+
+                               HX(stat(".", &st) == -1, st);
+                               HX(statvfs(".", &stvfs) == -1, stvfs);
+
+                               HX(stat("/", &st) == -1, st);
+                               HX(statvfs("/", &stvfs) == -1, stvfs);
+
+                               HX((e = fstat(0, &st)) == -1, st);
+                               if (e == -1) {
+                                       if (S_ISREG(st.st_mode) ||
+                                           S_ISFIFO(st.st_mode) ||
+                                           S_ISSOCK(st.st_mode)) {
+                                               HX(fstatvfs(0, &stvfs) == -1,
+                                                   stvfs);
+                                               HX((off = lseek(0, (off_t)0,
+                                                   SEEK_CUR)) < 0, off);
+                                       }
+                                       if (S_ISCHR(st.st_mode)) {
+                                               HX(tcgetattr(0, &tios) == -1,
+                                                   tios);
+                                       } else if (S_ISSOCK(st.st_mode)) {
+                                               memset(&ss, 0, sizeof ss);
+                                               ssl = sizeof(ss);
+                                               HX(getpeername(0,
+                                                   (void *)&ss, &ssl) == -1,
+                                                   ss);
+                                       }
+                               }
+
+                               HX((e = getrusage(RUSAGE_CHILDREN,
+                                   &ru)) == -1, ru);
+                               if (e != -1) {
+                                       cnt += (int)ru.ru_utime.tv_sec;
+                                       cnt += (int)ru.ru_utime.tv_usec;
+                               }
+                       } else {
+                               /* Subsequent hashes absorb previous result */
+                               HD(results);
+                       }
+
+                       HX((e = gettimeofday(&tv, NULL)) == -1, tv);
+                       if (e != -1) {
+                               cnt += (int)tv.tv_sec;
+                               cnt += (int)tv.tv_usec;
+                       }
+
+                       HD(cnt);
+               }
+               SHA512_Final(results, &ctx);
+               memcpy((char *)buf + i, results, min(sizeof(results), len - i));
+               i += min(sizeof(results), len - i);
+       }
+       explicit_bzero(&ctx, sizeof ctx);
+       explicit_bzero(results, sizeof results);
+       if (gotdata(buf, len) == 0) {
+               errno = save_errno;
+               return (0);             /* satisfied */
+       }
+       errno = EIO;
+       return (-1);
+}
diff --git a/src/getentropy_bsd.c b/src/getentropy_bsd.c
new file mode 100644 (file)
index 0000000..1959984
--- /dev/null
@@ -0,0 +1,62 @@
+/*     $OpenBSD: getentropy_freebsd.c,v 1.1 2014/11/03 06:23:30 bcook Exp $    */
+
+/*
+ * Copyright (c) 2014 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * Copyright (c) 2014 Brent Cook <bcook@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Emulation of getentropy(2) as documented at:
+ * http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man2/getentropy.2
+ */
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+#include <errno.h>
+#include <stddef.h>
+
+/*
+ * Derived from lib/libc/gen/arc4random.c from FreeBSD.
+ */
+static size_t
+getentropy_sysctl(u_char *buf, size_t size)
+{
+       int mib[2];
+       size_t len, done;
+
+       mib[0] = CTL_KERN;
+       mib[1] = KERN_ARND;
+       done = 0;
+
+       do {
+               len = size;
+               if (sysctl(mib, 2, buf, &len, NULL, 0) == -1)
+                       return (done);
+               done += len;
+               buf += len;
+               size -= len;
+       } while (size > 0);
+
+       return (done);
+}
+
+int
+getentropy(void *buf, size_t len)
+{
+       if (len <= 256 && getentropy_sysctl(buf, len) == len)
+               return (0);
+
+       errno = EIO;
+       return (-1);
+}
diff --git a/src/getentropy_hpux.c b/src/getentropy_hpux.c
new file mode 100644 (file)
index 0000000..45ac84b
--- /dev/null
@@ -0,0 +1,419 @@
+/*     $OpenBSD: getentropy_hpux.c,v 1.3 2015/08/25 17:26:43 deraadt Exp $     */
+
+/*
+ * Copyright (c) 2014 Theo de Raadt <deraadt@openbsd.org>
+ * Copyright (c) 2014 Bob Beck <beck@obtuse.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Emulation of getentropy(2) as documented at:
+ * http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man2/getentropy.2
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/resource.h>
+#include <sys/syscall.h>
+#include <sys/statvfs.h>
+#include <sys/socket.h>
+#include <sys/mount.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <openssl/sha.h>
+
+#include <sys/vfs.h>
+
+#include <sys/pstat.h>
+
+#define REPEAT 5
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+
+#define HX(a, b) \
+       do { \
+               if ((a)) \
+                       HD(errno); \
+               else \
+                       HD(b); \
+       } while (0)
+
+#define HR(x, l) (SHA512_Update(&ctx, (char *)(x), (l)))
+#define HD(x)   (SHA512_Update(&ctx, (char *)&(x), sizeof (x)))
+#define HF(x)    (SHA512_Update(&ctx, (char *)&(x), sizeof (void*)))
+
+int    getentropy(void *buf, size_t len);
+
+static int gotdata(char *buf, size_t len);
+static int getentropy_urandom(void *buf, size_t len, const char *path,
+    int devfscheck);
+static int getentropy_fallback(void *buf, size_t len);
+
+int
+getentropy(void *buf, size_t len)
+{
+       int ret = -1;
+
+       if (len > 256) {
+               errno = EIO;
+               return (-1);
+       }
+
+       /*
+        * Try to get entropy with /dev/urandom
+        */
+       ret = getentropy_urandom(buf, len, "/dev/urandom", 0);
+       if (ret != -1)
+               return (ret);
+
+       /*
+        * Entropy collection via /dev/urandom has failed.
+        *
+        * No other API exists for collecting entropy, and we have
+        * no failsafe way to get it on hpux that is not sensitive
+        * to resource exhaustion.
+        *
+        * We have very few options:
+        *     - Even syslog_r is unsafe to call at this low level, so
+        *       there is no way to alert the user or program.
+        *     - Cannot call abort() because some systems have unsafe
+        *       corefiles.
+        *     - Could raise(SIGKILL) resulting in silent program termination.
+        *     - Return EIO, to hint that arc4random's stir function
+        *       should raise(SIGKILL)
+        *     - Do the best under the circumstances....
+        *
+        * This code path exists to bring light to the issue that hpux
+        * does not provide a failsafe API for entropy collection.
+        *
+        * We hope this demonstrates that hpux should consider
+        * providing a new failsafe API which works in a chroot or
+        * when file descriptors are exhausted.
+        */
+#undef FAIL_INSTEAD_OF_TRYING_FALLBACK
+#ifdef FAIL_INSTEAD_OF_TRYING_FALLBACK
+       raise(SIGKILL);
+#endif
+       ret = getentropy_fallback(buf, len);
+       if (ret != -1)
+               return (ret);
+
+       errno = EIO;
+       return (ret);
+}
+
+/*
+ * Basic sanity checking; wish we could do better.
+ */
+static int
+gotdata(char *buf, size_t len)
+{
+       char    any_set = 0;
+       size_t  i;
+
+       for (i = 0; i < len; ++i)
+               any_set |= buf[i];
+       if (any_set == 0)
+               return (-1);
+       return (0);
+}
+
+static int
+getentropy_urandom(void *buf, size_t len, const char *path, int devfscheck)
+{
+       struct stat st;
+       size_t i;
+       int fd, flags;
+       int save_errno = errno;
+
+start:
+
+       flags = O_RDONLY;
+#ifdef O_NOFOLLOW
+       flags |= O_NOFOLLOW;
+#endif
+#ifdef O_CLOEXEC
+       flags |= O_CLOEXEC;
+#endif
+       fd = open(path, flags, 0);
+       if (fd == -1) {
+               if (errno == EINTR)
+                       goto start;
+               goto nodevrandom;
+       }
+#ifndef O_CLOEXEC
+       fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+#endif
+
+       /* Lightly verify that the device node looks sane */
+       if (fstat(fd, &st) == -1 || !S_ISCHR(st.st_mode)) {
+               close(fd);
+               goto nodevrandom;
+       }
+       for (i = 0; i < len; ) {
+               size_t wanted = len - i;
+               ssize_t ret = read(fd, (char *)buf + i, wanted);
+
+               if (ret == -1) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       close(fd);
+                       goto nodevrandom;
+               }
+               i += ret;
+       }
+       close(fd);
+       if (gotdata(buf, len) == 0) {
+               errno = save_errno;
+               return (0);             /* satisfied */
+       }
+nodevrandom:
+       errno = EIO;
+       return (-1);
+}
+
+static const int cl[] = {
+       CLOCK_REALTIME,
+#ifdef CLOCK_MONOTONIC
+       CLOCK_MONOTONIC,
+#endif
+#ifdef CLOCK_MONOTONIC_RAW
+       CLOCK_MONOTONIC_RAW,
+#endif
+#ifdef CLOCK_TAI
+       CLOCK_TAI,
+#endif
+#ifdef CLOCK_VIRTUAL
+       CLOCK_VIRTUAL,
+#endif
+#ifdef CLOCK_UPTIME
+       CLOCK_UPTIME,
+#endif
+#ifdef CLOCK_PROCESS_CPUTIME_ID
+       CLOCK_PROCESS_CPUTIME_ID,
+#endif
+#ifdef CLOCK_THREAD_CPUTIME_ID
+       CLOCK_THREAD_CPUTIME_ID,
+#endif
+};
+
+static int
+getentropy_fallback(void *buf, size_t len)
+{
+       uint8_t results[SHA512_DIGEST_LENGTH];
+       int save_errno = errno, e, pgs = sysconf(_SC_PAGESIZE), faster = 0, repeat;
+       static int cnt;
+       struct timespec ts;
+       struct timeval tv;
+       struct pst_vminfo pvi;
+       struct pst_vm_status pvs;
+       struct pst_dynamic pdy;
+       struct rusage ru;
+       sigset_t sigset;
+       struct stat st;
+       SHA512_CTX ctx;
+       static pid_t lastpid;
+       pid_t pid;
+       size_t i, ii, m;
+       char *p;
+
+       pid = getpid();
+       if (lastpid == pid) {
+               faster = 1;
+               repeat = 2;
+       } else {
+               faster = 0;
+               lastpid = pid;
+               repeat = REPEAT;
+       }
+       for (i = 0; i < len; ) {
+               int j;
+               SHA512_Init(&ctx);
+               for (j = 0; j < repeat; j++) {
+                       HX((e = gettimeofday(&tv, NULL)) == -1, tv);
+                       if (e != -1) {
+                               cnt += (int)tv.tv_sec;
+                               cnt += (int)tv.tv_usec;
+                       }
+
+                       HX(pstat_getvminfo(&pvi, sizeof(pvi), 1, 0) != 1, pvi);
+                       HX(pstat_getprocvm(&pvs, sizeof(pvs), 0, 0) != 1, pvs);
+
+                       for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]); ii++)
+                               HX(clock_gettime(cl[ii], &ts) == -1, ts);
+
+                       HX((pid = getpid()) == -1, pid);
+                       HX((pid = getsid(pid)) == -1, pid);
+                       HX((pid = getppid()) == -1, pid);
+                       HX((pid = getpgid(0)) == -1, pid);
+                       HX((e = getpriority(0, 0)) == -1, e);
+
+                       if(pstat_getdynamic(&pdy, sizeof(pdy), 1, 0) != 1) {
+                               HD(errno);
+                       } else {
+                               HD(pdy.psd_avg_1_min);
+                               HD(pdy.psd_avg_5_min);
+                               HD(pdy.psd_avg_15_min);
+                       }
+
+                       if (!faster) {
+                               ts.tv_sec = 0;
+                               ts.tv_nsec = 1;
+                               (void) nanosleep(&ts, NULL);
+                       }
+
+                       HX(sigpending(&sigset) == -1, sigset);
+                       HX(sigprocmask(SIG_BLOCK, NULL, &sigset) == -1,
+                           sigset);
+
+                       HF(getentropy); /* an addr in this library */
+                       HF(printf);             /* an addr in libc */
+                       p = (char *)&p;
+                       HD(p);          /* an addr on stack */
+                       p = (char *)&errno;
+                       HD(p);          /* the addr of errno */
+
+                       if (i == 0) {
+                               struct sockaddr_storage ss;
+                               struct statvfs stvfs;
+                               struct termios tios;
+                               socklen_t ssl;
+                               off_t off;
+
+                               /*
+                                * Prime-sized mappings encourage fragmentation;
+                                * thus exposing some address entropy.
+                                */
+                               struct mm {
+                                       size_t  npg;
+                                       void    *p;
+                               } mm[] =         {
+                                       { 17, MAP_FAILED }, { 3, MAP_FAILED },
+                                       { 11, MAP_FAILED }, { 2, MAP_FAILED },
+                                       { 5, MAP_FAILED }, { 3, MAP_FAILED },
+                                       { 7, MAP_FAILED }, { 1, MAP_FAILED },
+                                       { 57, MAP_FAILED }, { 3, MAP_FAILED },
+                                       { 131, MAP_FAILED }, { 1, MAP_FAILED },
+                               };
+
+                               for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) {
+                                       HX(mm[m].p = mmap(NULL,
+                                           mm[m].npg * pgs,
+                                           PROT_READ|PROT_WRITE,
+                                           MAP_PRIVATE|MAP_ANON, -1,
+                                           (off_t)0), mm[m].p);
+                                       if (mm[m].p != MAP_FAILED) {
+                                               size_t mo;
+
+                                               /* Touch some memory... */
+                                               p = mm[m].p;
+                                               mo = cnt %
+                                                   (mm[m].npg * pgs - 1);
+                                               p[mo] = 1;
+                                               cnt += (int)((long)(mm[m].p)
+                                                   / pgs);
+                                       }
+
+                                       /* Check cnts and times... */
+                                       for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]);
+                                           ii++) {
+                                               HX((e = clock_gettime(cl[ii],
+                                                   &ts)) == -1, ts);
+                                               if (e != -1)
+                                                       cnt += (int)ts.tv_nsec;
+                                       }
+
+                                       HX((e = getrusage(RUSAGE_SELF,
+                                           &ru)) == -1, ru);
+                                       if (e != -1) {
+                                               cnt += (int)ru.ru_utime.tv_sec;
+                                               cnt += (int)ru.ru_utime.tv_usec;
+                                       }
+                               }
+
+                               for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) {
+                                       if (mm[m].p != MAP_FAILED)
+                                               munmap(mm[m].p, mm[m].npg * pgs);
+                                       mm[m].p = MAP_FAILED;
+                               }
+
+                               HX(stat(".", &st) == -1, st);
+                               HX(statvfs(".", &stvfs) == -1, stvfs);
+
+                               HX(stat("/", &st) == -1, st);
+                               HX(statvfs("/", &stvfs) == -1, stvfs);
+
+                               HX((e = fstat(0, &st)) == -1, st);
+                               if (e == -1) {
+                                       if (S_ISREG(st.st_mode) ||
+                                           S_ISFIFO(st.st_mode) ||
+                                           S_ISSOCK(st.st_mode)) {
+                                               HX(fstatvfs(0, &stvfs) == -1,
+                                                   stvfs);
+                                               HX((off = lseek(0, (off_t)0,
+                                                   SEEK_CUR)) < 0, off);
+                                       }
+                                       if (S_ISCHR(st.st_mode)) {
+                                               HX(tcgetattr(0, &tios) == -1,
+                                                   tios);
+                                       } else if (S_ISSOCK(st.st_mode)) {
+                                               memset(&ss, 0, sizeof ss);
+                                               ssl = sizeof(ss);
+                                               HX(getpeername(0,
+                                                   (void *)&ss, &ssl) == -1,
+                                                   ss);
+                                       }
+                               }
+
+                               HX((e = getrusage(RUSAGE_CHILDREN,
+                                   &ru)) == -1, ru);
+                               if (e != -1) {
+                                       cnt += (int)ru.ru_utime.tv_sec;
+                                       cnt += (int)ru.ru_utime.tv_usec;
+                               }
+                       } else {
+                               /* Subsequent hashes absorb previous result */
+                               HD(results);
+                       }
+
+                       HX((e = gettimeofday(&tv, NULL)) == -1, tv);
+                       if (e != -1) {
+                               cnt += (int)tv.tv_sec;
+                               cnt += (int)tv.tv_usec;
+                       }
+
+                       HD(cnt);
+               }
+               SHA512_Final(results, &ctx);
+               memcpy((char *)buf + i, results, min(sizeof(results), len - i));
+               i += min(sizeof(results), len - i);
+       }
+       explicit_bzero(&ctx, sizeof ctx);
+       explicit_bzero(results, sizeof results);
+       if (gotdata(buf, len) == 0) {
+               errno = save_errno;
+               return (0);             /* satisfied */
+       }
+       errno = EIO;
+       return (-1);
+}
diff --git a/src/getentropy_linux.c b/src/getentropy_linux.c
new file mode 100644 (file)
index 0000000..0a5d6b8
--- /dev/null
@@ -0,0 +1,547 @@
+/*     $OpenBSD: getentropy_linux.c,v 1.40 2015/08/25 17:26:43 deraadt Exp $   */
+
+/*
+ * Copyright (c) 2014 Theo de Raadt <deraadt@openbsd.org>
+ * Copyright (c) 2014 Bob Beck <beck@obtuse.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Emulation of getentropy(2) as documented at:
+ * http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man2/getentropy.2
+ */
+
+#define        _POSIX_C_SOURCE 199309L
+#define        _GNU_SOURCE     1
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/resource.h>
+#include <sys/syscall.h>
+#ifdef SYS__sysctl
+#include <linux/sysctl.h>
+#endif
+#include <sys/statvfs.h>
+#include <sys/socket.h>
+#include <sys/mount.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <link.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <openssl/sha.h>
+
+#include <linux/types.h>
+#include <linux/random.h>
+#ifdef HAVE_GETAUXVAL
+#include <sys/auxv.h>
+#endif
+#include <sys/vfs.h>
+
+#define REPEAT 5
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+
+#define HX(a, b) \
+       do { \
+               if ((a)) \
+                       HD(errno); \
+               else \
+                       HD(b); \
+       } while (0)
+
+#define HR(x, l) (SHA512_Update(&ctx, (char *)(x), (l)))
+#define HD(x)   (SHA512_Update(&ctx, (char *)&(x), sizeof (x)))
+#define HF(x)    (SHA512_Update(&ctx, (char *)&(x), sizeof (void*)))
+
+int    getentropy(void *buf, size_t len);
+
+static int gotdata(char *buf, size_t len);
+#ifdef SYS_getrandom
+static int getentropy_getrandom(void *buf, size_t len);
+#endif
+static int getentropy_urandom(void *buf, size_t len);
+#ifdef SYS__sysctl
+static int getentropy_sysctl(void *buf, size_t len);
+#endif
+static int getentropy_fallback(void *buf, size_t len);
+static int getentropy_phdr(struct dl_phdr_info *info, size_t size, void *data);
+
+int
+getentropy(void *buf, size_t len)
+{
+       int ret = -1;
+
+       if (len > 256) {
+               errno = EIO;
+               return (-1);
+       }
+
+#ifdef SYS_getrandom
+       /*
+        * Try descriptor-less getrandom()
+        */
+       ret = getentropy_getrandom(buf, len);
+       if (ret != -1)
+               return (ret);
+       if (errno != ENOSYS)
+               return (-1);
+#endif
+
+       /*
+        * Try to get entropy with /dev/urandom
+        *
+        * This can fail if the process is inside a chroot or if file
+        * descriptors are exhausted.
+        */
+       ret = getentropy_urandom(buf, len);
+       if (ret != -1)
+               return (ret);
+
+#ifdef SYS__sysctl
+       /*
+        * Try to use sysctl CTL_KERN, KERN_RANDOM, RANDOM_UUID.
+        * sysctl is a failsafe API, so it guarantees a result.  This
+        * should work inside a chroot, or when file descriptors are
+        * exhuasted.
+        *
+        * However this can fail if the Linux kernel removes support
+        * for sysctl.  Starting in 2007, there have been efforts to
+        * deprecate the sysctl API/ABI, and push callers towards use
+        * of the chroot-unavailable fd-using /proc mechanism --
+        * essentially the same problems as /dev/urandom.
+        *
+        * Numerous setbacks have been encountered in their deprecation
+        * schedule, so as of June 2014 the kernel ABI still exists on
+        * most Linux architectures. The sysctl() stub in libc is missing
+        * on some systems.  There are also reports that some kernels
+        * spew messages to the console.
+        */
+       ret = getentropy_sysctl(buf, len);
+       if (ret != -1)
+               return (ret);
+#endif /* SYS__sysctl */
+
+       /*
+        * Entropy collection via /dev/urandom and sysctl have failed.
+        *
+        * No other API exists for collecting entropy.  See the large
+        * comment block above.
+        *
+        * We have very few options:
+        *     - Even syslog_r is unsafe to call at this low level, so
+        *       there is no way to alert the user or program.
+        *     - Cannot call abort() because some systems have unsafe
+        *       corefiles.
+        *     - Could raise(SIGKILL) resulting in silent program termination.
+        *     - Return EIO, to hint that arc4random's stir function
+        *       should raise(SIGKILL)
+        *     - Do the best under the circumstances....
+        *
+        * This code path exists to bring light to the issue that Linux
+        * does not provide a failsafe API for entropy collection.
+        *
+        * We hope this demonstrates that Linux should either retain their
+        * sysctl ABI, or consider providing a new failsafe API which
+        * works in a chroot or when file descriptors are exhausted.
+        */
+#undef FAIL_INSTEAD_OF_TRYING_FALLBACK
+#ifdef FAIL_INSTEAD_OF_TRYING_FALLBACK
+       raise(SIGKILL);
+#endif
+       ret = getentropy_fallback(buf, len);
+       if (ret != -1)
+               return (ret);
+
+       errno = EIO;
+       return (ret);
+}
+
+/*
+ * Basic sanity checking; wish we could do better.
+ */
+static int
+gotdata(char *buf, size_t len)
+{
+       char    any_set = 0;
+       size_t  i;
+
+       for (i = 0; i < len; ++i)
+               any_set |= buf[i];
+       if (any_set == 0)
+               return (-1);
+       return (0);
+}
+
+#ifdef SYS_getrandom
+static int
+getentropy_getrandom(void *buf, size_t len)
+{
+       int pre_errno = errno;
+       int ret;
+       if (len > 256)
+               return (-1);
+       do {
+               ret = syscall(SYS_getrandom, buf, len, 0);
+       } while (ret == -1 && errno == EINTR);
+
+       if (ret != (int)len)
+               return (-1);
+       errno = pre_errno;
+       return (0);
+}
+#endif
+
+static int
+getentropy_urandom(void *buf, size_t len)
+{
+       struct stat st;
+       size_t i;
+       int fd, cnt, flags;
+       int save_errno = errno;
+
+start:
+
+       flags = O_RDONLY;
+#ifdef O_NOFOLLOW
+       flags |= O_NOFOLLOW;
+#endif
+#ifdef O_CLOEXEC
+       flags |= O_CLOEXEC;
+#endif
+       fd = open("/dev/urandom", flags, 0);
+       if (fd == -1) {
+               if (errno == EINTR)
+                       goto start;
+               goto nodevrandom;
+       }
+#ifndef O_CLOEXEC
+       fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+#endif
+
+       /* Lightly verify that the device node looks sane */
+       if (fstat(fd, &st) == -1 || !S_ISCHR(st.st_mode)) {
+               close(fd);
+               goto nodevrandom;
+       }
+       if (ioctl(fd, RNDGETENTCNT, &cnt) == -1) {
+               close(fd);
+               goto nodevrandom;
+       }
+       for (i = 0; i < len; ) {
+               size_t wanted = len - i;
+               ssize_t ret = read(fd, (char *)buf + i, wanted);
+
+               if (ret == -1) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       close(fd);
+                       goto nodevrandom;
+               }
+               i += ret;
+       }
+       close(fd);
+       if (gotdata(buf, len) == 0) {
+               errno = save_errno;
+               return (0);             /* satisfied */
+       }
+nodevrandom:
+       errno = EIO;
+       return (-1);
+}
+
+#ifdef SYS__sysctl
+static int
+getentropy_sysctl(void *buf, size_t len)
+{
+       static int mib[] = { CTL_KERN, KERN_RANDOM, RANDOM_UUID };
+       size_t i;
+       int save_errno = errno;
+
+       for (i = 0; i < len; ) {
+               size_t chunk = min(len - i, 16);
+
+               /* SYS__sysctl because some systems already removed sysctl() */
+               struct __sysctl_args args = {
+                       .name = mib,
+                       .nlen = 3,
+                       .oldval = (char *)buf + i,
+                       .oldlenp = &chunk,
+               };
+               if (syscall(SYS__sysctl, &args) != 0)
+                       goto sysctlfailed;
+               i += chunk;
+       }
+       if (gotdata(buf, len) == 0) {
+               errno = save_errno;
+               return (0);                     /* satisfied */
+       }
+sysctlfailed:
+       errno = EIO;
+       return (-1);
+}
+#endif /* SYS__sysctl */
+
+static const int cl[] = {
+       CLOCK_REALTIME,
+#ifdef CLOCK_MONOTONIC
+       CLOCK_MONOTONIC,
+#endif
+#ifdef CLOCK_MONOTONIC_RAW
+       CLOCK_MONOTONIC_RAW,
+#endif
+#ifdef CLOCK_TAI
+       CLOCK_TAI,
+#endif
+#ifdef CLOCK_VIRTUAL
+       CLOCK_VIRTUAL,
+#endif
+#ifdef CLOCK_UPTIME
+       CLOCK_UPTIME,
+#endif
+#ifdef CLOCK_PROCESS_CPUTIME_ID
+       CLOCK_PROCESS_CPUTIME_ID,
+#endif
+#ifdef CLOCK_THREAD_CPUTIME_ID
+       CLOCK_THREAD_CPUTIME_ID,
+#endif
+};
+
+static int
+getentropy_phdr(struct dl_phdr_info *info, size_t size, void *data)
+{
+       SHA512_CTX *ctx = data;
+
+       SHA512_Update(ctx, &info->dlpi_addr, sizeof (info->dlpi_addr));
+       return (0);
+}
+
+static int
+getentropy_fallback(void *buf, size_t len)
+{
+       uint8_t results[SHA512_DIGEST_LENGTH];
+       int save_errno = errno, e, pgs = getpagesize(), faster = 0, repeat;
+       static int cnt;
+       struct timespec ts;
+       struct timeval tv;
+       struct rusage ru;
+       sigset_t sigset;
+       struct stat st;
+       SHA512_CTX ctx;
+       static pid_t lastpid;
+       pid_t pid;
+       size_t i, ii, m;
+       char *p;
+
+       pid = getpid();
+       if (lastpid == pid) {
+               faster = 1;
+               repeat = 2;
+       } else {
+               faster = 0;
+               lastpid = pid;
+               repeat = REPEAT;
+       }
+       for (i = 0; i < len; ) {
+               int j;
+               SHA512_Init(&ctx);
+               for (j = 0; j < repeat; j++) {
+                       HX((e = gettimeofday(&tv, NULL)) == -1, tv);
+                       if (e != -1) {
+                               cnt += (int)tv.tv_sec;
+                               cnt += (int)tv.tv_usec;
+                       }
+
+                       dl_iterate_phdr(getentropy_phdr, &ctx);
+
+                       for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]); ii++)
+                               HX(clock_gettime(cl[ii], &ts) == -1, ts);
+
+                       HX((pid = getpid()) == -1, pid);
+                       HX((pid = getsid(pid)) == -1, pid);
+                       HX((pid = getppid()) == -1, pid);
+                       HX((pid = getpgid(0)) == -1, pid);
+                       HX((e = getpriority(0, 0)) == -1, e);
+
+                       if (!faster) {
+                               ts.tv_sec = 0;
+                               ts.tv_nsec = 1;
+                               (void) nanosleep(&ts, NULL);
+                       }
+
+                       HX(sigpending(&sigset) == -1, sigset);
+                       HX(sigprocmask(SIG_BLOCK, NULL, &sigset) == -1,
+                           sigset);
+
+                       HF(getentropy); /* an addr in this library */
+                       HF(printf);             /* an addr in libc */
+                       p = (char *)&p;
+                       HD(p);          /* an addr on stack */
+                       p = (char *)&errno;
+                       HD(p);          /* the addr of errno */
+
+                       if (i == 0) {
+                               struct sockaddr_storage ss;
+                               struct statvfs stvfs;
+                               struct termios tios;
+                               struct statfs stfs;
+                               socklen_t ssl;
+                               off_t off;
+
+                               /*
+                                * Prime-sized mappings encourage fragmentation;
+                                * thus exposing some address entropy.
+                                */
+                               struct mm {
+                                       size_t  npg;
+                                       void    *p;
+                               } mm[] =         {
+                                       { 17, MAP_FAILED }, { 3, MAP_FAILED },
+                                       { 11, MAP_FAILED }, { 2, MAP_FAILED },
+                                       { 5, MAP_FAILED }, { 3, MAP_FAILED },
+                                       { 7, MAP_FAILED }, { 1, MAP_FAILED },
+                                       { 57, MAP_FAILED }, { 3, MAP_FAILED },
+                                       { 131, MAP_FAILED }, { 1, MAP_FAILED },
+                               };
+
+                               for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) {
+                                       HX(mm[m].p = mmap(NULL,
+                                           mm[m].npg * pgs,
+                                           PROT_READ|PROT_WRITE,
+                                           MAP_PRIVATE|MAP_ANON, -1,
+                                           (off_t)0), mm[m].p);
+                                       if (mm[m].p != MAP_FAILED) {
+                                               size_t mo;
+
+                                               /* Touch some memory... */
+                                               p = mm[m].p;
+                                               mo = cnt %
+                                                   (mm[m].npg * pgs - 1);
+                                               p[mo] = 1;
+                                               cnt += (int)((long)(mm[m].p)
+                                                   / pgs);
+                                       }
+
+                                       /* Check cnts and times... */
+                                       for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]);
+                                           ii++) {
+                                               HX((e = clock_gettime(cl[ii],
+                                                   &ts)) == -1, ts);
+                                               if (e != -1)
+                                                       cnt += (int)ts.tv_nsec;
+                                       }
+
+                                       HX((e = getrusage(RUSAGE_SELF,
+                                           &ru)) == -1, ru);
+                                       if (e != -1) {
+                                               cnt += (int)ru.ru_utime.tv_sec;
+                                               cnt += (int)ru.ru_utime.tv_usec;
+                                       }
+                               }
+
+                               for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) {
+                                       if (mm[m].p != MAP_FAILED)
+                                               munmap(mm[m].p, mm[m].npg * pgs);
+                                       mm[m].p = MAP_FAILED;
+                               }
+
+                               HX(stat(".", &st) == -1, st);
+                               HX(statvfs(".", &stvfs) == -1, stvfs);
+                               HX(statfs(".", &stfs) == -1, stfs);
+
+                               HX(stat("/", &st) == -1, st);
+                               HX(statvfs("/", &stvfs) == -1, stvfs);
+                               HX(statfs("/", &stfs) == -1, stfs);
+
+                               HX((e = fstat(0, &st)) == -1, st);
+                               if (e == -1) {
+                                       if (S_ISREG(st.st_mode) ||
+                                           S_ISFIFO(st.st_mode) ||
+                                           S_ISSOCK(st.st_mode)) {
+                                               HX(fstatvfs(0, &stvfs) == -1,
+                                                   stvfs);
+                                               HX(fstatfs(0, &stfs) == -1,
+                                                   stfs);
+                                               HX((off = lseek(0, (off_t)0,
+                                                   SEEK_CUR)) < 0, off);
+                                       }
+                                       if (S_ISCHR(st.st_mode)) {
+                                               HX(tcgetattr(0, &tios) == -1,
+                                                   tios);
+                                       } else if (S_ISSOCK(st.st_mode)) {
+                                               memset(&ss, 0, sizeof ss);
+                                               ssl = sizeof(ss);
+                                               HX(getpeername(0,
+                                                   (void *)&ss, &ssl) == -1,
+                                                   ss);
+                                       }
+                               }
+
+                               HX((e = getrusage(RUSAGE_CHILDREN,
+                                   &ru)) == -1, ru);
+                               if (e != -1) {
+                                       cnt += (int)ru.ru_utime.tv_sec;
+                                       cnt += (int)ru.ru_utime.tv_usec;
+                               }
+                       } else {
+                               /* Subsequent hashes absorb previous result */
+                               HD(results);
+                       }
+
+                       HX((e = gettimeofday(&tv, NULL)) == -1, tv);
+                       if (e != -1) {
+                               cnt += (int)tv.tv_sec;
+                               cnt += (int)tv.tv_usec;
+                       }
+
+                       HD(cnt);
+               }
+#ifdef HAVE_GETAUXVAL
+#ifdef AT_RANDOM
+               /* Not as random as you think but we take what we are given */
+               p = (char *) getauxval(AT_RANDOM);
+               if (p)
+                       HR(p, 16);
+#endif
+#ifdef AT_SYSINFO_EHDR
+               p = (char *) getauxval(AT_SYSINFO_EHDR);
+               if (p)
+                       HR(p, pgs);
+#endif
+#ifdef AT_BASE
+               p = (char *) getauxval(AT_BASE);
+               if (p)
+                       HD(p);
+#endif
+#endif
+
+               SHA512_Final(results, &ctx);
+               memcpy((char *)buf + i, results, min(sizeof(results), len - i));
+               i += min(sizeof(results), len - i);
+       }
+       explicit_bzero(&ctx, sizeof ctx);
+       explicit_bzero(results, sizeof results);
+       if (gotdata(buf, len) == 0) {
+               errno = save_errno;
+               return (0);             /* satisfied */
+       }
+       errno = EIO;
+       return (-1);
+}
diff --git a/src/getentropy_osx.c b/src/getentropy_osx.c
new file mode 100644 (file)
index 0000000..db67c4b
--- /dev/null
@@ -0,0 +1,429 @@
+/*     $OpenBSD: getentropy_osx.c,v 1.8 2014/07/21 20:19:47 guenther Exp $     */
+
+/*
+ * Copyright (c) 2014 Theo de Raadt <deraadt@openbsd.org>
+ * Copyright (c) 2014 Bob Beck <beck@obtuse.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Emulation of getentropy(2) as documented at:
+ * http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man2/getentropy.2
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/resource.h>
+#include <sys/syscall.h>
+#include <sys/sysctl.h>
+#include <sys/statvfs.h>
+#include <sys/socket.h>
+#include <sys/mount.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <mach/mach_time.h>
+#include <mach/mach_host.h>
+#include <mach/host_info.h>
+#include <sys/socketvar.h>
+#include <sys/vmmeter.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <netinet/ip_var.h>
+#include <netinet/tcp_var.h>
+#include <netinet/udp_var.h>
+#include <CommonCrypto/CommonDigest.h>
+#define SHA512_Update(a, b, c) (CC_SHA512_Update((a), (b), (c)))
+#define SHA512_Init(xxx) (CC_SHA512_Init((xxx)))
+#define SHA512_Final(xxx, yyy) (CC_SHA512_Final((xxx), (yyy)))
+#define SHA512_CTX CC_SHA512_CTX
+#define SHA512_DIGEST_LENGTH CC_SHA512_DIGEST_LENGTH
+
+#define REPEAT 5
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+
+#define HX(a, b) \
+       do { \
+               if ((a)) \
+                       HD(errno); \
+               else \
+                       HD(b); \
+       } while (0)
+
+#define HR(x, l) (SHA512_Update(&ctx, (char *)(x), (l)))
+#define HD(x)   (SHA512_Update(&ctx, (char *)&(x), sizeof (x)))
+#define HF(x)    (SHA512_Update(&ctx, (char *)&(x), sizeof (void*)))
+
+int    getentropy(void *buf, size_t len);
+
+static int gotdata(char *buf, size_t len);
+static int getentropy_urandom(void *buf, size_t len);
+static int getentropy_fallback(void *buf, size_t len);
+
+int
+getentropy(void *buf, size_t len)
+{
+       int ret = -1;
+
+       if (len > 256) {
+               errno = EIO;
+               return (-1);
+       }
+
+       /*
+        * Try to get entropy with /dev/urandom
+        *
+        * This can fail if the process is inside a chroot or if file
+        * descriptors are exhausted.
+        */
+       ret = getentropy_urandom(buf, len);
+       if (ret != -1)
+               return (ret);
+
+       /*
+        * Entropy collection via /dev/urandom and sysctl have failed.
+        *
+        * No other API exists for collecting entropy, and we have
+        * no failsafe way to get it on OSX that is not sensitive
+        * to resource exhaustion.
+        *
+        * We have very few options:
+        *     - Even syslog_r is unsafe to call at this low level, so
+        *       there is no way to alert the user or program.
+        *     - Cannot call abort() because some systems have unsafe
+        *       corefiles.
+        *     - Could raise(SIGKILL) resulting in silent program termination.
+        *     - Return EIO, to hint that arc4random's stir function
+        *       should raise(SIGKILL)
+        *     - Do the best under the circumstances....
+        *
+        * This code path exists to bring light to the issue that OSX
+        * does not provide a failsafe API for entropy collection.
+        *
+        * We hope this demonstrates that OSX should consider
+        * providing a new failsafe API which works in a chroot or
+        * when file descriptors are exhausted.
+        */
+#undef FAIL_INSTEAD_OF_TRYING_FALLBACK
+#ifdef FAIL_INSTEAD_OF_TRYING_FALLBACK
+       raise(SIGKILL);
+#endif
+       ret = getentropy_fallback(buf, len);
+       if (ret != -1)
+               return (ret);
+
+       errno = EIO;
+       return (ret);
+}
+
+/*
+ * Basic sanity checking; wish we could do better.
+ */
+static int
+gotdata(char *buf, size_t len)
+{
+       char    any_set = 0;
+       size_t  i;
+
+       for (i = 0; i < len; ++i)
+               any_set |= buf[i];
+       if (any_set == 0)
+               return (-1);
+       return (0);
+}
+
+static int
+getentropy_urandom(void *buf, size_t len)
+{
+       struct stat st;
+       size_t i;
+       int fd, flags;
+       int save_errno = errno;
+
+start:
+
+       flags = O_RDONLY;
+#ifdef O_NOFOLLOW
+       flags |= O_NOFOLLOW;
+#endif
+#ifdef O_CLOEXEC
+       flags |= O_CLOEXEC;
+#endif
+       fd = open("/dev/urandom", flags, 0);
+       if (fd == -1) {
+               if (errno == EINTR)
+                       goto start;
+               goto nodevrandom;
+       }
+#ifndef O_CLOEXEC
+       fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+#endif
+
+       /* Lightly verify that the device node looks sane */
+       if (fstat(fd, &st) == -1 || !S_ISCHR(st.st_mode)) {
+               close(fd);
+               goto nodevrandom;
+       }
+       for (i = 0; i < len; ) {
+               size_t wanted = len - i;
+               ssize_t ret = read(fd, (char *)buf + i, wanted);
+
+               if (ret == -1) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       close(fd);
+                       goto nodevrandom;
+               }
+               i += ret;
+       }
+       close(fd);
+       if (gotdata(buf, len) == 0) {
+               errno = save_errno;
+               return (0);             /* satisfied */
+       }
+nodevrandom:
+       errno = EIO;
+       return (-1);
+}
+
+static int tcpmib[] = { CTL_NET, AF_INET, IPPROTO_TCP, TCPCTL_STATS };
+static int udpmib[] = { CTL_NET, AF_INET, IPPROTO_UDP, UDPCTL_STATS };
+static int ipmib[] = { CTL_NET, AF_INET, IPPROTO_IP, IPCTL_STATS };
+static int kmib[] = { CTL_KERN, KERN_USRSTACK };
+static int hwmib[] = { CTL_HW, HW_USERMEM };
+
+static int
+getentropy_fallback(void *buf, size_t len)
+{
+       uint8_t results[SHA512_DIGEST_LENGTH];
+       int save_errno = errno, e, pgs = getpagesize(), faster = 0, repeat;
+       static int cnt;
+       struct timespec ts;
+       struct timeval tv;
+       struct rusage ru;
+       sigset_t sigset;
+       struct stat st;
+       SHA512_CTX ctx;
+       static pid_t lastpid;
+       pid_t pid;
+       size_t i, ii, m;
+       char *p;
+       struct tcpstat tcpstat;
+       struct udpstat udpstat;
+       struct ipstat ipstat;
+       uint64_t mach_time;
+       unsigned int idata;
+       void *addr;
+
+       pid = getpid();
+       if (lastpid == pid) {
+               faster = 1;
+               repeat = 2;
+       } else {
+               faster = 0;
+               lastpid = pid;
+               repeat = REPEAT;
+       }
+       for (i = 0; i < len; ) {
+               int j;
+               SHA512_Init(&ctx);
+               for (j = 0; j < repeat; j++) {
+                       HX((e = gettimeofday(&tv, NULL)) == -1, tv);
+                       if (e != -1) {
+                               cnt += (int)tv.tv_sec;
+                               cnt += (int)tv.tv_usec;
+                       }
+
+                       mach_time = mach_absolute_time();
+                       HD(mach_time);
+
+                       ii = sizeof(addr);
+                       HX(sysctl(kmib, sizeof(kmib) / sizeof(kmib[0]),
+                           &addr, &ii, NULL, 0) == -1, addr);
+
+                       ii = sizeof(idata);
+                       HX(sysctl(hwmib, sizeof(hwmib) / sizeof(hwmib[0]),
+                           &idata, &ii, NULL, 0) == -1, idata);
+
+                       ii = sizeof(tcpstat);
+                       HX(sysctl(tcpmib, sizeof(tcpmib) / sizeof(tcpmib[0]),
+                           &tcpstat, &ii, NULL, 0) == -1, tcpstat);
+
+                       ii = sizeof(udpstat);
+                       HX(sysctl(udpmib, sizeof(udpmib) / sizeof(udpmib[0]),
+                           &udpstat, &ii, NULL, 0) == -1, udpstat);
+
+                       ii = sizeof(ipstat);
+                       HX(sysctl(ipmib, sizeof(ipmib) / sizeof(ipmib[0]),
+                           &ipstat, &ii, NULL, 0) == -1, ipstat);
+
+                       HX((pid = getpid()) == -1, pid);
+                       HX((pid = getsid(pid)) == -1, pid);
+                       HX((pid = getppid()) == -1, pid);
+                       HX((pid = getpgid(0)) == -1, pid);
+                       HX((e = getpriority(0, 0)) == -1, e);
+
+                       if (!faster) {
+                               ts.tv_sec = 0;
+                               ts.tv_nsec = 1;
+                               (void) nanosleep(&ts, NULL);
+                       }
+
+                       HX(sigpending(&sigset) == -1, sigset);
+                       HX(sigprocmask(SIG_BLOCK, NULL, &sigset) == -1,
+                           sigset);
+
+                       HF(getentropy); /* an addr in this library */
+                       HF(printf);             /* an addr in libc */
+                       p = (char *)&p;
+                       HD(p);          /* an addr on stack */
+                       p = (char *)&errno;
+                       HD(p);          /* the addr of errno */
+
+                       if (i == 0) {
+                               struct sockaddr_storage ss;
+                               struct statvfs stvfs;
+                               struct termios tios;
+                               struct statfs stfs;
+                               socklen_t ssl;
+                               off_t off;
+
+                               /*
+                                * Prime-sized mappings encourage fragmentation;
+                                * thus exposing some address entropy.
+                                */
+                               struct mm {
+                                       size_t  npg;
+                                       void    *p;
+                               } mm[] =         {
+                                       { 17, MAP_FAILED }, { 3, MAP_FAILED },
+                                       { 11, MAP_FAILED }, { 2, MAP_FAILED },
+                                       { 5, MAP_FAILED }, { 3, MAP_FAILED },
+                                       { 7, MAP_FAILED }, { 1, MAP_FAILED },
+                                       { 57, MAP_FAILED }, { 3, MAP_FAILED },
+                                       { 131, MAP_FAILED }, { 1, MAP_FAILED },
+                               };
+
+                               for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) {
+                                       HX(mm[m].p = mmap(NULL,
+                                           mm[m].npg * pgs,
+                                           PROT_READ|PROT_WRITE,
+                                           MAP_PRIVATE|MAP_ANON, -1,
+                                           (off_t)0), mm[m].p);
+                                       if (mm[m].p != MAP_FAILED) {
+                                               size_t mo;
+
+                                               /* Touch some memory... */
+                                               p = mm[m].p;
+                                               mo = cnt %
+                                                   (mm[m].npg * pgs - 1);
+                                               p[mo] = 1;
+                                               cnt += (int)((long)(mm[m].p)
+                                                   / pgs);
+                                       }
+
+                                       /* Check cnts and times... */
+                                       mach_time = mach_absolute_time();
+                                       HD(mach_time);
+                                       cnt += (int)mach_time;
+
+                                       HX((e = getrusage(RUSAGE_SELF,
+                                           &ru)) == -1, ru);
+                                       if (e != -1) {
+                                               cnt += (int)ru.ru_utime.tv_sec;
+                                               cnt += (int)ru.ru_utime.tv_usec;
+                                       }
+                               }
+
+                               for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) {
+                                       if (mm[m].p != MAP_FAILED)
+                                               munmap(mm[m].p, mm[m].npg * pgs);
+                                       mm[m].p = MAP_FAILED;
+                               }
+
+                               HX(stat(".", &st) == -1, st);
+                               HX(statvfs(".", &stvfs) == -1, stvfs);
+                               HX(statfs(".", &stfs) == -1, stfs);
+
+                               HX(stat("/", &st) == -1, st);
+                               HX(statvfs("/", &stvfs) == -1, stvfs);
+                               HX(statfs("/", &stfs) == -1, stfs);
+
+                               HX((e = fstat(0, &st)) == -1, st);
+                               if (e == -1) {
+                                       if (S_ISREG(st.st_mode) ||
+                                           S_ISFIFO(st.st_mode) ||
+                                           S_ISSOCK(st.st_mode)) {
+                                               HX(fstatvfs(0, &stvfs) == -1,
+                                                   stvfs);
+                                               HX(fstatfs(0, &stfs) == -1,
+                                                   stfs);
+                                               HX((off = lseek(0, (off_t)0,
+                                                   SEEK_CUR)) < 0, off);
+                                       }
+                                       if (S_ISCHR(st.st_mode)) {
+                                               HX(tcgetattr(0, &tios) == -1,
+                                                   tios);
+                                       } else if (S_ISSOCK(st.st_mode)) {
+                                               memset(&ss, 0, sizeof ss);
+                                               ssl = sizeof(ss);
+                                               HX(getpeername(0,
+                                                   (void *)&ss, &ssl) == -1,
+                                                   ss);
+                                       }
+                               }
+
+                               HX((e = getrusage(RUSAGE_CHILDREN,
+                                   &ru)) == -1, ru);
+                               if (e != -1) {
+                                       cnt += (int)ru.ru_utime.tv_sec;
+                                       cnt += (int)ru.ru_utime.tv_usec;
+                               }
+                       } else {
+                               /* Subsequent hashes absorb previous result */
+                               HD(results);
+                       }
+
+                       HX((e = gettimeofday(&tv, NULL)) == -1, tv);
+                       if (e != -1) {
+                               cnt += (int)tv.tv_sec;
+                               cnt += (int)tv.tv_usec;
+                       }
+
+                       HD(cnt);
+               }
+
+               SHA512_Final(results, &ctx);
+               memcpy((char *)buf + i, results, min(sizeof(results), len - i));
+               i += min(sizeof(results), len - i);
+       }
+       explicit_bzero(&ctx, sizeof ctx);
+       explicit_bzero(results, sizeof results);
+       if (gotdata(buf, len) == 0) {
+               errno = save_errno;
+               return (0);             /* satisfied */
+       }
+       errno = EIO;
+       return (-1);
+}
diff --git a/src/getentropy_solaris.c b/src/getentropy_solaris.c
new file mode 100644 (file)
index 0000000..ca787f8
--- /dev/null
@@ -0,0 +1,445 @@
+/*     $OpenBSD: getentropy_solaris.c,v 1.10 2015/08/25 17:26:43 deraadt Exp $ */
+
+/*
+ * Copyright (c) 2014 Theo de Raadt <deraadt@openbsd.org>
+ * Copyright (c) 2014 Bob Beck <beck@obtuse.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Emulation of getentropy(2) as documented at:
+ * http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man2/getentropy.2
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/resource.h>
+#include <sys/syscall.h>
+#include <sys/statvfs.h>
+#include <sys/socket.h>
+#include <sys/mount.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <link.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/sha2.h>
+#define SHA512_Init SHA512Init
+#define SHA512_Update SHA512Update
+#define SHA512_Final SHA512Final
+
+#include <sys/vfs.h>
+#include <sys/statfs.h>
+#include <sys/loadavg.h>
+
+#define REPEAT 5
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+
+#define HX(a, b) \
+       do { \
+               if ((a)) \
+                       HD(errno); \
+               else \
+                       HD(b); \
+       } while (0)
+
+#define HR(x, l) (SHA512_Update(&ctx, (char *)(x), (l)))
+#define HD(x)   (SHA512_Update(&ctx, (char *)&(x), sizeof (x)))
+#define HF(x)    (SHA512_Update(&ctx, (char *)&(x), sizeof (void*)))
+
+int    getentropy(void *buf, size_t len);
+
+static int gotdata(char *buf, size_t len);
+static int getentropy_urandom(void *buf, size_t len, const char *path,
+    int devfscheck);
+static int getentropy_fallback(void *buf, size_t len);
+static int getentropy_phdr(struct dl_phdr_info *info, size_t size, void *data);
+
+int
+getentropy(void *buf, size_t len)
+{
+       int ret = -1;
+
+       if (len > 256) {
+               errno = EIO;
+               return (-1);
+       }
+
+       /*
+        * Try to get entropy with /dev/urandom
+        *
+        * Solaris provides /dev/urandom as a symbolic link to
+        * /devices/pseudo/random@0:urandom which is provided by
+        * a devfs filesystem.  Best practice is to use O_NOFOLLOW,
+        * so we must try the unpublished name directly.
+        *
+        * This can fail if the process is inside a chroot which lacks
+        * the devfs mount, or if file descriptors are exhausted.
+        */
+       ret = getentropy_urandom(buf, len,
+           "/devices/pseudo/random@0:urandom", 1);
+       if (ret != -1)
+               return (ret);
+
+       /*
+        * Unfortunately, chroot spaces on Solaris are sometimes setup
+        * with direct device node of the well-known /dev/urandom name
+        * (perhaps to avoid dragging all of devfs into the space).
+        *
+        * This can fail if the process is inside a chroot or if file
+        * descriptors are exhausted.
+        */
+       ret = getentropy_urandom(buf, len, "/dev/urandom", 0);
+       if (ret != -1)
+               return (ret);
+
+       /*
+        * Entropy collection via /dev/urandom has failed.
+        *
+        * No other API exists for collecting entropy, and we have
+        * no failsafe way to get it on Solaris that is not sensitive
+        * to resource exhaustion.
+        *
+        * We have very few options:
+        *     - Even syslog_r is unsafe to call at this low level, so
+        *       there is no way to alert the user or program.
+        *     - Cannot call abort() because some systems have unsafe
+        *       corefiles.
+        *     - Could raise(SIGKILL) resulting in silent program termination.
+        *     - Return EIO, to hint that arc4random's stir function
+        *       should raise(SIGKILL)
+        *     - Do the best under the circumstances....
+        *
+        * This code path exists to bring light to the issue that Solaris
+        * does not provide a failsafe API for entropy collection.
+        *
+        * We hope this demonstrates that Solaris should consider
+        * providing a new failsafe API which works in a chroot or
+        * when file descriptors are exhausted.
+        */
+#undef FAIL_INSTEAD_OF_TRYING_FALLBACK
+#ifdef FAIL_INSTEAD_OF_TRYING_FALLBACK
+       raise(SIGKILL);
+#endif
+       ret = getentropy_fallback(buf, len);
+       if (ret != -1)
+               return (ret);
+
+       errno = EIO;
+       return (ret);
+}
+
+/*
+ * Basic sanity checking; wish we could do better.
+ */
+static int
+gotdata(char *buf, size_t len)
+{
+       char    any_set = 0;
+       size_t  i;
+
+       for (i = 0; i < len; ++i)
+               any_set |= buf[i];
+       if (any_set == 0)
+               return (-1);
+       return (0);
+}
+
+static int
+getentropy_urandom(void *buf, size_t len, const char *path, int devfscheck)
+{
+       struct stat st;
+       size_t i;
+       int fd, flags;
+       int save_errno = errno;
+
+start:
+
+       flags = O_RDONLY;
+#ifdef O_NOFOLLOW
+       flags |= O_NOFOLLOW;
+#endif
+#ifdef O_CLOEXEC
+       flags |= O_CLOEXEC;
+#endif
+       fd = open(path, flags, 0);
+       if (fd == -1) {
+               if (errno == EINTR)
+                       goto start;
+               goto nodevrandom;
+       }
+#ifndef O_CLOEXEC
+       fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+#endif
+
+       /* Lightly verify that the device node looks sane */
+       if (fstat(fd, &st) == -1 || !S_ISCHR(st.st_mode) ||
+           (devfscheck && (strcmp(st.st_fstype, "devfs") != 0))) {
+               close(fd);
+               goto nodevrandom;
+       }
+       for (i = 0; i < len; ) {
+               size_t wanted = len - i;
+               ssize_t ret = read(fd, (char *)buf + i, wanted);
+
+               if (ret == -1) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       close(fd);
+                       goto nodevrandom;
+               }
+               i += ret;
+       }
+       close(fd);
+       if (gotdata(buf, len) == 0) {
+               errno = save_errno;
+               return (0);             /* satisfied */
+       }
+nodevrandom:
+       errno = EIO;
+       return (-1);
+}
+
+static const int cl[] = {
+       CLOCK_REALTIME,
+#ifdef CLOCK_MONOTONIC
+       CLOCK_MONOTONIC,
+#endif
+#ifdef CLOCK_MONOTONIC_RAW
+       CLOCK_MONOTONIC_RAW,
+#endif
+#ifdef CLOCK_TAI
+       CLOCK_TAI,
+#endif
+#ifdef CLOCK_VIRTUAL
+       CLOCK_VIRTUAL,
+#endif
+#ifdef CLOCK_UPTIME
+       CLOCK_UPTIME,
+#endif
+#ifdef CLOCK_PROCESS_CPUTIME_ID
+       CLOCK_PROCESS_CPUTIME_ID,
+#endif
+#ifdef CLOCK_THREAD_CPUTIME_ID
+       CLOCK_THREAD_CPUTIME_ID,
+#endif
+};
+
+static int
+getentropy_phdr(struct dl_phdr_info *info, size_t size, void *data)
+{
+       SHA512_CTX *ctx = data;
+
+       SHA512_Update(ctx, &info->dlpi_addr, sizeof (info->dlpi_addr));
+       return (0);
+}
+
+static int
+getentropy_fallback(void *buf, size_t len)
+{
+       uint8_t results[SHA512_DIGEST_LENGTH];
+       int save_errno = errno, e, pgs = getpagesize(), faster = 0, repeat;
+       static int cnt;
+       struct timespec ts;
+       struct timeval tv;
+       double loadavg[3];
+       struct rusage ru;
+       sigset_t sigset;
+       struct stat st;
+       SHA512_CTX ctx;
+       static pid_t lastpid;
+       pid_t pid;
+       size_t i, ii, m;
+       char *p;
+
+       pid = getpid();
+       if (lastpid == pid) {
+               faster = 1;
+               repeat = 2;
+       } else {
+               faster = 0;
+               lastpid = pid;
+               repeat = REPEAT;
+       }
+       for (i = 0; i < len; ) {
+               int j;
+               SHA512_Init(&ctx);
+               for (j = 0; j < repeat; j++) {
+                       HX((e = gettimeofday(&tv, NULL)) == -1, tv);
+                       if (e != -1) {
+                               cnt += (int)tv.tv_sec;
+                               cnt += (int)tv.tv_usec;
+                       }
+
+                       dl_iterate_phdr(getentropy_phdr, &ctx);
+
+                       for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]); ii++)
+                               HX(clock_gettime(cl[ii], &ts) == -1, ts);
+
+                       HX((pid = getpid()) == -1, pid);
+                       HX((pid = getsid(pid)) == -1, pid);
+                       HX((pid = getppid()) == -1, pid);
+                       HX((pid = getpgid(0)) == -1, pid);
+                       HX((e = getpriority(0, 0)) == -1, e);
+                       HX((getloadavg(loadavg, 3) == -1), loadavg);
+
+                       if (!faster) {
+                               ts.tv_sec = 0;
+                               ts.tv_nsec = 1;
+                               (void) nanosleep(&ts, NULL);
+                       }
+
+                       HX(sigpending(&sigset) == -1, sigset);
+                       HX(sigprocmask(SIG_BLOCK, NULL, &sigset) == -1,
+                           sigset);
+
+                       HF(getentropy); /* an addr in this library */
+                       HF(printf);             /* an addr in libc */
+                       p = (char *)&p;
+                       HD(p);          /* an addr on stack */
+                       p = (char *)&errno;
+                       HD(p);          /* the addr of errno */
+
+                       if (i == 0) {
+                               struct sockaddr_storage ss;
+                               struct statvfs stvfs;
+                               struct termios tios;
+                               socklen_t ssl;
+                               off_t off;
+
+                               /*
+                                * Prime-sized mappings encourage fragmentation;
+                                * thus exposing some address entropy.
+                                */
+                               struct mm {
+                                       size_t  npg;
+                                       void    *p;
+                               } mm[] =         {
+                                       { 17, MAP_FAILED }, { 3, MAP_FAILED },
+                                       { 11, MAP_FAILED }, { 2, MAP_FAILED },
+                                       { 5, MAP_FAILED }, { 3, MAP_FAILED },
+                                       { 7, MAP_FAILED }, { 1, MAP_FAILED },
+                                       { 57, MAP_FAILED }, { 3, MAP_FAILED },
+                                       { 131, MAP_FAILED }, { 1, MAP_FAILED },
+                               };
+
+                               for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) {
+                                       HX(mm[m].p = mmap(NULL,
+                                           mm[m].npg * pgs,
+                                           PROT_READ|PROT_WRITE,
+                                           MAP_PRIVATE|MAP_ANON, -1,
+                                           (off_t)0), mm[m].p);
+                                       if (mm[m].p != MAP_FAILED) {
+                                               size_t mo;
+
+                                               /* Touch some memory... */
+                                               p = mm[m].p;
+                                               mo = cnt %
+                                                   (mm[m].npg * pgs - 1);
+                                               p[mo] = 1;
+                                               cnt += (int)((long)(mm[m].p)
+                                                   / pgs);
+                                       }
+
+                                       /* Check cnts and times... */
+                                       for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]);
+                                           ii++) {
+                                               HX((e = clock_gettime(cl[ii],
+                                                   &ts)) == -1, ts);
+                                               if (e != -1)
+                                                       cnt += (int)ts.tv_nsec;
+                                       }
+
+                                       HX((e = getrusage(RUSAGE_SELF,
+                                           &ru)) == -1, ru);
+                                       if (e != -1) {
+                                               cnt += (int)ru.ru_utime.tv_sec;
+                                               cnt += (int)ru.ru_utime.tv_usec;
+                                       }
+                               }
+
+                               for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) {
+                                       if (mm[m].p != MAP_FAILED)
+                                               munmap(mm[m].p, mm[m].npg * pgs);
+                                       mm[m].p = MAP_FAILED;
+                               }
+
+                               HX(stat(".", &st) == -1, st);
+                               HX(statvfs(".", &stvfs) == -1, stvfs);
+
+                               HX(stat("/", &st) == -1, st);
+                               HX(statvfs("/", &stvfs) == -1, stvfs);
+
+                               HX((e = fstat(0, &st)) == -1, st);
+                               if (e == -1) {
+                                       if (S_ISREG(st.st_mode) ||
+                                           S_ISFIFO(st.st_mode) ||
+                                           S_ISSOCK(st.st_mode)) {
+                                               HX(fstatvfs(0, &stvfs) == -1,
+                                                   stvfs);
+                                               HX((off = lseek(0, (off_t)0,
+                                                   SEEK_CUR)) < 0, off);
+                                       }
+                                       if (S_ISCHR(st.st_mode)) {
+                                               HX(tcgetattr(0, &tios) == -1,
+                                                   tios);
+                                       } else if (S_ISSOCK(st.st_mode)) {
+                                               memset(&ss, 0, sizeof ss);
+                                               ssl = sizeof(ss);
+                                               HX(getpeername(0,
+                                                   (void *)&ss, &ssl) == -1,
+                                                   ss);
+                                       }
+                               }
+
+                               HX((e = getrusage(RUSAGE_CHILDREN,
+                                   &ru)) == -1, ru);
+                               if (e != -1) {
+                                       cnt += (int)ru.ru_utime.tv_sec;
+                                       cnt += (int)ru.ru_utime.tv_usec;
+                               }
+                       } else {
+                               /* Subsequent hashes absorb previous result */
+                               HD(results);
+                       }
+
+                       HX((e = gettimeofday(&tv, NULL)) == -1, tv);
+                       if (e != -1) {
+                               cnt += (int)tv.tv_sec;
+                               cnt += (int)tv.tv_usec;
+                       }
+
+                       HD(cnt);
+               }
+               SHA512_Final(results, &ctx);
+               memcpy((char *)buf + i, results, min(sizeof(results), len - i));
+               i += min(sizeof(results), len - i);
+       }
+       explicit_bzero(&ctx, sizeof ctx);
+       explicit_bzero(results, sizeof results);
+       if (gotdata(buf, len) == 0) {
+               errno = save_errno;
+               return (0);             /* satisfied */
+       }
+       errno = EIO;
+       return (-1);
+}