summaryrefslogtreecommitdiff
path: root/auth-pam.c
diff options
context:
space:
mode:
Diffstat (limited to 'auth-pam.c')
-rw-r--r--auth-pam.c107
1 files changed, 77 insertions, 30 deletions
diff --git a/auth-pam.c b/auth-pam.c
index 8425af1ea33a..348fe370acd7 100644
--- a/auth-pam.c
+++ b/auth-pam.c
@@ -68,9 +68,9 @@
/* OpenGroup RFC86.0 and XSSO specify no "const" on arguments */
#ifdef PAM_SUN_CODEBASE
-# define sshpam_const /* Solaris, HP-UX, AIX */
+# define sshpam_const /* Solaris, HP-UX, SunOS */
#else
-# define sshpam_const const /* LinuxPAM, OpenPAM */
+# define sshpam_const const /* LinuxPAM, OpenPAM, AIX */
#endif
/* Ambiguity in spec: is it an array of pointers or a pointer to an array? */
@@ -154,9 +154,12 @@ sshpam_sigchld_handler(int sig)
<= 0) {
/* PAM thread has not exitted, privsep slave must have */
kill(cleanup_ctxt->pam_thread, SIGTERM);
- if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, 0)
- <= 0)
- return; /* could not wait */
+ while (waitpid(cleanup_ctxt->pam_thread,
+ &sshpam_thread_status, 0) == -1) {
+ if (errno == EINTR)
+ continue;
+ return;
+ }
}
if (WIFSIGNALED(sshpam_thread_status) &&
WTERMSIG(sshpam_thread_status) == SIGTERM)
@@ -217,7 +220,11 @@ pthread_join(sp_pthread_t thread, void **value)
if (sshpam_thread_status != -1)
return (sshpam_thread_status);
signal(SIGCHLD, sshpam_oldsig);
- waitpid(thread, &status, 0);
+ while (waitpid(thread, &status, 0) == -1) {
+ if (errno == EINTR)
+ continue;
+ fatal("%s: waitpid: %s", __func__, strerror(errno));
+ }
return (status);
}
#endif
@@ -229,10 +236,10 @@ static int sshpam_authenticated = 0;
static int sshpam_session_open = 0;
static int sshpam_cred_established = 0;
static int sshpam_account_status = -1;
+static int sshpam_maxtries_reached = 0;
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
@@ -365,17 +372,6 @@ sshpam_thread_conv(int n, sshpam_const struct pam_message **msg,
for (i = 0; i < n; ++i) {
switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
case PAM_PROMPT_ECHO_OFF:
- buffer_put_cstring(&buffer,
- PAM_MSG_MEMBER(msg, i, msg));
- if (ssh_msg_send(ctxt->pam_csock,
- PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1)
- goto fail;
- if (ssh_msg_recv(ctxt->pam_csock, &buffer) == -1)
- goto fail;
- if (buffer_get_char(&buffer) != PAM_AUTHTOK)
- goto fail;
- reply[i].resp = buffer_get_string(&buffer, NULL);
- break;
case PAM_PROMPT_ECHO_ON:
buffer_put_cstring(&buffer,
PAM_MSG_MEMBER(msg, i, msg));
@@ -389,12 +385,6 @@ sshpam_thread_conv(int n, sshpam_const struct pam_message **msg,
reply[i].resp = buffer_get_string(&buffer, NULL);
break;
case PAM_ERROR_MSG:
- buffer_put_cstring(&buffer,
- PAM_MSG_MEMBER(msg, i, msg));
- if (ssh_msg_send(ctxt->pam_csock,
- PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1)
- goto fail;
- break;
case PAM_TEXT_INFO:
buffer_put_cstring(&buffer,
PAM_MSG_MEMBER(msg, i, msg));
@@ -468,6 +458,8 @@ sshpam_thread(void *ctxtp)
if (sshpam_err != PAM_SUCCESS)
goto auth_fail;
sshpam_err = pam_authenticate(sshpam_handle, flags);
+ if (sshpam_err == PAM_MAXTRIES)
+ sshpam_set_maxtries_reached(1);
if (sshpam_err != PAM_SUCCESS)
goto auth_fail;
@@ -519,6 +511,8 @@ sshpam_thread(void *ctxtp)
/* XXX - can't do much about an error here */
if (sshpam_err == PAM_ACCT_EXPIRED)
ssh_msg_send(ctxt->pam_csock, PAM_ACCT_EXPIRED, &buffer);
+ else if (sshpam_maxtries_reached)
+ ssh_msg_send(ctxt->pam_csock, PAM_MAXTRIES, &buffer);
else
ssh_msg_send(ctxt->pam_csock, PAM_AUTH_ERR, &buffer);
buffer_free(&buffer);
@@ -624,6 +618,7 @@ sshpam_init(Authctxt *authctxt)
extern char *__progname;
const char *pam_rhost, *pam_user, *user = authctxt->user;
const char **ptr_pam_user = &pam_user;
+ struct ssh *ssh = active_state; /* XXX */
if (sshpam_handle != NULL) {
/* We already have a PAM context; check if the user matches */
@@ -644,7 +639,7 @@ sshpam_init(Authctxt *authctxt)
sshpam_handle = NULL;
return (-1);
}
- pam_rhost = get_remote_name_or_ip(utmp_len, options.use_dns);
+ pam_rhost = auth_get_canonical_hostname(ssh, options.use_dns);
debug("PAM: setting PAM_RHOST to \"%s\"", pam_rhost);
sshpam_err = pam_set_item(sshpam_handle, PAM_RHOST, pam_rhost);
if (sshpam_err != PAM_SUCCESS) {
@@ -715,6 +710,7 @@ static int
sshpam_query(void *ctx, char **name, char **info,
u_int *num, char ***prompts, u_int **echo_on)
{
+ struct ssh *ssh = active_state; /* XXX */
Buffer buffer;
struct pam_ctxt *ctxt = ctx;
size_t plen;
@@ -757,7 +753,11 @@ sshpam_query(void *ctx, char **name, char **info,
free(msg);
break;
case PAM_ACCT_EXPIRED:
- sshpam_account_status = 0;
+ case PAM_MAXTRIES:
+ if (type == PAM_ACCT_EXPIRED)
+ sshpam_account_status = 0;
+ if (type == PAM_MAXTRIES)
+ sshpam_set_maxtries_reached(1);
/* FALLTHROUGH */
case PAM_AUTH_ERR:
debug3("PAM: %s", pam_strerror(sshpam_handle, type));
@@ -797,7 +797,7 @@ sshpam_query(void *ctx, char **name, char **info,
error("PAM: %s for %s%.100s from %.100s", msg,
sshpam_authctxt->valid ? "" : "illegal user ",
sshpam_authctxt->user,
- get_remote_name_or_ip(utmp_len, options.use_dns));
+ auth_get_canonical_hostname(ssh, options.use_dns));
/* FALLTHROUGH */
default:
*num = 0;
@@ -810,12 +810,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 = malloc(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) {
@@ -836,8 +859,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);
@@ -1181,6 +1207,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 "
@@ -1196,7 +1223,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);
@@ -1206,6 +1233,9 @@ sshpam_auth_passwd(Authctxt *authctxt, const char *password)
sshpam_err = pam_authenticate(sshpam_handle, flags);
sshpam_password = NULL;
+ free(fake);
+ if (sshpam_err == PAM_MAXTRIES)
+ sshpam_set_maxtries_reached(1);
if (sshpam_err == PAM_SUCCESS && authctxt->valid) {
debug("PAM: password authentication accepted for %.100s",
authctxt->user);
@@ -1217,4 +1247,21 @@ sshpam_auth_passwd(Authctxt *authctxt, const char *password)
return 0;
}
}
+
+int
+sshpam_get_maxtries_reached(void)
+{
+ return sshpam_maxtries_reached;
+}
+
+void
+sshpam_set_maxtries_reached(int reached)
+{
+ if (reached == 0 || sshpam_maxtries_reached)
+ return;
+ sshpam_maxtries_reached = 1;
+ options.password_authentication = 0;
+ options.kbd_interactive_authentication = 0;
+ options.challenge_response_authentication = 0;
+}
#endif /* USE_PAM */