#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: kern_entropy.c,v 1.74 2026/01/04 01:32:52 riastradh Exp $");
#include <sys/param.h>
#include <sys/types.h>
#include <sys/atomic.h>
#include <sys/compat_stub.h>
#include <sys/condvar.h>
#include <sys/cpu.h>
#include <sys/entropy.h>
#include <sys/errno.h>
#include <sys/evcnt.h>
#include <sys/event.h>
#include <sys/file.h>
#include <sys/intr.h>
#include <sys/kauth.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/kthread.h>
#include <sys/lwp.h>
#include <sys/module_hook.h>
#include <sys/mutex.h>
#include <sys/percpu.h>
#include <sys/poll.h>
#include <sys/proc.h>
#include <sys/queue.h>
#include <sys/reboot.h>
#include <sys/rnd.h>
#include <sys/rndio.h>
#include <sys/rndsource.h>
#include <sys/sdt.h>
#include <sys/select.h>
#include <sys/selinfo.h>
#include <sys/sha1.h>
#include <sys/stdint.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/xcall.h>
#include <lib/libkern/entpool.h>
#include <machine/limits.h>
#ifdef __HAVE_CPU_COUNTER
#include <machine/cpu_counter.h>
#endif
#define MINENTROPYBYTES ENTROPY_CAPACITY
#define MINENTROPYBITS (MINENTROPYBYTES*NBBY)
#define MINSAMPLES (2*MINENTROPYBITS)
struct entropy_cpu {
struct entropy_cpu_evcnt {
struct evcnt softint;
struct evcnt intrdrop;
struct evcnt intrtrunc;
} *ec_evcnt;
struct entpool *ec_pool;
unsigned ec_bitspending;
unsigned ec_samplespending;
bool ec_locked;
};
struct entropy_cpu_lock {
int ecl_s;
long ecl_pctr;
};
struct rndsource_cpu {
unsigned rc_entropybits;
unsigned rc_timesamples;
unsigned rc_datasamples;
rnd_delta_t rc_timedelta;
};
struct {
kmutex_t lock;
struct entpool pool;
unsigned bitsneeded;
unsigned bitspending;
unsigned samplesneeded;
unsigned samplespending;
unsigned timestamp;
unsigned epoch;
kcondvar_t cv;
struct selinfo selq;
struct lwp *sourcelock;
kcondvar_t sourcelock_cv;
LIST_HEAD(,krndsource) sources;
bool consolidate;
bool seed_rndsource;
bool seeded;
} entropy_global __cacheline_aligned = {
.bitsneeded = MINENTROPYBITS,
.samplesneeded = MINSAMPLES,
.epoch = (unsigned)-1,
.sources = LIST_HEAD_INITIALIZER(entropy_global.sources),
};
#define E (&entropy_global)
static struct percpu *entropy_percpu __read_mostly;
static void *entropy_sih __read_mostly;
static struct lwp *entropy_lwp __read_mostly;
static struct krndsource seed_rndsource __read_mostly;
static struct evcnt entropy_discretionary_evcnt =
EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "entropy", "discretionary");
EVCNT_ATTACH_STATIC(entropy_discretionary_evcnt);
static struct evcnt entropy_immediate_evcnt =
EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "entropy", "immediate");
EVCNT_ATTACH_STATIC(entropy_immediate_evcnt);
static struct evcnt entropy_partial_evcnt =
EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "entropy", "partial");
EVCNT_ATTACH_STATIC(entropy_partial_evcnt);
static struct evcnt entropy_consolidate_evcnt =
EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "entropy", "consolidate");
EVCNT_ATTACH_STATIC(entropy_consolidate_evcnt);
static struct evcnt entropy_extract_fail_evcnt =
EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "entropy", "extract fail");
EVCNT_ATTACH_STATIC(entropy_extract_fail_evcnt);
static struct evcnt entropy_request_evcnt =
EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "entropy", "request");
EVCNT_ATTACH_STATIC(entropy_request_evcnt);
static struct evcnt entropy_deplete_evcnt =
EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "entropy", "deplete");
EVCNT_ATTACH_STATIC(entropy_deplete_evcnt);
static struct evcnt entropy_notify_evcnt =
EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "entropy", "notify");
EVCNT_ATTACH_STATIC(entropy_notify_evcnt);
static bool entropy_collection = 1;
static bool entropy_depletion = 0;
static const struct sysctlnode *entropy_sysctlroot;
static struct sysctllog *entropy_sysctllog;
static void entropy_init_cpu(void *, void *, struct cpu_info *);
static void entropy_fini_cpu(void *, void *, struct cpu_info *);
static void entropy_account_cpu(struct entropy_cpu *);
static void entropy_enter(const void *, size_t, unsigned, bool);
static bool entropy_enter_intr(const void *, size_t, unsigned, bool);
static void entropy_softintr(void *);
static void entropy_thread(void *);
static bool entropy_pending(void);
static void entropy_pending_cpu(void *, void *, struct cpu_info *);
static void entropy_do_consolidate(void);
static void entropy_consolidate_xc(void *, void *);
static void entropy_notify(void);
static int sysctl_entropy_consolidate(SYSCTLFN_ARGS);
static int sysctl_entropy_gather(SYSCTLFN_ARGS);
static void filt_entropy_read_detach(struct knote *);
static int filt_entropy_read_event(struct knote *, long);
static int entropy_request(size_t, int);
static void rnd_add_data_internal(struct krndsource *, const void *,
uint32_t, uint32_t, bool);
static void rnd_add_data_1(struct krndsource *, const void *, uint32_t,
uint32_t, bool, uint32_t, bool);
static unsigned rndsource_entropybits(struct krndsource *);
static void rndsource_entropybits_cpu(void *, void *, struct cpu_info *);
static void rndsource_to_user(struct krndsource *, rndsource_t *);
static void rndsource_to_user_est(struct krndsource *, rndsource_est_t *);
static void rndsource_to_user_est_cpu(void *, void *, struct cpu_info *);
static inline uint32_t
entropy_timer(void)
{
struct bintime bt;
uint32_t v;
#ifdef __HAVE_CPU_COUNTER
if (__predict_true(cpu_hascounter()))
return cpu_counter32();
#endif
if (__predict_false(cold))
return 0;
binuptime(&bt);
v = bt.frac;
v ^= bt.frac >> 32;
v ^= bt.sec;
v ^= bt.sec >> 32;
return v;
}
static void
attach_seed_rndsource(void)
{
KASSERT(!cpu_intr_p());
KASSERT(!cpu_softintr_p());
KASSERT(cold);
if (E->seed_rndsource)
return;
rnd_attach_source(&seed_rndsource, "seed", RND_TYPE_UNKNOWN,
RND_FLAG_COLLECT_VALUE);
E->seed_rndsource = true;
}
static void
entropy_init(void)
{
uint32_t extra[2];
struct krndsource *rs;
unsigned i = 0;
KASSERT(cold);
extra[i++] = entropy_timer();
if (entpool_selftest() == -1)
panic("entropy pool crypto self-test failed");
sysctl_createv(&entropy_sysctllog, 0, NULL, &entropy_sysctlroot,
CTLFLAG_PERMANENT, CTLTYPE_NODE, "entropy",
SYSCTL_DESCR("Entropy (random number sources) options"),
NULL, 0, NULL, 0,
CTL_KERN, KERN_ENTROPY, CTL_EOL);
sysctl_createv(&entropy_sysctllog, 0, &entropy_sysctlroot, NULL,
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_BOOL, "collection",
SYSCTL_DESCR("Automatically collect entropy from hardware"),
NULL, 0, &entropy_collection, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(&entropy_sysctllog, 0, &entropy_sysctlroot, NULL,
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_BOOL, "depletion",
SYSCTL_DESCR("`Deplete' entropy pool when observed"),
NULL, 0, &entropy_depletion, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(&entropy_sysctllog, 0, &entropy_sysctlroot, NULL,
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT, "consolidate",
SYSCTL_DESCR("Trigger entropy consolidation now"),
sysctl_entropy_consolidate, 0, NULL, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(&entropy_sysctllog, 0, &entropy_sysctlroot, NULL,
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT, "gather",
SYSCTL_DESCR("Trigger entropy gathering from sources now"),
sysctl_entropy_gather, 0, NULL, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(&entropy_sysctllog, 0, &entropy_sysctlroot, NULL,
CTLFLAG_PERMANENT|CTLFLAG_READONLY|CTLFLAG_PRIVATE, CTLTYPE_INT,
"needed",
SYSCTL_DESCR("Systemwide entropy deficit (bits of entropy)"),
NULL, 0, &E->bitsneeded, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(&entropy_sysctllog, 0, &entropy_sysctlroot, NULL,
CTLFLAG_PERMANENT|CTLFLAG_READONLY|CTLFLAG_PRIVATE, CTLTYPE_INT,
"pending",
SYSCTL_DESCR("Number of bits of entropy pending on CPUs"),
NULL, 0, &E->bitspending, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(&entropy_sysctllog, 0, &entropy_sysctlroot, NULL,
CTLFLAG_PERMANENT|CTLFLAG_READONLY|CTLFLAG_PRIVATE, CTLTYPE_INT,
"samplesneeded",
SYSCTL_DESCR("Systemwide entropy deficit (samples)"),
NULL, 0, &E->samplesneeded, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(&entropy_sysctllog, 0, &entropy_sysctlroot, NULL,
CTLFLAG_PERMANENT|CTLFLAG_READONLY|CTLFLAG_PRIVATE, CTLTYPE_INT,
"samplespending",
SYSCTL_DESCR("Number of samples pending on CPUs"),
NULL, 0, &E->samplespending, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(&entropy_sysctllog, 0, &entropy_sysctlroot, NULL,
CTLFLAG_PERMANENT|CTLFLAG_READONLY, CTLTYPE_INT,
"epoch", SYSCTL_DESCR("Entropy epoch"),
NULL, 0, &E->epoch, 0, KERN_ENTROPY_EPOCH, CTL_EOL);
mutex_init(&E->lock, MUTEX_DEFAULT, IPL_SOFTSERIAL);
cv_init(&E->cv, "entropy");
selinit(&E->selq);
cv_init(&E->sourcelock_cv, "entsrclock");
attach_seed_rndsource();
if (!E->seeded)
aprint_debug("entropy: no seed from bootloader\n");
LIST_FOREACH(rs, &E->sources, list)
rs->state = percpu_alloc(sizeof(struct rndsource_cpu));
entropy_percpu = percpu_create(sizeof(struct entropy_cpu),
entropy_init_cpu, entropy_fini_cpu, NULL);
extra[i++] = entropy_timer();
KASSERT(i == __arraycount(extra));
entropy_enter(extra, sizeof extra, 0, false);
explicit_memset(extra, 0, sizeof extra);
}
static void
entropy_init_late(void)
{
int error;
KASSERT(cold);
entropy_sih = softint_establish(SOFTINT_SERIAL|SOFTINT_MPSAFE,
&entropy_softintr, NULL);
if (entropy_sih == NULL)
panic("unable to establish entropy softint");
error = kthread_create(PRI_NONE, KTHREAD_MPSAFE|KTHREAD_TS, NULL,
entropy_thread, NULL, &entropy_lwp, "entbutler");
if (error)
panic("unable to create entropy housekeeping thread: %d",
error);
}
static void
entropy_init_cpu(void *ptr, void *cookie, struct cpu_info *ci)
{
struct entropy_cpu *ec = ptr;
const char *cpuname;
ec->ec_evcnt = kmem_alloc(sizeof(*ec->ec_evcnt), KM_SLEEP);
ec->ec_pool = kmem_zalloc(sizeof(*ec->ec_pool), KM_SLEEP);
ec->ec_bitspending = 0;
ec->ec_samplespending = 0;
ec->ec_locked = false;
cpuname = ci->ci_cpuname[0] == '\0' ? "cpu0" : ci->ci_cpuname;
evcnt_attach_dynamic(&ec->ec_evcnt->softint, EVCNT_TYPE_MISC, NULL,
cpuname, "entropy softint");
evcnt_attach_dynamic(&ec->ec_evcnt->intrdrop, EVCNT_TYPE_MISC, NULL,
cpuname, "entropy intrdrop");
evcnt_attach_dynamic(&ec->ec_evcnt->intrtrunc, EVCNT_TYPE_MISC, NULL,
cpuname, "entropy intrtrunc");
}
static void
entropy_fini_cpu(void *ptr, void *cookie, struct cpu_info *ci)
{
struct entropy_cpu *ec = ptr;
explicit_memset(ec->ec_pool, 0, sizeof(*ec->ec_pool));
evcnt_detach(&ec->ec_evcnt->intrtrunc);
evcnt_detach(&ec->ec_evcnt->intrdrop);
evcnt_detach(&ec->ec_evcnt->softint);
kmem_free(ec->ec_pool, sizeof(*ec->ec_pool));
kmem_free(ec->ec_evcnt, sizeof(*ec->ec_evcnt));
}
static struct entropy_cpu *
entropy_cpu_get(struct entropy_cpu_lock *lock)
{
struct entropy_cpu *ec;
ec = percpu_getref(entropy_percpu);
lock->ecl_s = splsoftserial();
KASSERT(!ec->ec_locked);
ec->ec_locked = true;
lock->ecl_pctr = lwp_pctr();
__insn_barrier();
return ec;
}
static void
entropy_cpu_put(struct entropy_cpu_lock *lock, struct entropy_cpu *ec)
{
KASSERT(ec == percpu_getptr_remote(entropy_percpu, curcpu()));
KASSERT(ec->ec_locked);
__insn_barrier();
KASSERT(lock->ecl_pctr == lwp_pctr());
ec->ec_locked = false;
splx(lock->ecl_s);
percpu_putref(entropy_percpu);
}
static void
entropy_seed(rndsave_t *seed)
{
SHA1_CTX ctx;
uint8_t digest[SHA1_DIGEST_LENGTH];
bool seeded;
KASSERT(!cpu_intr_p());
KASSERT(!cpu_softintr_p());
KASSERT(cold);
SHA1Init(&ctx);
SHA1Update(&ctx, (const void *)&seed->entropy, sizeof(seed->entropy));
SHA1Update(&ctx, seed->data, sizeof(seed->data));
SHA1Final(digest, &ctx);
CTASSERT(sizeof(seed->digest) == sizeof(digest));
if (!consttime_memequal(digest, seed->digest, sizeof(digest))) {
printf("entropy: invalid seed checksum\n");
seed->entropy = 0;
}
explicit_memset(&ctx, 0, sizeof ctx);
explicit_memset(digest, 0, sizeof digest);
if (howmany(seed->entropy, NBBY) > sizeof(seed->data)) {
seed->entropy = bswap32(seed->entropy);
if (howmany(seed->entropy, NBBY) > sizeof(seed->data))
seed->entropy = 0;
}
attach_seed_rndsource();
seeded = E->seeded;
E->seeded = (seed->entropy > 0);
if (seeded) {
printf("entropy: double-seeded by bootloader\n");
seed->entropy = 0;
} else {
printf("entropy: entering seed from bootloader"
" with %u bits of entropy\n", (unsigned)seed->entropy);
}
rnd_add_data(&seed_rndsource, seed->data, sizeof(seed->data),
seed->entropy);
explicit_memset(seed, 0, sizeof(*seed));
}
void
entropy_bootrequest(void)
{
int error;
KASSERT(!cpu_intr_p());
KASSERT(!cpu_softintr_p());
KASSERT(cold);
error = entropy_request(MINENTROPYBYTES, ENTROPY_WAIT);
KASSERTMSG(error == 0, "error=%d", error);
}
unsigned
entropy_epoch(void)
{
return atomic_load_relaxed(&E->epoch);
}
bool
entropy_ready(void)
{
return atomic_load_relaxed(&E->bitsneeded) == 0;
}
static void
entropy_account_cpu(struct entropy_cpu *ec)
{
struct entropy_cpu_lock lock;
struct entropy_cpu *ec0;
unsigned bitsdiff, samplesdiff;
KASSERT(!cpu_intr_p());
KASSERT(!cold);
KASSERT(curlwp->l_pflag & LP_BOUND);
if (__predict_true(atomic_load_relaxed(&E->bitsneeded) == 0) &&
__predict_true(!atomic_load_relaxed(&entropy_depletion)) &&
__predict_true((time_uptime - E->timestamp) <= 60))
return;
mutex_enter(&E->lock);
ec0 = entropy_cpu_get(&lock);
KASSERT(ec0 == ec);
if (ec->ec_bitspending == 0 && ec->ec_samplespending == 0) {
} else if (E->bitsneeded != 0 && E->bitsneeded <= ec->ec_bitspending) {
uint8_t buf[ENTPOOL_CAPACITY];
entpool_extract(ec->ec_pool, buf, sizeof buf);
entpool_enter(&E->pool, buf, sizeof buf);
atomic_store_relaxed(&ec->ec_bitspending, 0);
atomic_store_relaxed(&ec->ec_samplespending, 0);
atomic_store_relaxed(&E->bitsneeded, 0);
atomic_store_relaxed(&E->samplesneeded, 0);
entropy_notify();
entropy_immediate_evcnt.ev_count++;
} else {
KASSERTMSG(E->bitspending <= MINENTROPYBITS,
"E->bitspending=%u", E->bitspending);
bitsdiff = MIN(ec->ec_bitspending,
MINENTROPYBITS - E->bitspending);
KASSERTMSG(E->samplespending <= MINSAMPLES,
"E->samplespending=%u", E->samplespending);
samplesdiff = MIN(ec->ec_samplespending,
MINSAMPLES - E->samplespending);
KASSERTMSG((bitsdiff || samplesdiff ||
E->bitspending == MINENTROPYBITS ||
E->samplespending == MINSAMPLES),
"bitsdiff=%u E->bitspending=%u ec->ec_bitspending=%u"
"samplesdiff=%u E->samplespending=%u"
" ec->ec_samplespending=%u"
" minentropybits=%u minsamples=%u",
bitsdiff, E->bitspending, ec->ec_bitspending,
samplesdiff, E->samplespending, ec->ec_samplespending,
(unsigned)MINENTROPYBITS, (unsigned)MINSAMPLES);
E->bitspending += bitsdiff;
KASSERTMSG(E->bitspending <= MINENTROPYBITS,
"E->bitspending=%u", E->bitspending);
atomic_store_relaxed(&ec->ec_bitspending,
ec->ec_bitspending - bitsdiff);
E->samplespending += samplesdiff;
KASSERTMSG(E->samplespending <= MINSAMPLES,
"E->samplespending=%u", E->samplespending);
atomic_store_relaxed(&ec->ec_samplespending,
ec->ec_samplespending - samplesdiff);
KASSERT(E->bitspending || E->samplespending);
if (E->bitsneeded <= E->bitspending ||
E->samplesneeded <= E->samplespending) {
E->consolidate = true;
if (E->epoch == (unsigned)-1)
cv_broadcast(&E->cv);
if (E->bitsneeded == 0)
entropy_discretionary_evcnt.ev_count++;
} else {
entropy_partial_evcnt.ev_count++;
}
}
entropy_cpu_put(&lock, ec);
mutex_exit(&E->lock);
}
static void
entropy_enter_early(const void *buf, size_t len, unsigned nbits)
{
bool notify = false;
int s;
KASSERT(cold);
s = splhigh();
entpool_enter(&E->pool, buf, len);
notify |= (E->bitsneeded && E->bitsneeded <= nbits);
notify |= (nbits >= MINENTROPYBITS);
E->bitsneeded -= MIN(E->bitsneeded, nbits);
if (notify) {
entropy_notify();
entropy_immediate_evcnt.ev_count++;
}
splx(s);
}
static void
entropy_enter(const void *buf, size_t len, unsigned nbits, bool count)
{
struct entropy_cpu_lock lock;
struct entropy_cpu *ec;
unsigned bitspending, samplespending;
int bound;
KASSERTMSG(!cpu_intr_p(),
"use entropy_enter_intr from interrupt context");
KASSERTMSG(howmany(nbits, NBBY) <= len,
"impossible entropy rate: %u bits in %zu-byte string", nbits, len);
if (__predict_false(cold)) {
entropy_enter_early(buf, len, nbits);
return;
}
bound = curlwp_bind();
ec = entropy_cpu_get(&lock);
entpool_enter(ec->ec_pool, buf, len);
bitspending = ec->ec_bitspending;
bitspending += MIN(MINENTROPYBITS - bitspending, nbits);
atomic_store_relaxed(&ec->ec_bitspending, bitspending);
samplespending = ec->ec_samplespending;
if (__predict_true(count)) {
samplespending += MIN(MINSAMPLES - samplespending, 1);
atomic_store_relaxed(&ec->ec_samplespending, samplespending);
}
entropy_cpu_put(&lock, ec);
if (bitspending > 0 || samplespending >= MINSAMPLES)
entropy_account_cpu(ec);
curlwp_bindx(bound);
}
static bool
entropy_enter_intr(const void *buf, size_t len, unsigned nbits, bool count)
{
struct entropy_cpu *ec;
bool fullyused = false;
uint32_t bitspending, samplespending;
int s;
KASSERTMSG(howmany(nbits, NBBY) <= len,
"impossible entropy rate: %u bits in %zu-byte string", nbits, len);
if (__predict_false(cold)) {
entropy_enter_early(buf, len, nbits);
return true;
}
s = splsoftserial();
ec = percpu_getref(entropy_percpu);
if (ec->ec_locked) {
ec->ec_evcnt->intrdrop.ev_count++;
goto out0;
}
ec->ec_locked = true;
__insn_barrier();
if (!entpool_enter_nostir(ec->ec_pool, buf, len)) {
if (__predict_true(!cold))
softint_schedule(entropy_sih);
ec->ec_evcnt->intrtrunc.ev_count++;
goto out1;
}
fullyused = true;
bitspending = ec->ec_bitspending;
bitspending += MIN(MINENTROPYBITS - bitspending, nbits);
atomic_store_relaxed(&ec->ec_bitspending, bitspending);
if (__predict_true(count)) {
samplespending = ec->ec_samplespending;
samplespending += MIN(MINSAMPLES - samplespending, 1);
atomic_store_relaxed(&ec->ec_samplespending, samplespending);
}
if (__predict_false(atomic_load_relaxed(&E->bitsneeded) ||
atomic_load_relaxed(&entropy_depletion)) &&
(nbits != 0 || count) &&
__predict_true(!cold))
softint_schedule(entropy_sih);
out1:
KASSERT(ec->ec_locked);
__insn_barrier();
ec->ec_locked = false;
out0: percpu_putref(entropy_percpu);
splx(s);
return fullyused;
}
static void
entropy_softintr(void *cookie)
{
struct entropy_cpu_lock lock;
struct entropy_cpu *ec;
unsigned bitspending, samplespending;
ec = entropy_cpu_get(&lock);
ec->ec_evcnt->softint.ev_count++;
entpool_stir(ec->ec_pool);
bitspending = ec->ec_bitspending;
samplespending = ec->ec_samplespending;
entropy_cpu_put(&lock, ec);
if (bitspending > 0 || samplespending >= MINSAMPLES)
entropy_account_cpu(ec);
}
static void
entropy_thread(void *cookie)
{
bool consolidate;
#ifndef _RUMPKERNEL
KASSERT(!cold);
#endif
for (;;) {
if (entropy_pending()) {
consolidate = true;
} else {
mutex_enter(&E->lock);
if (!E->consolidate)
cv_timedwait(&E->cv, &E->lock, 60*hz);
consolidate = E->consolidate;
E->consolidate = false;
mutex_exit(&E->lock);
}
if (consolidate) {
entropy_do_consolidate();
kpause("entropy", false, hz, NULL);
}
}
}
struct entropy_pending_count {
uint32_t bitspending;
uint32_t samplespending;
};
static bool
entropy_pending(void)
{
struct entropy_pending_count count = { 0, 0 }, *C = &count;
percpu_foreach(entropy_percpu, &entropy_pending_cpu, C);
return C->bitspending >= MINENTROPYBITS ||
C->samplespending >= MINSAMPLES;
}
static void
entropy_pending_cpu(void *ptr, void *cookie, struct cpu_info *ci)
{
struct entropy_cpu *ec = ptr;
struct entropy_pending_count *C = cookie;
uint32_t cpu_bitspending;
uint32_t cpu_samplespending;
cpu_bitspending = atomic_load_relaxed(&ec->ec_bitspending);
cpu_samplespending = atomic_load_relaxed(&ec->ec_samplespending);
C->bitspending += MIN(MINENTROPYBITS - C->bitspending,
cpu_bitspending);
C->samplespending += MIN(MINSAMPLES - C->samplespending,
cpu_samplespending);
}
static void
entropy_do_consolidate(void)
{
static const struct timeval interval = {.tv_sec = 60, .tv_usec = 0};
static struct timeval lasttime;
struct entpool pool;
uint8_t buf[ENTPOOL_CAPACITY];
unsigned bitsdiff, samplesdiff;
uint64_t ticket;
KASSERT(!cold);
ASSERT_SLEEPABLE();
memset(&pool, 0, sizeof pool);
ticket = xc_broadcast(0, &entropy_consolidate_xc, &pool, NULL);
xc_wait(ticket);
mutex_enter(&E->lock);
entropy_consolidate_evcnt.ev_count++;
E->timestamp = time_uptime;
entpool_extract(&pool, buf, sizeof buf);
entpool_enter(&E->pool, buf, sizeof buf);
explicit_memset(&pool, 0, sizeof pool);
bitsdiff = MIN(E->bitsneeded, E->bitspending);
atomic_store_relaxed(&E->bitsneeded, E->bitsneeded - bitsdiff);
E->bitspending -= bitsdiff;
if (__predict_false(E->bitsneeded > 0) && bitsdiff != 0) {
if ((boothowto & AB_DEBUG) != 0 &&
ratecheck(&lasttime, &interval)) {
printf("WARNING:"
" consolidating less than full entropy\n");
}
}
samplesdiff = MIN(E->samplesneeded, E->samplespending);
atomic_store_relaxed(&E->samplesneeded,
E->samplesneeded - samplesdiff);
E->samplespending -= samplesdiff;
entropy_notify();
mutex_exit(&E->lock);
}
static void
entropy_consolidate_xc(void *vpool, void *arg2 __unused)
{
struct entpool *pool = vpool;
struct entropy_cpu_lock lock;
struct entropy_cpu *ec;
uint8_t buf[ENTPOOL_CAPACITY];
uint32_t extra[7];
unsigned i = 0;
extra[i++] = cpu_number();
extra[i++] = entropy_timer();
ec = entropy_cpu_get(&lock);
extra[i++] = entropy_timer();
entpool_extract(ec->ec_pool, buf, sizeof buf);
atomic_store_relaxed(&ec->ec_bitspending, 0);
atomic_store_relaxed(&ec->ec_samplespending, 0);
extra[i++] = entropy_timer();
entropy_cpu_put(&lock, ec);
extra[i++] = entropy_timer();
mutex_enter(&E->lock);
extra[i++] = entropy_timer();
entpool_enter(pool, buf, sizeof buf);
explicit_memset(buf, 0, sizeof buf);
extra[i++] = entropy_timer();
KASSERT(i == __arraycount(extra));
entpool_enter(pool, extra, sizeof extra);
explicit_memset(extra, 0, sizeof extra);
mutex_exit(&E->lock);
}
static void
entropy_notify(void)
{
static const struct timeval interval = {.tv_sec = 60, .tv_usec = 0};
static struct timeval lasttime;
static bool ready = false, besteffort = false;
unsigned epoch;
KASSERT(__predict_false(cold) || mutex_owned(&E->lock));
if (__predict_false(!ready)) {
if (E->bitsneeded == 0) {
printf("entropy: ready\n");
ready = true;
} else if (E->samplesneeded == 0 && !besteffort) {
printf("entropy: best effort\n");
besteffort = true;
}
}
if (__predict_true(!atomic_load_relaxed(&entropy_depletion)) ||
ratecheck(&lasttime, &interval)) {
epoch = E->epoch + 1;
if (epoch == 0 || epoch == (unsigned)-1)
epoch = 1;
atomic_store_relaxed(&E->epoch, epoch);
}
KASSERT(E->epoch != (unsigned)-1);
if (__predict_true(!cold)) {
cv_broadcast(&E->cv);
selnotify(&E->selq, POLLIN|POLLRDNORM, NOTE_SUBMIT);
}
entropy_notify_evcnt.ev_count++;
}
int
entropy_consolidate(void)
{
uint64_t ticket;
int error;
KASSERT(!cold);
ASSERT_SLEEPABLE();
mutex_enter(&E->lock);
ticket = entropy_consolidate_evcnt.ev_count;
E->consolidate = true;
cv_broadcast(&E->cv);
while (ticket == entropy_consolidate_evcnt.ev_count) {
error = cv_wait_sig(&E->cv, &E->lock);
if (error)
break;
}
mutex_exit(&E->lock);
return error;
}
static int
sysctl_entropy_consolidate(SYSCTLFN_ARGS)
{
struct sysctlnode node = *rnode;
int arg = 0;
int error;
node.sysctl_data = &arg;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
if (arg)
error = entropy_consolidate();
return error;
}
int
entropy_gather(void)
{
int error;
mutex_enter(&E->lock);
error = entropy_request(ENTROPY_CAPACITY, ENTROPY_WAIT|ENTROPY_SIG);
mutex_exit(&E->lock);
return error;
}
static int
sysctl_entropy_gather(SYSCTLFN_ARGS)
{
struct sysctlnode node = *rnode;
int arg = 0;
int error;
node.sysctl_data = &arg;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
if (arg)
error = entropy_gather();
return error;
}
int
entropy_extract(void *buf, size_t len, int flags)
{
static const struct timeval interval = {.tv_sec = 60, .tv_usec = 0};
static struct timeval lasttime;
bool printed = false;
int s = -1, error;
if (ISSET(flags, ENTROPY_WAIT)) {
ASSERT_SLEEPABLE();
KASSERT(!cold);
}
KASSERT(!cpu_intr_p());
if (__predict_false(cold))
s = splhigh();
else
mutex_enter(&E->lock);
error = 0;
if (E->bitsneeded > 0 && E->samplesneeded == 0) {
(void)entropy_request(ENTROPY_CAPACITY, flags);
} else while (E->bitsneeded > 0 && E->samplesneeded > 0) {
error = entropy_request(len, flags);
if (error)
break;
if (E->bitsneeded == 0 || E->samplesneeded == 0) {
KASSERT(error == 0);
break;
}
if (!ISSET(flags, ENTROPY_WAIT)) {
error = SET_ERROR(EWOULDBLOCK);
break;
}
KASSERT(!cold);
if (!printed) {
printf("entropy: pid %d (%s) waiting for entropy(7)\n",
curproc->p_pid, curproc->p_comm);
printed = true;
}
if (ISSET(flags, ENTROPY_SIG)) {
error = cv_timedwait_sig(&E->cv, &E->lock, hz);
if (error && error != EWOULDBLOCK)
break;
} else {
cv_timedwait(&E->cv, &E->lock, hz);
}
}
if (error) {
if (ISSET(flags, ENTROPY_HARDFAIL))
goto out;
entropy_extract_fail_evcnt.ev_count++;
}
if (E->bitsneeded > 0 && E->samplesneeded > 0) {
if (__predict_false(E->epoch == (unsigned)-1) &&
ratecheck(&lasttime, &interval)) {
printf("WARNING:"
" system needs entropy for security;"
" see entropy(7)\n");
}
atomic_store_relaxed(&E->bitsneeded, MINENTROPYBITS);
atomic_store_relaxed(&E->samplesneeded, MINSAMPLES);
}
entpool_extract(&E->pool, buf, len);
if (__predict_false(atomic_load_relaxed(&entropy_depletion)) &&
error == 0) {
unsigned cost = MIN(len, ENTROPY_CAPACITY)*NBBY;
unsigned bitsneeded = E->bitsneeded;
unsigned samplesneeded = E->samplesneeded;
bitsneeded += MIN(MINENTROPYBITS - bitsneeded, cost);
samplesneeded += MIN(MINSAMPLES - samplesneeded, cost);
atomic_store_relaxed(&E->bitsneeded, bitsneeded);
atomic_store_relaxed(&E->samplesneeded, samplesneeded);
entropy_deplete_evcnt.ev_count++;
}
out:
if (__predict_false(cold))
splx(s);
else
mutex_exit(&E->lock);
return error;
}
int
entropy_poll(int events)
{
int revents = 0;
KASSERT(!cold);
revents |= events & (POLLOUT|POLLWRNORM);
events &= POLLIN|POLLRDNORM;
if (events == 0)
return revents;
if (__predict_true(atomic_load_relaxed(&E->bitsneeded) == 0 ||
atomic_load_relaxed(&E->samplesneeded) == 0) &&
__predict_true(!atomic_load_relaxed(&entropy_depletion)))
return revents | events;
mutex_enter(&E->lock);
if (E->bitsneeded == 0 || E->samplesneeded == 0)
revents |= events;
else
selrecord(curlwp, &E->selq);
mutex_exit(&E->lock);
return revents;
}
static void
filt_entropy_read_detach(struct knote *kn)
{
KASSERT(!cold);
mutex_enter(&E->lock);
selremove_knote(&E->selq, kn);
mutex_exit(&E->lock);
}
static int
filt_entropy_read_event(struct knote *kn, long hint)
{
int ret;
KASSERT(!cold);
if (hint == NOTE_SUBMIT)
KASSERT(mutex_owned(&E->lock));
else
mutex_enter(&E->lock);
if (E->bitsneeded != 0 && E->samplesneeded != 0) {
ret = 0;
} else {
if (atomic_load_relaxed(&entropy_depletion))
kn->kn_data = ENTROPY_CAPACITY;
else
kn->kn_data = MIN(INT64_MAX, SSIZE_MAX);
ret = 1;
}
if (hint == NOTE_SUBMIT)
KASSERT(mutex_owned(&E->lock));
else
mutex_exit(&E->lock);
return ret;
}
static const struct filterops entropy_read_filtops = {
.f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE,
.f_attach = NULL,
.f_detach = filt_entropy_read_detach,
.f_event = filt_entropy_read_event,
};
int
entropy_kqfilter(struct knote *kn)
{
KASSERT(!cold);
switch (kn->kn_filter) {
case EVFILT_READ:
mutex_enter(&E->lock);
kn->kn_fop = &entropy_read_filtops;
selrecord_knote(&E->selq, kn);
mutex_exit(&E->lock);
return 0;
case EVFILT_WRITE:
kn->kn_fop = &seltrue_filtops;
return 0;
default:
return SET_ERROR(EINVAL);
}
}
void
rndsource_setcb(struct krndsource *rs, void (*get)(size_t, void *),
void *getarg)
{
rs->get = get;
rs->getarg = getarg;
}
void
rnd_attach_source(struct krndsource *rs, const char *name, uint32_t type,
uint32_t flags)
{
uint32_t extra[4];
unsigned i = 0;
KASSERTMSG(name[0] != '\0', "rndsource must have nonempty name");
extra[i++] = entropy_timer();
switch (type) {
case RND_TYPE_NET:
flags |= RND_FLAG_NO_COLLECT;
break;
}
KASSERT(!ISSET(flags, RND_FLAG_HASCB) || rs->get != NULL);
memset(rs->name, 0, sizeof(rs->name));
strlcpy(rs->name, name, sizeof(rs->name));
memset(&rs->time_delta, 0, sizeof(rs->time_delta));
memset(&rs->value_delta, 0, sizeof(rs->value_delta));
rs->total = 0;
rs->type = type;
rs->flags = flags;
if (entropy_percpu != NULL)
rs->state = percpu_alloc(sizeof(struct rndsource_cpu));
extra[i++] = entropy_timer();
if (__predict_true(!cold))
mutex_enter(&E->lock);
LIST_INSERT_HEAD(&E->sources, rs, list);
if (__predict_true(!cold))
mutex_exit(&E->lock);
extra[i++] = entropy_timer();
if (ISSET(flags, RND_FLAG_HASCB))
(*rs->get)(ENTROPY_CAPACITY, rs->getarg);
extra[i++] = entropy_timer();
KASSERT(i == __arraycount(extra));
entropy_enter(extra, sizeof extra, 0, __predict_true(!cold));
explicit_memset(extra, 0, sizeof extra);
}
void
rnd_detach_source(struct krndsource *rs)
{
if (__predict_false(cold) && entropy_percpu == NULL) {
LIST_REMOVE(rs, list);
return;
}
ASSERT_SLEEPABLE();
mutex_enter(&E->lock);
while (E->sourcelock)
cv_wait(&E->sourcelock_cv, &E->lock);
LIST_REMOVE(rs, list);
mutex_exit(&E->lock);
percpu_free(rs->state, sizeof(struct rndsource_cpu));
}
static int __attribute__((warn_unused_result))
rnd_lock_sources(int flags)
{
int error;
KASSERT(__predict_false(cold) || mutex_owned(&E->lock));
KASSERT(!cpu_intr_p());
while (E->sourcelock) {
KASSERT(!cold);
if (!ISSET(flags, ENTROPY_WAIT))
return SET_ERROR(EWOULDBLOCK);
if (ISSET(flags, ENTROPY_SIG)) {
error = cv_wait_sig(&E->sourcelock_cv, &E->lock);
if (error)
return error;
} else {
cv_wait(&E->sourcelock_cv, &E->lock);
}
}
E->sourcelock = curlwp;
return 0;
}
static void
rnd_unlock_sources(void)
{
KASSERT(__predict_false(cold) || mutex_owned(&E->lock));
KASSERT(!cpu_intr_p());
KASSERTMSG(E->sourcelock == curlwp, "lwp %p releasing lock held by %p",
curlwp, E->sourcelock);
E->sourcelock = NULL;
if (__predict_true(!cold))
cv_signal(&E->sourcelock_cv);
}
static bool __diagused
rnd_sources_locked(void)
{
return E->sourcelock == curlwp;
}
static int
entropy_request(size_t nbytes, int flags)
{
struct krndsource *rs;
int error;
KASSERT(__predict_false(cold) || mutex_owned(&E->lock));
KASSERT(!cpu_intr_p());
if ((flags & ENTROPY_WAIT) != 0 && __predict_false(!cold))
ASSERT_SLEEPABLE();
error = rnd_lock_sources(flags);
if (error)
return error;
entropy_request_evcnt.ev_count++;
nbytes = MIN(nbytes, ENTROPY_CAPACITY);
LIST_FOREACH(rs, &E->sources, list) {
if (!ISSET(rs->flags, RND_FLAG_HASCB))
continue;
if (ISSET(rs->flags, RND_FLAG_NO_COLLECT))
continue;
if (__predict_true(!cold))
mutex_exit(&E->lock);
(*rs->get)(nbytes, rs->getarg);
if (__predict_true(!cold))
mutex_enter(&E->lock);
}
rnd_unlock_sources();
return 0;
}
static inline uint32_t
rnd_delta_estimate(rnd_delta_t *d, uint32_t v, int32_t delta)
{
int32_t delta2, delta3;
delta2 = d->dx - delta;
if (delta2 < 0)
delta2 = -delta2;
delta3 = d->d2x - delta2;
if (delta3 < 0)
delta3 = -delta3;
d->x = v;
d->dx = delta;
d->d2x = delta2;
if (delta == 0 || delta2 == 0 || delta3 == 0)
return 0;
return 1;
}
static inline uint32_t
rnd_dt_estimate(struct krndsource *rs, uint32_t t)
{
int32_t delta;
uint32_t ret;
rnd_delta_t *d;
struct rndsource_cpu *rc;
rc = percpu_getref(rs->state);
d = &rc->rc_timedelta;
if (t < d->x) {
delta = UINT32_MAX - d->x + t;
} else {
delta = d->x - t;
}
if (delta < 0) {
delta = -delta;
}
ret = rnd_delta_estimate(d, t, delta);
KASSERT(d->x == t);
KASSERT(d->dx == delta);
percpu_putref(rs->state);
return ret;
}
void
rnd_add_uint32(struct krndsource *rs, uint32_t value)
{
bool intr_p = true;
rnd_add_data_internal(rs, &value, sizeof value, 0, intr_p);
}
void
_rnd_add_uint32(struct krndsource *rs, uint32_t value)
{
bool intr_p = true;
rnd_add_data_internal(rs, &value, sizeof value, 0, intr_p);
}
void
_rnd_add_uint64(struct krndsource *rs, uint64_t value)
{
bool intr_p = true;
rnd_add_data_internal(rs, &value, sizeof value, 0, intr_p);
}
void
rnd_add_data(struct krndsource *rs, const void *buf, uint32_t len,
uint32_t entropybits)
{
bool intr_p = cpu_intr_p();
if (rs == NULL) {
uint32_t extra;
KASSERT(!intr_p);
KASSERTMSG(howmany(entropybits, NBBY) <= len,
"%s: impossible entropy rate:"
" %"PRIu32" bits in %"PRIu32"-byte string",
rs ? rs->name : "(anonymous)", entropybits, len);
entropy_enter(buf, len, entropybits, false);
extra = entropy_timer();
entropy_enter(&extra, sizeof extra, 0, false);
explicit_memset(&extra, 0, sizeof extra);
return;
}
rnd_add_data_internal(rs, buf, len, entropybits, intr_p);
}
void
rnd_add_data_intr(struct krndsource *rs, const void *buf, uint32_t len,
uint32_t entropybits)
{
bool intr_p = true;
rnd_add_data_internal(rs, buf, len, entropybits, intr_p);
}
static void
rnd_add_data_internal(struct krndsource *rs, const void *buf, uint32_t len,
uint32_t entropybits, bool intr_p)
{
uint32_t flags;
KASSERTMSG(howmany(entropybits, NBBY) <= len,
"%s: impossible entropy rate:"
" %"PRIu32" bits in %"PRIu32"-byte string",
rs ? rs->name : "(anonymous)", entropybits, len);
kpreempt_disable();
flags = atomic_load_relaxed(&rs->flags);
if (!atomic_load_relaxed(&entropy_collection) ||
ISSET(flags, RND_FLAG_NO_COLLECT) ||
!ISSET(flags, RND_FLAG_COLLECT_VALUE|RND_FLAG_COLLECT_TIME))
goto out;
if (ISSET(flags, RND_FLAG_NO_ESTIMATE))
entropybits = 0;
if (ISSET(flags, RND_FLAG_COLLECT_VALUE)) {
rnd_add_data_1(rs, buf, len, entropybits, false,
RND_FLAG_COLLECT_VALUE, intr_p);
}
if (ISSET(flags, RND_FLAG_COLLECT_TIME)) {
uint32_t extra;
bool count;
extra = entropy_timer();
if ((flags & (RND_FLAG_ESTIMATE_TIME|RND_FLAG_NO_ESTIMATE)) ==
RND_FLAG_ESTIMATE_TIME && __predict_true(!cold))
count = rnd_dt_estimate(rs, extra);
else
count = false;
rnd_add_data_1(rs, &extra, sizeof extra, 0, count,
RND_FLAG_COLLECT_TIME, intr_p);
}
out:
kpreempt_enable();
}
static unsigned
add_sat(unsigned a, unsigned b)
{
unsigned c = a + b;
return (c < a ? UINT_MAX : c);
}
static void
rnd_add_data_1(struct krndsource *rs, const void *buf, uint32_t len,
uint32_t entropybits, bool count, uint32_t flag, bool intr_p)
{
bool fullyused;
if (intr_p) {
fullyused = entropy_enter_intr(buf, len, entropybits, count);
} else {
entropy_enter(buf, len, entropybits, count);
fullyused = true;
}
if (fullyused) {
if (__predict_false(cold)) {
const int s = splhigh();
rs->total = add_sat(rs->total, entropybits);
switch (flag) {
case RND_FLAG_COLLECT_TIME:
rs->time_delta.insamples =
add_sat(rs->time_delta.insamples, 1);
break;
case RND_FLAG_COLLECT_VALUE:
rs->value_delta.insamples =
add_sat(rs->value_delta.insamples, 1);
break;
}
splx(s);
} else {
struct rndsource_cpu *rc = percpu_getref(rs->state);
atomic_store_relaxed(&rc->rc_entropybits,
add_sat(rc->rc_entropybits, entropybits));
switch (flag) {
case RND_FLAG_COLLECT_TIME:
atomic_store_relaxed(&rc->rc_timesamples,
add_sat(rc->rc_timesamples, 1));
break;
case RND_FLAG_COLLECT_VALUE:
atomic_store_relaxed(&rc->rc_datasamples,
add_sat(rc->rc_datasamples, 1));
break;
}
percpu_putref(rs->state);
}
}
}
void
rnd_add_data_sync(struct krndsource *rs, const void *buf, uint32_t len,
uint32_t entropybits)
{
rnd_add_data(rs, buf, len, entropybits);
}
static unsigned
rndsource_entropybits(struct krndsource *rs)
{
unsigned nbits = rs->total;
KASSERT(!cold);
KASSERT(rnd_sources_locked());
percpu_foreach(rs->state, rndsource_entropybits_cpu, &nbits);
return nbits;
}
static void
rndsource_entropybits_cpu(void *ptr, void *cookie, struct cpu_info *ci)
{
struct rndsource_cpu *rc = ptr;
unsigned *nbitsp = cookie;
unsigned cpu_nbits;
cpu_nbits = atomic_load_relaxed(&rc->rc_entropybits);
*nbitsp += MIN(UINT_MAX - *nbitsp, cpu_nbits);
}
static void
rndsource_to_user(struct krndsource *rs, rndsource_t *urs)
{
KASSERT(!cold);
KASSERT(rnd_sources_locked());
memset(urs, 0, sizeof(*urs));
CTASSERT(sizeof(urs->name) == sizeof(rs->name));
strlcpy(urs->name, rs->name, sizeof(urs->name));
urs->total = rndsource_entropybits(rs);
urs->type = rs->type;
urs->flags = atomic_load_relaxed(&rs->flags);
}
static void
rndsource_to_user_est(struct krndsource *rs, rndsource_est_t *urse)
{
KASSERT(!cold);
KASSERT(rnd_sources_locked());
memset(urse, 0, sizeof(*urse));
rndsource_to_user(rs, &urse->rt);
urse->dt_samples = rs->time_delta.insamples;
urse->dt_total = 0;
urse->dv_samples = rs->value_delta.insamples;
urse->dv_total = urse->rt.total;
percpu_foreach(rs->state, rndsource_to_user_est_cpu, urse);
}
static void
rndsource_to_user_est_cpu(void *ptr, void *cookie, struct cpu_info *ci)
{
struct rndsource_cpu *rc = ptr;
rndsource_est_t *urse = cookie;
urse->dt_samples = add_sat(urse->dt_samples,
atomic_load_relaxed(&rc->rc_timesamples));
urse->dv_samples = add_sat(urse->dv_samples,
atomic_load_relaxed(&rc->rc_datasamples));
}
static void
entropy_reset_xc(void *arg1 __unused, void *arg2 __unused)
{
uint32_t extra = entropy_timer();
struct entropy_cpu_lock lock;
struct entropy_cpu *ec;
ec = entropy_cpu_get(&lock);
ec->ec_bitspending = 0;
ec->ec_samplespending = 0;
entpool_enter(ec->ec_pool, &extra, sizeof extra);
entropy_cpu_put(&lock, ec);
}
void
entropy_reset(void)
{
xc_broadcast(0, &entropy_reset_xc, NULL, NULL);
mutex_enter(&E->lock);
E->bitspending = 0;
E->samplespending = 0;
atomic_store_relaxed(&E->bitsneeded, MINENTROPYBITS);
atomic_store_relaxed(&E->samplesneeded, MINSAMPLES);
E->consolidate = false;
mutex_exit(&E->lock);
}
int
entropy_ioctl(unsigned long cmd, void *data)
{
struct krndsource *rs;
bool privileged;
int error;
KASSERT(!cold);
switch (cmd) {
case RNDGETENTCNT:
case RNDGETPOOLSTAT:
case RNDGETSRCNUM:
case RNDGETSRCNAME:
case RNDGETESTNUM:
case RNDGETESTNAME:
error = kauth_authorize_device(kauth_cred_get(),
KAUTH_DEVICE_RND_GETPRIV, NULL, NULL, NULL, NULL);
break;
case RNDCTL:
error = kauth_authorize_device(kauth_cred_get(),
KAUTH_DEVICE_RND_SETPRIV, NULL, NULL, NULL, NULL);
break;
case RNDADDDATA:
error = kauth_authorize_device(kauth_cred_get(),
KAUTH_DEVICE_RND_ADDDATA, NULL, NULL, NULL, NULL);
if (kauth_authorize_device(kauth_cred_get(),
KAUTH_DEVICE_RND_ADDDATA_ESTIMATE,
NULL, NULL, NULL, NULL) == 0)
privileged = true;
break;
default: {
static const struct fileops fops = {
.fo_ioctl = rnd_system_ioctl,
};
struct file f = {
.f_ops = &fops,
};
MODULE_HOOK_CALL(rnd_ioctl_50_hook, (&f, cmd, data),
enosys(), error);
#if defined(_LP64)
if (error == ENOSYS)
MODULE_HOOK_CALL(rnd_ioctl32_50_hook, (&f, cmd, data),
enosys(), error);
#endif
if (error == ENOSYS)
error = SET_ERROR(ENOTTY);
break;
}
}
if (error)
return error;
switch (cmd) {
case RNDGETENTCNT: {
uint32_t *countp = data;
mutex_enter(&E->lock);
*countp = MINENTROPYBITS - E->bitsneeded;
mutex_exit(&E->lock);
break;
}
case RNDGETPOOLSTAT: {
rndpoolstat_t *pstat = data;
mutex_enter(&E->lock);
pstat->poolsize = ENTPOOL_SIZE/sizeof(uint32_t);
pstat->threshold = MINENTROPYBITS/NBBY;
pstat->maxentropy = ENTROPY_CAPACITY*NBBY;
pstat->added = 0;
pstat->curentropy = MINENTROPYBITS - E->bitsneeded;
pstat->removed = 0;
pstat->discarded = 0;
pstat->generated = MINSAMPLES - E->samplesneeded;
pstat->generated -= MIN(pstat->generated, pstat->curentropy);
mutex_exit(&E->lock);
break;
}
case RNDGETSRCNUM: {
rndstat_t *stat = data;
uint32_t start = 0, i = 0;
if (stat->count == 0)
break;
if (stat->count > RND_MAXSTATCOUNT)
return SET_ERROR(EINVAL);
mutex_enter(&E->lock);
error = rnd_lock_sources(ENTROPY_WAIT|ENTROPY_SIG);
if (error) {
mutex_exit(&E->lock);
return error;
}
LIST_FOREACH(rs, &E->sources, list) {
if (start++ == stat->start)
break;
}
while (i < stat->count && rs != NULL) {
mutex_exit(&E->lock);
rndsource_to_user(rs, &stat->source[i++]);
mutex_enter(&E->lock);
rs = LIST_NEXT(rs, list);
}
KASSERT(i <= stat->count);
stat->count = i;
rnd_unlock_sources();
mutex_exit(&E->lock);
break;
}
case RNDGETESTNUM: {
rndstat_est_t *estat = data;
uint32_t start = 0, i = 0;
if (estat->count == 0)
break;
if (estat->count > RND_MAXSTATCOUNT)
return SET_ERROR(EINVAL);
mutex_enter(&E->lock);
error = rnd_lock_sources(ENTROPY_WAIT|ENTROPY_SIG);
if (error) {
mutex_exit(&E->lock);
return error;
}
LIST_FOREACH(rs, &E->sources, list) {
if (start++ == estat->start)
break;
}
while (i < estat->count && rs != NULL) {
mutex_exit(&E->lock);
rndsource_to_user_est(rs, &estat->source[i++]);
mutex_enter(&E->lock);
rs = LIST_NEXT(rs, list);
}
KASSERT(i <= estat->count);
estat->count = i;
rnd_unlock_sources();
mutex_exit(&E->lock);
break;
}
case RNDGETSRCNAME: {
rndstat_name_t *nstat = data;
const size_t n = sizeof(rs->name);
CTASSERT(sizeof(rs->name) == sizeof(nstat->name));
mutex_enter(&E->lock);
error = rnd_lock_sources(ENTROPY_WAIT|ENTROPY_SIG);
if (error) {
mutex_exit(&E->lock);
return error;
}
LIST_FOREACH(rs, &E->sources, list) {
if (strncmp(rs->name, nstat->name, n) == 0)
break;
}
if (rs != NULL) {
mutex_exit(&E->lock);
rndsource_to_user(rs, &nstat->source);
mutex_enter(&E->lock);
} else {
error = SET_ERROR(ENOENT);
}
rnd_unlock_sources();
mutex_exit(&E->lock);
break;
}
case RNDGETESTNAME: {
rndstat_est_name_t *enstat = data;
const size_t n = sizeof(rs->name);
CTASSERT(sizeof(rs->name) == sizeof(enstat->name));
mutex_enter(&E->lock);
error = rnd_lock_sources(ENTROPY_WAIT|ENTROPY_SIG);
if (error) {
mutex_exit(&E->lock);
return error;
}
LIST_FOREACH(rs, &E->sources, list) {
if (strncmp(rs->name, enstat->name, n) == 0)
break;
}
if (rs != NULL) {
mutex_exit(&E->lock);
rndsource_to_user_est(rs, &enstat->source);
mutex_enter(&E->lock);
} else {
error = SET_ERROR(ENOENT);
}
rnd_unlock_sources();
mutex_exit(&E->lock);
break;
}
case RNDCTL: {
rndctl_t *rndctl = data;
const size_t n = sizeof(rs->name);
uint32_t resetflags = RND_FLAG_NO_ESTIMATE|RND_FLAG_NO_COLLECT;
uint32_t flags;
bool reset = false, request = false;
CTASSERT(sizeof(rs->name) == sizeof(rndctl->name));
rndctl->mask &= RND_FLAG_NO_ESTIMATE|RND_FLAG_NO_COLLECT;
mutex_enter(&E->lock);
LIST_FOREACH(rs, &E->sources, list) {
if (rndctl->type != 0xff) {
if (rs->type != rndctl->type)
continue;
} else if (rndctl->name[0] != '\0') {
if (strncmp(rs->name, rndctl->name, n) != 0)
continue;
}
flags = rs->flags & ~rndctl->mask;
flags |= rndctl->flags & rndctl->mask;
if ((rs->flags & resetflags) == 0 &&
(flags & resetflags) != 0)
reset = true;
if ((rs->flags ^ flags) & resetflags)
request = true;
atomic_store_relaxed(&rs->flags, flags);
}
mutex_exit(&E->lock);
if (reset)
entropy_reset();
if (request)
error = entropy_gather();
break;
}
case RNDADDDATA: {
rnddata_t *rdata = data;
unsigned entropybits = 0;
if (!atomic_load_relaxed(&entropy_collection))
break;
if (rdata->len > MIN(sizeof(rdata->data), UINT32_MAX/NBBY))
return SET_ERROR(EINVAL);
if (privileged && rdata->entropy && rdata->len) {
mutex_enter(&E->lock);
if (!E->seeded) {
entropybits = MIN(rdata->entropy,
MIN(rdata->len, ENTROPY_CAPACITY)*NBBY);
E->seeded = true;
}
mutex_exit(&E->lock);
}
rnd_add_data(&seed_rndsource, rdata->data, rdata->len,
entropybits);
error = entropy_consolidate();
break;
}
default:
error = SET_ERROR(ENOTTY);
}
return error;
}
void
rnd_seed(void *seed, size_t len)
{
if (len != sizeof(rndsave_t)) {
printf("entropy: invalid seed length: %zu,"
" expected sizeof(rndsave_t) = %zu\n",
len, sizeof(rndsave_t));
return;
}
entropy_seed(seed);
}
void
rnd_init(void)
{
entropy_init();
}
void
rnd_init_softint(void)
{
entropy_init_late();
entropy_bootrequest();
}
int
rnd_system_ioctl(struct file *fp, unsigned long cmd, void *data)
{
return entropy_ioctl(cmd, data);
}