Require getrandom in all FIPS builds.

It is now 2022. See if we can assume getrandom in this configuration.

Update-Note: The /dev/urandom fallback is no longer available in FIPS
builds. This fallback relied on RNGGETENTCNT and was quite flaky.

Change-Id: Icf6d29f6d5952fb6c5656c9039a4cfaf1de2d724
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/54127
Auto-Submit: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
chromium-5359
David Benjamin 2 years ago committed by Boringssl LUCI CQ
parent aca1930403
commit 19009c51bf
  1. 38
      crypto/fipsmodule/rand/urandom.c
  2. 91
      crypto/fipsmodule/rand/urandom_test.cc

@ -62,14 +62,11 @@
#include <sys/random.h>
#endif
#if defined(OPENSSL_FREEBSD)
#define URANDOM_BLOCKS_FOR_ENTROPY
#if __FreeBSD__ >= 12
#if defined(OPENSSL_FREEBSD) && __FreeBSD__ >= 12
// getrandom is supported in FreeBSD 12 and up.
#define FREEBSD_GETRANDOM
#include <sys/random.h>
#endif
#endif
#include <openssl/thread.h>
#include <openssl/mem.h>
@ -190,8 +187,14 @@ static void init_once(void) {
return;
#endif
// Android FIPS builds must support getrandom.
#if defined(BORINGSSL_FIPS) && defined(OPENSSL_ANDROID)
// FIPS builds must support getrandom.
//
// Historically, only Android FIPS builds required getrandom, while Linux FIPS
// builds had a /dev/urandom fallback which used RNDGETENTCNT as a poor
// approximation for getrandom's blocking behavior. This is now removed, but
// avoid making assumptions on this removal until March 2023, in case it needs
// to be restored. This comment can be deleted after March 2023.
#if defined(BORINGSSL_FIPS)
perror("getrandom not found");
abort();
#endif
@ -255,29 +258,6 @@ static void wait_for_entropy(void) {
#endif // USE_NR_getrandom
return;
}
#if defined(BORINGSSL_FIPS) && !defined(URANDOM_BLOCKS_FOR_ENTROPY)
// In FIPS mode on platforms where urandom doesn't block at startup, we ensure
// that the kernel has sufficient entropy before continuing. This is
// automatically handled by getrandom, which requires that the entropy pool
// has been initialised, but for urandom we have to poll.
for (;;) {
int entropy_bits;
if (ioctl(fd, RNDGETENTCNT, &entropy_bits)) {
fprintf(stderr,
"RNDGETENTCNT on /dev/urandom failed. We cannot continue in this "
"case when in FIPS mode.\n");
abort();
}
static const int kBitsNeeded = 256;
if (entropy_bits >= kBitsNeeded) {
break;
}
usleep(250000);
}
#endif // BORINGSSL_FIPS && !URANDOM_BLOCKS_FOR_ENTROPY
}
// fill_with_entropy writes |len| bytes of entropy into |out|. It returns one

@ -45,7 +45,6 @@ struct Event {
kGetRandom,
kOpen,
kUrandomRead,
kUrandomIoctl,
kAbort,
};
@ -77,11 +76,6 @@ struct Event {
return e;
}
static Event UrandomIoctl() {
Event e(Syscall::kUrandomIoctl);
return e;
}
static Event Abort() {
Event e(Syscall::kAbort);
return e;
@ -103,9 +97,6 @@ struct Event {
snprintf(buf, sizeof(buf), "read(urandom_fd, _, %zu)", length);
break;
case Syscall::kUrandomIoctl:
return "ioctl(urandom_fd, RNDGETENTCNT, _)";
case Syscall::kAbort:
return "abort()";
}
@ -139,14 +130,11 @@ static const unsigned NO_GETRANDOM = 1;
static const unsigned NO_URANDOM = 2;
// getrandom always returns |EAGAIN| if given |GRNG_NONBLOCK|.
static const unsigned GETRANDOM_NOT_READY = 4;
// The ioctl on urandom returns only 255 bits of entropy the first time that
// it's called.
static const unsigned URANDOM_NOT_READY = 8;
// getrandom gives |EINVAL| unless |NO_GETRANDOM| is set.
static const unsigned GETRANDOM_ERROR = 16;
static const unsigned GETRANDOM_ERROR = 8;
// Reading from /dev/urandom gives |EINVAL|.
static const unsigned URANDOM_ERROR = 32;
static const unsigned NEXT_FLAG = 64;
static const unsigned URANDOM_ERROR = 16;
static const unsigned NEXT_FLAG = 32;
// GetTrace runs |thunk| in a forked process and observes the resulting system
// calls using ptrace. It simulates a variety of failures based on the contents
@ -204,8 +192,6 @@ static void GetTrace(std::vector<Event> *out_trace, unsigned flags,
const auto syscall_number = regs.orig_rax;
bool is_opening_urandom = false;
bool is_urandom_ioctl = false;
uintptr_t ioctl_output_addr = 0;
// inject_error is zero to indicate that the system call should run
// normally. Otherwise it's, e.g. -EINVAL, to indicate that the system call
// should not run and that error should be injected on return.
@ -251,16 +237,6 @@ static void GetTrace(std::vector<Event> *out_trace, unsigned flags,
}
break;
}
case __NR_ioctl: {
const int ioctl_fd = regs.rdi;
if (urandom_fd >= 0 && ioctl_fd == urandom_fd &&
regs.rsi == RNDGETENTCNT) {
out_trace->push_back(Event::UrandomIoctl());
is_urandom_ioctl = true;
ioctl_output_addr = regs.rdx;
}
}
}
if (inject_error) {
@ -294,33 +270,6 @@ static void GetTrace(std::vector<Event> *out_trace, unsigned flags,
} else if (is_opening_urandom) {
ASSERT_EQ(0, ptrace(PTRACE_GETREGS, child_pid, nullptr, &regs));
urandom_fd = regs.rax;
} else if (is_urandom_ioctl) {
// The result is the number of bits of entropy that the kernel currently
// believes that it has. urandom.c waits until 256 bits are ready.
int result = 256;
// If we are simulating urandom not being ready then we have the ioctl
// indicate one too few bits of entropy the first time it's queried.
if (flags & URANDOM_NOT_READY) {
result--;
flags &= ~URANDOM_NOT_READY;
}
// ptrace always works with ill-defined "words", which appear to be 64-bit
// on x86-64. Since the ioctl result is a 32-bit int, do a
// read-modify-write to inject the answer.
const uintptr_t aligned_addr = ioctl_output_addr & ~7;
const uintptr_t offset = ioctl_output_addr - aligned_addr;
union {
uint64_t word;
uint8_t bytes[8];
} u;
u.word = ptrace(PTRACE_PEEKDATA, child_pid,
reinterpret_cast<void *>(aligned_addr), nullptr);
memcpy(&u.bytes[offset], &result, sizeof(result));
ASSERT_EQ(0, ptrace(PTRACE_POKEDATA, child_pid,
reinterpret_cast<void *>(aligned_addr),
reinterpret_cast<void *>(u.word)));
}
}
}
@ -347,7 +296,6 @@ static std::vector<Event> TestFunctionPRNGModel(unsigned flags) {
#endif
std::vector<Event> ret;
bool urandom_probed = false;
bool getrandom_ready = false;
// Probe for getrandom support
@ -356,32 +304,19 @@ static std::vector<Event> TestFunctionPRNGModel(unsigned flags) {
std::function<bool(bool, size_t)> sysrand;
if (flags & NO_GETRANDOM) {
ret.push_back(Event::Open("/dev/urandom"));
if (flags & NO_URANDOM) {
if (is_fips) {
// FIPS builds require getrandom.
ret.push_back(Event::Abort());
return ret;
}
wait_for_entropy = [&ret, &urandom_probed, flags] {
if (!is_fips || urandom_probed) {
return;
}
// Probe urandom for entropy.
ret.push_back(Event::UrandomIoctl());
if (flags & URANDOM_NOT_READY) {
// If the first attempt doesn't report enough entropy, probe
// repeatedly until it does, which will happen with the second attempt.
ret.push_back(Event::UrandomIoctl());
ret.push_back(Event::Open("/dev/urandom"));
if (flags & NO_URANDOM) {
ret.push_back(Event::Abort());
return ret;
}
urandom_probed = true;
};
sysrand = [&ret, &wait_for_entropy, flags](bool block, size_t len) {
if (block) {
wait_for_entropy();
}
sysrand = [&ret, flags](bool block, size_t len) {
ret.push_back(Event::UrandomRead(len));
if (flags & URANDOM_ERROR) {
ret.push_back(Event::Abort());
@ -457,11 +392,6 @@ static void CheckInvariants(const std::vector<Event> &events) {
}
break;
case Event::Syscall::kUrandomIoctl:
ADD_FAILURE() << "Urandom polling found with RDRAND: "
<< ToString(events);
break;
default:
break;
}
@ -483,7 +413,6 @@ TEST(URandomTest, Test) {
TRACE_FLAG(NO_GETRANDOM);
TRACE_FLAG(NO_URANDOM);
TRACE_FLAG(GETRANDOM_NOT_READY);
TRACE_FLAG(URANDOM_NOT_READY);
TRACE_FLAG(GETRANDOM_ERROR);
TRACE_FLAG(URANDOM_ERROR);

Loading…
Cancel
Save