File openssh-7.2p2-prevent_timing_user_enumeration.patch of Package openssh.29886
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(-)
diff --git a/auth-pam.c b/auth-pam.c
index d436155a..c3a6b61c 100644
--- a/auth-pam.c
+++ b/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,35 @@ sshpam_query(void *ctx, char **name, char **info,
return (-1);
}
+/*
+ * 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 *fake;
debug2("PAM: %s entering, %u responses", __func__, num);
switch (ctxt->pam_done) {
@@ -838,8 +860,11 @@ sshpam_respond(void *ctx, u_int num, char **resp)
(sshpam_authctxt->pw->pw_uid != 0 ||
options.permit_root_login == PERMIT_YES))
buffer_put_cstring(&buffer, *resp);
- else
- buffer_put_cstring(&buffer, badpw);
+ else {
+ fake = fake_password(*resp);
+ buffer_put_cstring(&buffer, fake);
+ free(fake);
+ }
if (ssh_msg_send(ctxt->pam_psock, PAM_AUTHTOK, &buffer) == -1) {
buffer_free(&buffer);
return (-1);
@@ -1183,6 +1208,7 @@ sshpam_auth_passwd(Authctxt *authctxt, const char *password)
{
int flags = (options.permit_empty_passwd == 0 ?
PAM_DISALLOW_NULL_AUTHTOK : 0);
+ char *fake = NULL;
if (!options.use_pam || sshpam_handle == NULL)
fatal("PAM: %s called when PAM disabled or failed to "
@@ -1198,7 +1224,7 @@ sshpam_auth_passwd(Authctxt *authctxt, const char *password)
*/
if (!authctxt->valid || (authctxt->pw->pw_uid == 0 &&
options.permit_root_login != PERMIT_YES))
- sshpam_password = badpw;
+ sshpam_password = fake = fake_password(password);
sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
(const void *)&passwd_conv);
@@ -1208,6 +1234,7 @@ sshpam_auth_passwd(Authctxt *authctxt, const char *password)
sshpam_err = pam_authenticate(sshpam_handle, flags);
sshpam_password = NULL;
+ free(fake);
if (sshpam_err == PAM_SUCCESS && authctxt->valid) {
debug("PAM: password authentication accepted for %.100s",
authctxt->user);
diff --git a/auth-passwd.c b/auth-passwd.c
index 63ccf3ca..530b5d4f 100644
--- a/auth-passwd.c
+++ b/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, const char *password)
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
diff --git a/openbsd-compat/xcrypt.c b/openbsd-compat/xcrypt.c
index 8577cbd8..8913bb81 100644
--- a/openbsd-compat/xcrypt.c
+++ b/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);
--
2.38.0