Package: release.debian.org Severity: normal Tags: bookworm X-Debbugs-Cc: dropb...@packages.debian.org Control: affects -1 + src:dropbear User: release.debian....@packages.debian.org Usertags: pu
[ Reason ] Fix CVE-2025-47203 (shell injection vulnerability in multihop handling). [ Impact ] dbclient(1) hostnames could result in running arbitrary shell commands locally during multihop handling. [ Tests ] Manual tests for the injection itself and also to ensure that multihop still works. [ Risks ] Low risk: backported patches from upstream's 2022.84 to 2025.88 release. [ Checklist ] [x] *all* changes are documented in the d/changelog [x] I reviewed all changes and I approve them [x] attach debdiff against the package in stable [x] the issue is verified as fixed in unstable [ Changes ] Backport upstream changes to fix CVE-2025-47203. -- Guilhem.
diffstat for dropbear-2022.83 dropbear-2022.83 changelog | 6 patches/CVE-2025-47203.patch | 365 ++++++++++ patches/Handle-arbitrary-length-paths-and-commands-in-multihop_pa.patch | 99 ++ patches/series | 2 4 files changed, 472 insertions(+) diff -Nru dropbear-2022.83/debian/changelog dropbear-2022.83/debian/changelog --- dropbear-2022.83/debian/changelog 2024-07-09 14:22:02.000000000 +0200 +++ dropbear-2022.83/debian/changelog 2025-05-16 15:01:36.000000000 +0200 @@ -1,3 +1,9 @@ +dropbear (2022.83-1+deb12u3) bookworm; urgency=high + + * Fix CVE-2025-47203: Shell injection vulnerability in multihop handling. + + -- Guilhem Moulin <guil...@debian.org> Fri, 16 May 2025 15:01:36 +0200 + dropbear (2022.83-1+deb12u2) bookworm; urgency=medium * Fix noremotetcp behavior. Keepalive packets were being ignored when the diff -Nru dropbear-2022.83/debian/patches/CVE-2025-47203.patch dropbear-2022.83/debian/patches/CVE-2025-47203.patch --- dropbear-2022.83/debian/patches/CVE-2025-47203.patch 1970-01-01 01:00:00.000000000 +0100 +++ dropbear-2022.83/debian/patches/CVE-2025-47203.patch 2025-05-16 15:01:36.000000000 +0200 @@ -0,0 +1,365 @@ +From: Matt Johnston <m...@ucc.asn.au> +Date: Mon, 5 May 2025 23:14:19 +0800 +Subject: Execute multihop commands directly, no shell + +This avoids problems with shell escaping if arguments contain special +characters. + +Origin: https://github.com/mkj/dropbear/commit/e5a0ef27c227f7ae69d9a9fec98a056494409b9b +--- + cli-main.c | 61 +++++++++++++++++++++++------------ + cli-runopts.c | 100 ++++++++++++++++++++++++++++++++++------------------------ + dbutil.c | 9 ++++-- + dbutil.h | 1 + + runopts.h | 5 +++ + 5 files changed, 113 insertions(+), 63 deletions(-) + +diff --git a/cli-main.c b/cli-main.c +index 065fd76..2fafa88 100644 +--- a/cli-main.c ++++ b/cli-main.c +@@ -77,9 +77,8 @@ int main(int argc, char ** argv) { + } + + #if DROPBEAR_CLI_PROXYCMD +- if (cli_opts.proxycmd) { ++ if (cli_opts.proxycmd || cli_opts.proxyexec) { + cli_proxy_cmd(&sock_in, &sock_out, &proxy_cmd_pid); +- m_free(cli_opts.proxycmd); + if (signal(SIGINT, kill_proxy_sighandler) == SIG_ERR || + signal(SIGTERM, kill_proxy_sighandler) == SIG_ERR || + signal(SIGHUP, kill_proxy_sighandler) == SIG_ERR) { +@@ -101,7 +100,8 @@ int main(int argc, char ** argv) { + } + #endif /* DBMULTI stuff */ + +-static void exec_proxy_cmd(const void *user_data_cmd) { ++#if DROPBEAR_CLI_PROXYCMD ++static void shell_proxy_cmd(const void *user_data_cmd) { + const char *cmd = user_data_cmd; + char *usershell; + +@@ -110,41 +110,62 @@ static void exec_proxy_cmd(const void *user_data_cmd) { + dropbear_exit("Failed to run '%s'\n", cmd); + } + +-#if DROPBEAR_CLI_PROXYCMD ++static void exec_proxy_cmd(const void *unused) { ++ (void)unused; ++ run_command(cli_opts.proxyexec[0], cli_opts.proxyexec, ses.maxfd); ++ dropbear_exit("Failed to run '%s'\n", cli_opts.proxyexec[0]); ++} ++ + static void cli_proxy_cmd(int *sock_in, int *sock_out, pid_t *pid_out) { +- char * ex_cmd = NULL; +- size_t ex_cmdlen; ++ char * cmd_arg = NULL; ++ void (*exec_fn)(const void *user_data) = NULL; + int ret; + ++ /* exactly one of cli_opts.proxycmd or cli_opts.proxyexec should be set */ ++ + /* File descriptor "-j &3" */ +- if (*cli_opts.proxycmd == '&') { ++ if (cli_opts.proxycmd && *cli_opts.proxycmd == '&') { + char *p = cli_opts.proxycmd + 1; + int sock = strtoul(p, &p, 10); + /* must be a single number, and not stdin/stdout/stderr */ + if (sock > 2 && sock < 1024 && *p == '\0') { + *sock_in = sock; + *sock_out = sock; +- return; ++ goto cleanup; + } + } + +- /* Normal proxycommand */ +- +- /* So that spawn_command knows which shell to run */ +- fill_passwd(cli_opts.own_user); +- +- ex_cmdlen = strlen(cli_opts.proxycmd) + 6; /* "exec " + command + '\0' */ +- ex_cmd = m_malloc(ex_cmdlen); +- snprintf(ex_cmd, ex_cmdlen, "exec %s", cli_opts.proxycmd); ++ if (cli_opts.proxycmd) { ++ /* Normal proxycommand */ ++ size_t shell_cmdlen; ++ /* So that spawn_command knows which shell to run */ ++ fill_passwd(cli_opts.own_user); ++ ++ shell_cmdlen = strlen(cli_opts.proxycmd) + 6; /* "exec " + command + '\0' */ ++ cmd_arg = m_malloc(shell_cmdlen); ++ snprintf(cmd_arg, shell_cmdlen, "exec %s", cli_opts.proxycmd); ++ exec_fn = shell_proxy_cmd; ++ } else { ++ /* No shell */ ++ exec_fn = exec_proxy_cmd; ++ } + +- ret = spawn_command(exec_proxy_cmd, ex_cmd, +- sock_out, sock_in, NULL, pid_out); +- DEBUG1(("cmd: %s pid=%d", ex_cmd,*pid_out)) +- m_free(ex_cmd); ++ ret = spawn_command(exec_fn, cmd_arg, sock_out, sock_in, NULL, pid_out); + if (ret == DROPBEAR_FAILURE) { + dropbear_exit("Failed running proxy command"); + *sock_in = *sock_out = -1; + } ++ ++cleanup: ++ m_free(cli_opts.proxycmd); ++ m_free(cmd_arg); ++ if (cli_opts.proxyexec) { ++ char **a = NULL; ++ for (a = cli_opts.proxyexec; *a; a++) { ++ m_free_direct(*a); ++ } ++ m_free(cli_opts.proxyexec); ++ } + } + + static void kill_proxy_sighandler(int UNUSED(signo)) { +diff --git a/cli-runopts.c b/cli-runopts.c +index 632d097..06b686a 100644 +--- a/cli-runopts.c ++++ b/cli-runopts.c +@@ -530,58 +530,81 @@ static void loadidentityfile(const char* filename, int warnfail) { + + /* Fill out -i, -y, -W options that make sense for all + * the intermediate processes */ +-static char* multihop_passthrough_args(void) { +- char *args = NULL; +- unsigned int len, total; ++static char** multihop_args(const char* argv0, const char* prior_hops) { ++ /* null terminated array */ ++ char **args = NULL; ++ size_t max_args = 14, pos = 0, len; + #if DROPBEAR_CLI_PUBKEY_AUTH + m_list_elem *iter; + #endif +- /* Sufficient space for non-string args */ +- len = 100; + +- /* String arguments have arbitrary length, so determine space required */ +- if (cli_opts.proxycmd) { +- len += strlen(cli_opts.proxycmd); +- } + #if DROPBEAR_CLI_PUBKEY_AUTH + for (iter = cli_opts.privkeys->first; iter; iter = iter->next) + { +- sign_key * key = (sign_key*)iter->item; +- len += 4 + strlen(key->filename); ++ /* "-i file" for each */ ++ max_args += 2; + } + #endif + +- args = m_malloc(len); +- total = 0; ++ args = m_malloc(sizeof(char*) * max_args); ++ pos = 0; + +- /* Create new argument string */ ++ args[pos] = m_strdup(argv0); ++ pos++; + + if (cli_opts.quiet) { +- total += m_snprintf(args+total, len-total, "-q "); ++ args[pos] = m_strdup("-q"); ++ pos++; + } + + if (cli_opts.no_hostkey_check) { +- total += m_snprintf(args+total, len-total, "-y -y "); ++ args[pos] = m_strdup("-y"); ++ pos++; ++ args[pos] = m_strdup("-y"); ++ pos++; + } else if (cli_opts.always_accept_key) { +- total += m_snprintf(args+total, len-total, "-y "); ++ args[pos] = m_strdup("-y"); ++ pos++; + } + + if (cli_opts.proxycmd) { +- total += m_snprintf(args+total, len-total, "-J '%s' ", cli_opts.proxycmd); ++ args[pos] = m_strdup("-J"); ++ pos++; ++ args[pos] = m_strdup(cli_opts.proxycmd); ++ pos++; + } + + if (opts.recv_window != DEFAULT_RECV_WINDOW) { +- total += m_snprintf(args+total, len-total, "-W %u ", opts.recv_window); ++ args[pos] = m_strdup("-W"); ++ pos++; ++ args[pos] = m_malloc(11); ++ m_snprintf(args[pos], 11, "%u", opts.recv_window); ++ pos++; + } + + #if DROPBEAR_CLI_PUBKEY_AUTH + for (iter = cli_opts.privkeys->first; iter; iter = iter->next) + { + sign_key * key = (sign_key*)iter->item; +- total += m_snprintf(args+total, len-total, "-i %s ", key->filename); ++ args[pos] = m_strdup("-i"); ++ pos++; ++ args[pos] = m_strdup(key->filename); ++ pos++; + } + #endif /* DROPBEAR_CLI_PUBKEY_AUTH */ + ++ /* last hop */ ++ args[pos] = m_strdup("-B"); ++ pos++; ++ len = strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport) + 2; ++ args[pos] = m_malloc(len); ++ snprintf(args[pos], len, "%s:%s", cli_opts.remotehost, cli_opts.remoteport); ++ pos++; ++ ++ /* hostnames of prior hops */ ++ args[pos] = m_strdup(prior_hops); ++ pos++; ++ + return args; + } + +@@ -596,7 +619,7 @@ static char* multihop_passthrough_args(void) { + * etc for as many hosts as we want. + * + * Note that "-J" arguments aren't actually used, instead +- * below sets cli_opts.proxycmd directly. ++ * below sets cli_opts.proxyexec directly. + * + * Ports for hosts can be specified as host/port. + */ +@@ -604,7 +627,7 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0) + char *userhostarg = NULL; + char *hostbuf = NULL; + char *last_hop = NULL; +- char *remainder = NULL; ++ char *prior_hops = NULL; + + /* both scp and rsync parse a user@host argument + * and turn it into "-l user host". This breaks +@@ -622,6 +645,8 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0) + } + userhostarg = hostbuf; + ++ /* Split off any last hostname and use that as remotehost/remoteport. ++ * That is used for authorized_keys checking etc */ + last_hop = strrchr(userhostarg, ','); + if (last_hop) { + if (last_hop == userhostarg) { +@@ -629,35 +654,28 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0) + } + *last_hop = '\0'; + last_hop++; +- remainder = userhostarg; ++ prior_hops = userhostarg; + userhostarg = last_hop; + } + ++ /* Update cli_opts.remotehost and cli_opts.remoteport */ + parse_hostname(userhostarg); + +- if (last_hop) { +- /* Set up the proxycmd */ +- unsigned int cmd_len = 0; +- char *passthrough_args = multihop_passthrough_args(); +- if (cli_opts.remoteport == NULL) { +- cli_opts.remoteport = "22"; ++ /* Construct any multihop proxy command. Use proxyexec to ++ * avoid worrying about shell escaping. */ ++ if (prior_hops) { ++ cli_opts.proxyexec = multihop_args(argv0, prior_hops); ++ /* Any -J argument has been copied to proxyexec */ ++ if (cli_opts.proxycmd) { ++ m_free(cli_opts.proxycmd); + } +- cmd_len = strlen(argv0) + strlen(remainder) +- + strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport) +- + strlen(passthrough_args) +- + 30; +- /* replace proxycmd. old -J arguments have been copied +- to passthrough_args */ +- cli_opts.proxycmd = m_realloc(cli_opts.proxycmd, cmd_len); +- m_snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s %s", +- argv0, cli_opts.remotehost, cli_opts.remoteport, +- passthrough_args, remainder); ++ + #ifndef DISABLE_ZLIB +- /* The stream will be incompressible since it's encrypted. */ ++ /* This outer stream will be incompressible since it's encrypted. */ + opts.compress_mode = DROPBEAR_COMPRESS_OFF; + #endif +- m_free(passthrough_args); + } ++ + m_free(hostbuf); + } + #endif /* !DROPBEAR_CLI_MULTIHOP */ +diff --git a/dbutil.c b/dbutil.c +index bd66454..910fa27 100644 +--- a/dbutil.c ++++ b/dbutil.c +@@ -371,7 +371,6 @@ int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data, + void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) { + char * argv[4]; + char * baseshell = NULL; +- unsigned int i; + + baseshell = basename(usershell); + +@@ -393,6 +392,12 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) { + argv[1] = NULL; + } + ++ run_command(usershell, argv, maxfd); ++} ++ ++void run_command(const char* argv0, char** args, unsigned int maxfd) { ++ unsigned int i; ++ + /* Re-enable SIGPIPE for the executed process */ + if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) { + dropbear_exit("signal() error"); +@@ -404,7 +409,7 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) { + m_close(i); + } + +- execv(usershell, argv); ++ execv(argv0, args); + } + + #if DEBUG_TRACE +diff --git a/dbutil.h b/dbutil.h +index 64af170..bfc1f1f 100644 +--- a/dbutil.h ++++ b/dbutil.h +@@ -63,6 +63,7 @@ char * stripcontrol(const char * text); + int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data, + int *writefd, int *readfd, int *errfd, pid_t *pid); + void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell); ++void run_command(const char* argv0, char** args, unsigned int maxfd); + #if ENABLE_CONNECT_UNIX + int connect_unix(const char* addr); + #endif +diff --git a/runopts.h b/runopts.h +index 1675836..11c3ef2 100644 +--- a/runopts.h ++++ b/runopts.h +@@ -188,7 +188,12 @@ typedef struct cli_runopts { + unsigned int netcat_port; + #endif + #if DROPBEAR_CLI_PROXYCMD ++ /* A proxy command to run via the user's shell */ + char *proxycmd; ++#endif ++#if DROPBEAR_CLI_MULTIHOP ++ /* Similar to proxycmd, but is arguments for execve(), not shell */ ++ char **proxyexec; + #endif + char *bind_address; + char *bind_port; diff -Nru dropbear-2022.83/debian/patches/Handle-arbitrary-length-paths-and-commands-in-multihop_pa.patch dropbear-2022.83/debian/patches/Handle-arbitrary-length-paths-and-commands-in-multihop_pa.patch --- dropbear-2022.83/debian/patches/Handle-arbitrary-length-paths-and-commands-in-multihop_pa.patch 1970-01-01 01:00:00.000000000 +0100 +++ dropbear-2022.83/debian/patches/Handle-arbitrary-length-paths-and-commands-in-multihop_pa.patch 2025-05-16 15:01:36.000000000 +0200 @@ -0,0 +1,99 @@ +From: Matt Johnston <m...@ucc.asn.au> +Date: Mon, 1 Apr 2024 11:50:26 +0800 +Subject: Handle arbitrary length paths and commands in + multihop_passthrough_args() + +Origin: https://github.com/mkj/dropbear/commit/2f1177e55f33afd676e08c9449ab7ab517fc3b30 +Origin: https://github.com/mkj/dropbear/commit/697b1f86c0b2b0caf12e9e32bab29161093ab5d4 +Origin: https://github.com/mkj/dropbear/commit/dd03da772bfad6174425066ff9752b60e25ed183 +Origin: https://github.com/mkj/dropbear/commit/d59436a4d56de58b856142a5d489a4a8fc7382ed +--- + cli-runopts.c | 45 +++++++++++++++++++++++++-------------------- + 1 file changed, 25 insertions(+), 20 deletions(-) + +diff --git a/cli-runopts.c b/cli-runopts.c +index 38a73f7..632d097 100644 +--- a/cli-runopts.c ++++ b/cli-runopts.c +@@ -528,56 +528,61 @@ static void loadidentityfile(const char* filename, int warnfail) { + + #if DROPBEAR_CLI_MULTIHOP + +-static char* +-multihop_passthrough_args() { +- char *ret; ++/* Fill out -i, -y, -W options that make sense for all ++ * the intermediate processes */ ++static char* multihop_passthrough_args(void) { ++ char *args = NULL; + unsigned int len, total; ++#if DROPBEAR_CLI_PUBKEY_AUTH + m_list_elem *iter; +- /* Fill out -i, -y, -W options that make sense for all +- * the intermediate processes */ +- len = 30; /* space for "-q -y -y -W <size>\0" */ ++#endif ++ /* Sufficient space for non-string args */ ++ len = 100; ++ ++ /* String arguments have arbitrary length, so determine space required */ ++ if (cli_opts.proxycmd) { ++ len += strlen(cli_opts.proxycmd); ++ } + #if DROPBEAR_CLI_PUBKEY_AUTH + for (iter = cli_opts.privkeys->first; iter; iter = iter->next) + { + sign_key * key = (sign_key*)iter->item; +- len += 3 + strlen(key->filename); +- } +-#endif /* DROPBEAR_CLI_PUBKEY_AUTH */ +- if (cli_opts.proxycmd) { +- /* "-J 'cmd'" */ +- len += 6 + strlen(cli_opts.proxycmd); ++ len += 4 + strlen(key->filename); + } ++#endif + +- ret = m_malloc(len); ++ args = m_malloc(len); + total = 0; + ++ /* Create new argument string */ ++ + if (cli_opts.quiet) { +- total += m_snprintf(ret+total, len-total, "-q "); ++ total += m_snprintf(args+total, len-total, "-q "); + } + + if (cli_opts.no_hostkey_check) { +- total += m_snprintf(ret+total, len-total, "-y -y "); ++ total += m_snprintf(args+total, len-total, "-y -y "); + } else if (cli_opts.always_accept_key) { +- total += m_snprintf(ret+total, len-total, "-y "); ++ total += m_snprintf(args+total, len-total, "-y "); + } + + if (cli_opts.proxycmd) { +- total += m_snprintf(ret+total, len-total, "-J '%s' ", cli_opts.proxycmd); ++ total += m_snprintf(args+total, len-total, "-J '%s' ", cli_opts.proxycmd); + } + + if (opts.recv_window != DEFAULT_RECV_WINDOW) { +- total += m_snprintf(ret+total, len-total, "-W %u ", opts.recv_window); ++ total += m_snprintf(args+total, len-total, "-W %u ", opts.recv_window); + } + + #if DROPBEAR_CLI_PUBKEY_AUTH + for (iter = cli_opts.privkeys->first; iter; iter = iter->next) + { + sign_key * key = (sign_key*)iter->item; +- total += m_snprintf(ret+total, len-total, "-i %s ", key->filename); ++ total += m_snprintf(args+total, len-total, "-i %s ", key->filename); + } + #endif /* DROPBEAR_CLI_PUBKEY_AUTH */ + +- return ret; ++ return args; + } + + /* Sets up 'onion-forwarding' connections. This will spawn diff -Nru dropbear-2022.83/debian/patches/series dropbear-2022.83/debian/patches/series --- dropbear-2022.83/debian/patches/series 2024-07-09 14:22:02.000000000 +0200 +++ dropbear-2022.83/debian/patches/series 2025-05-16 15:01:36.000000000 +0200 @@ -3,3 +3,5 @@ raise-connection-delay-in-tests.patch CVE-2023-48795.patch fix-noremotetcp-behavior.patch +Handle-arbitrary-length-paths-and-commands-in-multihop_pa.patch +CVE-2025-47203.patch
signature.asc
Description: PGP signature