[tz] [PROPOSED 4/7] Improve randomness of zic temp file names
Paul Eggert
eggert at cs.ucla.edu
Thu Oct 20 19:44:03 UTC 2022
* private.h (HAVE_GETRANDOM): New macro.
* zic.c [HAVE_GETRANDOM]: Include <sys/random.h>.
(get_rand_u64): New function with more randomness,
by using getrandom and/or clock_gettime if available.
Use simpler test for initialization.
(random_dirent): Use it. Avoid slightly biasing the output of the
random number generator.
---
Makefile | 1 +
private.h | 5 ++++
zic.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
3 files changed, 86 insertions(+), 7 deletions(-)
diff --git a/Makefile b/Makefile
index 8a2856f..6f8b2f9 100644
--- a/Makefile
+++ b/Makefile
@@ -210,6 +210,7 @@ LDLIBS=
# -DHAVE_DECL_ENVIRON if <unistd.h> declares 'environ'
# -DHAVE_DIRECT_H if mkdir needs <direct.h> (MS-Windows)
# -DHAVE_GENERIC=0 if _Generic does not work
+# -DHAVE_GETRANDOM if getgrandom works (e.g., GNU/Linux)*
# -DHAVE_GETTEXT if 'gettext' works (e.g., GNU/Linux, FreeBSD, Solaris)*
# -DHAVE_INCOMPATIBLE_CTIME_R if your system's time.h declares
# ctime_r and asctime_r incompatibly with the POSIX standard
diff --git a/private.h b/private.h
index 5624abc..8024df5 100644
--- a/private.h
+++ b/private.h
@@ -62,6 +62,11 @@
# define HAVE_GENERIC (201112 <= __STDC_VERSION__)
#endif
+#ifndef HAVE_GETRANDOM
+# define HAVE_GETRANDOM HAS_INCLUDE(<sys/random.h>, \
+ (2 < __GLIBC__ + (25 <= __GLIBC_MINOR__)))
+#endif
+
#ifndef HAVE_GETTEXT
# define HAVE_GETTEXT HAS_INCLUDE(<libintl.h>, false)
#endif
diff --git a/zic.c b/zic.c
index 38a65eb..ddfdc79 100644
--- a/zic.c
+++ b/zic.c
@@ -38,6 +38,10 @@ typedef int_fast64_t zic_t;
# define mkdir(name, mode) _mkdir(name)
#endif
+#if HAVE_GETRANDOM
+# include <sys/random.h>
+#endif
+
#if HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
@@ -1037,6 +1041,63 @@ namecheck(const char *name)
return componentcheck(name, component, cp);
}
+/* Return a random uint_fast64_t. */
+static uint_fast64_t
+get_rand_u64(void)
+{
+#if HAVE_GETRANDOM
+ static uint_fast64_t entropy_buffer[max(1, 256 / sizeof (uint_fast64_t))];
+ static int nwords;
+ if (!nwords) {
+ ssize_t s;
+ do
+ s = getrandom(entropy_buffer, sizeof entropy_buffer, 0);
+ while (s < 0 && errno == EINTR);
+
+ nwords = s < 0 ? -1 : s / sizeof *entropy_buffer;
+ }
+ if (0 < nwords)
+ return entropy_buffer[--nwords];
+#endif
+
+ /* getrandom didn't work, so fall back on portable code that is
+ not the best because the seed doesn't necessarily have enough bits,
+ the seed isn't cryptographically random on platforms lacking
+ getrandom, and 'rand' might not be cryptographically secure. */
+ {
+ static bool initialized;
+ if (!initialized) {
+ unsigned seed;
+#ifdef CLOCK_REALTIME
+ struct timespec now;
+ clock_gettime (CLOCK_REALTIME, &now);
+ seed = now.tv_sec ^ now.tv_nsec;
+#else
+ seed = time(NULL);
+#endif
+ srand(seed);
+ initialized = true;
+ }
+ }
+
+ /* Return a random number if rand() yields a random number and in
+ the typical case where RAND_MAX is one less than a power of two.
+ In other cases this code yields a sort-of-random number. */
+ {
+ uint_fast64_t
+ rand_max = RAND_MAX,
+ multiplier = rand_max + 1, /* It's OK if this overflows to 0. */
+ r = 0, rmax = 0;
+ do {
+ uint_fast64_t rmax1 = rmax * multiplier + rand_max;
+ r = r * multiplier + rand();
+ rmax = rmax < rmax1 ? rmax1 : UINT_FAST64_MAX;
+ } while (rmax < UINT_FAST64_MAX);
+
+ return r;
+ }
+}
+
/* Generate a randomish name in the same directory as *NAME. If
*NAMEALLOC, put the name into *NAMEALLOC which is assumed to be
that returned by a previous call and is thus already almost set up
@@ -1056,8 +1117,19 @@ random_dirent(char const **name, char **namealloc)
int suffixlen = 6;
char const *lastslash = strrchr(src, '/');
ptrdiff_t dirlen = lastslash ? lastslash + 1 - src : 0;
- static unsigned short initialized;
int i;
+ uint_fast64_t r;
+ uint_fast64_t base = alphabetlen;
+
+ /* BASE**6 */
+ uint_fast64_t base__6 = base * base * base * base * base * base;
+
+ /* The largest uintmax_t that is a multiple of BASE**6. Any random
+ uintmax_t value that is this value or greater, yields a biased
+ remainder when divided by BASE**6. UNFAIR_MIN equals the
+ mathematical value of ((UINTMAX_MAX + 1) - (UINTMAX_MAX + 1) % BASE**6)
+ computed without overflow. */
+ uint_fast64_t unfair_min = - ((UINTMAX_MAX % base__6 + 1) % base__6);
if (!dst) {
dst = emalloc(dirlen + prefixlen + suffixlen + 1);
@@ -1067,13 +1139,14 @@ random_dirent(char const **name, char **namealloc)
*name = *namealloc = dst;
}
- /* This randomization is not the best, but is portable to C89. */
- if (!initialized++) {
- unsigned now = time(NULL);
- srand(rand() ^ now);
+ do
+ r = get_rand_u64();
+ while (unfair_min <= r);
+
+ for (i = 0; i < suffixlen; i++) {
+ dst[dirlen + prefixlen + i] = alphabet[r % alphabetlen];
+ r /= alphabetlen;
}
- for (i = 0; i < suffixlen; i++)
- dst[dirlen + prefixlen + i] = alphabet[rand() % alphabetlen];
}
/* Prepare to write to the file *OUTNAME, using *TEMPNAME to store the
--
2.37.3
More information about the tz
mailing list