summaryrefslogtreecommitdiff
path: root/util/libreboot-utils/lib/rand.c
blob: bfcde5ac0e74ac10fd4e13af42f3754006a24fcc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/* SPDX-License-Identifier: MIT
 * Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
 *
 * Random number generation
 */

#ifndef RAND_H
#define RAND_H

#ifdef __OpenBSD__
#include <sys/param.h>
#endif
#include <sys/types.h>

#include <errno.h>
#if !((defined(__OpenBSD__) && (OpenBSD) >= 201) || \
    defined(__FreeBSD__) || \
    defined(__NetBSD__) || defined(__APPLE__))
#include <fcntl.h> /* if not arc4random: /dev/urandom */
#endif
#include <limits.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#include "../include/common.h"

/* Random numbers
 */

/* when calling this: save errno
 * first, then set errno to zero.
 * on error, this function will
 * set errno and possibly return
 *
 * rlong also preserves errno
 * and leaves it unchanged on
 * success, so if you do it
 * right, you can detect error.
 * this is because it uses
 * /dev/urandom which can err.
 * ditto getrandom (EINTR),
 * theoretically.
 */

/* for the linux version: we use only the
 * syscall, because we cannot trust /dev/urandom
 * to be as robust, and some libc implementations
 * may default to /dev/urandom under fault conditions.
 *
 * for general high reliability, we must abort on
 * failure. in practise, it will likely never fail.
 * the arc4random call on bsd never returns error.
 */

size_t
rlong(void)
{
	size_t rval;
	int saved_errno = errno;

#if (defined(__OpenBSD__) || defined(__FreeBSD__) || \
    defined(__NetBSD__) || defined(__APPLE__) || \
    defined(__DragonFly__))

	arc4random_buf(&rval, sizeof(size_t));
	goto out;

#elif defined(USE_URANDOM) && \
    ((USE_URANDOM) > 0)

	/* Use of /dev/urandom is ill advised, due
	   to FD exhaustion */

	int fd = -1;
	ssize_t rc = 0;

	errno = 0;

	if ((fd = open("/dev/urandom", O_RDONLY)) < 0)
		goto err;

retry_rand:

	if ((rc = read(fd, &rval, sizeof(rval))) < 0) {
		if (errno == EINTR || errno == EAGAIN)
			goto retry_rand;
		goto err;
	}

	if ((rval += (size_t)rc) < sizeof(rval))
		goto retry_rand;

#elif defined(__linux__)

	size_t off = 0;
	size_t len = sizeof(rval);
	ssize_t rc;

	errno = 0;

retry_rand:
	rc = (ssize_t)syscall(SYS_getrandom,
	    (char *)&rval + off, len - off, 0);

	if (rc < 0) {
		if (errno == EINTR || errno == EAGAIN) {
			usleep(100);
			goto retry_rand;
		}

		goto err; /* possibly unsupported by kernel */
	}

	if ((off += (size_t)rc) < len)
		goto retry_rand;

	goto out;
#else
#error Unsupported operating system (possibly unsecure randomisation)
#endif

out:
	errno = saved_errno;
	return rval;
err:
	/*
	 * getrandom can return with error, but arc4random
	 * doesn't. generally, getrandom will be reliable,
	 * but we of course have to maintain parity with
	 * BSD. So a rand failure is to be interpreted as
	 * a major systems failure, and we act accordingly.
	 */
	err_no_cleanup(1, ECANCELED,
	    "Randomisation failure, possibly unsupported in your kernel.");
	exit(EXIT_FAILURE);

	return 0;
}
#endif