summaryrefslogtreecommitdiff
path: root/lib/util-get-random-bytes.c
blob: 79816db3a8bacec0267d6eeaa0ba5fa5650fc373 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/* Retrieval of cryptographically random bytes from the operating system.
 *
 * Written by Zack Weinberg <zackw at panix.com> in 2017.
 *
 * No copyright is claimed, and the software is hereby placed in the public
 * domain.  In case this attempt to disclaim copyright and place the software
 * in the public domain is deemed null and void, then the software is
 * Copyright (c) 2017 Zack Weinberg and it is hereby released to the
 * general public under the following terms:
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted.
 *
 * There's ABSOLUTELY NO WARRANTY, express or implied.
 */

#include "crypt-port.h"

#include <errno.h>
#include <stdlib.h>

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_SYS_RANDOM_H
#include <sys/random.h>
#endif
#ifdef HAVE_SYS_SYSCALL_H
#include <sys/syscall.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

/* If we have O_CLOEXEC, we use it, but if we don't, we don't worry
   about it.  */
#ifndef O_CLOEXEC
#define O_CLOEXEC 0
#endif

/* There is no universally portable way to access a system CSPRNG.
   If the C library provides any of the following functions, we try them,
   in order of preference: arc4random_buf, getentropy, getrandom.
   If none of those are available or they don't work, we attempt to
   make direct system calls for getentropy and getrandom.  If *that*
   doesn't work, we try opening and reading /dev/urandom.

   This function returns true if the exact number of requested bytes
   was successfully read, false otherwise; if it returns false, errno
   has been set.  It may block.  It cannot be used to read more than
   256 bytes at a time (this is a limitation inherited from
   getentropy() and enforced regardless of the actual back-end in use).

   If we fall all the way back to /dev/urandom, we open and close it on
   each call.  */

bool
get_random_bytes(void *buf, size_t buflen)
{
  if (buflen == 0)
    return true;

  /* Some, but not all, of the primitives below are limited to
     producing no more than 256 bytes of random data.  Impose this
     constraint on our callers regardless of which primitive is
     actually used.  */
  if (buflen > 256)
    {
      errno = EIO;
      return false;
    }

  /* To eliminate the possibility of one of the primitives below failing
     with EFAULT, force a crash now if the buffer is unwritable.  */
  explicit_bzero (buf, buflen);

#ifdef HAVE_ARC4RANDOM_BUF
  /* arc4random_buf, if it exists, can never fail.  */
  arc4random_buf (buf, buflen);
  return true;

#else /* no arc4random_buf */

#ifdef HAVE_GETENTROPY
  /* getentropy may exist but lack kernel support.  */
  static bool getentropy_doesnt_work;
  if (!getentropy_doesnt_work)
    {
      if (!getentropy (buf, buflen))
        return true;
      getentropy_doesnt_work = true;
    }
#endif

#ifdef HAVE_GETRANDOM
  /* Likewise getrandom.  */
  static bool getrandom_doesnt_work;
  if (!getrandom_doesnt_work)
    {
      if ((size_t)getrandom (buf, buflen, 0) == buflen)
        return true;
      getrandom_doesnt_work = true;
    }
#endif

  /* If we can make arbitrary syscalls, try getentropy and getrandom
     again that way.  */
#ifdef HAVE_SYSCALL
#ifdef SYS_getentropy
  static bool sys_getentropy_doesnt_work;
  if (!sys_getentropy_doesnt_work)
    {
      if (!syscall (SYS_getentropy, buf, buflen))
        return true;
      sys_getentropy_doesnt_work = true;
    }
#endif

#ifdef SYS_getrandom
  static bool sys_getrandom_doesnt_work;
  if (!sys_getrandom_doesnt_work)
    {
      if ((size_t)syscall (SYS_getrandom, buf, buflen, 0) == buflen)
        return true;
      sys_getrandom_doesnt_work = true;
    }
#endif
#endif

#if defined HAVE_SYS_STAT_H && defined HAVE_FCNTL_H && defined HAVE_UNISTD_H
  /* Try reading from /dev/urandom.  */
  static bool dev_urandom_doesnt_work;
  if (!dev_urandom_doesnt_work)
    {
      int fd = open ("/dev/urandom", O_RDONLY|O_CLOEXEC);
      if (fd == -1)
        dev_urandom_doesnt_work = true;
      else
        {
          ssize_t nread = read (fd, buf, buflen);
          if (nread < 0 || (size_t)nread < buflen)
            dev_urandom_doesnt_work = true;

          close(fd);
          return !dev_urandom_doesnt_work;
        }
    }
#endif
#endif /* no arc4random_buf */

  /* if we get here, we're just completely hosed */
  errno = ENOSYS;
  return false;
}