Commit | Line | Data |
---|---|---|
bff8215a JG |
1 | /* |
2 | * Copyright (C) 2023 Jérémie Galarneau <jeremie.galarneau@efficios.com> | |
3 | * | |
4 | * SPDX-License-Identifier: LGPL-2.1-only | |
5 | * | |
6 | */ | |
7 | ||
8 | #include <lttng/constant.h> | |
9 | ||
10 | #include <common/error.h> | |
11 | #include <common/hashtable/utils.h> | |
12 | #include <common/random.h> | |
13 | #include <common/readwrite.h> | |
14 | #include <common/time.h> | |
15 | ||
16 | #include <errno.h> | |
17 | #include <fcntl.h> | |
18 | #include <unistd.h> | |
19 | ||
20 | #ifdef HAVE_SYS_SYSCALL_H | |
21 | #include <sys/syscall.h> | |
22 | #endif | |
23 | ||
24 | /* getrandom is available in Linux >= 3.17. */ | |
25 | #if defined(__linux__) && defined(SYS_getrandom) && defined(HAVE_SYS_RANDOM_H) | |
26 | ||
27 | #include <sys/random.h> | |
28 | ||
29 | /* A glibc wrapper is provided only for glibc >= 2.25. */ | |
30 | #if defined(HAVE_GETRANDOM) | |
31 | /* Simply use the existing wrapper, passing the non-block flag. */ | |
32 | static ssize_t _call_getrandom_nonblock(char *out_data, size_t size) | |
33 | { | |
34 | return getrandom(out_data, size, GRND_NONBLOCK); | |
35 | } | |
36 | #else | |
37 | static ssize_t _call_getrandom_nonblock(char *out_data, size_t size) | |
38 | { | |
39 | const int grnd_nonblock_flag = 0x1; | |
40 | long ret = syscall(SYS_getrandom, out_data, size, grnd_nonblock_flag); | |
41 | ||
42 | if (ret < 0) { | |
43 | errno = -ret; | |
44 | ret = -1; | |
45 | } | |
46 | ||
47 | return ret; | |
48 | } | |
49 | #endif /* defined(HAVE_GETRANDOM) */ | |
50 | ||
51 | /* Returns either with a full read or throws. */ | |
52 | static int getrandom_nonblock(char *out_data, size_t size) | |
53 | { | |
54 | /* | |
55 | * Since GRND_RANDOM is _not_ used, a partial read can only be caused | |
56 | * by a signal interruption. In this case, retry. | |
57 | */ | |
58 | int ret = 0; | |
59 | ssize_t random_ret; | |
60 | ||
61 | do { | |
62 | random_ret = _call_getrandom_nonblock(out_data, size); | |
63 | } while ((random_ret > 0 && random_ret != size) || (random_ret == -1 && errno == EINTR)); | |
64 | ||
65 | if (random_ret < 0) { | |
66 | PERROR("Failed to get true random data using getrandom(): size=%zu", size); | |
67 | ret = -1; | |
68 | } | |
69 | ||
70 | return ret; | |
71 | } | |
72 | #else /* defined(__linux__) && defined(SYS_getrandom) && defined(HAVE_SYS_RANDOM_H) */ | |
73 | static int getrandom_nonblock(char *out_data, size_t size) | |
74 | { | |
75 | WARN("getrandom() is not supported by this platform"); | |
76 | return -1; | |
77 | } | |
78 | #endif /* defined(__linux__) && defined(SYS_getrandom) && defined(HAVE_SYS_RANDOM_H) */ | |
79 | ||
80 | static int produce_pseudo_random_seed(seed_t *out_seed) | |
81 | { | |
82 | int ret; | |
83 | struct timespec real_time = {}; | |
84 | struct timespec monotonic_time = {}; | |
85 | unsigned long hash_seed; | |
86 | char hostname[LTTNG_HOST_NAME_MAX] = {}; | |
87 | unsigned long seed; | |
88 | unsigned long pid; | |
89 | ||
90 | ret = clock_gettime(CLOCK_REALTIME, &real_time); | |
91 | if (ret) { | |
92 | PERROR("Failed to read real time while generating pseudo-random seed"); | |
93 | goto error; | |
94 | } | |
95 | ||
96 | ret = clock_gettime(CLOCK_MONOTONIC, &monotonic_time); | |
97 | if (ret) { | |
98 | PERROR("Failed to read monotonic time while generating pseudo-random seed"); | |
99 | goto error; | |
100 | } | |
101 | ||
102 | ret = gethostname(hostname, sizeof(hostname)); | |
103 | if (ret) { | |
104 | PERROR("Failed to get host name while generating pseudo-random seed"); | |
105 | goto error; | |
106 | } | |
107 | ||
108 | hash_seed = (unsigned long) real_time.tv_nsec ^ (unsigned long) real_time.tv_sec ^ | |
109 | (unsigned long) monotonic_time.tv_nsec ^ | |
110 | (unsigned long) monotonic_time.tv_sec; | |
111 | seed = hash_key_ulong((void *) real_time.tv_sec, hash_seed); | |
112 | seed ^= hash_key_ulong((void *) real_time.tv_nsec, hash_seed); | |
113 | seed ^= hash_key_ulong((void *) monotonic_time.tv_sec, hash_seed); | |
114 | seed ^= hash_key_ulong((void *) monotonic_time.tv_nsec, hash_seed); | |
115 | ||
116 | pid = getpid(); | |
117 | seed ^= hash_key_ulong((void *) pid, hash_seed); | |
118 | seed ^= hash_key_str(hostname, hash_seed); | |
119 | ret = 0; | |
120 | ||
121 | *out_seed = (seed_t) seed; | |
122 | error: | |
123 | return ret; | |
124 | } | |
125 | ||
126 | static int produce_random_seed_from_urandom(seed_t *out_seed) | |
127 | { | |
128 | int ret = 0, read_ret; | |
129 | const int urandom_raw_fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); | |
130 | ||
131 | if (urandom_raw_fd < 0) { | |
132 | PERROR("Failed to open `/dev/urandom`"); | |
133 | ret = -1; | |
134 | goto end; | |
135 | } | |
136 | ||
137 | read_ret = lttng_read(urandom_raw_fd, out_seed, sizeof(*out_seed)); | |
138 | if (read_ret != sizeof(*out_seed)) { | |
139 | PERROR("Failed to read from `/dev/urandom`: size=%zu", | |
140 | sizeof(*out_seed)); | |
141 | ret = -1; | |
142 | goto end; | |
143 | } | |
144 | ||
145 | end: | |
146 | if (urandom_raw_fd >= 0) { | |
147 | if (close(urandom_raw_fd)) { | |
148 | PERROR("Failed to close `/dev/urandom` file descriptor"); | |
149 | } | |
150 | } | |
151 | return ret; | |
152 | } | |
153 | ||
154 | int lttng_produce_true_random_seed(seed_t *out_seed) | |
155 | { | |
156 | return getrandom_nonblock((char *) out_seed, sizeof(*out_seed)); | |
157 | } | |
158 | ||
159 | int lttng_produce_best_effort_random_seed(seed_t *out_seed) | |
160 | { | |
161 | int ret; | |
162 | ||
163 | ret = lttng_produce_true_random_seed(out_seed); | |
164 | if (!ret) { | |
165 | goto end; | |
166 | } else { | |
167 | WARN("Failed to produce a random seed using getrandom(), falling back to pseudo-random device seed generation which will block until its pool is initialized"); | |
168 | } | |
169 | ||
170 | ret = produce_random_seed_from_urandom(out_seed); | |
171 | if (!ret) { | |
172 | goto end; | |
173 | } else { | |
174 | WARN("Failed to produce a random seed from the urandom device"); | |
175 | } | |
176 | ||
177 | /* Fallback to seed generation based on time and system configuration. */ | |
178 | ret = produce_pseudo_random_seed(out_seed); | |
179 | end: | |
180 | return ret; | |
181 | } |