From e89d4c97385ff8180e6e81e84c5aa745daf28a79 Mon Sep 17 00:00:00 2001
From: Thorsten Kukuk <kukuk@thkukuk.de>
Date: Mon, 22 Jun 2015 14:53:01 +0200
Subject: Release version 1.2.1

Security fix: CVE-2015-3238

If the process executing pam_sm_authenticate or pam_sm_chauthtok method
of pam_unix is not privileged enough to check the password, e.g.
if selinux is enabled, the _unix_run_helper_binary function is called.
When a long enough password is supplied (16 pages or more, i.e. 65536+
bytes on a system with 4K pages), this helper function hangs
indefinitely, blocked in the write(2) call while writing to a blocking
pipe that has a limited capacity.
With this fix, the verifiable password length will be limited to
PAM_MAX_RESP_SIZE bytes (i.e. 512 bytes) for pam_exec and pam_unix.

diff --git a/modules/pam_exec/pam_exec.c b/modules/pam_exec/pam_exec.c
index 5ab9630..17ba6ca 100644
--- a/modules/pam_exec/pam_exec.c
+++ b/modules/pam_exec/pam_exec.c
@@ -178,11 +178,11 @@ call_exec (const char *pam_type, pam_handle_t *pamh,
 		}
 
 	      pam_set_item (pamh, PAM_AUTHTOK, resp);
-	      authtok = strdupa (resp);
+	      authtok = strndupa (resp, PAM_MAX_RESP_SIZE);
 	      _pam_drop (resp);
 	    }
 	  else
-	    authtok = void_pass;
+	    authtok = strndupa (void_pass, PAM_MAX_RESP_SIZE);
 
 	  if (pipe(fds) != 0)
 	    {
diff --git a/modules/pam_unix/pam_unix_passwd.c b/modules/pam_unix/pam_unix_passwd.c
index 2d330e5..c2e5de5 100644
--- a/modules/pam_unix/pam_unix_passwd.c
+++ b/modules/pam_unix/pam_unix_passwd.c
@@ -240,15 +240,22 @@ static int _unix_run_update_binary(pam_handle_t *pamh, unsigned int ctrl, const
 	/* wait for child */
 	/* if the stored password is NULL */
         int rc=0;
-	if (fromwhat)
-	  pam_modutil_write(fds[1], fromwhat, strlen(fromwhat)+1);
-	else
-	  pam_modutil_write(fds[1], "", 1);
-	if (towhat) {
-	  pam_modutil_write(fds[1], towhat, strlen(towhat)+1);
+	if (fromwhat) {
+	    int len = strlen(fromwhat);
+
+	    if (len > PAM_MAX_RESP_SIZE)
+	      len = PAM_MAX_RESP_SIZE;
+	    pam_modutil_write(fds[1], fromwhat, len);
 	}
-	else
-	  pam_modutil_write(fds[1], "", 1);
+        pam_modutil_write(fds[1], "", 1);
+	if (towhat) {
+	    int len = strlen(towhat);
+
+	    if (len > PAM_MAX_RESP_SIZE)
+	      len = PAM_MAX_RESP_SIZE;
+	    pam_modutil_write(fds[1], towhat, len);
+        }
+        pam_modutil_write(fds[1], "", 1);
 
 	close(fds[0]);       /* close here to avoid possible SIGPIPE above */
 	close(fds[1]);
diff --git a/modules/pam_unix/passverify.c b/modules/pam_unix/passverify.c
index b325602..e79b55e 100644
--- a/modules/pam_unix/passverify.c
+++ b/modules/pam_unix/passverify.c
@@ -1115,12 +1115,15 @@ getuidname(uid_t uid)
 int
 read_passwords(int fd, int npass, char **passwords)
 {
+        /* The passwords array must contain npass preallocated
+         * buffers of length MAXPASS + 1
+         */
         int rbytes = 0;
         int offset = 0;
         int i = 0;
         char *pptr;
         while (npass > 0) {
-                rbytes = read(fd, passwords[i]+offset, MAXPASS-offset);
+                rbytes = read(fd, passwords[i]+offset, MAXPASS+1-offset);
 
                 if (rbytes < 0) {
                         if (errno == EINTR) continue;
diff --git a/modules/pam_unix/passverify.h b/modules/pam_unix/passverify.h
index 3de6759..caf7ae8 100644
--- a/modules/pam_unix/passverify.h
+++ b/modules/pam_unix/passverify.h
@@ -8,7 +8,7 @@
 
 #define PAM_UNIX_RUN_HELPER PAM_CRED_INSUFFICIENT
 
-#define MAXPASS		200	/* the maximum length of a password */
+#define MAXPASS PAM_MAX_RESP_SIZE  /* the maximum length of a password */
 
 #define OLD_PASSWORDS_FILE      "/etc/security/opasswd"
 
diff --git a/modules/pam_unix/support.c b/modules/pam_unix/support.c
index fdb45c2..abccd82 100644
--- a/modules/pam_unix/support.c
+++ b/modules/pam_unix/support.c
@@ -609,7 +609,12 @@ static int _unix_run_helper_binary(pam_handle_t *pamh, const char *passwd,
 	/* if the stored password is NULL */
         int rc=0;
 	if (passwd != NULL) {            /* send the password to the child */
-	    if (write(fds[1], passwd, strlen(passwd)+1) == -1) {
+	    int len = strlen(passwd);
+
+	    if (len > PAM_MAX_RESP_SIZE)
+	      len = PAM_MAX_RESP_SIZE;
+	    if (write(fds[1], passwd, len) == -1 ||
+	        write(fds[1], "", 1) == -1) {
 	      pam_syslog (pamh, LOG_ERR, "Cannot send password to helper: %m");
 	      retval = PAM_AUTH_ERR;
 	    }
--- a/modules/pam_unix/pam_unix.8	2017-05-27 15:38:27.000000000 +0000
+++ b/modules/pam_unix/pam_unix.8	2017-05-27 15:34:49.000000000 +0000
@@ -56,6 +56,10 @@
 \fBnoreap\fR
 module argument can be used to suppress this temporary shielding and may be needed for use with certain applications\&.
 .PP
+The maximum length of a password supported by the pam_unix module via the helper binary is
+\fIPAM_MAX_RESP_SIZE\fR
+\- currently 512 bytes\&. The rest of the password provided by the conversation function to the module will be ignored\&.
+.PP
 The password component of this module performs the task of updating the user\*(Aqs password\&. The default encryption hash is taken from the
 \fBENCRYPT_METHOD\fR
 variable from
--- a/modules/pam_exec/pam_exec.8	2017-05-27 15:38:27.000000000 +0000
+++ b/modules/pam_exec/pam_exec.8	2017-05-27 15:56:25.000000000 +0000
@@ -65,7 +65,9 @@
 \fBexpose_authtok\fR
 .RS 4
 During authentication the calling command can read the password from
-\fBstdin\fR(3)\&.
+\fBstdin\fR(3)\&. Only first
+\fIPAM_MAX_RESP_SIZE\fR
+bytes of a password are provided to the command\&.
 .RE
 .PP
 \fBlog=\fR\fB\fIfile\fR\fR
