1 | /* |
2 | * Copyright 2011-present Facebook, Inc. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | |
17 | #include <folly/Random.h> |
18 | |
19 | #include <array> |
20 | #include <atomic> |
21 | #include <mutex> |
22 | #include <random> |
23 | |
24 | #include <folly/File.h> |
25 | #include <folly/FileUtil.h> |
26 | #include <folly/SingletonThreadLocal.h> |
27 | #include <folly/ThreadLocal.h> |
28 | #include <folly/portability/SysTime.h> |
29 | #include <folly/portability/Unistd.h> |
30 | #include <folly/synchronization/CallOnce.h> |
31 | #include <glog/logging.h> |
32 | |
33 | #ifdef _MSC_VER |
34 | #include <wincrypt.h> // @manual |
35 | #endif |
36 | |
37 | namespace folly { |
38 | |
39 | namespace { |
40 | |
41 | void readRandomDevice(void* data, size_t size) { |
42 | #ifdef _MSC_VER |
43 | static folly::once_flag flag; |
44 | static HCRYPTPROV cryptoProv; |
45 | folly::call_once(flag, [&] { |
46 | if (!CryptAcquireContext( |
47 | &cryptoProv, |
48 | nullptr, |
49 | nullptr, |
50 | PROV_RSA_FULL, |
51 | CRYPT_VERIFYCONTEXT)) { |
52 | if (GetLastError() == NTE_BAD_KEYSET) { |
53 | // Mostly likely cause of this is that no key container |
54 | // exists yet, so try to create one. |
55 | PCHECK(CryptAcquireContext( |
56 | &cryptoProv, nullptr, nullptr, PROV_RSA_FULL, CRYPT_NEWKEYSET)); |
57 | } else { |
58 | LOG(FATAL) << "Failed to acquire the default crypto context." ; |
59 | } |
60 | } |
61 | }); |
62 | CHECK(size <= std::numeric_limits<DWORD>::max()); |
63 | PCHECK(CryptGenRandom(cryptoProv, (DWORD)size, (BYTE*)data)); |
64 | #else |
65 | // Keep the random device open for the duration of the program. |
66 | static int randomFd = ::open("/dev/urandom" , O_RDONLY | O_CLOEXEC); |
67 | PCHECK(randomFd >= 0); |
68 | auto bytesRead = readFull(randomFd, data, size); |
69 | PCHECK(bytesRead >= 0); |
70 | CHECK_EQ(size_t(bytesRead), size); |
71 | #endif |
72 | } |
73 | |
74 | class BufferedRandomDevice { |
75 | public: |
76 | static once_flag flag; |
77 | static constexpr size_t kDefaultBufferSize = 128; |
78 | |
79 | static void notifyNewGlobalEpoch() { |
80 | globalEpoch_.fetch_add(1, std::memory_order_relaxed); |
81 | } |
82 | |
83 | explicit BufferedRandomDevice(size_t bufferSize = kDefaultBufferSize); |
84 | |
85 | void get(void* data, size_t size) { |
86 | auto const globalEpoch = globalEpoch_.load(std::memory_order_relaxed); |
87 | if (LIKELY(globalEpoch == epoch_ && size <= remaining())) { |
88 | memcpy(data, ptr_, size); |
89 | ptr_ += size; |
90 | } else { |
91 | getSlow(static_cast<unsigned char*>(data), size); |
92 | } |
93 | } |
94 | |
95 | private: |
96 | void getSlow(unsigned char* data, size_t size); |
97 | |
98 | inline size_t remaining() const { |
99 | return size_t(buffer_.get() + bufferSize_ - ptr_); |
100 | } |
101 | |
102 | static std::atomic<size_t> globalEpoch_; |
103 | |
104 | size_t epoch_{size_t(-1)}; // refill on first use |
105 | const size_t bufferSize_; |
106 | std::unique_ptr<unsigned char[]> buffer_; |
107 | unsigned char* ptr_; |
108 | }; |
109 | |
110 | once_flag BufferedRandomDevice::flag; |
111 | std::atomic<size_t> BufferedRandomDevice::globalEpoch_{0}; |
112 | struct RandomTag {}; |
113 | |
114 | BufferedRandomDevice::BufferedRandomDevice(size_t bufferSize) |
115 | : bufferSize_(bufferSize), |
116 | buffer_(new unsigned char[bufferSize]), |
117 | ptr_(buffer_.get() + bufferSize) { // refill on first use |
118 | call_once(flag, [this]() { |
119 | detail::AtFork::registerHandler( |
120 | this, |
121 | /*prepare*/ []() { return true; }, |
122 | /*parent*/ []() {}, |
123 | /*child*/ |
124 | []() { |
125 | // Ensure child and parent do not share same entropy pool. |
126 | BufferedRandomDevice::notifyNewGlobalEpoch(); |
127 | }); |
128 | }); |
129 | } |
130 | |
131 | void BufferedRandomDevice::getSlow(unsigned char* data, size_t size) { |
132 | auto const globalEpoch = globalEpoch_.load(std::memory_order_relaxed); |
133 | if (globalEpoch != epoch_) { |
134 | epoch_ = globalEpoch_; |
135 | ptr_ = buffer_.get() + bufferSize_; |
136 | } |
137 | |
138 | DCHECK_GT(size, remaining()); |
139 | if (size >= bufferSize_) { |
140 | // Just read directly. |
141 | readRandomDevice(data, size); |
142 | return; |
143 | } |
144 | |
145 | size_t copied = remaining(); |
146 | memcpy(data, ptr_, copied); |
147 | data += copied; |
148 | size -= copied; |
149 | |
150 | // refill |
151 | readRandomDevice(buffer_.get(), bufferSize_); |
152 | ptr_ = buffer_.get(); |
153 | |
154 | memcpy(data, ptr_, size); |
155 | ptr_ += size; |
156 | } |
157 | |
158 | } // namespace |
159 | |
160 | void Random::secureRandom(void* data, size_t size) { |
161 | using Single = SingletonThreadLocal<BufferedRandomDevice, RandomTag>; |
162 | Single::get().get(data, size); |
163 | } |
164 | |
165 | ThreadLocalPRNG::result_type ThreadLocalPRNG::operator()() { |
166 | struct Wrapper { |
167 | Random::DefaultGenerator object{Random::create()}; |
168 | }; |
169 | using Single = SingletonThreadLocal<Wrapper, RandomTag>; |
170 | return Single::get().object(); |
171 | } |
172 | } // namespace folly |
173 | |