File 0004-add-workarounds-for-blowfish-signedness-bug.diff of Package pam-modules

From 7a3e5fd2d79657674e72212ad13ea350d72e8306 Mon Sep 17 00:00:00 2001
From: Ludwig Nussel <ludwig.nussel@suse.de>
Date: Wed, 13 Jul 2011 08:50:58 +0200
Subject: [PATCH 4/4] add workarounds for blowfish signedness bug

The option BLOWFISH_2a2x allows to enable compat modes.
---
 configure.in      |   18 +++++++++++++++++-
 etc/passwd        |   16 ++++++++++++++++
 src/get_options.c |   11 +++++++++++
 src/public.h      |    4 ++++
 src/support.c     |   30 ++++++++++++++++++++++++++++++
 src/unix_auth.c   |    8 +-------
 src/unix_passwd.c |   10 +++++++---
 7 files changed, 86 insertions(+), 11 deletions(-)

diff --git a/configure.in b/configure.in
index f4cdcd1..10c4e1f 100644
--- a/configure.in
+++ b/configure.in
@@ -48,13 +48,29 @@ dnl Should we compile with SELinux support? default: yes
 AC_ARG_ENABLE([selinux],
    AC_HELP_STRING([--disable-selinux],[Enable SELinux support (default=yes)]),
    WITH_SELINUX=$enableval, WITH_SELINUX=yes)
-if test "$WITH_SELINUX" == "yes" ; then
+if test "$WITH_SELINUX" = "yes" ; then
    AC_CHECK_LIB(selinux,is_selinux_enabled,
         [AC_DEFINE(WITH_SELINUX,1,
                 [Define if you want to compile in SELinux support])
          LIBS="$LIBS -lselinux"])
 fi
 
+AC_ARG_ENABLE([blowfish-bug-workaround],
+   AC_HELP_STRING([--disable-blowfish-bug-workaround],[Enable workarounds for blowfish signedness bug]),
+   CRYPT_BLOWFISH_SIGNEDNESS_BUG_WORKAROUNDS=$enableval, CRYPT_BLOWFISH_SIGNEDNESS_BUG_WORKAROUNDS=yes)
+if test "$CRYPT_BLOWFISH_SIGNEDNESS_BUG_WORKAROUNDS" = "yes" ; then
+        AC_DEFINE(CRYPT_BLOWFISH_SIGNEDNESS_BUG_WORKAROUNDS,1,
+                [Define if you want to enable workarounds for blowfish signedness bug])
+fi
+
+AC_ARG_ENABLE([blowfish-bug-compatmode],
+   AC_HELP_STRING([--enable-blowfish-bug-compatmode],[Enable blowfish compat mode by default]),
+   CRYPT_BLOWFISH_COMPATMODE=$enableval, CRYPT_BLOWFISH_COMPATMODE=no)
+if test "$CRYPT_BLOWFISH_COMPATMODE" = "yes" ; then
+	AC_DEFINE(CRYPT_BLOWFISH_COMPATMODE,1,
+		[Define if you want to enable blowfish compat mode by default])
+fi
+
 dnl Check standard headers
 AC_HEADER_STDC
 AC_CHECK_HEADERS(crypt.h)
diff --git a/etc/passwd b/etc/passwd
index bd86963..ec6f0d4 100644
--- a/etc/passwd
+++ b/etc/passwd
@@ -36,3 +36,19 @@ CRYPT=
 # sha256/sha512: 1000-9999999
 # SHA512_CRYPT_FILES=1000
 
+# In June 2011 it was discovered that the Linux crypt_blowfish
+# implementation contained a bug that made passwords with non-ASCII
+# characters easier to crack (CVE-2011-2483). Affected passwords are
+# also incompatible with the original, correct OpenBSD
+# implementation. Therefore the $2a hash identifier previously used
+# for blowfish now is ambiguous as it could mean the hash was
+# generated with the correct implementation on OpenBSD or the buggy
+# one on Linux. To avoid the ambiguity two new identifier were
+# introduced. $2x now explicitly identifies hashes that were
+# generated with the buggy algorithm while $2y is used for hashes
+# generated with the correct algorithm. New passwords are now
+# generated with the $2y identifier.
+#
+# Setting the following option to "yes" tells the sytem that $2a
+# hashes are to be treated as generated with the buggy algorithm.
+BLOWFISH_2a2x=
diff --git a/src/get_options.c b/src/get_options.c
index faf8aa0..f899155 100644
--- a/src/get_options.c
+++ b/src/get_options.c
@@ -138,6 +138,17 @@ get_options (pam_handle_t *pamh, options_t *options, const char *type,
   /* Set some default values, which could be overwritten later.  */
   options->use_crypt = NONE;
 
+#ifdef CRYPT_BLOWFISH_SIGNEDNESS_BUG_WORKAROUNDS
+  options->blowfish_2a2x = getlogindefs_bool("BLOWFISH_2a2x",
+#ifdef CRYPT_BLOWFISH_COMPATMODE
+    1
+#else
+    0
+#endif
+    );
+  free_getlogindefs_data();
+#endif
+
   /* Parse parameters for module */
   for ( ; argc-- > 0; argv++)
     parse_option (pamh, *argv, type, options);
diff --git a/src/public.h b/src/public.h
index 7200759..ec476b8 100644
--- a/src/public.h
+++ b/src/public.h
@@ -68,6 +68,7 @@ struct options_t {
   int nullok;
   int use_authtok;
   int use_first_pass;
+  int blowfish_2a2x;
   char **use_other_modules;
   char *nisdir;
   crypt_t use_crypt;
@@ -86,6 +87,9 @@ extern int __call_other_module(pam_handle_t * pamh, int flags,
 			const char *mod_name, const char *func_name,
 			options_t *options);
 
+extern int __check_password_match (const char *hash, const char *pass,
+			options_t *options);
+
 extern int get_options (pam_handle_t *pamh, options_t *options,
 			const char *type, int argc, const char **argv);
 
diff --git a/src/support.c b/src/support.c
index 6a1ce5e..b3b94c8 100644
--- a/src/support.c
+++ b/src/support.c
@@ -48,6 +48,11 @@
 #include <security/pam_ext.h>
 #endif
 
+#if defined(HAVE_CRYPT_H)
+#define _OW_SOURCE
+#include <crypt.h>
+#endif
+
 #include "public.h"
 
 int
@@ -360,3 +365,28 @@ struct pam_module _pam_unix2_modstruct =
   pam_sm_chauthtok
 };
 #endif
+
+int
+__check_password_match (const char *hash, const char *pass, options_t *options)
+{
+  struct crypt_data output;
+  char *h = NULL;
+  int r;
+
+  memset (&output, 0, sizeof (output));
+
+#ifdef CRYPT_BLOWFISH_SIGNEDNESS_BUG_WORKAROUNDS
+  if ((options->blowfish_2a2x)
+    && !strncmp(hash, "$2a$", 4))
+    {
+      h = strdupa(hash);
+      h[2] = 'x';
+      hash = h;
+    }
+#endif
+
+  r = (strcmp (hash, crypt_r (pass, hash, &output)) == 0);
+  if (h)
+    _pam_overwrite(h);
+  return r;
+}
diff --git a/src/unix_auth.c b/src/unix_auth.c
index e61c45e..e78c520 100644
--- a/src/unix_auth.c
+++ b/src/unix_auth.c
@@ -61,10 +61,6 @@
 #include <security/pam_ext.h>
 #endif
 
-#if defined(HAVE_CRYPT_H)
-#include <crypt.h>
-#endif
-
 #include "public.h"
 
 
@@ -124,7 +120,6 @@ int
 pam_sm_authenticate (pam_handle_t *pamh, int flags, int argc,
 		     const char **argv)
 {
-  struct crypt_data output;
   int retval;
   int sp_buflen = 256;
   char *sp_buffer = alloca (sp_buflen);
@@ -141,7 +136,6 @@ pam_sm_authenticate (pam_handle_t *pamh, int flags, int argc,
   options_t options;
   int ask_user, ask_password;
 
-  memset (&output, 0, sizeof (output));
   memset (&options, 0, sizeof (options));
 
   if (get_options (pamh, &options, "auth", argc, argv) < 0)
@@ -325,7 +319,7 @@ pam_sm_authenticate (pam_handle_t *pamh, int flags, int argc,
       *cp = '\0';
     }
 
-  if (strcmp (crypt_r (password, salt, &output), salt) != 0)
+  if (!__check_password_match(salt, password, &options))
     {
       if (options.debug)
 	pam_syslog (pamh, LOG_DEBUG, "wrong password, return PAM_AUTH_ERR");
diff --git a/src/unix_passwd.c b/src/unix_passwd.c
index 8491adf..e00615b 100644
--- a/src/unix_passwd.c
+++ b/src/unix_passwd.c
@@ -257,8 +257,7 @@ pam_sm_chauthtok (pam_handle_t *pamh, int flags, int argc, const char **argv)
 
   /* Check if the old password was correct.  */
   if ((getuid () || (flags & PAM_CHANGE_EXPIRED_AUTHTOK)) &&
-      strcmp (data->oldpassword,
-	      crypt_r (oldpass, data->oldpassword, &output)) != 0)
+      !__check_password_match(data->oldpassword, oldpass, &options))
     {
       if (options.debug)
 	pam_syslog (pamh, LOG_DEBUG,
@@ -686,7 +685,12 @@ __do_setpass (pam_handle_t *pamh, int flags, user_t *data,
     case BLOWFISH:
       salt = make_crypt_salt ("$2a$", options->crypt_rounds, pamh, flags);
       if (salt != NULL)
-	newpassword = crypt_r (data->newpassword, salt, output);
+	{
+#ifdef CRYPT_BLOWFISH_SIGNEDNESS_BUG_WORKAROUNDS
+	  salt[2] = 'y';
+#endif
+	  newpassword = crypt_r (data->newpassword, salt, output);
+	}
       else
 	{
 	  __write_message (pamh, flags, PAM_ERROR_MSG,
-- 
1.7.3.4