summaryrefslogtreecommitdiff
path: root/util/libreboot-utils/lib/rand.c
blob: a8fbb706b975cb826d7bf7f054f50235a1c5f514 (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
/* 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;
	errno = 0;

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

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

#elif defined(__linux__)

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

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;
err:
	/*
	 * getrandom can return with error, butt arc4random
	 * doesn't. generally, getrandom will be reliably,
	 * 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);

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

out:
	errno = saved_errno;
	return rval;
}
#endif