So this is the final verison of the patch solving the following
problems:
>The program is broken in multiple ways: return value clamping, casting
>from double to uint32_t, wrong error checking for putchar, lack of
>warnings when compiling.
Now the program is more pedantic and complicated, but at least it does
what is says in the man page.
Ok, deraadt, tb?
Index: Makefile
===================================================================
RCS file: /cvs/src/games/random/Makefile,v
retrieving revision 1.2
diff -u -p -r1.2 Makefile
--- Makefile 21 Sep 1997 11:36:51 -0000 1.2
+++ Makefile 5 Aug 2022 18:59:11 -0000
@@ -2,5 +2,6 @@
PROG= random
MAN= random.6
+CFLAGS=-Wall -Wconversion
.include <bsd.prog.mk>
Index: random.6
===================================================================
RCS file: /cvs/src/games/random/random.6,v
retrieving revision 1.7
diff -u -p -r1.7 random.6
--- random.6 31 May 2007 19:19:18 -0000 1.7
+++ random.6 5 Aug 2022 18:59:11 -0000
@@ -55,9 +55,7 @@ If the
option is specified,
.Nm
does not read or write anything, and simply exits with a random
-exit value of 0 to
-.Ar denominator
-\&- 1, inclusive.
+exit value of 0 to 255, inclusive.
.It Fl r
The
.Fl r
Index: random.c
===================================================================
RCS file: /cvs/src/games/random/random.c,v
retrieving revision 1.20
diff -u -p -r1.20 random.c
--- random.c 7 Mar 2016 12:07:56 -0000 1.20
+++ random.c 5 Aug 2022 18:59:11 -0000
@@ -35,11 +35,15 @@
#include <err.h>
#include <errno.h>
+#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
__dead void usage(void);
+unsigned clz64(uint64_t x);
+uint64_t random64(void);
+double random_real(void);
int
main(int argc, char *argv[])
@@ -84,9 +88,12 @@ main(int argc, char *argv[])
usage();
}
- /* Compute a random exit status between 0 and denom - 1. */
+ /*
+ * Compute a random exit status between 0 and 255.
+ * Shells only consider the first 8 bits of the return code.
+ */
if (random_exit)
- return (arc4random_uniform(denom));
+ return (int)arc4random_uniform(256);
/*
* Act as a filter, randomly choosing lines of the standard input
@@ -101,18 +108,19 @@ main(int argc, char *argv[])
* 0 (which has a 1 / denom chance of being true), we select the
* line.
*/
- selected = arc4random_uniform(denom) == 0;
+ selected = random_real() < 1 / denom;
while ((ch = getchar()) != EOF) {
- if (selected)
- (void)putchar(ch);
- if (ch == '\n') {
- /* End of that line. See if we got an error. */
- if (ferror(stdout))
- err(2, "stdout");
+ int retch;
- /* Now see if the next line is to be printed. */
- selected = arc4random_uniform(denom) == 0;
+ if (selected) {
+ errno = 0;
+ retch = putchar(ch);
+ if (retch == EOF && errno)
+ err(2, "putchar");
}
+ if (ch == '\n')
+ /* Now see if the next line is to be printed. */
+ selected = random_real() < 1 / denom;
}
if (ferror(stdin))
err(2, "stdin");
@@ -123,6 +131,128 @@ void
usage(void)
{
- (void)fprintf(stderr, "usage: %s [-er] [denominator]\n", getprogname());
+ if (fprintf(stderr, "usage: %s [-er] [denominator]\n", getprogname()) <
0)
+ err(2, "fprintf");
exit(1);
}
+
+unsigned
+clz64(uint64_t x)
+{
+ static const uint64_t mask[] = {
+ 0xffffffff00000000, 0xffff0000, 0xff00, 0xf0, 0xc, 0x2,
+ };
+ static unsigned zeroes[] = {
+ 32, 16, 8, 4, 2, 1,
+ };
+ unsigned clz = 0;
+ int i;
+
+ if (x == 0)
+ return 64;
+
+ for (i = 0; i < 6; i++) {
+ if ((x & mask[i]) == 0)
+ clz += zeroes[i];
+ else
+ x >>= zeroes[i];
+ }
+
+ return clz;
+}
+
+uint64_t
+random64(void)
+{
+ uint64_t r;
+
+ arc4random_buf(&r, sizeof(uint64_t));
+
+ return r;
+}
+
+/*
+ * random_real: Generate a stream of bits uniformly at random and
+ * interpret it as the fractional part of the binary expansion of a
+ * number in [0, 1], 0.00001010011111010100...; then round it.
+ */
+double
+random_real(void)
+{
+ int exponent = -64;
+ uint64_t significand;
+ unsigned shift;
+
+ /*
+ * Read zeros into the exponent until we hit a one; the rest
+ * will go into the significand.
+ */
+ while (__predict_false((significand = random64()) == 0)) {
+ exponent -= 64;
+ /*
+ * If the exponent falls below -1074 = emin + 1 - p,
+ * the exponent of the smallest subnormal, we are
+ * guaranteed the result will be rounded to zero. This
+ * case is so unlikely it will happen in realistic
+ * terms only if random64 is broken.
+ */
+ if (__predict_false(exponent < -1074))
+ return 0;
+ }
+
+ /*
+ * There is a 1 somewhere in significand, not necessarily in
+ * the most significant position. If there are leading zeros,
+ * shift them into the exponent and refill the less-significant
+ * bits of the significand. Can't predict one way or another
+ * whether there are leading zeros: there's a fifty-fifty
+ * chance, if random64 is uniformly distributed.
+ */
+ shift = clz64(significand);
+ if (shift != 0) {
+ exponent -= shift;
+ significand <<= shift;
+ significand |= (random64() >> (64 - shift));
+ }
+
+ /*
+ * Set the sticky bit, since there is almost surely another 1
+ * in the bit stream. Otherwise, we might round what looks
+ * like a tie to even when, almost surely, were we to look
+ * further in the bit stream, there would be a 1 breaking the
+ * tie.
+ */
+ significand |= 1;
+
+ /*
+ * Finally, convert to double (rounding) and scale by
+ * 2^exponent.
+ */
+ return ldexp((double)significand, exponent);
+}
+
+/*-
+ * Copyright (c) 2014 Taylor R. Campbell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */