]>
Commit | Line | Data |
---|---|---|
89c4ed63 A |
1 | /* $OpenBSD: getentropy_solaris.c,v 1.3 2014/07/12 14:46:31 deraadt Exp $ */ |
2 | ||
3 | /* | |
4 | * Copyright (c) 2014 Theo de Raadt <deraadt@openbsd.org> | |
5 | * Copyright (c) 2014 Bob Beck <beck@obtuse.com> | |
6 | * | |
7 | * Permission to use, copy, modify, and distribute this software for any | |
8 | * purpose with or without fee is hereby granted, provided that the above | |
9 | * copyright notice and this permission notice appear in all copies. | |
10 | * | |
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
18 | */ | |
19 | #include "config.h" | |
20 | ||
21 | #include <sys/types.h> | |
22 | #include <sys/param.h> | |
23 | #include <sys/ioctl.h> | |
24 | #include <sys/resource.h> | |
25 | #include <sys/syscall.h> | |
26 | #include <sys/statvfs.h> | |
27 | #include <sys/socket.h> | |
28 | #include <sys/mount.h> | |
29 | #include <sys/mman.h> | |
30 | #include <sys/stat.h> | |
31 | #include <sys/time.h> | |
32 | #include <stdlib.h> | |
33 | #include <stdint.h> | |
34 | #include <stdio.h> | |
35 | #include <termios.h> | |
36 | #include <fcntl.h> | |
37 | #include <signal.h> | |
38 | #include <string.h> | |
39 | #include <errno.h> | |
40 | #include <unistd.h> | |
41 | #include <time.h> | |
42 | #include <sys/sha2.h> | |
43 | #define SHA512_Init SHA512Init | |
44 | #define SHA512_Update SHA512Update | |
45 | #define SHA512_Final SHA512Final | |
46 | ||
47 | #include <sys/vfs.h> | |
48 | #include <sys/statfs.h> | |
49 | #include <sys/loadavg.h> | |
50 | ||
51 | #define REPEAT 5 | |
52 | #define min(a, b) (((a) < (b)) ? (a) : (b)) | |
53 | ||
54 | #define HX(a, b) \ | |
55 | do { \ | |
56 | if ((a)) \ | |
57 | HD(errno); \ | |
58 | else \ | |
59 | HD(b); \ | |
60 | } while (0) | |
61 | ||
62 | #define HR(x, l) (SHA512_Update(&ctx, (char *)(x), (l))) | |
63 | #define HD(x) (SHA512_Update(&ctx, (char *)&(x), sizeof (x))) | |
64 | #define HF(x) (SHA512_Update(&ctx, (char *)&(x), sizeof (void*))) | |
65 | ||
66 | int getentropy(void *buf, size_t len); | |
67 | ||
68 | #ifdef CAN_REFERENCE_MAIN | |
69 | extern int main(int, char *argv[]); | |
70 | #endif | |
71 | static int gotdata(char *buf, size_t len); | |
72 | static int getentropy_urandom(void *buf, size_t len, const char *path, | |
73 | int devfscheck); | |
74 | static int getentropy_fallback(void *buf, size_t len); | |
75 | ||
76 | int | |
77 | getentropy(void *buf, size_t len) | |
78 | { | |
79 | int ret = -1; | |
80 | ||
81 | if (len > 256) { | |
82 | errno = EIO; | |
83 | return -1; | |
84 | } | |
85 | ||
86 | /* | |
87 | * Try to get entropy with /dev/urandom | |
88 | * | |
89 | * Solaris provides /dev/urandom as a symbolic link to | |
90 | * /devices/pseudo/random@0:urandom which is provided by | |
91 | * a devfs filesystem. Best practice is to use O_NOFOLLOW, | |
92 | * so we must try the unpublished name directly. | |
93 | * | |
94 | * This can fail if the process is inside a chroot which lacks | |
95 | * the devfs mount, or if file descriptors are exhausted. | |
96 | */ | |
97 | ret = getentropy_urandom(buf, len, | |
98 | "/devices/pseudo/random@0:urandom", 1); | |
99 | if (ret != -1) | |
100 | return (ret); | |
101 | ||
102 | /* | |
103 | * Unfortunately, chroot spaces on Solaris are sometimes setup | |
104 | * with direct device node of the well-known /dev/urandom name | |
105 | * (perhaps to avoid dragging all of devfs into the space). | |
106 | * | |
107 | * This can fail if the process is inside a chroot or if file | |
108 | * descriptors are exhausted. | |
109 | */ | |
110 | ret = getentropy_urandom(buf, len, "/dev/urandom", 0); | |
111 | if (ret != -1) | |
112 | return (ret); | |
113 | ||
114 | /* | |
115 | * Entropy collection via /dev/urandom has failed. | |
116 | * | |
117 | * No other API exists for collecting entropy, and we have | |
118 | * no failsafe way to get it on Solaris that is not sensitive | |
119 | * to resource exhaustion. | |
120 | * | |
121 | * We have very few options: | |
122 | * - Even syslog_r is unsafe to call at this low level, so | |
123 | * there is no way to alert the user or program. | |
124 | * - Cannot call abort() because some systems have unsafe | |
125 | * corefiles. | |
126 | * - Could raise(SIGKILL) resulting in silent program termination. | |
127 | * - Return EIO, to hint that arc4random's stir function | |
128 | * should raise(SIGKILL) | |
129 | * - Do the best under the circumstances.... | |
130 | * | |
131 | * This code path exists to bring light to the issue that Solaris | |
132 | * does not provide a failsafe API for entropy collection. | |
133 | * | |
134 | * We hope this demonstrates that Solaris should consider | |
135 | * providing a new failsafe API which works in a chroot or | |
136 | * when file descriptors are exhausted. | |
137 | */ | |
138 | #undef FAIL_INSTEAD_OF_TRYING_FALLBACK | |
139 | #ifdef FAIL_INSTEAD_OF_TRYING_FALLBACK | |
140 | raise(SIGKILL); | |
141 | #endif | |
142 | ret = getentropy_fallback(buf, len); | |
143 | if (ret != -1) | |
144 | return (ret); | |
145 | ||
146 | errno = EIO; | |
147 | return (ret); | |
148 | } | |
149 | ||
150 | /* | |
151 | * Basic sanity checking; wish we could do better. | |
152 | */ | |
153 | static int | |
154 | gotdata(char *buf, size_t len) | |
155 | { | |
156 | char any_set = 0; | |
157 | size_t i; | |
158 | ||
159 | for (i = 0; i < len; ++i) | |
160 | any_set |= buf[i]; | |
161 | if (any_set == 0) | |
162 | return -1; | |
163 | return 0; | |
164 | } | |
165 | ||
166 | static int | |
167 | getentropy_urandom(void *buf, size_t len, const char *path, int devfscheck) | |
168 | { | |
169 | struct stat st; | |
170 | size_t i; | |
171 | int fd, flags; | |
172 | int save_errno = errno; | |
173 | ||
174 | start: | |
175 | ||
176 | flags = O_RDONLY; | |
177 | #ifdef O_NOFOLLOW | |
178 | flags |= O_NOFOLLOW; | |
179 | #endif | |
180 | #ifdef O_CLOEXEC | |
181 | flags |= O_CLOEXEC; | |
182 | #endif | |
183 | fd = open(path, flags, 0); | |
184 | if (fd == -1) { | |
185 | if (errno == EINTR) | |
186 | goto start; | |
187 | goto nodevrandom; | |
188 | } | |
189 | #ifndef O_CLOEXEC | |
190 | fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); | |
191 | #endif | |
192 | ||
193 | /* Lightly verify that the device node looks sane */ | |
194 | if (fstat(fd, &st) == -1 || !S_ISCHR(st.st_mode) || | |
195 | (devfscheck && (strcmp(st.st_fstype, "devfs") != 0))) { | |
196 | close(fd); | |
197 | goto nodevrandom; | |
198 | } | |
199 | for (i = 0; i < len; ) { | |
200 | size_t wanted = len - i; | |
201 | ssize_t ret = read(fd, (char*)buf + i, wanted); | |
202 | ||
203 | if (ret == -1) { | |
204 | if (errno == EAGAIN || errno == EINTR) | |
205 | continue; | |
206 | close(fd); | |
207 | goto nodevrandom; | |
208 | } | |
209 | i += ret; | |
210 | } | |
211 | close(fd); | |
212 | if (gotdata(buf, len) == 0) { | |
213 | errno = save_errno; | |
214 | return 0; /* satisfied */ | |
215 | } | |
216 | nodevrandom: | |
217 | errno = EIO; | |
218 | return -1; | |
219 | } | |
220 | ||
221 | static const int cl[] = { | |
222 | CLOCK_REALTIME, | |
223 | #ifdef CLOCK_MONOTONIC | |
224 | CLOCK_MONOTONIC, | |
225 | #endif | |
226 | #ifdef CLOCK_MONOTONIC_RAW | |
227 | CLOCK_MONOTONIC_RAW, | |
228 | #endif | |
229 | #ifdef CLOCK_TAI | |
230 | CLOCK_TAI, | |
231 | #endif | |
232 | #ifdef CLOCK_VIRTUAL | |
233 | CLOCK_VIRTUAL, | |
234 | #endif | |
235 | #ifdef CLOCK_UPTIME | |
236 | CLOCK_UPTIME, | |
237 | #endif | |
238 | #ifdef CLOCK_PROCESS_CPUTIME_ID | |
239 | CLOCK_PROCESS_CPUTIME_ID, | |
240 | #endif | |
241 | #ifdef CLOCK_THREAD_CPUTIME_ID | |
242 | CLOCK_THREAD_CPUTIME_ID, | |
243 | #endif | |
244 | }; | |
245 | ||
246 | static int | |
247 | getentropy_fallback(void *buf, size_t len) | |
248 | { | |
249 | uint8_t results[SHA512_DIGEST_LENGTH]; | |
250 | int save_errno = errno, e, pgs = getpagesize(), faster = 0, repeat; | |
251 | static int cnt; | |
252 | struct timespec ts; | |
253 | struct timeval tv; | |
254 | double loadavg[3]; | |
255 | struct rusage ru; | |
256 | sigset_t sigset; | |
257 | struct stat st; | |
258 | SHA512_CTX ctx; | |
259 | static pid_t lastpid; | |
260 | pid_t pid; | |
261 | size_t i, ii, m; | |
262 | char *p; | |
263 | ||
264 | pid = getpid(); | |
265 | if (lastpid == pid) { | |
266 | faster = 1; | |
267 | repeat = 2; | |
268 | } else { | |
269 | faster = 0; | |
270 | lastpid = pid; | |
271 | repeat = REPEAT; | |
272 | } | |
273 | for (i = 0; i < len; ) { | |
274 | int j; | |
275 | SHA512_Init(&ctx); | |
276 | for (j = 0; j < repeat; j++) { | |
277 | HX((e = gettimeofday(&tv, NULL)) == -1, tv); | |
278 | if (e != -1) { | |
279 | cnt += (int)tv.tv_sec; | |
280 | cnt += (int)tv.tv_usec; | |
281 | } | |
282 | ||
283 | for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]); ii++) | |
284 | HX(clock_gettime(cl[ii], &ts) == -1, ts); | |
285 | ||
286 | HX((pid = getpid()) == -1, pid); | |
287 | HX((pid = getsid(pid)) == -1, pid); | |
288 | HX((pid = getppid()) == -1, pid); | |
289 | HX((pid = getpgid(0)) == -1, pid); | |
290 | HX((e = getpriority(0, 0)) == -1, e); | |
291 | HX((getloadavg(loadavg, 3) == -1), loadavg); | |
292 | ||
293 | if (!faster) { | |
294 | ts.tv_sec = 0; | |
295 | ts.tv_nsec = 1; | |
296 | (void) nanosleep(&ts, NULL); | |
297 | } | |
298 | ||
299 | HX(sigpending(&sigset) == -1, sigset); | |
300 | HX(sigprocmask(SIG_BLOCK, NULL, &sigset) == -1, | |
301 | sigset); | |
302 | ||
303 | #ifdef CAN_REFERENCE_MAIN | |
304 | HF(main); /* an addr in program */ | |
305 | #endif | |
306 | HF(getentropy); /* an addr in this library */ | |
307 | HF(printf); /* an addr in libc */ | |
308 | p = (char *)&p; | |
309 | HD(p); /* an addr on stack */ | |
310 | p = (char *)&errno; | |
311 | HD(p); /* the addr of errno */ | |
312 | ||
313 | if (i == 0) { | |
314 | struct sockaddr_storage ss; | |
315 | struct statvfs stvfs; | |
316 | struct termios tios; | |
317 | socklen_t ssl; | |
318 | off_t off; | |
319 | ||
320 | /* | |
321 | * Prime-sized mappings encourage fragmentation; | |
322 | * thus exposing some address entropy. | |
323 | */ | |
324 | struct mm { | |
325 | size_t npg; | |
326 | void *p; | |
327 | } mm[] = { | |
328 | { 17, MAP_FAILED }, { 3, MAP_FAILED }, | |
329 | { 11, MAP_FAILED }, { 2, MAP_FAILED }, | |
330 | { 5, MAP_FAILED }, { 3, MAP_FAILED }, | |
331 | { 7, MAP_FAILED }, { 1, MAP_FAILED }, | |
332 | { 57, MAP_FAILED }, { 3, MAP_FAILED }, | |
333 | { 131, MAP_FAILED }, { 1, MAP_FAILED }, | |
334 | }; | |
335 | ||
336 | for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) { | |
337 | HX(mm[m].p = mmap(NULL, | |
338 | mm[m].npg * pgs, | |
339 | PROT_READ|PROT_WRITE, | |
340 | MAP_PRIVATE|MAP_ANON, -1, | |
341 | (off_t)0), mm[m].p); | |
342 | if (mm[m].p != MAP_FAILED) { | |
343 | size_t mo; | |
344 | ||
345 | /* Touch some memory... */ | |
346 | p = mm[m].p; | |
347 | mo = cnt % | |
348 | (mm[m].npg * pgs - 1); | |
349 | p[mo] = 1; | |
350 | cnt += (int)((long)(mm[m].p) | |
351 | / pgs); | |
352 | } | |
353 | ||
354 | /* Check cnts and times... */ | |
355 | for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]); | |
356 | ii++) { | |
357 | HX((e = clock_gettime(cl[ii], | |
358 | &ts)) == -1, ts); | |
359 | if (e != -1) | |
360 | cnt += (int)ts.tv_nsec; | |
361 | } | |
362 | ||
363 | HX((e = getrusage(RUSAGE_SELF, | |
364 | &ru)) == -1, ru); | |
365 | if (e != -1) { | |
366 | cnt += (int)ru.ru_utime.tv_sec; | |
367 | cnt += (int)ru.ru_utime.tv_usec; | |
368 | } | |
369 | } | |
370 | ||
371 | for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) { | |
372 | if (mm[m].p != MAP_FAILED) | |
373 | munmap(mm[m].p, mm[m].npg * pgs); | |
374 | mm[m].p = MAP_FAILED; | |
375 | } | |
376 | ||
377 | HX(stat(".", &st) == -1, st); | |
378 | HX(statvfs(".", &stvfs) == -1, stvfs); | |
379 | ||
380 | HX(stat("/", &st) == -1, st); | |
381 | HX(statvfs("/", &stvfs) == -1, stvfs); | |
382 | ||
383 | HX((e = fstat(0, &st)) == -1, st); | |
384 | if (e == -1) { | |
385 | if (S_ISREG(st.st_mode) || | |
386 | S_ISFIFO(st.st_mode) || | |
387 | S_ISSOCK(st.st_mode)) { | |
388 | HX(fstatvfs(0, &stvfs) == -1, | |
389 | stvfs); | |
390 | HX((off = lseek(0, (off_t)0, | |
391 | SEEK_CUR)) < 0, off); | |
392 | } | |
393 | if (S_ISCHR(st.st_mode)) { | |
394 | HX(tcgetattr(0, &tios) == -1, | |
395 | tios); | |
396 | } else if (S_ISSOCK(st.st_mode)) { | |
397 | memset(&ss, 0, sizeof ss); | |
398 | ssl = sizeof(ss); | |
399 | HX(getpeername(0, | |
400 | (void *)&ss, &ssl) == -1, | |
401 | ss); | |
402 | } | |
403 | } | |
404 | ||
405 | HX((e = getrusage(RUSAGE_CHILDREN, | |
406 | &ru)) == -1, ru); | |
407 | if (e != -1) { | |
408 | cnt += (int)ru.ru_utime.tv_sec; | |
409 | cnt += (int)ru.ru_utime.tv_usec; | |
410 | } | |
411 | } else { | |
412 | /* Subsequent hashes absorb previous result */ | |
413 | HD(results); | |
414 | } | |
415 | ||
416 | HX((e = gettimeofday(&tv, NULL)) == -1, tv); | |
417 | if (e != -1) { | |
418 | cnt += (int)tv.tv_sec; | |
419 | cnt += (int)tv.tv_usec; | |
420 | } | |
421 | ||
422 | HD(cnt); | |
423 | } | |
424 | SHA512_Final(results, &ctx); | |
425 | memcpy((char*)buf + i, results, min(sizeof(results), len - i)); | |
426 | i += min(sizeof(results), len - i); | |
427 | } | |
428 | memset(results, 0, sizeof results); | |
429 | if (gotdata(buf, len) == 0) { | |
430 | errno = save_errno; | |
431 | return 0; /* satisfied */ | |
432 | } | |
433 | errno = EIO; | |
434 | return -1; | |
435 | } |