# HG changeset patch # User Matt Johnston # Date 1553098478 -28800 # Node ID a2bbc22ea1e6f24f333b8869968c7794f5de0092 # Parent 96e4c9b2cc00d58cc1f23bb7ca4d8a08569cf82f# Parent 228b086794b7f381d21f34699edbf77ede826625 merge coverity diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 README --- a/README Wed Mar 21 00:52:02 2018 +0800 +++ b/README Thu Mar 21 00:14:38 2019 +0800 @@ -51,7 +51,7 @@ ============================================================================ -To run the server, you need to server keys, this is one-off: +To run the server, you need to generate server keys, this is one-off: ./dropbearkey -t rsa -f dropbear_rsa_host_key ./dropbearkey -t dss -f dropbear_dss_host_key ./dropbearkey -t ecdsa -f dropbear_ecdsa_host_key diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 auth.h --- a/auth.h Wed Mar 21 00:52:02 2018 +0800 +++ b/auth.h Thu Mar 21 00:14:38 2019 +0800 @@ -37,9 +37,9 @@ void send_msg_userauth_failure(int partial, int incrfail); void send_msg_userauth_success(void); void send_msg_userauth_banner(const buffer *msg); -void svr_auth_password(void); -void svr_auth_pubkey(void); -void svr_auth_pam(void); +void svr_auth_password(int valid_user); +void svr_auth_pubkey(int valid_user); +void svr_auth_pam(int valid_user); #if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT int svr_pubkey_allows_agentfwd(void); @@ -78,7 +78,7 @@ void cli_auth_pubkey_cleanup(void); -#define MAX_USERNAME_LEN 25 /* arbitrary for the moment */ +#define MAX_USERNAME_LEN 100 /* arbitrary for the moment */ #define AUTH_TYPE_NONE 1 #define AUTH_TYPE_PUBKEY (1 << 1) @@ -108,11 +108,14 @@ unsigned int authdone; /* 0 if we haven't authed, 1 if we have. Applies for client and server (though has differing meanings). */ + unsigned int perm_warn; /* Server only, set if bad permissions on ~/.ssh/authorized_keys have already been logged. */ unsigned int checkusername_failed; /* Server only, set if checkusername has already failed */ + struct timespec auth_starttime; /* Server only, time of receiving current + SSH_MSG_USERAUTH_REQUEST */ /* These are only used for the server */ uid_t pw_uid; diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 channel.h --- a/channel.h Wed Mar 21 00:52:02 2018 +0800 +++ b/channel.h Thu Mar 21 00:14:38 2019 +0800 @@ -69,10 +69,6 @@ int sent_close, recv_close; int recv_eof, sent_eof; - /* Set after running the ChanType-specific close hander - * to ensure we don't run it twice (nor type->checkclose()). */ - int close_handler_done; - struct dropbear_progress_connection *conn_pending; int initconn; /* used for TCP forwarding, whether the channel has been fully initialised */ @@ -95,10 +91,17 @@ int sepfds; /* Whether this channel has separate pipes for in/out or not */ const char *name; + /* Sets up the channel */ int (*inithandler)(struct Channel*); + /* Called to check whether a channel should close, separately from the FD being closed. + Used for noticing process exiting */ int (*check_close)(const struct Channel*); + /* Handler for ssh_msg_channel_request */ void (*reqhandler)(struct Channel*); + /* Called prior to sending ssh_msg_channel_close, used for sending exit status */ void (*closehandler)(const struct Channel*); + /* Frees resources, called just prior to channel being removed */ + void (*cleanup)(const struct Channel*); }; /* Callback for connect_remote */ diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 cli-agentfwd.c --- a/cli-agentfwd.c Wed Mar 21 00:52:02 2018 +0800 +++ b/cli-agentfwd.c Thu Mar 21 00:14:38 2019 +0800 @@ -52,6 +52,7 @@ new_agent_chan, NULL, NULL, + NULL, NULL }; diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 cli-chansession.c --- a/cli-chansession.c Wed Mar 21 00:52:02 2018 +0800 +++ b/cli-chansession.c Thu Mar 21 00:14:38 2019 +0800 @@ -35,7 +35,7 @@ #include "chansession.h" #include "agentfwd.h" -static void cli_closechansess(const struct Channel *channel); +static void cli_cleanupchansess(const struct Channel *channel); static int cli_initchansess(struct Channel *channel); static void cli_chansessreq(struct Channel *channel); static void send_chansess_pty_req(const struct Channel *channel); @@ -51,7 +51,8 @@ cli_initchansess, /* inithandler */ NULL, /* checkclosehandler */ cli_chansessreq, /* reqhandler */ - cli_closechansess, /* closehandler */ + NULL, /* closehandler */ + cli_cleanupchansess, /* cleanup */ }; static void cli_chansessreq(struct Channel *channel) { @@ -83,7 +84,7 @@ /* If the main session goes, we close it up */ -static void cli_closechansess(const struct Channel *UNUSED(channel)) { +static void cli_cleanupchansess(const struct Channel *UNUSED(channel)) { cli_tty_cleanup(); /* Restore tty modes etc */ /* This channel hasn't gone yet, so we have > 1 */ @@ -387,7 +388,8 @@ cli_init_netcat, /* inithandler */ NULL, NULL, - cli_closechansess + NULL, + cli_cleanupchansess }; void cli_send_netcat_request() { diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 cli-runopts.c --- a/cli-runopts.c Wed Mar 21 00:52:02 2018 +0800 +++ b/cli-runopts.c Thu Mar 21 00:14:38 2019 +0800 @@ -891,6 +891,7 @@ #ifndef DISABLE_SYSLOG "\tUseSyslog\n" #endif + "\tPort\n" ); exit(EXIT_SUCCESS); } @@ -909,5 +910,10 @@ } #endif + if (match_extendedopt(&optstr, "Port") == DROPBEAR_SUCCESS) { + cli_opts.remoteport = optstr; + return; + } + dropbear_log(LOG_WARNING, "Ignoring unknown configuration option '%s'", origstr); } diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 cli-session.c --- a/cli-session.c Wed Mar 21 00:52:02 2018 +0800 +++ b/cli-session.c Thu Mar 21 00:14:38 2019 +0800 @@ -356,6 +356,7 @@ } static void cli_finished() { + TRACE(("cli_finised()")) session_cleanup(); fprintf(stderr, "Connection to %s@%s:%s closed.\n", cli_opts.username, diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 cli-tcpfwd.c --- a/cli-tcpfwd.c Wed Mar 21 00:52:02 2018 +0800 +++ b/cli-tcpfwd.c Thu Mar 21 00:14:38 2019 +0800 @@ -40,6 +40,7 @@ newtcpforwarded, NULL, NULL, + NULL, NULL }; #endif @@ -55,6 +56,7 @@ tcp_prio_inithandler, NULL, NULL, + NULL, NULL }; #endif @@ -135,7 +137,7 @@ tcpinfo->chantype = &cli_chan_tcplocal; tcpinfo->tcp_type = direct; - ret = listen_tcpfwd(tcpinfo); + ret = listen_tcpfwd(tcpinfo, NULL); if (ret == DROPBEAR_FAILURE) { m_free(tcpinfo); diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 common-channel.c --- a/common-channel.c Wed Mar 21 00:52:02 2018 +0800 +++ b/common-channel.c Thu Mar 21 00:14:38 2019 +0800 @@ -144,7 +144,6 @@ newchan->index = i; newchan->sent_close = newchan->recv_close = 0; newchan->sent_eof = newchan->recv_eof = 0; - newchan->close_handler_done = 0; newchan->remotechan = remotechan; newchan->transwindow = transwindow; @@ -286,7 +285,7 @@ channel->extrabuf ? cbuf_getused(channel->extrabuf) : 0)) if (!channel->flushing - && !channel->close_handler_done + && !channel->sent_close && channel->type->check_close && channel->type->check_close(channel)) { @@ -298,7 +297,7 @@ channel, to ensure that the shell has exited (and the exit status retrieved) before we close things up. */ if (!channel->type->check_close - || channel->close_handler_done + || channel->sent_close || channel->type->check_close(channel)) { close_allowed = 1; } @@ -385,10 +384,8 @@ static void send_msg_channel_close(struct Channel *channel) { TRACE(("enter send_msg_channel_close %p", (void*)channel)) - if (channel->type->closehandler - && !channel->close_handler_done) { + if (channel->type->closehandler) { channel->type->closehandler(channel); - channel->close_handler_done = 1; } CHECKCLEARTOWRITE(); @@ -661,10 +658,8 @@ m_close(channel->errfd); } - if (!channel->close_handler_done - && channel->type->closehandler) { - channel->type->closehandler(channel); - channel->close_handler_done = 1; + if (channel->type->cleanup) { + channel->type->cleanup(channel); } if (channel->conn_pending) { @@ -690,13 +685,7 @@ TRACE(("enter recv_msg_channel_request %p", (void*)channel)) - if (channel->sent_close) { - TRACE(("leave recv_msg_channel_request: already closed channel")) - return; - } - - if (channel->type->reqhandler - && !channel->close_handler_done) { + if (channel->type->reqhandler) { channel->type->reqhandler(channel); } else { int wantreply; @@ -1011,6 +1000,11 @@ void send_msg_channel_failure(const struct Channel *channel) { TRACE(("enter send_msg_channel_failure")) + + if (channel->sent_close) { + TRACE(("Skipping sending msg_channel_failure for closed channel")) + return; + } CHECKCLEARTOWRITE(); buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_FAILURE); @@ -1024,6 +1018,10 @@ void send_msg_channel_success(const struct Channel *channel) { TRACE(("enter send_msg_channel_success")) + if (channel->sent_close) { + TRACE(("Skipping sending msg_channel_success for closed channel")) + return; + } CHECKCLEARTOWRITE(); buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_SUCCESS); diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 common-session.c --- a/common-session.c Wed Mar 21 00:52:02 2018 +0800 +++ b/common-session.c Thu Mar 21 00:14:38 2019 +0800 @@ -68,6 +68,16 @@ /* Sets it to lowdelay */ update_channel_prio(); +#if !DROPBEAR_SVR_MULTIUSER + /* A sanity check to prevent an accidental configuration option + leaving multiuser systems exposed */ + errno = 0; + getuid(); + if (errno != ENOSYS) { + dropbear_exit("Non-multiuser Dropbear requires a non-multiuser kernel"); + } +#endif + now = monotonic_now(); ses.connect_time = now; ses.last_packet_time_keepalive_recv = now; diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 configure.ac --- a/configure.ac Wed Mar 21 00:52:02 2018 +0800 +++ b/configure.ac Thu Mar 21 00:14:38 2019 +0800 @@ -497,6 +497,9 @@ AC_CHECK_FUNCS(setutxent utmpxname) AC_CHECK_FUNCS(logout updwtmp logwtmp) +# POSIX monotonic time +AC_CHECK_FUNCS(clock_gettime) + # OS X monotonic time AC_CHECK_HEADERS([mach/mach_time.h]) AC_CHECK_FUNCS(mach_absolute_time) diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 dbutil.c --- a/dbutil.c Wed Mar 21 00:52:02 2018 +0800 +++ b/dbutil.c Thu Mar 21 00:14:38 2019 +0800 @@ -605,71 +605,67 @@ return c; } -#if defined(__linux__) && defined(SYS_clock_gettime) -/* CLOCK_MONOTONIC_COARSE was added in Linux 2.6.32 but took a while to -reach userspace include headers */ -#ifndef CLOCK_MONOTONIC_COARSE -#define CLOCK_MONOTONIC_COARSE 6 -#endif -/* Some old toolchains know SYS_clock_gettime but not CLOCK_MONOTONIC */ -#ifndef CLOCK_MONOTONIC -#define CLOCK_MONOTONIC 1 -#endif -static clockid_t get_linux_clock_source() { - struct timespec ts; - if (syscall(SYS_clock_gettime, CLOCK_MONOTONIC_COARSE, &ts) == 0) { - return CLOCK_MONOTONIC_COARSE; - } - - if (syscall(SYS_clock_gettime, CLOCK_MONOTONIC, &ts) == 0) { - return CLOCK_MONOTONIC; - } - return -1; -} -#endif - -time_t monotonic_now() { +/* higher-resolution monotonic timestamp, falls back to gettimeofday */ +void gettime_wrapper(struct timespec *now) { + struct timeval tv; #if DROPBEAR_FUZZ if (fuzz.fuzzing) { /* time stands still when fuzzing */ - return 5; + now->tv_sec = 5; + now->tv_nsec = 0; } #endif + +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) + /* POSIX monotonic clock. Newer Linux, BSD, MacOSX >10.12 */ + if (clock_gettime(CLOCK_MONOTONIC, now) == 0) { + return; + } +#endif + #if defined(__linux__) && defined(SYS_clock_gettime) { - static clockid_t clock_source = -2; - - if (clock_source == -2) { - /* First run, find out which one works. - -1 will fall back to time() */ - clock_source = get_linux_clock_source(); - } - - if (clock_source >= 0) { - struct timespec ts; - if (syscall(SYS_clock_gettime, clock_source, &ts) != 0) { - /* Intermittent clock failures should not happen */ - dropbear_exit("Clock broke"); + /* Old linux toolchain - kernel might support it but not the build headers */ + /* Also glibc <2.17 requires -lrt which we neglect to add */ + static int linux_monotonic_failed = 0; + if (!linux_monotonic_failed) { + /* CLOCK_MONOTONIC isn't in some headers */ + int clock_source_monotonic = 1; + if (syscall(SYS_clock_gettime, clock_source_monotonic, now) == 0) { + return; + } else { + /* Don't try again */ + linux_monotonic_failed = 1; } - return ts.tv_sec; } } -#endif /* linux clock_gettime */ +#endif /* linux fallback clock_gettime */ #if defined(HAVE_MACH_ABSOLUTE_TIME) { - /* OS X, see https://developer.apple.com/library/mac/qa/qa1398/_index.html */ + /* OS X pre 10.12, see https://developer.apple.com/library/mac/qa/qa1398/_index.html */ static mach_timebase_info_data_t timebase_info; + uint64_t scaled_time; if (timebase_info.denom == 0) { mach_timebase_info(&timebase_info); } - return mach_absolute_time() * timebase_info.numer / timebase_info.denom - / 1e9; + scaled_time = mach_absolute_time() * timebase_info.numer / timebase_info.denom; + now->tv_sec = scaled_time / 1000000000; + now->tv_nsec = scaled_time % 1000000000; } #endif /* osx mach_absolute_time */ /* Fallback for everything else - this will sometimes go backwards */ - return time(NULL); + gettimeofday(&tv, NULL); + now->tv_sec = tv.tv_sec; + now->tv_nsec = 1000*tv.tv_usec; +} + +/* second-resolution monotonic timestamp */ +time_t monotonic_now() { + struct timespec ts; + gettime_wrapper(&ts); + return ts.tv_sec; } void fsync_parent_dir(const char* fn) { diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 dbutil.h --- a/dbutil.h Wed Mar 21 00:52:02 2018 +0800 +++ b/dbutil.h Thu Mar 21 00:14:38 2019 +0800 @@ -83,6 +83,8 @@ /* Returns a time in seconds that doesn't go backwards - does not correspond to a real-world clock */ time_t monotonic_now(void); +/* Higher resolution clock_gettime(CLOCK_MONOTONIC) wrapper */ +void gettime_wrapper(struct timespec *now); char * expand_homedir_path(const char *inpath); diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 default_options.h --- a/default_options.h Wed Mar 21 00:52:02 2018 +0800 +++ b/default_options.h Thu Mar 21 00:14:38 2019 +0800 @@ -196,6 +196,11 @@ * authorized_keys file into account */ #define DROPBEAR_SVR_PUBKEY_OPTIONS 1 +/* Set this to 0 if your system does not have multiple user support. + (Linux kernel CONFIG_MULTIUSER option) + The resulting binary will not run on a normal system. */ +#define DROPBEAR_SVR_MULTIUSER 1 + /* Client authentication options */ #define DROPBEAR_CLI_PASSWORD_AUTH 1 #define DROPBEAR_CLI_PUBKEY_AUTH 1 diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 ifndef_wrapper.sh --- a/ifndef_wrapper.sh Wed Mar 21 00:52:02 2018 +0800 +++ b/ifndef_wrapper.sh Thu Mar 21 00:14:38 2019 +0800 @@ -2,6 +2,6 @@ # Wrap all "#define X Y" with a #ifndef X...#endif" -sed -E 's/^( *#define ([^ ]+) .*)/#ifndef \2\ +sed 's/^\( *#define \([^ ][^ ]*\) .*\)/#ifndef \2\ \1\ #endif/' diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 includes.h diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 kex.h --- a/kex.h Wed Mar 21 00:52:02 2018 +0800 +++ b/kex.h Thu Mar 21 00:14:38 2019 +0800 @@ -106,7 +106,4 @@ int curve25519_donna(unsigned char *out, const unsigned char *secret, const unsigned char *other); #endif - -#define MAX_KEXHASHBUF 2000 - #endif /* DROPBEAR_KEX_H_ */ diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 keyimport.c --- a/keyimport.c Wed Mar 21 00:52:02 2018 +0800 +++ b/keyimport.c Thu Mar 21 00:14:38 2019 +0800 @@ -1097,7 +1097,9 @@ buf_putbytes(seq_buf, curve_oid, curve_oid_len); buf_incrwritepos(seq_buf, - ber_write_id_len(buf_getwriteptr(seq_buf, 10), 1, 2+1+pubkey_size, 0xa0)); + ber_write_id_len(buf_getwriteptr(seq_buf, 10), 1, + (pubkey_size +1 < 128 ? 2 : 3 ) +1 +pubkey_size, 0xa0)); + buf_incrwritepos(seq_buf, ber_write_id_len(buf_getwriteptr(seq_buf, 10), 3, 1+pubkey_size, 0)); buf_putbyte(seq_buf, 0); diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 netio.c --- a/netio.c Wed Mar 21 00:52:02 2018 +0800 +++ b/netio.c Thu Mar 21 00:14:38 2019 +0800 @@ -224,7 +224,6 @@ void set_connect_fds(fd_set *writefd) { m_list_elem *iter; - TRACE(("enter set_connect_fds")) iter = ses.conn_pending.first; while (iter) { m_list_elem *next_iter = iter->next; @@ -245,12 +244,10 @@ } iter = next_iter; } - TRACE(("leave set_connect_fds")) } void handle_connect_fds(const fd_set *writefd) { m_list_elem *iter; - TRACE(("enter handle_connect_fds")) for (iter = ses.conn_pending.first; iter; iter = iter->next) { int val; socklen_t vallen = sizeof(val); @@ -284,7 +281,6 @@ return; } } - TRACE(("leave handle_connect_fds - end iter")) } void connect_set_writequeue(struct dropbear_progress_connection *c, struct Queue *writequeue) { @@ -298,7 +294,11 @@ buffer *writebuf; #ifndef IOV_MAX - #define IOV_MAX UIO_MAXIOV + #if defined(__CYGWIN__) && !defined(UIO_MAXIOV) + #define IOV_MAX 1024 + #else + #define IOV_MAX UIO_MAXIOV + #endif #endif *iov_count = MIN(MIN(queue->count, IOV_MAX), *iov_count); diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 packet.c --- a/packet.c Wed Mar 21 00:52:02 2018 +0800 +++ b/packet.c Thu Mar 21 00:14:38 2019 +0800 @@ -58,7 +58,7 @@ void write_packet() { ssize_t written; -#ifdef HAVE_WRITEV +#if defined(HAVE_WRITEV) && (defined(IOV_MAX) || defined(UIO_MAXIOV)) /* 50 is somewhat arbitrary */ unsigned int iov_count = 50; struct iovec iov[50]; @@ -110,8 +110,6 @@ /* Get the next buffer in the queue of encrypted packets to write*/ writebuf = (buffer*)examine(&ses.writequeue); - /* The last byte of the buffer is not to be transmitted, but is - * a cleartext packet_type indicator */ len = writebuf->len - writebuf->pos; dropbear_assert(len > 0); /* Try to write as much as possible */ diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 scpmisc.c --- a/scpmisc.c Wed Mar 21 00:52:02 2018 +0800 +++ b/scpmisc.c Thu Mar 21 00:14:38 2019 +0800 @@ -102,7 +102,7 @@ len = strlen(str) + 1; cp = xmalloc(len); - strncpy(cp, str, len); + strlcpy(cp, str, len); return cp; } diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 scpmisc.h --- a/scpmisc.h Wed Mar 21 00:52:02 2018 +0800 +++ b/scpmisc.h Thu Mar 21 00:14:38 2019 +0800 @@ -27,8 +27,8 @@ typedef struct arglist arglist; struct arglist { char **list; - int num; - int nalloc; + u_int num; + u_int nalloc; }; void addargs(arglist *, char *, ...); void replacearg(arglist *, u_int, char *, ...); diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 svr-agentfwd.c --- a/svr-agentfwd.c Wed Mar 21 00:52:02 2018 +0800 +++ b/svr-agentfwd.c Thu Mar 21 00:14:38 2019 +0800 @@ -151,6 +151,7 @@ if (chansess->agentfile != NULL && chansess->agentdir != NULL) { +#if DROPBEAR_SVR_MULTIUSER /* Remove the dir as the user. That way they can't cause problems except * for themselves */ uid = getuid(); @@ -159,6 +160,7 @@ (seteuid(ses.authstate.pw_uid)) < 0) { dropbear_exit("Failed to set euid"); } +#endif /* 2 for "/" and "\0" */ len = strlen(chansess->agentdir) + strlen(chansess->agentfile) + 2; @@ -170,10 +172,12 @@ rmdir(chansess->agentdir); +#if DROPBEAR_SVR_MULTIUSER if ((seteuid(uid)) < 0 || (setegid(gid)) < 0) { dropbear_exit("Failed to revert euid"); } +#endif m_free(chansess->agentfile); m_free(chansess->agentdir); @@ -187,6 +191,7 @@ NULL, NULL, NULL, + NULL, NULL }; @@ -208,13 +213,14 @@ struct sockaddr_un addr; unsigned int prefix; - char path[sizeof(addr.sun_path)], sockfile[sizeof(addr.sun_path)]; + char path[(sizeof(addr.sun_path)-1)/2], sockfile[(sizeof(addr.sun_path)-1)/2]; mode_t mode; int i; uid_t uid; gid_t gid; int ret = DROPBEAR_FAILURE; +#if DROPBEAR_SVR_MULTIUSER /* drop to user privs to make the dir/file */ uid = getuid(); gid = getgid(); @@ -222,6 +228,7 @@ (seteuid(ses.authstate.pw_uid)) < 0) { dropbear_exit("Failed to set euid"); } +#endif memset((void*)&addr, 0x0, sizeof(addr)); addr.sun_family = AF_UNIX; @@ -261,10 +268,12 @@ out: +#if DROPBEAR_SVR_MULTIUSER if ((seteuid(uid)) < 0 || (setegid(gid)) < 0) { dropbear_exit("Failed to revert euid"); } +#endif return ret; } diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 svr-auth.c --- a/svr-auth.c Wed Mar 21 00:52:02 2018 +0800 +++ b/svr-auth.c Thu Mar 21 00:14:38 2019 +0800 @@ -79,6 +79,9 @@ TRACE(("enter recv_msg_userauth_request")) + /* for compensating failure delay */ + gettime_wrapper(&ses.authstate.auth_starttime); + /* ignore packets if auth is already done */ if (ses.authstate.authdone == 1) { TRACE(("leave recv_msg_userauth_request: authdone already")) @@ -149,10 +152,8 @@ if (methodlen == AUTH_METHOD_PASSWORD_LEN && strncmp(methodname, AUTH_METHOD_PASSWORD, AUTH_METHOD_PASSWORD_LEN) == 0) { - if (valid_user) { - svr_auth_password(); - goto out; - } + svr_auth_password(valid_user); + goto out; } } #endif @@ -164,10 +165,8 @@ if (methodlen == AUTH_METHOD_PASSWORD_LEN && strncmp(methodname, AUTH_METHOD_PASSWORD, AUTH_METHOD_PASSWORD_LEN) == 0) { - if (valid_user) { - svr_auth_pam(); - goto out; - } + svr_auth_pam(valid_user); + goto out; } } #endif @@ -177,12 +176,7 @@ if (methodlen == AUTH_METHOD_PUBKEY_LEN && strncmp(methodname, AUTH_METHOD_PUBKEY, AUTH_METHOD_PUBKEY_LEN) == 0) { - if (valid_user) { - svr_auth_pubkey(); - } else { - /* pubkey has no failure delay */ - send_msg_userauth_failure(0, 0); - } + svr_auth_pubkey(valid_user); goto out; } #endif @@ -282,7 +276,7 @@ /* check if we are running as non-root, and login user is different from the server */ uid = geteuid(); - if (uid != 0 && uid != ses.authstate.pw_uid) { + if (!(DROPBEAR_SVR_MULTIUSER && uid == 0) && uid != ses.authstate.pw_uid) { TRACE(("running as nonroot, only server uid is allowed")) dropbear_log(LOG_WARNING, "Login attempt with wrong user %s from %s", @@ -391,16 +385,48 @@ encrypt_packet(); if (incrfail) { - unsigned int delay; - genrandom((unsigned char*)&delay, sizeof(delay)); - /* We delay for 300ms +- 50ms */ - delay = 250000 + (delay % 100000); + /* The SSH_MSG_AUTH_FAILURE response is delayed to attempt to + avoid user enumeration and slow brute force attempts. + The delay is adjusted by the time already spent in processing + authentication (ses.authstate.auth_starttime timestamp). */ + + /* Desired total delay 300ms +-50ms (in nanoseconds). + Beware of integer overflow if increasing these values */ + const unsigned int mindelay = 250000000; + const unsigned int vardelay = 100000000; + unsigned int rand_delay; + struct timespec delay; + + gettime_wrapper(&delay); + delay.tv_sec -= ses.authstate.auth_starttime.tv_sec; + delay.tv_nsec -= ses.authstate.auth_starttime.tv_nsec; + + /* carry */ + if (delay.tv_nsec < 0) { + delay.tv_nsec += 1000000000; + delay.tv_sec -= 1; + } + + genrandom((unsigned char*)&rand_delay, sizeof(rand_delay)); + rand_delay = mindelay + (rand_delay % vardelay); + + if (delay.tv_sec == 0 && delay.tv_nsec <= mindelay) { + /* Compensate for elapsed time */ + delay.tv_nsec = rand_delay - delay.tv_nsec; + } else { + /* No time left or time went backwards, just delay anyway */ + delay.tv_sec = 0; + delay.tv_nsec = rand_delay; + } + + #if DROPBEAR_FUZZ if (!fuzz.fuzzing) #endif { - usleep(delay); + while (nanosleep(&delay, &delay) == -1 && errno == EINTR) { /* Go back to sleep */ } } + ses.authstate.failcount++; } diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 svr-authpam.c --- a/svr-authpam.c Wed Mar 21 00:52:02 2018 +0800 +++ b/svr-authpam.c Thu Mar 21 00:14:38 2019 +0800 @@ -178,13 +178,14 @@ * Keyboard interactive would be a lot nicer, but since PAM is synchronous, it * gets very messy trying to send the interactive challenges, and read the * interactive responses, over the network. */ -void svr_auth_pam() { +void svr_auth_pam(int valid_user) { struct UserDataS userData = {NULL, NULL}; struct pam_conv pamConv = { pamConvFunc, &userData /* submitted to pamvConvFunc as appdata_ptr */ }; + const char* printable_user = NULL; pam_handle_t* pamHandlep = NULL; @@ -204,12 +205,23 @@ password = buf_getstring(ses.payload, &passwordlen); + /* We run the PAM conversation regardless of whether the username is valid + in case the conversation function has an inherent delay. + Use ses.authstate.username rather than ses.authstate.pw_name. + After PAM succeeds we then check the valid_user flag too */ + /* used to pass data to the PAM conversation function - don't bother with * strdup() etc since these are touched only by our own conversation * function (above) which takes care of it */ - userData.user = ses.authstate.pw_name; + userData.user = ses.authstate.username; userData.passwd = password; + if (ses.authstate.pw_name) { + printable_user = ses.authstate.pw_name; + } else { + printable_user = ""; + } + /* Init pam */ if ((rc = pam_start("sshd", NULL, &pamConv, &pamHandlep)) != PAM_SUCCESS) { dropbear_log(LOG_WARNING, "pam_start() failed, rc=%d, %s", @@ -242,7 +254,7 @@ rc, pam_strerror(pamHandlep, rc)); dropbear_log(LOG_WARNING, "Bad PAM password attempt for '%s' from %s", - ses.authstate.pw_name, + printable_user, svr_ses.addrstring); send_msg_userauth_failure(0, 1); goto cleanup; @@ -253,12 +265,19 @@ rc, pam_strerror(pamHandlep, rc)); dropbear_log(LOG_WARNING, "Bad PAM password attempt for '%s' from %s", - ses.authstate.pw_name, + printable_user, svr_ses.addrstring); send_msg_userauth_failure(0, 1); goto cleanup; } + if (!valid_user) { + /* PAM auth succeeded but the username isn't allowed in for another reason + (checkusername() failed) */ + send_msg_userauth_failure(0, 1); + goto cleanup; + } + /* successful authentication */ dropbear_log(LOG_NOTICE, "PAM password auth succeeded for '%s' from %s", ses.authstate.pw_name, diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 svr-authpasswd.c --- a/svr-authpasswd.c Wed Mar 21 00:52:02 2018 +0800 +++ b/svr-authpasswd.c Thu Mar 21 00:14:38 2019 +0800 @@ -48,22 +48,14 @@ /* Process a password auth request, sending success or failure messages as * appropriate */ -void svr_auth_password() { +void svr_auth_password(int valid_user) { char * passwdcrypt = NULL; /* the crypt from /etc/passwd or /etc/shadow */ char * testcrypt = NULL; /* crypt generated from the user's password sent */ - char * password; + char * password = NULL; unsigned int passwordlen; - unsigned int changepw; - passwdcrypt = ses.authstate.pw_passwd; - -#ifdef DEBUG_HACKCRYPT - /* debugging crypt for non-root testing with shadows */ - passwdcrypt = DEBUG_HACKCRYPT; -#endif - /* check if client wants to change password */ changepw = buf_getbool(ses.payload); if (changepw) { @@ -73,12 +65,30 @@ } password = buf_getstring(ses.payload, &passwordlen); - - /* the first bytes of passwdcrypt are the salt */ - testcrypt = crypt(password, passwdcrypt); + if (valid_user && passwordlen <= DROPBEAR_MAX_PASSWORD_LEN) { + /* the first bytes of passwdcrypt are the salt */ + passwdcrypt = ses.authstate.pw_passwd; + testcrypt = crypt(password, passwdcrypt); + } m_burn(password, passwordlen); m_free(password); + /* After we have got the payload contents we can exit if the username + is invalid. Invalid users have already been logged. */ + if (!valid_user) { + send_msg_userauth_failure(0, 1); + return; + } + + if (passwordlen > DROPBEAR_MAX_PASSWORD_LEN) { + dropbear_log(LOG_WARNING, + "Too-long password attempt for '%s' from %s", + ses.authstate.pw_name, + svr_ses.addrstring); + send_msg_userauth_failure(0, 1); + return; + } + if (testcrypt == NULL) { /* crypt() with an invalid salt like "!!" */ dropbear_log(LOG_WARNING, "User account '%s' is locked", diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 svr-authpubkey.c --- a/svr-authpubkey.c Wed Mar 21 00:52:02 2018 +0800 +++ b/svr-authpubkey.c Thu Mar 21 00:14:38 2019 +0800 @@ -79,7 +79,7 @@ /* process a pubkey auth request, sending success or failure message as * appropriate */ -void svr_auth_pubkey() { +void svr_auth_pubkey(int valid_user) { unsigned char testkey; /* whether we're just checking if a key is usable */ char* algo = NULL; /* pubkey algo */ @@ -102,6 +102,15 @@ keybloblen = buf_getint(ses.payload); keyblob = buf_getptr(ses.payload, keybloblen); + if (!valid_user) { + /* Return failure once we have read the contents of the packet + required to validate a public key. + Avoids blind user enumeration though it isn't possible to prevent + testing for user existence if the public key is known */ + send_msg_userauth_failure(0, 0); + goto out; + } + /* check if the key is valid */ if (checkpubkey(algo, algolen, keyblob, keybloblen) == DROPBEAR_FAILURE) { send_msg_userauth_failure(0, 0); @@ -338,6 +347,7 @@ snprintf(filename, len + 22, "%s/.ssh/authorized_keys", ses.authstate.pw_dir); +#if DROPBEAR_SVR_MULTIUSER /* open the file as the authenticating user. */ origuid = getuid(); origgid = getgid(); @@ -345,13 +355,16 @@ (seteuid(ses.authstate.pw_uid)) < 0) { dropbear_exit("Failed to set euid"); } +#endif authfile = fopen(filename, "r"); +#if DROPBEAR_SVR_MULTIUSER if ((seteuid(origuid)) < 0 || (setegid(origgid)) < 0) { dropbear_exit("Failed to revert euid"); } +#endif if (authfile == NULL) { goto out; @@ -415,8 +428,9 @@ /* allocate max required pathname storage, * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */ - filename = m_malloc(len + 22); - strncpy(filename, ses.authstate.pw_dir, len+1); + len += 22; + filename = m_malloc(len); + strlcpy(filename, ses.authstate.pw_dir, len); /* check ~ */ if (checkfileperm(filename) != DROPBEAR_SUCCESS) { @@ -424,13 +438,13 @@ } /* check ~/.ssh */ - strncat(filename, "/.ssh", 5); /* strlen("/.ssh") == 5 */ + strlcat(filename, "/.ssh", len); if (checkfileperm(filename) != DROPBEAR_SUCCESS) { goto out; } /* now check ~/.ssh/authorized_keys */ - strncat(filename, "/authorized_keys", 16); + strlcat(filename, "/authorized_keys", len); if (checkfileperm(filename) != DROPBEAR_SUCCESS) { goto out; } diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 svr-chansession.c --- a/svr-chansession.c Wed Mar 21 00:52:02 2018 +0800 +++ b/svr-chansession.c Thu Mar 21 00:14:38 2019 +0800 @@ -51,6 +51,7 @@ static void addchildpid(struct ChanSess *chansess, pid_t pid); static void sesssigchild_handler(int val); static void closechansess(const struct Channel *channel); +static void cleanupchansess(const struct Channel *channel); static int newchansess(struct Channel *channel); static void chansessionrequest(struct Channel *channel); static int sesscheckclose(const struct Channel *channel); @@ -69,6 +70,7 @@ sesscheckclose, /* checkclosehandler */ chansessionrequest, /* reqhandler */ closechansess, /* closehandler */ + cleanupchansess /* cleanup */ }; /* required to clear environment */ @@ -91,7 +93,7 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { unsigned int i; struct exitinfo *ex = NULL; - TRACE(("sigchld handler: pid %d", pid)) + TRACE(("svr_chansess_checksignal : pid %d", pid)) ex = NULL; /* find the corresponding chansess */ @@ -285,8 +287,25 @@ return li; } +/* send exit status message before the channel is closed */ +static void closechansess(const struct Channel *channel) { + struct ChanSess *chansess; + + TRACE(("enter closechansess")) + + chansess = (struct ChanSess*)channel->typedata; + + if (chansess == NULL) { + TRACE(("leave closechansess: chansess == NULL")) + return; + } + + send_exitsignalstatus(channel); + TRACE(("leave closechansess")) +} + /* clean a session channel */ -static void closechansess(const struct Channel *channel) { +static void cleanupchansess(const struct Channel *channel) { struct ChanSess *chansess; unsigned int i; @@ -301,8 +320,6 @@ return; } - send_exitsignalstatus(channel); - m_free(chansess->cmd); m_free(chansess->term); @@ -932,6 +949,7 @@ #endif /* HAVE_CLEARENV */ #endif /* DEBUG_VALGRIND */ +#if DROPBEAR_SVR_MULTIUSER /* We can only change uid/gid as root ... */ if (getuid() == 0) { @@ -955,6 +973,7 @@ dropbear_exit("Couldn't change user as non-root"); } } +#endif /* set env vars */ addnewvar("USER", ses.authstate.pw_name); diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 svr-tcpfwd.c --- a/svr-tcpfwd.c Wed Mar 21 00:52:02 2018 +0800 +++ b/svr-tcpfwd.c Thu Mar 21 00:14:38 2019 +0800 @@ -57,6 +57,7 @@ tcp_prio_inithandler, NULL, NULL, + NULL, NULL }; @@ -168,6 +169,7 @@ unsigned int addrlen; struct TCPListener *tcpinfo = NULL; unsigned int port; + struct Listener *listener = NULL; TRACE(("enter remotetcpreq")) @@ -208,9 +210,9 @@ tcpinfo->listenaddr = m_strdup(request_addr); } - ret = listen_tcpfwd(tcpinfo); + ret = listen_tcpfwd(tcpinfo, &listener); if (DROPBEAR_SUCCESS == ret) { - tcpinfo->listenport = get_sock_port(ses.listeners[0]->socks[0]); + tcpinfo->listenport = get_sock_port(listener->socks[0]); *allocated_listen_port = tcpinfo->listenport; } @@ -237,7 +239,8 @@ newtcpdirect, /* init */ NULL, /* checkclose */ NULL, /* reqhandler */ - NULL /* closehandler */ + NULL, /* closehandler */ + NULL /* cleanup */ }; /* Called upon creating a new direct tcp channel (ie we connect out to an diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 svr-x11fwd.c --- a/svr-x11fwd.c Wed Mar 21 00:52:02 2018 +0800 +++ b/svr-x11fwd.c Thu Mar 21 00:14:38 2019 +0800 @@ -216,7 +216,8 @@ x11_inithandler, /* inithandler */ NULL, /* checkclose */ NULL, /* reqhandler */ - NULL /* closehandler */ + NULL, /* closehandler */ + NULL /* cleanup */ }; diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 sysoptions.h --- a/sysoptions.h Wed Mar 21 00:52:02 2018 +0800 +++ b/sysoptions.h Thu Mar 21 00:14:38 2019 +0800 @@ -86,6 +86,8 @@ /* Required for pubkey auth */ #define DROPBEAR_SIGNKEY_VERIFY ((DROPBEAR_SVR_PUBKEY_AUTH) || (DROPBEAR_CLIENT)) +#define DROPBEAR_MAX_PASSWORD_LEN 100 + #define SHA1_HASH_SIZE 20 #define MD5_HASH_SIZE 16 #define MAX_HASH_SIZE 64 /* sha512 */ @@ -225,7 +227,7 @@ #define DROPBEAR_ZLIB_MEM_LEVEL 8 #if (DROPBEAR_SVR_PASSWORD_AUTH) && (DROPBEAR_SVR_PAM_AUTH) -#error "You can't turn on PASSWORD and PAM auth both at once. Fix it in options.h" +#error "You can't turn on PASSWORD and PAM auth both at once. Fix it in localoptions.h" #endif /* PAM requires ./configure --enable-pam */ diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 tcp-accept.c --- a/tcp-accept.c Wed Mar 21 00:52:02 2018 +0800 +++ b/tcp-accept.c Thu Mar 21 00:14:38 2019 +0800 @@ -110,12 +110,12 @@ } } -int listen_tcpfwd(struct TCPListener* tcpinfo) { +int listen_tcpfwd(struct TCPListener* tcpinfo, struct Listener **ret_listener) { char portstring[NI_MAXSERV]; int socks[DROPBEAR_MAX_SOCKS]; - struct Listener *listener = NULL; int nsocks; + struct Listener *listener; char* errstring = NULL; TRACE(("enter listen_tcpfwd")) @@ -142,6 +142,10 @@ return DROPBEAR_FAILURE; } + if (ret_listener) { + *ret_listener = listener; + } + TRACE(("leave listen_tcpfwd: success")) return DROPBEAR_SUCCESS; } diff -r 96e4c9b2cc00 -r a2bbc22ea1e6 tcpfwd.h --- a/tcpfwd.h Wed Mar 21 00:52:02 2018 +0800 +++ b/tcpfwd.h Thu Mar 21 00:14:38 2019 +0800 @@ -26,6 +26,7 @@ #include "channel.h" #include "list.h" +#include "listener.h" struct TCPListener { @@ -69,7 +70,7 @@ void cli_recv_msg_request_failure(void); /* Common */ -int listen_tcpfwd(struct TCPListener* tcpinfo); +int listen_tcpfwd(struct TCPListener* tcpinfo, struct Listener **ret_listener); int tcp_prio_inithandler(struct Channel* chan); /* A random identifier */