random(4): Poll for signals during large reads

Occasionally poll for signals during large reads of the /dev/u?random
devices.  This allows cancellation via SIGINT of accidental invocations of
very large reads.  (A 2GB /dev/random read, which takes about 10 seconds on
my 2017 AMD Zen processor, can be aborted.)

I believe this behavior was intended since 2014 (r273997), just not fully
implemented.

This is motivated by a potential getrandom(2) interface that may not
explicitly forbid extremely large reads on 64-bit platforms -- even larger
than the 2GB limit imposed on devfs I/O by default.  Such reads, if they are
to be allowed, should be cancellable by the user or administrator.

Reviewed by:	delphij
Approved by:	secteam (delphij)
Sponsored by:	Dell EMC Isilon
Differential Revision:	https://reviews.freebsd.org/D14684
This commit is contained in:
cem 2018-03-16 18:50:26 +00:00
parent 64b1deb8c7
commit d9759abd65

View File

@ -130,6 +130,12 @@ READ_RANDOM_UIO(struct uio *uio, bool nonblock)
uint8_t *random_buf;
int error, spamcount;
ssize_t read_len, total_read, c;
/* 16 MiB takes about 0.08 s CPU time on my 2017 AMD Zen CPU */
#define SIGCHK_PERIOD (16 * 1024 * 1024)
const size_t sigchk_period = SIGCHK_PERIOD;
CTASSERT(SIGCHK_PERIOD % PAGE_SIZE == 0);
#undef SIGCHK_PERIOD
random_buf = malloc(PAGE_SIZE, M_ENTROPY, M_WAITOK);
p_random_alg_context->ra_pre_read();
@ -167,11 +173,22 @@ READ_RANDOM_UIO(struct uio *uio, bool nonblock)
read_len = MIN(read_len, PAGE_SIZE);
p_random_alg_context->ra_read(random_buf, read_len);
c = MIN(uio->uio_resid, read_len);
/*
* uiomove() may yield the CPU before each 'c' bytes
* (up to PAGE_SIZE) are copied out.
*/
error = uiomove(random_buf, c, uio);
total_read += c;
/*
* Poll for signals every few MBs to avoid very long
* uninterruptible syscalls.
*/
if (error == 0 && uio->uio_resid != 0 &&
total_read % sigchk_period == 0)
error = tsleep_sbt(&random_alg_context, PCATCH,
"randrd", SBT_1NS, 0, C_HARDCLOCK);
}
if (total_read != uio->uio_resid && (error == ERESTART || error == EINTR))
/* Return partial read, not error. */
if (error == ERESTART || error == EINTR)
error = 0;
}
free(random_buf, M_ENTROPY);