/src/libgit2/src/util/rand.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) |
2 | | |
3 | | To the extent possible under law, the author has dedicated all copyright |
4 | | and related and neighboring rights to this software to the public domain |
5 | | worldwide. This software is distributed without any warranty. |
6 | | |
7 | | See <http://creativecommons.org/publicdomain/zero/1.0/>. */ |
8 | | |
9 | | #include "git2_util.h" |
10 | | #include "rand.h" |
11 | | #include "runtime.h" |
12 | | |
13 | | #if defined(GIT_WIN32) |
14 | | # include <wincrypt.h> |
15 | | #endif |
16 | | |
17 | | static uint64_t state[4]; |
18 | | static git_mutex state_lock; |
19 | | |
20 | | typedef union { |
21 | | double f; |
22 | | uint64_t d; |
23 | | } bits; |
24 | | |
25 | | #if defined(GIT_WIN32) |
26 | | GIT_INLINE(int) getseed(uint64_t *seed) |
27 | | { |
28 | | HCRYPTPROV provider; |
29 | | SYSTEMTIME systemtime; |
30 | | FILETIME filetime, idletime, kerneltime, usertime; |
31 | | |
32 | | if (CryptAcquireContext(&provider, 0, 0, PROV_RSA_FULL, |
33 | | CRYPT_VERIFYCONTEXT|CRYPT_SILENT)) { |
34 | | BOOL success = CryptGenRandom(provider, sizeof(uint64_t), (void *)seed); |
35 | | CryptReleaseContext(provider, 0); |
36 | | |
37 | | if (success) |
38 | | return 0; |
39 | | } |
40 | | |
41 | | GetSystemTime(&systemtime); |
42 | | if (!SystemTimeToFileTime(&systemtime, &filetime)) { |
43 | | git_error_set(GIT_ERROR_OS, "could not get time for random seed"); |
44 | | return -1; |
45 | | } |
46 | | |
47 | | /* Fall-through: generate a seed from the time and system state */ |
48 | | *seed = 0; |
49 | | *seed |= ((uint64_t)filetime.dwLowDateTime << 32); |
50 | | *seed |= ((uint64_t)filetime.dwHighDateTime); |
51 | | |
52 | | GetSystemTimes(&idletime, &kerneltime, &usertime); |
53 | | |
54 | | *seed ^= ((uint64_t)idletime.dwLowDateTime << 32); |
55 | | *seed ^= ((uint64_t)kerneltime.dwLowDateTime); |
56 | | *seed ^= ((uint64_t)usertime.dwLowDateTime << 32); |
57 | | |
58 | | *seed ^= ((uint64_t)idletime.dwHighDateTime); |
59 | | *seed ^= ((uint64_t)kerneltime.dwHighDateTime << 12); |
60 | | *seed ^= ((uint64_t)usertime.dwHighDateTime << 24); |
61 | | |
62 | | *seed ^= ((uint64_t)GetCurrentProcessId() << 32); |
63 | | *seed ^= ((uint64_t)GetCurrentThreadId() << 48); |
64 | | |
65 | | *seed ^= git_time_monotonic(); |
66 | | |
67 | | /* Mix in the addresses of some functions and variables */ |
68 | | *seed ^= (((uint64_t)((uintptr_t)seed) << 32)); |
69 | | *seed ^= (((uint64_t)((uintptr_t)&errno))); |
70 | | |
71 | | return 0; |
72 | | } |
73 | | |
74 | | #else |
75 | | |
76 | | GIT_INLINE(int) getseed(uint64_t *seed) |
77 | 2 | { |
78 | 2 | struct timeval tv; |
79 | 2 | int fd; |
80 | | |
81 | 2 | # if defined(GIT_RAND_GETLOADAVG) |
82 | 2 | double loadavg[3]; |
83 | 2 | bits convert; |
84 | 2 | # endif |
85 | | |
86 | 2 | # if defined(GIT_RAND_GETENTROPY) |
87 | 2 | GIT_UNUSED((fd = 0)); |
88 | | |
89 | 2 | if (getentropy(seed, sizeof(uint64_t)) == 0) |
90 | 2 | return 0; |
91 | | # else |
92 | | /* |
93 | | * Try to read from /dev/urandom; most modern systems will have |
94 | | * this, but we may be chrooted, etc, so it's not a fatal error |
95 | | */ |
96 | | if ((fd = open("/dev/urandom", O_RDONLY)) >= 0) { |
97 | | ssize_t ret = read(fd, seed, sizeof(uint64_t)); |
98 | | close(fd); |
99 | | |
100 | | if (ret == (ssize_t)sizeof(uint64_t)) |
101 | | return 0; |
102 | | } |
103 | | # endif |
104 | | |
105 | | /* Fall-through: generate a seed from the time and system state */ |
106 | 0 | if (gettimeofday(&tv, NULL) < 0) { |
107 | 0 | git_error_set(GIT_ERROR_OS, "could get time for random seed"); |
108 | 0 | return -1; |
109 | 0 | } |
110 | | |
111 | 0 | *seed = 0; |
112 | 0 | *seed |= ((uint64_t)tv.tv_usec << 40); |
113 | 0 | *seed |= ((uint64_t)tv.tv_sec); |
114 | |
|
115 | 0 | *seed ^= ((uint64_t)getpid() << 48); |
116 | 0 | *seed ^= ((uint64_t)getppid() << 32); |
117 | 0 | *seed ^= ((uint64_t)getpgid(0) << 28); |
118 | 0 | *seed ^= ((uint64_t)getsid(0) << 16); |
119 | 0 | *seed ^= ((uint64_t)getuid() << 8); |
120 | 0 | *seed ^= ((uint64_t)getgid()); |
121 | |
|
122 | 0 | # if defined(GIT_RAND_GETLOADAVG) |
123 | 0 | getloadavg(loadavg, 3); |
124 | |
|
125 | 0 | convert.f = loadavg[0]; *seed ^= (convert.d >> 36); |
126 | 0 | convert.f = loadavg[1]; *seed ^= (convert.d); |
127 | 0 | convert.f = loadavg[2]; *seed ^= (convert.d >> 16); |
128 | 0 | # endif |
129 | |
|
130 | 0 | *seed ^= git_time_monotonic(); |
131 | | |
132 | | /* Mix in the addresses of some variables */ |
133 | 0 | *seed ^= ((uint64_t)((size_t)((void *)seed)) << 32); |
134 | 0 | *seed ^= ((uint64_t)((size_t)((void *)&errno))); |
135 | |
|
136 | 0 | return 0; |
137 | 0 | } |
138 | | #endif |
139 | | |
140 | | static void git_rand_global_shutdown(void) |
141 | 0 | { |
142 | 0 | git_mutex_free(&state_lock); |
143 | 0 | } |
144 | | |
145 | | int git_rand_global_init(void) |
146 | 2 | { |
147 | 2 | uint64_t seed = 0; |
148 | | |
149 | 2 | if (git_mutex_init(&state_lock) < 0 || getseed(&seed) < 0) |
150 | 0 | return -1; |
151 | | |
152 | 2 | if (!seed) { |
153 | 0 | git_error_set(GIT_ERROR_INTERNAL, "failed to generate random seed"); |
154 | 0 | return -1; |
155 | 0 | } |
156 | | |
157 | 2 | git_rand_seed(seed); |
158 | 2 | git_runtime_shutdown_register(git_rand_global_shutdown); |
159 | | |
160 | 2 | return 0; |
161 | 2 | } |
162 | | |
163 | | /* |
164 | | * This is splitmix64. xoroshiro256** uses 256 bit seed; this is used |
165 | | * to generate 256 bits of seed from the given 64, per the author's |
166 | | * recommendation. |
167 | | */ |
168 | | GIT_INLINE(uint64_t) splitmix64(uint64_t *in) |
169 | 8 | { |
170 | 8 | uint64_t z; |
171 | | |
172 | 8 | *in += 0x9e3779b97f4a7c15; |
173 | | |
174 | 8 | z = *in; |
175 | 8 | z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; |
176 | 8 | z = (z ^ (z >> 27)) * 0x94d049bb133111eb; |
177 | 8 | return z ^ (z >> 31); |
178 | 8 | } |
179 | | |
180 | | void git_rand_seed(uint64_t seed) |
181 | 2 | { |
182 | 2 | uint64_t mixer; |
183 | | |
184 | 2 | mixer = seed; |
185 | | |
186 | 2 | git_mutex_lock(&state_lock); |
187 | 2 | state[0] = splitmix64(&mixer); |
188 | 2 | state[1] = splitmix64(&mixer); |
189 | 2 | state[2] = splitmix64(&mixer); |
190 | 2 | state[3] = splitmix64(&mixer); |
191 | 2 | git_mutex_unlock(&state_lock); |
192 | 2 | } |
193 | | |
194 | | /* This is xoshiro256** 1.0, one of our all-purpose, rock-solid |
195 | | generators. It has excellent (sub-ns) speed, a state (256 bits) that is |
196 | | large enough for any parallel application, and it passes all tests we |
197 | | are aware of. |
198 | | |
199 | | For generating just floating-point numbers, xoshiro256+ is even faster. |
200 | | |
201 | | The state must be seeded so that it is not everywhere zero. If you have |
202 | | a 64-bit seed, we suggest to seed a splitmix64 generator and use its |
203 | | output to fill s. */ |
204 | | |
205 | 4 | GIT_INLINE(uint64_t) rotl(const uint64_t x, int k) { |
206 | 4 | return (x << k) | (x >> (64 - k)); |
207 | 4 | } |
208 | | |
209 | 2 | uint64_t git_rand_next(void) { |
210 | 2 | uint64_t t, result; |
211 | | |
212 | 2 | git_mutex_lock(&state_lock); |
213 | | |
214 | 2 | result = rotl(state[1] * 5, 7) * 9; |
215 | | |
216 | 2 | t = state[1] << 17; |
217 | | |
218 | 2 | state[2] ^= state[0]; |
219 | 2 | state[3] ^= state[1]; |
220 | 2 | state[1] ^= state[2]; |
221 | 2 | state[0] ^= state[3]; |
222 | | |
223 | 2 | state[2] ^= t; |
224 | | |
225 | 2 | state[3] = rotl(state[3], 45); |
226 | | |
227 | 2 | git_mutex_unlock(&state_lock); |
228 | | |
229 | 2 | return result; |
230 | 2 | } |