dropbear: patch CVE-2025-47203

CVE patch [1] as mentioned in [2] relies on several patches not yet
available in version 2020.81 we have in kirkstone.
The good folks from Debian did the hard work identifying them as they
have the same version in bullseye release.
The commits were picked from [3] and they have their references to
dropbear upstream commits.

[1] e5a0ef27c2
[2] https://security-tracker.debian.org/tracker/CVE-2025-47203
[3] 7f48e75892

(From OE-Core rev: 91eeffaf14917c7c994a8de794b915231e69c5d6)

Signed-off-by: Peter Marko <peter.marko@siemens.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
This commit is contained in:
Peter Marko
2025-07-26 11:21:48 +02:00
committed by Steve Sakoman
parent 1ccf83e5d5
commit fc448b1b26
4 changed files with 521 additions and 0 deletions

View File

@@ -31,6 +31,9 @@ SRC_URI = "http://matt.ucc.asn.au/dropbear/releases/dropbear-${PV}.tar.bz2 \
file://CVE-2021-36369.patch \
file://CVE-2023-36328.patch \
file://CVE-2023-48795.patch \
file://0001-Add-m_snprintf-that-won-t-return-negative.patch \
file://0001-Handle-arbitrary-length-paths-and-commands-in-multih.patch \
file://CVE-2025-47203.patch \
"
PAM_SRC_URI = "file://0005-dropbear-enable-pam.patch \

View File

@@ -0,0 +1,48 @@
From ac2433cb8daa1279d14f8b2cd4c7e1f3405787d4 Mon Sep 17 00:00:00 2001
From: Matt Johnston <matt@ucc.asn.au>
Date: Fri, 1 Apr 2022 12:10:48 +0800
Subject: [PATCH] Add m_snprintf() that won't return negative
Origin: https://github.com/mkj/dropbear/commit/ac2433cb8daa1279d14f8b2cd4c7e1f3405787d4
Upstream-Status: Backport [https://github.com/mkj/dropbear/commit/ac2433cb8daa1279d14f8b2cd4c7e1f3405787d4]
Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
dbutil.c | 13 +++++++++++++
dbutil.h | 2 ++
2 files changed, 15 insertions(+)
diff --git a/dbutil.c b/dbutil.c
index 5af6330..d4c3298 100644
--- a/dbutil.c
+++ b/dbutil.c
@@ -691,3 +691,16 @@ void fsync_parent_dir(const char* fn) {
m_free(fn_dir);
#endif
}
+
+int m_snprintf(char *str, size_t size, const char *format, ...) {
+ va_list param;
+ int ret;
+
+ va_start(param, format);
+ ret = vsnprintf(str, size, format, param);
+ va_end(param);
+ if (ret < 0) {
+ dropbear_exit("snprintf failed");
+ }
+ return ret;
+}
diff --git a/dbutil.h b/dbutil.h
index 2a1c82c..71cffe8 100644
--- a/dbutil.h
+++ b/dbutil.h
@@ -70,6 +70,8 @@ void m_close(int fd);
void setnonblocking(int fd);
void disallow_core(void);
int m_str_to_uint(const char* str, unsigned int *val);
+/* The same as snprintf() but exits rather than returning negative */
+int m_snprintf(char *str, size_t size, const char *format, ...);
/* Used to force mp_ints to be initialised */
#define DEF_MP_INT(X) mp_int X = {0, 0, 0, NULL}

View File

@@ -0,0 +1,126 @@
From fe15c36664a984de9e1b2386ac52d4b8577cac93 Mon Sep 17 00:00:00 2001
From: Matt Johnston <matt@ucc.asn.au>
Date: Mon, 1 Apr 2024 11:50:26 +0800
Subject: [PATCH] Handle arbitrary length paths and commands in
multihop_passthrough_args()
Origin: https://github.com/mkj/dropbear/commit/7894254afa9b1d3a836911b7ccea1fe18391b881
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
Upstream-Status: Backport [see commits above]
Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
cli-runopts.c | 63 +++++++++++++++++++++------------------------------
1 file changed, 26 insertions(+), 37 deletions(-)
diff --git a/cli-runopts.c b/cli-runopts.c
index 255b47e..9798f62 100644
--- a/cli-runopts.c
+++ b/cli-runopts.c
@@ -523,61 +523,50 @@ static void loadidentityfile(const char* filename, int warnfail) {
#if DROPBEAR_CLI_MULTIHOP
-static char*
-multihop_passthrough_args() {
- char *ret;
- int total;
- unsigned int len = 0;
+/* 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 */
+#endif
+ /* Sufficient space for non-string args */
+ len = 100;
+
+ /* String arguments have arbitrary length, so determine space required */
#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);
+ len += 4 + strlen(key->filename);
}
-#endif /* DROPBEAR_CLI_PUBKEY_AUTH */
+#endif
- len += 30; /* space for -W <size>, terminator. */
- ret = m_malloc(len);
+ args = m_malloc(len);
total = 0;
- if (cli_opts.no_hostkey_check)
- {
- int written = snprintf(ret+total, len-total, "-y -y ");
- total += written;
- }
- else if (cli_opts.always_accept_key)
- {
- int written = snprintf(ret+total, len-total, "-y ");
- total += written;
+ /* Create new argument string */
+
+ if (cli_opts.no_hostkey_check) {
+ total += m_snprintf(args+total, len-total, "-y -y ");
+ } else if (cli_opts.always_accept_key) {
+ total += m_snprintf(args+total, len-total, "-y ");
}
- if (opts.recv_window != DEFAULT_RECV_WINDOW)
- {
- int written = snprintf(ret+total, len-total, "-W %u ", opts.recv_window);
- total += written;
+ if (opts.recv_window != DEFAULT_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;
- const size_t size = len - total;
- int written = snprintf(ret+total, size, "-i %s ", key->filename);
- dropbear_assert((unsigned int)written < size);
- total += written;
+ total += m_snprintf(args+total, len-total, "-i %s ", key->filename);
}
#endif /* DROPBEAR_CLI_PUBKEY_AUTH */
- /* if args were passed, total will be not zero, and it will have a space at the end, so remove that */
- if (total > 0)
- {
- total--;
- }
-
- return ret;
+ return args;
}
/* Sets up 'onion-forwarding' connections. This will spawn
@@ -608,7 +597,7 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
&& strchr(cli_opts.username, '@')) {
unsigned int len = strlen(orighostarg) + strlen(cli_opts.username) + 2;
hostbuf = m_malloc(len);
- snprintf(hostbuf, len, "%s@%s", cli_opts.username, orighostarg);
+ m_snprintf(hostbuf, len, "%s@%s", cli_opts.username, orighostarg);
} else {
hostbuf = m_strdup(orighostarg);
}
@@ -642,7 +631,7 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
+ strlen(passthrough_args)
+ 30;
cli_opts.proxycmd = m_malloc(cmd_len);
- snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s %s",
+ 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

View File

@@ -0,0 +1,344 @@
From e5a0ef27c227f7ae69d9a9fec98a056494409b9b Mon Sep 17 00:00:00 2001
From: Matt Johnston <matt@ucc.asn.au>
Date: Mon, 5 May 2025 23:14:19 +0800
Subject: [PATCH] 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
Bug: https://www.openwall.com/lists/oss-security/2025/05/13/1
Bug-Debian: https://deb.freexian.com/extended-lts/tracker/CVE-2025-47203
CVE: CVE-2025-47203
Upstream-Status: Backport [https://github.com/mkj/dropbear/commit/e5a0ef27c227f7ae69d9a9fec98a056494409b9b]
Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
cli-main.c | 60 ++++++++++++++++++++++++++++--------------
cli-runopts.c | 84 +++++++++++++++++++++++++++++++++++------------------------
dbutil.c | 9 +++++--
dbutil.h | 1 +
runopts.h | 5 ++++
5 files changed, 104 insertions(+), 55 deletions(-)
diff --git a/cli-main.c b/cli-main.c
index 7f455d1..53c55c1 100644
--- a/cli-main.c
+++ b/cli-main.c
@@ -73,9 +73,8 @@ int main(int argc, char ** argv) {
pid_t proxy_cmd_pid = 0;
#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) {
@@ -96,7 +95,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;
@@ -105,40 +105,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);
- 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 9798f62..0f3dcd0 100644
--- a/cli-runopts.c
+++ b/cli-runopts.c
@@ -525,47 +525,69 @@ 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 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.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 (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;
}
@@ -585,7 +607,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
@@ -603,6 +625,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) {
@@ -610,36 +634,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();
+ /* 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) {
dropbear_exit("-J can't be used with multihop mode");
}
- if (cli_opts.remoteport == NULL) {
- cli_opts.remoteport = "22";
- }
- cmd_len = strlen(argv0) + strlen(remainder)
- + strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport)
- + strlen(passthrough_args)
- + 30;
- cli_opts.proxycmd = m_malloc(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 d4c3298..a51c1f9 100644
--- a/dbutil.c
+++ b/dbutil.c
@@ -347,7 +347,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);
@@ -369,6 +368,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");
@@ -380,7 +385,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 71cffe8..5d86485 100644
--- a/dbutil.h
+++ b/dbutil.h
@@ -60,6 +60,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 01201d2..b49dc13 100644
--- a/runopts.h
+++ b/runopts.h
@@ -179,7 +179,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;