1
0
mirror of https://github.com/systemd/systemd.git synced 2026-01-26 04:33:06 +03:00
Files
systemd/src/basic/random-util.c
2025-07-01 12:24:32 +09:00

234 lines
7.1 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <elf.h>
#include <fcntl.h>
#include <linux/random.h>
#include <string.h>
#include <sys/auxv.h>
#include <sys/ioctl.h>
#include <sys/random.h>
#include <threads.h>
#include <unistd.h>
#include "alloc-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "io-util.h"
#include "iovec-util.h"
#include "log.h"
#include "parse-util.h"
#include "pidfd-util.h"
#include "process-util.h"
#include "random-util.h"
#include "sha256.h"
#include "time-util.h"
/* This is a "best effort" kind of thing, but has no real security value. So, this should only be used by
* random_bytes(), which is not meant for crypto. This could be made better, but we're *not* trying to roll a
* userspace prng here, or even have forward secrecy, but rather just do the shortest thing that is at least
* better than libc rand(). */
static void fallback_random_bytes(void *p, size_t n) {
static thread_local uint64_t fallback_counter = 0;
struct {
char label[32];
uint64_t call_id, block_id;
usec_t stamp_mono, stamp_real;
pid_t pid, tid;
uint64_t pidfdid;
uint8_t auxval[16];
} state = {
/* Arbitrary domain separation to prevent other usage of AT_RANDOM from clashing. */
.call_id = fallback_counter++,
.stamp_mono = now(CLOCK_MONOTONIC),
.stamp_real = now(CLOCK_REALTIME),
.pid = getpid_cached(),
.tid = gettid(),
};
memcpy(state.label, "systemd fallback random bytes v1", sizeof(state.label));
memcpy(state.auxval, ULONG_TO_PTR(getauxval(AT_RANDOM)), sizeof(state.auxval));
(void) pidfd_get_inode_id_self_cached(&state.pidfdid);
while (n > 0) {
struct sha256_ctx ctx;
sha256_init_ctx(&ctx);
sha256_process_bytes(&state, sizeof(state), &ctx);
if (n < SHA256_DIGEST_SIZE) {
uint8_t partial[SHA256_DIGEST_SIZE];
sha256_finish_ctx(&ctx, partial);
memcpy(p, partial, n);
break;
}
sha256_finish_ctx(&ctx, p);
p = (uint8_t *) p + SHA256_DIGEST_SIZE;
n -= SHA256_DIGEST_SIZE;
++state.block_id;
}
}
void random_bytes(void *p, size_t n) {
static bool have_grndinsecure = true;
assert(p || n == 0);
if (n == 0)
return;
for (;;) {
ssize_t l;
l = getrandom(p, n, have_grndinsecure ? GRND_INSECURE : GRND_NONBLOCK);
if (l < 0 && errno == EINVAL && have_grndinsecure) {
/* No GRND_INSECURE; fallback to GRND_NONBLOCK. */
have_grndinsecure = false;
continue;
}
if (l <= 0)
break; /* Will block (with GRND_NONBLOCK), or unexpected error. Give up and fallback
to /dev/urandom. */
if ((size_t) l == n)
return; /* Done reading, success. */
p = (uint8_t *) p + l;
n -= l;
/* Interrupted by a signal; keep going. */
}
_cleanup_close_ int fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd >= 0 && loop_read_exact(fd, p, n, false) >= 0)
return;
/* This is a terrible fallback. Oh well. */
fallback_random_bytes(p, n);
}
int crypto_random_bytes(void *p, size_t n) {
assert(p || n == 0);
if (n == 0)
return 0;
for (;;) {
ssize_t l;
l = getrandom(p, n, 0);
if (l < 0)
return -errno;
if (l == 0)
return -EIO; /* Weird, should never happen. */
if ((size_t) l == n)
return 0; /* Done reading, success. */
p = (uint8_t *) p + l;
n -= l;
/* Interrupted by a signal; keep going. */
}
}
int crypto_random_bytes_allocate_iovec(size_t n, struct iovec *ret) {
_cleanup_free_ void *p = NULL;
int r;
assert(ret);
p = malloc(MAX(n, 1U));
if (!p)
return -ENOMEM;
r = crypto_random_bytes(p, n);
if (r < 0)
return r;
*ret = IOVEC_MAKE(TAKE_PTR(p), n);
return 0;
}
size_t random_pool_size(void) {
_cleanup_free_ char *s = NULL;
int r;
/* Read pool size, if possible */
r = read_one_line_file("/proc/sys/kernel/random/poolsize", &s);
if (r < 0)
log_debug_errno(r, "Failed to read pool size from kernel: %m");
else {
unsigned sz;
r = safe_atou(s, &sz);
if (r < 0)
log_debug_errno(r, "Failed to parse pool size: %s", s);
else
/* poolsize is in bits on 2.6, but we want bytes */
return CLAMP(sz / 8, RANDOM_POOL_SIZE_MIN, RANDOM_POOL_SIZE_MAX);
}
/* Use the minimum as default, if we can't retrieve the correct value */
return RANDOM_POOL_SIZE_MIN;
}
int random_write_entropy(int fd, const void *seed, size_t size, bool credit) {
_cleanup_close_ int opened_fd = -EBADF;
int r;
assert(seed || size == 0);
if (size == 0)
return 0;
if (fd < 0) {
opened_fd = open("/dev/urandom", O_WRONLY|O_CLOEXEC|O_NOCTTY);
if (opened_fd < 0)
return -errno;
fd = opened_fd;
}
if (credit) {
_cleanup_free_ struct rand_pool_info *info = NULL;
/* The kernel API only accepts "int" as entropy count (which is in bits), let's avoid any
* chance for confusion here. */
if (size > INT_MAX / 8)
return -EOVERFLOW;
info = malloc(offsetof(struct rand_pool_info, buf) + size);
if (!info)
return -ENOMEM;
info->entropy_count = size * 8;
info->buf_size = size;
memcpy(info->buf, seed, size);
if (ioctl(fd, RNDADDENTROPY, info) < 0)
return -errno;
} else {
r = loop_write(fd, seed, size);
if (r < 0)
return r;
}
return 1;
}
uint64_t random_u64_range(uint64_t m) {
uint64_t x, remainder;
/* Generates a random number in the range 0…m-1, unbiased. (Java's algorithm) */
if (m == 0) /* Let's take m == 0 as special case to return an integer from the full range */
return random_u64();
if (m == 1)
return 0;
remainder = UINT64_MAX % m;
do {
x = random_u64();
} while (x >= UINT64_MAX - remainder);
return x % m;
}