| 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 | } |