From 8630843eb7bade3acb5f549ba07e80d352c80950 Mon Sep 17 00:00:00 2001
From: Old openssh patches <pcerny@suse.com>
Date: Wed, 26 Oct 2022 09:54:29 +0200
Subject: [PATCH] openssh-7.2p2-prevent_timing_user_enumeration

# HG changeset patch
# Parent  40ce0de74a0d8b8181862b237b693d938310527b
Prevent user enumeration through password processing timing
CVE-2016-6210
bsc#989363

non-PAM part:
upstream commit: 9286875a73b2de7736b5e50692739d314cd8d9dc

PAM part:
upstream commit: 283b97ff33ea2c641161950849931bd578de6946
---
 auth-pam.c              | 35 +++++++++++++++++++++++++++++++----
 auth-passwd.c           | 12 ++++++++----
 openbsd-compat/xcrypt.c | 34 ++++++++++++++++++++++++++++++++++
 3 files changed, 73 insertions(+), 8 deletions(-)

Index: openssh-7.2p2/auth-pam.c
===================================================================
--- openssh-7.2p2.orig/auth-pam.c
+++ openssh-7.2p2/auth-pam.c
@@ -232,7 +232,6 @@ static int sshpam_account_status = -1;
 static char **sshpam_env = NULL;
 static Authctxt *sshpam_authctxt = NULL;
 static const char *sshpam_password = NULL;
-static char badpw[] = "\b\n\r\177INCORRECT";
 
 /* Some PAM implementations don't implement this */
 #ifndef HAVE_PAM_GETENVLIST
@@ -812,12 +811,48 @@ sshpam_query(void *ctx, char **name, cha
 	return (-1);
 }
 
+#define PASSWORD_LEN_MAX 512
+
+static void
+safe_clear (char *mem, int len)
+{
+	volatile char *volatile volmem = (volatile char *volatile) mem;
+	int i;
+
+	for (i = 0; i < len; i++)
+		volmem [i] = 0;
+}
+
+/*
+ * Returns a junk password of identical length to that the user supplied.
+ * Used to mitigate timing attacks against crypt(3)/PAM stacks that
+ * vary processing time in proportion to password length.
+ */
+static char *
+fake_password(const char *wire_password)
+{
+	const char junk[] = "\b\n\r\177INCORRECT";
+	char *ret = NULL;
+	size_t i, l = wire_password != NULL ? strlen(wire_password) : 0;
+
+	if (l >= INT_MAX)
+		fatal("%s: password length too long: %zu", __func__, l);
+
+	ret = xmalloc(l + 1);
+	for (i = 0; i < l; i++)
+		ret[i] = junk[i % (sizeof(junk) - 1)];
+	ret[i] = '\0';
+	return ret;
+}
+
 /* XXX - see also comment in auth-chall.c:verify_response */
 static int
 sshpam_respond(void *ctx, u_int num, char **resp)
 {
 	Buffer buffer;
 	struct pam_ctxt *ctxt = ctx;
+	char *resp_dup = NULL;
+	char *fake;
 
 	debug2("PAM: %s entering, %u responses", __func__, num);
 	switch (ctxt->pam_done) {
@@ -833,13 +868,27 @@ sshpam_respond(void *ctx, u_int num, cha
 		error("PAM: expected one response, got %u", num);
 		return (-1);
 	}
+
+	if (resp && *resp) {
+		resp_dup = strndup(*resp, PASSWORD_LEN_MAX);
+	}
+
 	buffer_init(&buffer);
 	if (sshpam_authctxt->valid &&
 	    (sshpam_authctxt->pw->pw_uid != 0 ||
 	    options.permit_root_login == PERMIT_YES))
-		buffer_put_cstring(&buffer, *resp);
-	else
-		buffer_put_cstring(&buffer, badpw);
+		buffer_put_cstring(&buffer, resp_dup);
+	else {
+		fake = fake_password(resp_dup);
+		buffer_put_cstring(&buffer, fake);
+		free(fake);
+	}
+
+	if (resp_dup) {
+		safe_clear(resp_dup, strlen(resp_dup));
+		free(resp_dup);
+	}
+
 	if (ssh_msg_send(ctxt->pam_psock, PAM_AUTHTOK, &buffer) == -1) {
 		buffer_free(&buffer);
 		return (-1);
@@ -1183,12 +1232,19 @@ sshpam_auth_passwd(Authctxt *authctxt, c
 {
 	int flags = (options.permit_empty_passwd == 0 ?
 	    PAM_DISALLOW_NULL_AUTHTOK : 0);
+	char *password_dup = NULL;
+	char *fake = NULL;
 
 	if (!options.use_pam || sshpam_handle == NULL)
 		fatal("PAM: %s called when PAM disabled or failed to "
 		    "initialise.", __func__);
 
-	sshpam_password = password;
+	/* Limit the password length to 512 bytes. This reduces the potential
+	 * for successful timing analysis if PAM shortcuts and returns
+	 * immediately on nonexistent accounts. */
+	password_dup = password ? strndup(password, PASSWORD_LEN_MAX) : NULL;
+
+	sshpam_password = password_dup;
 	sshpam_authctxt = authctxt;
 
 	/*
@@ -1198,7 +1254,7 @@ sshpam_auth_passwd(Authctxt *authctxt, c
 	 */
 	if (!authctxt->valid || (authctxt->pw->pw_uid == 0 &&
 	    options.permit_root_login != PERMIT_YES))
-		sshpam_password = badpw;
+		sshpam_password = fake = fake_password(password_dup);
 
 	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
 	    (const void *)&passwd_conv);
@@ -1208,6 +1264,13 @@ sshpam_auth_passwd(Authctxt *authctxt, c
 
 	sshpam_err = pam_authenticate(sshpam_handle, flags);
 	sshpam_password = NULL;
+	free(fake);
+
+	if (password_dup) {
+		safe_clear(password_dup, strlen(password_dup));
+		free(password_dup);
+	}
+
 	if (sshpam_err == PAM_SUCCESS && authctxt->valid) {
 		debug("PAM: password authentication accepted for %.100s",
 		    authctxt->user);
Index: openssh-7.2p2/auth-passwd.c
===================================================================
--- openssh-7.2p2.orig/auth-passwd.c
+++ openssh-7.2p2/auth-passwd.c
@@ -193,7 +193,7 @@ int
 sys_auth_passwd(Authctxt *authctxt, const char *password)
 {
 	struct passwd *pw = authctxt->pw;
-	char *encrypted_password;
+	char *encrypted_password, *salt = NULL;
 
 	/* Just use the supplied fake password if authctxt is invalid */
 	char *pw_password = authctxt->valid ? shadow_pw(pw) : pw->pw_passwd;
@@ -202,9 +202,13 @@ sys_auth_passwd(Authctxt *authctxt, cons
 	if (strcmp(pw_password, "") == 0 && strcmp(password, "") == 0)
 		return (1);
 
-	/* Encrypt the candidate password using the proper salt. */
-	encrypted_password = xcrypt(password,
-	    (pw_password[0] && pw_password[1]) ? pw_password : "xx");
+	/*
+	 * Encrypt the candidate password using the proper salt, or pass a
+	 * NULL and let xcrypt pick one.
+	 */
+	if (authctxt->valid && pw_password[0] && pw_password[1])
+		salt = pw_password;
+	encrypted_password = xcrypt(password, salt);
 
 	/*
 	 * Authentication is accepted if the encrypted passwords
Index: openssh-7.2p2/openbsd-compat/xcrypt.c
===================================================================
--- openssh-7.2p2.orig/openbsd-compat/xcrypt.c
+++ openssh-7.2p2/openbsd-compat/xcrypt.c
@@ -25,6 +25,7 @@
 #include "includes.h"
 
 #include <sys/types.h>
+#include <string.h>
 #include <unistd.h>
 #include <pwd.h>
 
@@ -62,11 +63,44 @@
 #  define crypt DES_crypt
 # endif
 
+/*
+ * Pick an appropriate password encryption type and salt for the running
+ * system.
+ */
+static const char *
+pick_salt(void)
+{
+	struct passwd *pw;
+	char *passwd, *p;
+	size_t typelen;
+	static char salt[32];
+
+	if (salt[0] != '\0')
+		return salt;
+	strlcpy(salt, "xx", sizeof(salt));
+	if ((pw = getpwuid(0)) == NULL)
+		return salt;
+	passwd = shadow_pw(pw);
+	if (passwd[0] != '$' || (p = strrchr(passwd + 1, '$')) == NULL)
+		return salt;  /* no $, DES */
+	typelen = p - passwd + 1;
+	strlcpy(salt, passwd, MIN(typelen, sizeof(salt)));
+	explicit_bzero(passwd, strlen(passwd));
+	return salt;
+}
+
 char *
 xcrypt(const char *password, const char *salt)
 {
 	char *crypted;
 
+	/*
+	 * If we don't have a salt we are encrypting a fake password for
+	 * for timing purposes.  Pick an appropriate salt.
+	 */
+	if (salt == NULL)
+		salt = pick_salt();
+
 # ifdef HAVE_MD5_PASSWORDS
         if (is_md5_salt(salt))
                 crypted = md5_crypt(password, salt);
