From 1e4d64d300eb4d1cb15f7f2dca9aee720b0c0e98 Mon Sep 17 00:00:00 2001 From: Tuomas Haikarainen Date: Thu, 7 Jul 2022 11:37:31 +0300 Subject: [PATCH 1/7] Added permitopen option to authorized_keys It is now possible to limit local port forwarding to specified destination(s) by using the permitopen option in authorized_keys. Resolves #181 --- auth.h | 13 +++++++ dropbear.8 | 8 ++++ svr-authpubkeyoptions.c | 81 +++++++++++++++++++++++++++++++++++++++++ svr-tcpfwd.c | 5 +++ 4 files changed, 107 insertions(+) diff --git a/auth.h b/auth.h index 2063cad..264c1f1 100644 --- a/auth.h +++ b/auth.h @@ -28,6 +28,7 @@ #include "includes.h" #include "signkey.h" #include "chansession.h" +#include "list.h" void svr_authinitialise(void); @@ -45,6 +46,7 @@ int svr_pubkey_allows_agentfwd(void); int svr_pubkey_allows_tcpfwd(void); int svr_pubkey_allows_x11fwd(void); int svr_pubkey_allows_pty(void); +int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port); void svr_pubkey_set_forced_command(struct ChanSess *chansess); void svr_pubkey_options_cleanup(void); int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filename); @@ -54,6 +56,9 @@ int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filena #define svr_pubkey_allows_tcpfwd() 1 #define svr_pubkey_allows_x11fwd() 1 #define svr_pubkey_allows_pty() 1 +static inline int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port) + { (void)host; (void)port; return 1; } + static inline void svr_pubkey_set_forced_command(struct ChanSess *chansess) { } static inline void svr_pubkey_options_cleanup(void) { } #define svr_add_pubkey_options(x,y,z) DROPBEAR_SUCCESS @@ -93,6 +98,7 @@ void cli_auth_pubkey_cleanup(void); #define AUTH_METHOD_INTERACT "keyboard-interactive" #define AUTH_METHOD_INTERACT_LEN 20 +#define PUBKEY_OPTIONS_ANY_PORT UINT_MAX /* This structure is shared between server and client - it contains @@ -139,6 +145,13 @@ struct PubKeyOptions { int no_pty_flag; /* "command=" option. */ char * forced_command; + /* "permitopen=" option */ + m_list *permit_open_destinations; +}; + +struct PermitTCPFwdEntry { + char *host; + unsigned int port; }; #endif diff --git a/dropbear.8 b/dropbear.8 index 3073dd0..30df631 100644 --- a/dropbear.8 +++ b/dropbear.8 @@ -144,6 +144,14 @@ same functionality with other means even if no-pty is set. .B restrict Applies all the no- restrictions listed above. +.TP +.B permitopen=\fR"\fIhost:port\fR" +Restrict local port forwarding so that connection is allowed only to the +specified host and port. Multiple permitopen options separated by commas +can be set in authorized_keys. Wildcard character ('*') may be used in +port specification for matching any port. Hosts must be literal domain names or +IP addresses. + .TP .B command=\fR"\fIforced_command\fR" Disregard the command provided by the user and always run \fIforced_command\fR. diff --git a/svr-authpubkeyoptions.c b/svr-authpubkeyoptions.c index 447f4b7..e25bcf1 100644 --- a/svr-authpubkeyoptions.c +++ b/svr-authpubkeyoptions.c @@ -46,6 +46,7 @@ #include "dbutil.h" #include "signkey.h" #include "auth.h" +#include "runopts.h" #if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT @@ -88,6 +89,29 @@ int svr_pubkey_allows_pty() { return 1; } +/* Returns 1 if pubkey allows local tcp fowarding to the provided destination, + * 0 otherwise */ +int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port) { + if (ses.authstate.pubkey_options + && ses.authstate.pubkey_options->permit_open_destinations) { + m_list_elem *iter = ses.authstate.pubkey_options->permit_open_destinations->first; + while (iter) { + struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)iter->item; + if (strcmp(entry->host, host) == 0) { + if ((entry->port == PUBKEY_OPTIONS_ANY_PORT) || (entry->port == port)) { + return 1; + } + } + + iter = iter->next; + } + + return 0; + } + + return 1; +} + /* Set chansession command to the one forced * by any 'command' public key option. */ void svr_pubkey_set_forced_command(struct ChanSess *chansess) { @@ -113,6 +137,16 @@ void svr_pubkey_options_cleanup() { if (ses.authstate.pubkey_options->forced_command) { m_free(ses.authstate.pubkey_options->forced_command); } + if (ses.authstate.pubkey_options->permit_open_destinations) { + m_list_elem *iter = ses.authstate.pubkey_options->permit_open_destinations->first; + while (iter) { + struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)list_remove(iter); + m_free(entry->host); + m_free(entry); + iter = ses.authstate.pubkey_options->permit_open_destinations->first; + } + m_free(ses.authstate.pubkey_options->permit_open_destinations); + } m_free(ses.authstate.pubkey_options); } if (ses.authstate.pubkey_info) { @@ -205,6 +239,53 @@ int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filena dropbear_log(LOG_WARNING, "Badly formatted command= authorized_keys option"); goto bad_option; } + if (match_option(options_buf, "permitopen=\"") == DROPBEAR_SUCCESS) { + int valid_option = 0; + const unsigned char* permitopen_start = buf_getptr(options_buf, 0); + + if (!ses.authstate.pubkey_options->permit_open_destinations) { + ses.authstate.pubkey_options->permit_open_destinations = list_new(); + } + + while (options_buf->pos < options_buf->len) { + const char c = buf_getbyte(options_buf); + if (c == '"') { + char *spec = NULL; + char *portstring = NULL; + const int permitopen_len = buf_getptr(options_buf, 0) - permitopen_start; + struct PermitTCPFwdEntry *entry = + (struct PermitTCPFwdEntry*)m_malloc(sizeof(struct PermitTCPFwdEntry)); + + list_append(ses.authstate.pubkey_options->permit_open_destinations, entry); + spec = m_malloc(permitopen_len); + memcpy(spec, permitopen_start, permitopen_len - 1); + spec[permitopen_len - 1] = '\0'; + if ((split_address_port(spec, &entry->host, &portstring) == DROPBEAR_SUCCESS) + && entry->host && portstring) { + if (strcmp(portstring, "*") == 0) { + valid_option = 1; + entry->port = PUBKEY_OPTIONS_ANY_PORT; + TRACE(("local port forwarding allowed to host '%s'", entry->host)); + } else if (m_str_to_uint(portstring, &entry->port) == DROPBEAR_SUCCESS) { + valid_option = 1; + TRACE(("local port forwarding allowed to host '%s' and port '%u'", + entry->host, entry->port)); + } + } + + m_free(spec); + m_free(portstring); + break; + } + } + + if (valid_option) { + goto next_option; + } else { + dropbear_log(LOG_WARNING, "Badly formatted permitopen= authorized_keys option"); + goto bad_option; + } + } next_option: /* diff --git a/svr-tcpfwd.c b/svr-tcpfwd.c index 4aa3152..7967cfa 100644 --- a/svr-tcpfwd.c +++ b/svr-tcpfwd.c @@ -289,6 +289,11 @@ static int newtcpdirect(struct Channel * channel) { goto out; } + if (!svr_pubkey_allows_local_tcpfwd(desthost, destport)) { + TRACE(("leave newtcpdirect: local tcp forwarding not permitted to requested destination")); + goto out; + } + snprintf(portstring, sizeof(portstring), "%u", destport); channel->conn_pending = connect_remote(desthost, portstring, channel_connect_done, channel, NULL, NULL, DROPBEAR_PRIO_NORMAL); From 4c67d036565cd93388389b1693f0d72551dde6f7 Mon Sep 17 00:00:00 2001 From: Michele Giacomoli Date: Mon, 19 Sep 2022 16:50:43 +0200 Subject: [PATCH 2/7] Fix utx var typo --- loginrec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loginrec.c b/loginrec.c index af10d95..f93e12e 100644 --- a/loginrec.c +++ b/loginrec.c @@ -829,7 +829,7 @@ utmpx_perform_login(struct logininfo *li) return 0; } # else - if (!utmpx_write_direct(li, &ut)) { + if (!utmpx_write_direct(li, &utx)) { dropbear_log(LOG_WARNING, "utmpx_perform_login: utmp_write_direct() failed"); return 0; } From b734e5a4239c7f906a0752f2230a316eddf9eb59 Mon Sep 17 00:00:00 2001 From: Michele Giacomoli Date: Mon, 19 Sep 2022 17:45:55 +0200 Subject: [PATCH 3/7] Resort cli auth method tries --- cli-auth.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/cli-auth.c b/cli-auth.c index 32c9c91..20d6371 100644 --- a/cli-auth.c +++ b/cli-auth.c @@ -296,18 +296,6 @@ int cli_auth_try() { } #endif -#if DROPBEAR_CLI_PASSWORD_AUTH - if (!finished && (ses.authstate.authtypes & AUTH_TYPE_PASSWORD)) { - if (ses.keys->trans.algo_crypt->cipherdesc == NULL) { - fprintf(stderr, "Sorry, I won't let you use password auth unencrypted.\n"); - } else { - cli_auth_password(); - finished = 1; - cli_ses.lastauthtype = AUTH_TYPE_PASSWORD; - } - } -#endif - #if DROPBEAR_CLI_INTERACT_AUTH if (!finished && (ses.authstate.authtypes & AUTH_TYPE_INTERACT)) { if (ses.keys->trans.algo_crypt->cipherdesc == NULL) { @@ -322,6 +310,18 @@ int cli_auth_try() { } #endif +#if DROPBEAR_CLI_PASSWORD_AUTH + if (!finished && (ses.authstate.authtypes & AUTH_TYPE_PASSWORD)) { + if (ses.keys->trans.algo_crypt->cipherdesc == NULL) { + fprintf(stderr, "Sorry, I won't let you use password auth unencrypted.\n"); + } else { + cli_auth_password(); + finished = 1; + cli_ses.lastauthtype = AUTH_TYPE_PASSWORD; + } + } +#endif + TRACE(("cli_auth_try lastauthtype %d", cli_ses.lastauthtype)) if (finished) { From acf50a4f980690bea33092e509ca9236d4e23085 Mon Sep 17 00:00:00 2001 From: Michele Giacomoli Date: Mon, 19 Sep 2022 18:09:38 +0200 Subject: [PATCH 4/7] Flush stdin after fingerprint confirmation --- cli-kex.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli-kex.c b/cli-kex.c index c3b8846..2824edc 100644 --- a/cli-kex.c +++ b/cli-kex.c @@ -229,6 +229,8 @@ static void ask_to_confirm(const unsigned char* keyblob, unsigned int keybloblen fclose(tty); } else { response = getc(stdin); + // flush stdin buffer + while ((getchar()) != '\n'); } if (response == 'y') { From b36707ba4660bdf6ea996476eb7225156bc27b51 Mon Sep 17 00:00:00 2001 From: Michele Giacomoli Date: Mon, 19 Sep 2022 18:25:26 +0200 Subject: [PATCH 5/7] Fix comment style --- cli-kex.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli-kex.c b/cli-kex.c index 2824edc..6cb75c2 100644 --- a/cli-kex.c +++ b/cli-kex.c @@ -229,7 +229,7 @@ static void ask_to_confirm(const unsigned char* keyblob, unsigned int keybloblen fclose(tty); } else { response = getc(stdin); - // flush stdin buffer + /* flush stdin buffer */ while ((getchar()) != '\n'); } From 86efbae708f4d22af1e45488e732dccd990f729a Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Tue, 25 Oct 2022 21:12:08 +0800 Subject: [PATCH 6/7] Add flags so libtommath builds with -O3 This was the default prior to 2022.82 and makes a significant difference to performance. Perhaps at a later time this could be made more configurable. Discussion in https://github.com/mkj/dropbear/issues/174 --- libtommath/makefile_include.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libtommath/makefile_include.mk b/libtommath/makefile_include.mk index 711b630..f3ceb9c 100644 --- a/libtommath/makefile_include.mk +++ b/libtommath/makefile_include.mk @@ -104,7 +104,7 @@ LIBTOOLFLAGS += -no-undefined endif # add in the standard FLAGS -LTM_CFLAGS += $(CFLAGS) +LTM_CFLAGS := $(CFLAGS) $(LTM_CFLAGS) LTM_LFLAGS += $(LFLAGS) LTM_LDFLAGS += $(LDFLAGS) LTM_LIBTOOLFLAGS += $(LIBTOOLFLAGS) From f7d306e963685c9bb9d5d71cce2303b9b8b85cc4 Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Tue, 25 Oct 2022 21:17:56 +0800 Subject: [PATCH 7/7] Fix decompression size check Dropbear's decompression could erroneously exit with "bad packet, oversized decompressed" for a valid 32768 byte decompressed payload (an off-by-one error). It could be triggered particularly with larger SSH window sizes. This change also simplifies the function by allocating a single 32kB buffer rather than incrementally increasing the size. --- packet.c | 54 +++++++++++++++++++++--------------------------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/packet.c b/packet.c index fc8fe02..1055588 100644 --- a/packet.c +++ b/packet.c @@ -430,44 +430,32 @@ static buffer* buf_decompress(const buffer* buf, unsigned int len) { z_streamp zstream; zstream = ses.keys->recv.zstream; - ret = buf_new(len); + /* We use RECV_MAX_PAYLOAD_LEN+1 here to ensure that + we can detect an oversized payload after inflate() */ + ret = buf_new(RECV_MAX_PAYLOAD_LEN+1); zstream->avail_in = len; zstream->next_in = buf_getptr(buf, len); + zstream->avail_out = ret->size; + zstream->next_out = ret->data; - /* decompress the payload, incrementally resizing the output buffer */ - while (1) { - - zstream->avail_out = ret->size - ret->pos; - zstream->next_out = buf_getwriteptr(ret, zstream->avail_out); - - result = inflate(zstream, Z_SYNC_FLUSH); - - buf_setlen(ret, ret->size - zstream->avail_out); - buf_setpos(ret, ret->len); - - if (result != Z_BUF_ERROR && result != Z_OK) { - dropbear_exit("zlib error"); - } - - if (zstream->avail_in == 0 && - (zstream->avail_out != 0 || result == Z_BUF_ERROR)) { - /* we can only exit if avail_out hasn't all been used, - * and there's no remaining input */ - return ret; - } - - if (zstream->avail_out == 0) { - int new_size = 0; - if (ret->size >= RECV_MAX_PAYLOAD_LEN) { - /* Already been increased as large as it can go, - * yet didn't finish up the decompression */ - dropbear_exit("bad packet, oversized decompressed"); - } - new_size = MIN(RECV_MAX_PAYLOAD_LEN, ret->size + ZLIB_DECOMPRESS_INCR); - ret = buf_resize(ret, new_size); - } + result = inflate(zstream, Z_SYNC_FLUSH); + if (result != Z_OK) { + dropbear_exit("zlib error"); } + + buf_setlen(ret, ret->size - zstream->avail_out); + + if (zstream->avail_in > 0 || ret->len > RECV_MAX_PAYLOAD_LEN) { + /* The remote side sent larger than a payload size + * of uncompressed data. + */ + dropbear_exit("bad packet, oversized decompressed"); + } + + /* Success. All input was consumed and avail_out > 0 */ + return ret; + } #endif