Skip to content

Commit 224e6eb

Browse files
committed
util: Specific GetOSRandom for Linux/FreeBSD/OpenBSD
These are available in sandboxes without access to files or devices. Also [they are safer and more straightforward](https://en.wikipedia.org/wiki/Entropy-supplying_system_calls) to use than `/dev/urandom` as reading from a file has quite a few edge cases: - Linux: `getrandom(buf, buflen, 0)`. [getrandom(2)](http://man7.org/linux/man-pages/man2/getrandom.2.html) was introduced in version 3.17 of the Linux kernel. - OpenBSD: `getentropy(buf, buflen)`. The [getentropy(2)](http://man.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man2/getentropy.2) function appeared in OpenBSD 5.6. - FreeBSD and NetBSD: `sysctl(KERN_ARND)`. Not sure when this was added but it has existed for quite a while. Alternatives: - Linux has sysctl `CTL_KERN` / `KERN_RANDOM` / `RANDOM_UUID` which gives 16 bytes of randomness. This may be available on older kernels, however [sysctl is deprecated on Linux](https://lwn.net/Articles/605392/) and even removed in some distros so we shouldn't use it. Add tests for `GetOSRand()`: - Test that no error happens (otherwise `RandFailure()` which aborts) - Test that all 32 bytes are overwritten (initialize with zeros, try multiple times) Discussion: - When to use these? Currently they are always used when available. Another option would be to use them only when `/dev/urandom` is not available. But this would mean these code paths receive less testing, and I'm not sure there is any reason to prefer `/dev/urandom`. Closes: #9676
1 parent 5f0556d commit 224e6eb

File tree

5 files changed

+132
-6
lines changed

5 files changed

+132
-6
lines changed

configure.ac

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,33 @@ AC_LINK_IFELSE([AC_LANG_SOURCE([
558558
]
559559
)
560560

561+
# Check for different ways of gathering OS randomness
562+
AC_MSG_CHECKING(for Linux getrandom syscall)
563+
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <unistd.h>
564+
#include <sys/syscall.h>
565+
#include <linux/random.h>]],
566+
[[ syscall(SYS_getrandom, nullptr, 32, 0); ]])],
567+
[ AC_MSG_RESULT(yes); AC_DEFINE(HAVE_SYS_GETRANDOM, 1,[Define this symbol if the Linux getrandom system call is available]) ],
568+
[ AC_MSG_RESULT(no)]
569+
)
570+
571+
AC_MSG_CHECKING(for getentropy)
572+
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <unistd.h>]],
573+
[[ getentropy(nullptr, 32) ]])],
574+
[ AC_MSG_RESULT(yes); AC_DEFINE(HAVE_GETENTROPY, 1,[Define this symbol if the BSD getentropy system call is available]) ],
575+
[ AC_MSG_RESULT(no)]
576+
)
577+
578+
AC_MSG_CHECKING(for sysctl KERN_ARND)
579+
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <sys/types.h>
580+
#include <sys/sysctl.h>]],
581+
[[ static const int name[2] = {CTL_KERN, KERN_ARND};
582+
sysctl(name, 2, nullptr, nullptr, nullptr, 0); ]])],
583+
[ AC_MSG_RESULT(yes); AC_DEFINE(HAVE_SYSCTL_ARND, 1,[Define this symbol if the BSD sysctl(KERN_ARND) is available]) ],
584+
[ AC_MSG_RESULT(no)]
585+
)
586+
587+
# Check for reduced exports
561588
if test x$use_reduce_exports = xyes; then
562589
AX_CHECK_COMPILE_FLAG([-fvisibility=hidden],[RE_CXXFLAGS="-fvisibility=hidden"],
563590
[AC_MSG_ERROR([Cannot set default symbol visibility. Use --disable-reduce-exports.])])

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ BITCOIN_TESTS =\
110110
test/policyestimator_tests.cpp \
111111
test/pow_tests.cpp \
112112
test/prevector_tests.cpp \
113+
test/random_tests.cpp \
113114
test/raii_event_tests.cpp \
114115
test/reverselock_tests.cpp \
115116
test/rpc_tests.cpp \

src/random.cpp

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@
2121
#include <sys/time.h>
2222
#endif
2323

24+
#ifdef HAVE_SYS_GETRANDOM
25+
#include <sys/syscall.h>
26+
#include <linux/random.h>
27+
#endif
28+
#ifdef HAVE_GETENTROPY
29+
#include <unistd.h>
30+
#endif
31+
#ifdef HAVE_SYSCTL_ARND
32+
#include <sys/sysctl.h>
33+
#endif
34+
2435
#include <openssl/err.h>
2536
#include <openssl/rand.h>
2637

@@ -92,32 +103,65 @@ static void RandAddSeedPerfmon()
92103
}
93104

94105
/** Get 32 bytes of system entropy. */
95-
static void GetOSRand(unsigned char *ent32)
106+
void GetOSRand(unsigned char *ent32)
96107
{
97-
#ifdef WIN32
108+
#if defined(WIN32)
98109
HCRYPTPROV hProvider;
99110
int ret = CryptAcquireContextW(&hProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
100111
if (!ret) {
101112
RandFailure();
102113
}
103-
ret = CryptGenRandom(hProvider, 32, ent32);
114+
ret = CryptGenRandom(hProvider, NUM_OS_RANDOM_BYTES, ent32);
104115
if (!ret) {
105116
RandFailure();
106117
}
107118
CryptReleaseContext(hProvider, 0);
119+
#elif defined(HAVE_SYS_GETRANDOM)
120+
/* Linux. From the getrandom(2) man page:
121+
* "If the urandom source has been initialized, reads of up to 256 bytes
122+
* will always return as many bytes as requested and will not be
123+
* interrupted by signals."
124+
*/
125+
if (syscall(SYS_getrandom, ent32, NUM_OS_RANDOM_BYTES, 0) != NUM_OS_RANDOM_BYTES) {
126+
RandFailure();
127+
}
128+
#elif defined(HAVE_GETENTROPY)
129+
/* On OpenBSD this can return up to 256 bytes of entropy, will return an
130+
* error if more are requested.
131+
* The call cannot return less than the requested number of bytes.
132+
*/
133+
if (getentropy(ent32, NUM_OS_RANDOM_BYTES) != 0) {
134+
RandFailure();
135+
}
136+
#elif defined(HAVE_SYSCTL_ARND)
137+
/* FreeBSD and similar. It is possible for the call to return less
138+
* bytes than requested, so need to read in a loop.
139+
*/
140+
static const int name[2] = {CTL_KERN, KERN_ARND};
141+
int have = 0;
142+
do {
143+
size_t len = NUM_OS_RANDOM_BYTES - have;
144+
if (sysctl(name, ARRAYLEN(name), ent32 + have, &len, NULL, 0) != 0) {
145+
RandFailure();
146+
}
147+
have += len;
148+
} while (have < NUM_OS_RANDOM_BYTES);
108149
#else
150+
/* Fall back to /dev/urandom if there is no specific method implemented to
151+
* get system entropy for this OS.
152+
*/
109153
int f = open("/dev/urandom", O_RDONLY);
110154
if (f == -1) {
111155
RandFailure();
112156
}
113157
int have = 0;
114158
do {
115-
ssize_t n = read(f, ent32 + have, 32 - have);
116-
if (n <= 0 || n + have > 32) {
159+
ssize_t n = read(f, ent32 + have, NUM_OS_RANDOM_BYTES - have);
160+
if (n <= 0 || n + have > NUM_OS_RANDOM_BYTES) {
117161
RandFailure();
118162
}
119163
have += n;
120-
} while (have < 32);
164+
} while (have < NUM_OS_RANDOM_BYTES);
121165
close(f);
122166
#endif
123167
}

src/random.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,12 @@ class FastRandomContext {
4646
uint32_t Rw;
4747
};
4848

49+
/* Number of random bytes returned by GetOSRand */
50+
static const ssize_t NUM_OS_RANDOM_BYTES = 32;
51+
52+
/** Get 32 bytes of system entropy. Do not use this in application code: use
53+
* GetStrongRandBytes instead.
54+
*/
55+
void GetOSRand(unsigned char *ent32);
56+
4957
#endif // BITCOIN_RANDOM_H

src/test/random_tests.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (c) 2017 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include "random.h"
6+
7+
#include "test/test_bitcoin.h"
8+
9+
#include <boost/test/unit_test.hpp>
10+
11+
BOOST_FIXTURE_TEST_SUITE(random_tests, BasicTestingSetup)
12+
13+
static const ssize_t MAX_TRIES = 1024;
14+
15+
BOOST_AUTO_TEST_CASE(osrandom_tests)
16+
{
17+
/* This does not measure the quality of randomness, but it does test that
18+
* OSRandom() overwrites all 32 bytes of the output given a maximum
19+
* number of tries.
20+
*/
21+
uint8_t data[NUM_OS_RANDOM_BYTES];
22+
bool overwritten[NUM_OS_RANDOM_BYTES] = {}; /* Tracks which bytes have been overwritten at least once */
23+
int num_overwritten;
24+
int tries = 0;
25+
/* Loop until all bytes have been overwritten at least once */
26+
do {
27+
memset(data, 0, NUM_OS_RANDOM_BYTES);
28+
GetOSRand(data);
29+
for (int x=0; x < NUM_OS_RANDOM_BYTES; ++x) {
30+
overwritten[x] |= (data[x] != 0);
31+
}
32+
33+
num_overwritten = 0;
34+
for (int x=0; x < NUM_OS_RANDOM_BYTES; ++x) {
35+
if (overwritten[x]) {
36+
num_overwritten += 1;
37+
}
38+
}
39+
40+
tries += 1;
41+
} while (num_overwritten < NUM_OS_RANDOM_BYTES && tries < MAX_TRIES);
42+
BOOST_CHECK(num_overwritten == NUM_OS_RANDOM_BYTES); /* If this failed, bailed out after too many tries */
43+
}
44+
45+
BOOST_AUTO_TEST_SUITE_END()
46+

0 commit comments

Comments
 (0)