Hi

The gpw program is BSD-3-clause so eventually compatible with the randnum.c
in GPL. It just needs to retain the GPL copyright in the debian/copyright
file to clarify. Anyway, I will forward the bug to upstream, who could
be eventually interested in supporting even after 26 years...

-cheers



On Sun, Nov 22, 2020 at 04:35:06PM +0000, Carles Pina i Estany wrote:
Package: gpw
Version: 0.0.19940601-9+b1
Severity: important
Tags: patch upstream

Dear Maintainer,


Recently I read https://github.com/rclone/rclone/issues/4783 . rclone
was generating weak passwords: all the passwords were in a set of 33
million possible generated passwords. I checked Debian packages that
could have the same problem and found that "gpw" has a similar problem
(1 million sets of passwords for a given password length). I initially
reported this bug to the Debian security team and they suggested to open
a public bug.

The problem is that "gpw" uses a seed for the random generator based
only on microseconds: a number between 0 and 999999. The generated
passwords for a certain password length depend on the dictionaries
installed at compilation time (the same for all the Debian users) and
the seed. There are only 1 million sets of different passwords (for the
same length) generated by "gpw". E.g, if faketime is installed, Debian 9
or Debian 10 gpw would generate:

carles@pinux:~$ faketime -f '2008-12-24 08:15:43' gpw
demoduls
pebfurge
ratentso
prockerm
ndivical
ualksect
alidedit
iredgedr
pledonsu
lizensms
carles@pinux:~$

(faketime doesn't support microseconds and by default sets it to .0
microseconds)

In other words: If anyone is generating passwords with "gpw 1" (in
Debian) there are only 1 million possible sets of passwords for this
length. Or even doing "gpw 1 99": only 1 million possible passwords of
length 99.

I am not an expert in cryptography and my solution might be wrong but
here is a possible patch:

Possible patch: I copied two functions from "pwgen" and used them from
"gpw". Then the seed is one in 9223372036854775807 (2^63-1) instead of
one in 1000000 (2^20 approx.). Another approach would be to use
libgcrypt from gpw (other software, like apg, use this approach). I'm
unsure if the license of gpw is compatible with GPL (randnum.c is GPL).
Perhaps the maintainer or upstream can clarify this / provide a
different fix if it's not correct, etc.

Interestingly: "pwgen" in 2013 had a bug with a similar issue:
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=767008 . In this case
it generated the seed using /dev/urandom. If it failed it used
/dev/random and if this also failed it used a combination of seconds,
getpgrp(), getpid() and microseconds. This was considered too weak and
it was updated to use only /dev/urandom and /dev/random
(https://github.com/tytso/pwgen/commit/ccda6f21c678188074aaa1c673008a8c7ac1b3cf).

-- System Information:
Debian Release: 10.6
 APT prefers stable-updates
 APT policy: (500, 'stable-updates'), (500, 'stable')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 4.19.0-12-amd64 (SMP w/4 CPU cores)
Kernel taint flags: TAINT_WARN, TAINT_OOT_MODULE, TAINT_UNSIGNED_MODULE
Locale: LANG=ca_ES.UTF-8, LC_CTYPE=ca_ES.UTF-8 (charmap=UTF-8), 
LANGUAGE=ca_ES.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled

Versions of packages gpw depends on:
ii  libc6  2.28-10

gpw recommends no packages.

gpw suggests no packages.

-- no debconf information

diff --git a/Makefile b/Makefile
index 8b2c826..4fd5dee 100644
--- a/Makefile
+++ b/Makefile
@@ -9,7 +9,10 @@ all : gpw loadtris
        echo gpw created, can delete loadtris

gpw : gpw.o
-       $(COMPILER) $(DEBUGARGS) -o gpw gpw.o
+       $(COMPILER) $(DEBUGARGS) -o gpw gpw.o randnum.o
+
+randnum.o: randnum.c
+       $(COMPILER) $(DEBUGARGS) -o randnum.o -c randnum.c

trigram.h : loadtris
        ./loadtris /usr/dict/words | sed "s/, }/}/" > trigram.h
@@ -24,4 +27,4 @@ loadtris.o : loadtris.c
        $(COMPILER) $(DEBUGARGS) -o loadtris.o -c loadtris.c

clean :
-       rm gpw loadtris loadtris.o gpw.o # trigram.h
+       rm gpw loadtris loadtris.o gpw.o randnum.o # trigram.h
diff --git a/Makefile.Debian b/Makefile.Debian
index 3e7baed..60ef181 100644
--- a/Makefile.Debian
+++ b/Makefile.Debian
@@ -14,8 +14,11 @@ BIN=$(DESTDIR)/usr/bin
all : gpw loadtris
        echo gpw created, can delete loadtris

-gpw : gpw.o
-       $(COMPILER) $(DEBUGARGS) -o gpw gpw.o
+gpw : gpw.o randnum.o
+       $(COMPILER) $(DEBUGARGS) -o gpw gpw.o randnum.o
+
+randnum.o: randnum.c
+       $(COMPILER) $(DEBUGARGS) -o randnum.o -c randnum.c

trigram.h : loadtris
        ./loadtris /usr/share/dict/words | sed "s/, }/}/" > trigram.h
@@ -30,7 +33,7 @@ loadtris.o : loadtris.c
        $(COMPILER) $(DEBUGARGS) -o loadtris.o -c loadtris.c

clean :
-       rm -f gpw loadtris loadtris.o gpw.o trigram.h
+       rm -f gpw loadtris loadtris.o gpw.o randnum.o trigram.h



diff --git a/gpw.c b/gpw.c
index be3c307..2316f95 100644
--- a/gpw.c
+++ b/gpw.c
@@ -13,11 +13,6 @@
   and looking for real words in its output.. they are very rare, on the
   order of one in a thousand.

-   This program uses "drand48()" to get random numbers, and "srand48()"
-   to set the seed to the microsecond part of the system clock.  Works
-   for AIX C++ compiler and runtime.  Might have to change this to port
-   to another environment.
-
   The best way to use this program is to generate multiple words.  Then
   pick one you like and transform it with punctuation, capitalization,
   and other changes to come up with a new password.
@@ -35,6 +30,7 @@
/* #include <bsd/sys/time.h> */
/* following for BSD */
#include <sys/time.h>
+#include <limits.h>

int main (int argc, char ** argv) {
        int password_length;            /* how long should each password be */
@@ -47,14 +43,11 @@ int main (int argc, char ** argv) {
        long sum;                                       /* running total of 
frequencies */
        char password[100];                     /* buffer to develop a password 
*/
        int nchar;                                      /* number of chars in 
password so far */
-       struct timeval systime;         /* time reading for random seed */
-       struct timezone tz;                     /* unused arg to gettimeofday */

        password_length = 8;            /* Default value for password length */
        n_passwords = 10;                       /* Default value for number of 
pws to generate */

-    gettimeofday (&systime, &tz); /* Read clock. */
-       srand48 (systime.tv_usec);    /* Set random seed. */
+       srand48 (pw_random_number(LONG_MAX));    /* Set random seed. */

        if (argc > 1) {                              /* If args are given, 
convert to numbers. */
                n_passwords = atoi (&argv[1][0]);
diff --git a/randnum.c b/randnum.c
new file mode 100644
index 0000000..7e55264
--- /dev/null
+++ b/randnum.c
@@ -0,0 +1,77 @@
+/*
+ * randnum.c -- generate (good) randum numbers.
+ *
+ * Copyright (C) 2001,2002 by Theodore Ts'o
+ *
+ * This file may be distributed under the terms of the GNU Public
+ * License.
+ */
+
+ /** This file is from pwgen package, adapted for gpw */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef HAVE_DRAND48
+extern double drand48(void);
+#endif
+
+static int get_random_fd(void);
+
+/* Borrowed/adapted from e2fsprogs's UUID generation code */
+static int get_random_fd()
+{
+       struct timeval  tv;
+       static int      fd = -2;
+
+       if (fd == -2) {
+               gettimeofday(&tv, 0);
+               fd = open("/dev/urandom", O_RDONLY);
+               if (fd == -1)
+                       fd = open("/dev/random", O_RDONLY | O_NONBLOCK);
+       }
+       return fd;
+}
+
+/*
+ * Generate a random number n, where 0 <= n < max_num, using
+ * /dev/urandom if possible.
+ */
+long int pw_random_number(max_num)
+       long int max_num;
+{
+       long int rand_num;
+       int i, fd = get_random_fd();
+       int lose_counter = 0, nbytes = sizeof(rand_num);
+       char *cp = (char *) &rand_num;
+
+       if (fd >= 0) {
+               while (nbytes > 0) {
+                       i = read(fd, cp, nbytes);
+                       if ((i < 0) &&
+                           ((errno == EINTR) || (errno == EAGAIN)))
+                               continue;
+                       if (i <= 0) {
+                               if (lose_counter++ == 8)
+                                       break;
+                               continue;
+                       }
+                       nbytes -= i;
+                       cp += i;
+                       lose_counter = 0;
+               }
+       }
+       if (nbytes == 0)
+               return (rand_num % max_num);
+
+       /* We weren't able to use /dev/random, fail hard */
+
+       fprintf(stderr, "No entropy available!\n");
+       exit(1);
+}


--
Francesco P. Lovergine

Reply via email to