From 3ed9218c336607846563daea5d5ab4f701f4e042 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Tue, 8 Mar 2016 14:01:29 -0800 Subject: unbreak PAM after canohost refactor --- auth-pam.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'auth-pam.c') diff --git a/auth-pam.c b/auth-pam.c index 8425af1ea..7cf9cf5fc 100644 --- a/auth-pam.c +++ b/auth-pam.c @@ -624,6 +624,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 +645,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 +716,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; @@ -797,7 +799,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; -- cgit v1.2.3 From 39c0cecaa188a37a2e134795caa68e03f3ced592 Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Fri, 20 May 2016 10:01:58 +1000 Subject: Fix comment about sshpam_const and AIX. From mschwager via github. --- auth-pam.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'auth-pam.c') diff --git a/auth-pam.c b/auth-pam.c index 7cf9cf5fc..f80278c3a 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? */ -- cgit v1.2.3 From 009891afc8df37bc2101e15d1e0b6433cfb90549 Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Fri, 17 Jun 2016 14:34:09 +1000 Subject: Remove duplicate code from PAM. ok djm@ --- auth-pam.c | 17 ----------------- 1 file changed, 17 deletions(-) (limited to 'auth-pam.c') diff --git a/auth-pam.c b/auth-pam.c index f80278c3a..451de78cd 100644 --- a/auth-pam.c +++ b/auth-pam.c @@ -365,17 +365,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 +378,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)); -- cgit v1.2.3 From 283b97ff33ea2c641161950849931bd578de6946 Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Fri, 15 Jul 2016 13:49:44 +1000 Subject: Mitigate timing of disallowed users PAM logins. When sshd decides to not allow a login (eg PermitRootLogin=no) and it's using PAM, it sends a fake password to PAM so that the timing for the failure is not noticeably different whether or not the password is correct. This behaviour can be detected by sending a very long password string which is slower to hash than the fake password. Mitigate by constructing an invalid password that is the same length as the one from the client and thus takes the same time to hash. Diff from djm@ --- auth-pam.c | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) (limited to 'auth-pam.c') diff --git a/auth-pam.c b/auth-pam.c index 451de78cd..465b5a702 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 @@ -795,12 +794,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) { @@ -821,8 +843,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); @@ -1166,6 +1191,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 " @@ -1181,7 +1207,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); @@ -1191,6 +1217,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); -- cgit v1.2.3 From 01558b7b07af43da774d3a11a5c51fa9c310849d Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Mon, 18 Jul 2016 09:33:25 +1000 Subject: Handle PAM_MAXTRIES from modules. bz#2249: handle the case where PAM returns PAM_MAXTRIES by ceasing to offer password and keyboard-interative authentication methods. Should prevent "sshd ignoring max retries" warnings in the log. ok djm@ It probably won't trigger with keyboard-interactive in the default configuration because the retry counter is stored in module-private storage which goes away with the sshd PAM process (see bz#688). On the other hand, those cases probably won't log a warning either. --- auth-pam.c | 30 +++++++++++++++++++++++++++++- auth-pam.h | 2 ++ monitor.c | 5 +++++ monitor_wrap.c | 5 +++++ 4 files changed, 41 insertions(+), 1 deletion(-) (limited to 'auth-pam.c') diff --git a/auth-pam.c b/auth-pam.c index 465b5a702..1f13c181c 100644 --- a/auth-pam.c +++ b/auth-pam.c @@ -229,6 +229,7 @@ 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; @@ -450,6 +451,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; @@ -501,6 +504,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); @@ -741,7 +746,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)); @@ -1218,6 +1227,8 @@ 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); @@ -1229,4 +1240,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 */ diff --git a/auth-pam.h b/auth-pam.h index a1a2b52d8..2e9a0c0a3 100644 --- a/auth-pam.h +++ b/auth-pam.h @@ -45,6 +45,8 @@ void free_pam_environment(char **); void sshpam_thread_cleanup(void); void sshpam_cleanup(void); int sshpam_auth_passwd(Authctxt *, const char *); +int sshpam_get_maxtries_reached(void); +void sshpam_set_maxtries_reached(int); int is_pam_session_open(void); #endif /* USE_PAM */ diff --git a/monitor.c b/monitor.c index 8b3c27a76..fbe965e7c 100644 --- a/monitor.c +++ b/monitor.c @@ -75,6 +75,7 @@ #include "cipher.h" #include "kex.h" #include "dh.h" +#include "auth-pam.h" #ifdef TARGET_OS_MAC /* XXX Broken krb5 headers on Mac */ #undef TARGET_OS_MAC #include "zlib.h" @@ -920,6 +921,9 @@ mm_answer_authpassword(int sock, Buffer *m) buffer_clear(m); buffer_put_int(m, authenticated); +#ifdef USE_PAM + buffer_put_int(m, sshpam_get_maxtries_reached()); +#endif debug3("%s: sending result %d", __func__, authenticated); mm_request_send(sock, MONITOR_ANS_AUTHPASSWORD, m); @@ -1119,6 +1123,7 @@ mm_answer_pam_query(int sock, Buffer *m) free(name); buffer_put_cstring(m, info); free(info); + buffer_put_int(m, sshpam_get_maxtries_reached()); buffer_put_int(m, num); for (i = 0; i < num; ++i) { buffer_put_cstring(m, prompts[i]); diff --git a/monitor_wrap.c b/monitor_wrap.c index 552004902..99dc13b61 100644 --- a/monitor_wrap.c +++ b/monitor_wrap.c @@ -60,6 +60,7 @@ #include "packet.h" #include "mac.h" #include "log.h" +#include "auth-pam.h" #ifdef TARGET_OS_MAC /* XXX Broken krb5 headers on Mac */ #undef TARGET_OS_MAC #include "zlib.h" @@ -362,6 +363,9 @@ mm_auth_password(Authctxt *authctxt, char *password) mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_AUTHPASSWORD, &m); authenticated = buffer_get_int(&m); +#ifdef USE_PAM + sshpam_set_maxtries_reached(buffer_get_int(&m)); +#endif buffer_free(&m); @@ -644,6 +648,7 @@ mm_sshpam_query(void *ctx, char **name, char **info, debug3("%s: pam_query returned %d", __func__, ret); *name = buffer_get_string(&m, NULL); *info = buffer_get_string(&m, NULL); + sshpam_set_maxtries_reached(buffer_get_int(&m)); *num = buffer_get_int(&m); if (*num > PAM_MAX_NUM_MSG) fatal("%s: recieved %u PAM messages, expected <= %u", -- cgit v1.2.3 From 10358abd087ab228b7ce2048efc4f3854a9ab9a6 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Fri, 22 Jul 2016 14:06:36 +1000 Subject: retry waitpid on EINTR failure patch from Jakub Jelen on bz#2581; ok dtucker@ --- auth-pam.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'auth-pam.c') diff --git a/auth-pam.c b/auth-pam.c index 1f13c181c..348fe370a 100644 --- a/auth-pam.c +++ b/auth-pam.c @@ -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 -- cgit v1.2.3