File
Blob: src/workerd/util/entropy.c++
| 1 | // Copyright (c) 2017-2022 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | |
| 5 | #include "entropy.h" |
| 6 | |
| 7 | #include <ncrypto.h> |
| 8 | #include <openssl/crypto.h> |
| 9 | #include <openssl/rand.h> |
| 10 | |
| 11 | #include <kj/debug.h> |
| 12 | #include <kj/exception.h> |
| 13 | |
| 14 | #ifdef __unix__ |
| 15 | #include <unistd.h> |
| 16 | #endif |
| 17 | |
| 18 | namespace workerd { |
| 19 | |
| 20 | void getEntropy(kj::ArrayPtr<kj::byte> output) { |
| 21 | static constexpr size_t BUFFER_SIZE = 4096; |
| 22 | struct BufferState { |
| 23 | kj::FixedArray<kj::byte, BUFFER_SIZE> store; |
| 24 | kj::ArrayPtr<kj::byte> data; // Starts empty to trigger initial fill |
| 25 | #if defined(KJ_DEBUG) && defined(__unix__) |
| 26 | // Track the PID separately to detect cross-fork usage. |
| 27 | // This should be preserved across fork so we can detect PID changes. |
| 28 | pid_t lastSeenPid = 0; |
| 29 | #endif |
| 30 | }; |
| 31 | |
| 32 | thread_local BufferState state{}; |
| 33 | |
| 34 | #if defined(KJ_DEBUG) && defined(__unix__) |
| 35 | // Verify that getpid() hasn't changed. This code should be called strictly post-fork. |
| 36 | // If we see crashes here in tests, it means there's some pre-fork call to getEntropy() |
| 37 | // that needs to be removed. |
| 38 | pid_t currentPid = getpid(); |
| 39 | if (state.lastSeenPid == 0) { |
| 40 | state.lastSeenPid = currentPid; |
| 41 | } else { |
| 42 | KJ_ASSERT(state.lastSeenPid == currentPid, |
| 43 | "PID changed from previous call to getEntropy() - this indicates a pre-fork call " |
| 44 | "to getEntropy() that should be removed", |
| 45 | state.lastSeenPid, currentPid); |
| 46 | } |
| 47 | #endif |
| 48 | |
| 49 | while (output != nullptr) { |
| 50 | if (state.data == nullptr) { |
| 51 | ncrypto::ClearErrorOnReturn clearErrorOnReturn; |
| 52 | if (RAND_bytes(state.store.begin(), BUFFER_SIZE) != 1) { |
| 53 | KJ_FAIL_REQUIRE("RAND_bytes failed to generate random data"); |
| 54 | } |
| 55 | |
| 56 | state.data = state.store.asPtr(); |
| 57 | } |
| 58 | |
| 59 | size_t toCopy = kj::min(state.data.size(), output.size()); |
| 60 | output.first(toCopy).copyFrom(state.data.first(toCopy)); |
| 61 | // Zero out the source buffer after copying to prevent sensitive data from remaining in memory |
| 62 | OPENSSL_cleanse(state.data.first(toCopy).begin(), toCopy); |
| 63 | state.data = state.data.slice(toCopy); |
| 64 | output = output.slice(toCopy); |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | } // namespace workerd |