shadow/libmisc
Alejandro Colomar 1a0e13f94e Optimize csrand_uniform()
Use a different algorithm to minimize rejection.  This is essentially
the same algorithm implemented in the Linux kernel for
__get_random_u32_below(), but written in a more readable way, and
avoiding microopimizations that make it less readable.

Which (the Linux kernel implementation) is itself based on Daniel
Lemire's algorithm from "Fast Random Integer Generation in an Interval",
linked below.  However, I couldn't really understand that paper very
much, so I had to reconstruct the proofs from scratch, just from what I
could understand from the Linux kernel implementation source code.

I constructed some graphical explanation of how it works, and why it
is optimal, because I needed to visualize it to understand it.  It is
published in the GitHub pull request linked below.

Here goes a wordy explanation of why this algorithm based on
multiplication is better optimized than my original implementation based
on masking.

masking:

	It discards the extra bits of entropy that are not necessary for
	this operation.  This works as if dividing the entire space of
	possible csrand() values into smaller spaces of a size that is
	a smaller power of 2.  Each of those smaller spaces has a
	rejection band, so we get as many rejection bands as spaces
	there are.  For smaller values of 'n', the size of each
	rejection band is smaller, but having more rejection bands
	compensates for this, and results in the same inefficiency as
	for large values of 'n'.

multiplication:

	It divides the entire space of possible random numbers in
	chunks of size exactly 'n', so that there is only one rejection
	band that is the remainder of `2^64 % n`.  The worst case is
	still similar to the masking algorithm, a rejection band that is
	almost half the entire space (n = 2^63 + 1), but for lower
	values of 'n', by only having one small rejection band, it is
	much faster than the masking algorithm.

	This algorithm, however, has one caveat: the implementation
	is harder to read, since it relies on several bitwise tricky
	operations to perform operations like `2^64 % n`, `mult % 2^64`,
	and `mult / 2^64`.  And those operations are different depending
	on the number of bits of the maximum possible random number
	generated by the function.  This means that while this algorithm
	could also be applied to get uniform random numbers in the range
	[0, n-1] quickly from a function like rand(3), which only
	produces 31 bits of (non-CS) random numbers, it would need to be
	implemented differently.  However, that's not a concern for us,
	it's just a note so that nobody picks this code and expects it
	to just work with rand(3) (which BTW I tried for testing it, and
	got a bit confused until I realized this).

Finally, here's some light testing of this implementation, just to know
that I didn't goof it.  I pasted this function into a standalone
program, and run it many times to find if it has any bias (I tested also
to see how many iterations it performs, and it's also almost always 1,
but that test is big enough to not paste it here).

int main(int argc, char *argv[])
{
	printf("%lu\n", csrand_uniform(atoi(argv[1])));
}

$ seq 1 1000 | while read _; do ./a.out 3; done | grep 1 | wc -l
341
$ seq 1 1000 | while read _; do ./a.out 3; done | grep 1 | wc -l
339
$ seq 1 1000 | while read _; do ./a.out 3; done | grep 1 | wc -l
338
$ seq 1 1000 | while read _; do ./a.out 3; done | grep 2 | wc -l
336
$ seq 1 1000 | while read _; do ./a.out 3; done | grep 2 | wc -l
328
$ seq 1 1000 | while read _; do ./a.out 3; done | grep 2 | wc -l
335
$ seq 1 1000 | while read _; do ./a.out 3; done | grep 0 | wc -l
332
$ seq 1 1000 | while read _; do ./a.out 3; done | grep 0 | wc -l
331
$ seq 1 1000 | while read _; do ./a.out 3; done | grep 0 | wc -l
327

This isn't a complete test for a cryptographically-secure random number
generator, of course, but I leave that for interested parties.

Link: <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e9a688bcb19348862afe30d7c85bc37c4c293471>
Link: <https://github.com/shadow-maint/shadow/pull/624#discussion_r1059574358>
Link: <https://arxiv.org/abs/1805.10941>
Cc: "Jason A. Donenfeld" <Jason@zx2c4.com>
Cc: Cristian Rodríguez <crrodriguez@opensuse.org>
Cc: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Cc: Björn Esser <besser82@fedoraproject.org>
Cc: Yann Droneaud <ydroneaud@opteya.com>
Cc: Joseph Myers <joseph@codesourcery.com>
Cc: Sam James <sam@gentoo.org>
Cc: Serge Hallyn <serge@hallyn.com>
Cc: Iker Pedrosa <ipedrosa@redhat.com>
[Daniel Lemire: Added link to research paper in source code]
Cc: Daniel Lemire <daniel@lemire.me>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
2023-01-27 21:48:37 -06:00
..
.indent.pro Commit the last version from the PLD CVS repository. 2007-10-07 14:36:51 +00:00
addgrps.c Don't redefine errno(3) 2022-12-22 11:43:29 +01:00
age.c Update licensing info 2021-12-23 19:36:50 -06:00
agetpass.c agetpass: Hook into build-system 2022-12-05 10:47:19 +01:00
audit_help.c Don't redefine errno(3) 2022-12-22 11:43:29 +01:00
basename.c Update licensing info 2021-12-23 19:36:50 -06:00
bit.c Add bit manipulation functions 2023-01-27 21:48:37 -06:00
btrfs.c Declare read-only data const 2022-08-06 11:27:56 -05:00
chkname.c shadow: use relaxed usernames 2022-09-02 20:27:14 -05:00
chkname.h Update licensing info 2021-12-23 19:36:50 -06:00
chowndir.c Avoid races in chown_tree() 2022-08-17 12:34:01 -05:00
chowntty.c Merge pull request #451 from hallyn/2021-12-05/license 2022-01-02 18:38:42 -06:00
cleanup_group.c Merge pull request #451 from hallyn/2021-12-05/license 2022-01-02 18:38:42 -06:00
cleanup_user.c Merge pull request #451 from hallyn/2021-12-05/license 2022-01-02 18:38:42 -06:00
cleanup.c Update licensing info 2021-12-23 19:36:50 -06:00
console.c Use strlcpy(3) instead of its pattern 2022-12-22 18:03:39 -06:00
copydir.c copydir: fix impl usage 2023-01-25 12:31:17 +01:00
csrand.c Optimize csrand_uniform() 2023-01-27 21:48:37 -06:00
date_to_str.c Use strlcpy(3) instead of its pattern 2022-12-22 18:03:39 -06:00
entry.c Update licensing info 2021-12-23 19:36:50 -06:00
env.c Drop redundant declaration 2023-01-25 12:31:17 +01:00
failure.c Cosmetic fixes 2022-12-22 10:31:43 +01:00
failure.h Disable utmpx permanently 2022-12-22 10:31:43 +01:00
find_new_gid.c libmisc: minimum id check for system accounts 2022-10-06 20:09:35 -05:00
find_new_sub_gids.c Don't redefine errno(3) 2022-12-22 11:43:29 +01:00
find_new_sub_uids.c Don't redefine errno(3) 2022-12-22 11:43:29 +01:00
find_new_uid.c libmisc: minimum id check for system accounts 2022-10-06 20:09:35 -05:00
getdate.h Update licensing info 2021-12-23 19:36:50 -06:00
getdate.y Use isdigit(3) instead of a reimplementation of it 2021-12-29 02:41:09 +01:00
getgr_nam_gid.c Update licensing info 2021-12-23 19:36:50 -06:00
getrange.c Declare read-only parameters const 2022-08-06 11:27:56 -05:00
gettime.c Avoid comparisons of different signs 2023-01-25 12:31:17 +01:00
hushed.c Update licensing info 2021-12-23 19:36:50 -06:00
idmapping.c Use WIDTHOF() instead of its expansion 2023-01-27 21:48:37 -06:00
idmapping.h Add include for uid_t 2022-08-06 11:27:56 -05:00
isexpired.c Update licensing info 2021-12-23 19:36:50 -06:00
limits.c Don't redefine errno(3) 2022-12-22 11:43:29 +01:00
list.c Update licensing info 2021-12-23 19:36:50 -06:00
log.c Update licensing info 2021-12-23 19:36:50 -06:00
loginprompt.c Assume SIGTSTP is defined 2022-12-15 16:22:05 -06:00
mail.c Update licensing info 2021-12-23 19:36:50 -06:00
Makefile.am Add bit manipulation functions 2023-01-27 21:48:37 -06:00
motd.c Drop register keyword 2022-08-06 11:27:56 -05:00
myname.c Update licensing info 2021-12-23 19:36:50 -06:00
obscure.c Don't redefine errno(3) 2022-12-22 11:43:29 +01:00
pam_pass_non_interactive.c Don't redefine errno(3) 2022-12-22 11:43:29 +01:00
pam_pass.c Don't redefine errno(3) 2022-12-22 11:43:29 +01:00
prefix_flag.c Use strict prototypes 2022-01-03 15:09:17 +01:00
pwd2spwd.c Don't redefine errno(3) 2022-12-22 11:43:29 +01:00
pwd_init.c Assume SIGTTOU is defined 2022-12-15 16:22:05 -06:00
pwdcheck.c Don't redefine errno(3) 2022-12-22 11:43:29 +01:00
remove_tree.c Avoid races in remove_tree() 2022-08-17 12:34:01 -05:00
rlogin.c Assume B[0-9]* macros are defined 2022-12-15 16:22:05 -06:00
root_flag.c libmisc/root_flag: add tips for --root flag only support abspath 2022-08-06 15:04:06 -05:00
salt.c Rewrite csrand_interval() as a wrapper around csrand_uniform() 2023-01-27 21:48:37 -06:00
setugid.c Update licensing info 2021-12-23 19:36:50 -06:00
setupenv.c Merge pull request #451 from hallyn/2021-12-05/license 2022-01-02 18:38:42 -06:00
shell.c Do not drop const qualifier for Basename 2022-01-03 15:09:17 +01:00
strtoday.c strtoday.c: remove unused defines.h inclusion 2022-12-22 10:39:45 -06:00
sub.c Update licensing info 2021-12-23 19:36:50 -06:00
sulog.c Update licensing info 2021-12-23 19:36:50 -06:00
ttytype.c Update licensing info 2021-12-23 19:36:50 -06:00
tz.c Don't redefine errno(3) 2022-12-22 11:43:29 +01:00
ulimit.c Remove comments that survived the Helicoprion 2022-12-15 16:22:05 -06:00
user_busy.c Disable utmpx permanently 2022-12-22 10:31:43 +01:00
utmp.c Use strlcpy(3) instead of its pattern 2022-12-22 18:03:39 -06:00
valid.c Update licensing info 2021-12-23 19:36:50 -06:00
xgetgrgid.c Assume getgrgid_r(3) exists 2022-12-15 16:22:05 -06:00
xgetgrnam.c libmisc: fix grammar 2023-01-26 22:44:39 -06:00
xgetpwnam.c libmisc: fix grammar 2023-01-26 22:44:39 -06:00
xgetpwuid.c Assume getpwuid_r(3) exists 2022-12-15 16:22:05 -06:00
xgetspnam.c libmisc: fix grammar 2023-01-26 22:44:39 -06:00
xgetXXbyYY.c libmisc: fix grammar 2023-01-26 22:44:39 -06:00
xmalloc.c Don't test for NULL before calling free(3) 2022-09-29 16:03:53 +02:00
yesno.c Update licensing info 2021-12-23 19:36:50 -06:00