summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Marko <peter.marko@siemens.com>2025-07-26 11:21:48 +0200
committerSteve Sakoman <steve@sakoman.com>2025-08-04 06:40:00 -0700
commitfc448b1b26b22ce7486c407456ac5bf22c2e738b (patch)
treed0c2828e4b3d305e4c447000095c75e0257943c7
parent1ccf83e5d561a2876ea648cc3505ab35511a2c0d (diff)
downloadpoky-fc448b1b26b22ce7486c407456ac5bf22c2e738b.tar.gz
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] https://github.com/mkj/dropbear/commit/e5a0ef27c227f7ae69d9a9fec98a056494409b9b [2] https://security-tracker.debian.org/tracker/CVE-2025-47203 [3] https://salsa.debian.org/debian/dropbear/-/commit/7f48e75892c40cfc6336137d62581d2c4ca7d84c (From OE-Core rev: 91eeffaf14917c7c994a8de794b915231e69c5d6) Signed-off-by: Peter Marko <peter.marko@siemens.com> Signed-off-by: Steve Sakoman <steve@sakoman.com>
-rw-r--r--meta/recipes-core/dropbear/dropbear.inc3
-rw-r--r--meta/recipes-core/dropbear/dropbear/0001-Add-m_snprintf-that-won-t-return-negative.patch48
-rw-r--r--meta/recipes-core/dropbear/dropbear/0001-Handle-arbitrary-length-paths-and-commands-in-multih.patch126
-rw-r--r--meta/recipes-core/dropbear/dropbear/CVE-2025-47203.patch344
4 files changed, 521 insertions, 0 deletions
diff --git a/meta/recipes-core/dropbear/dropbear.inc b/meta/recipes-core/dropbear/dropbear.inc
index a32242949b..94059df258 100644
--- a/meta/recipes-core/dropbear/dropbear.inc
+++ b/meta/recipes-core/dropbear/dropbear.inc
@@ -31,6 +31,9 @@ SRC_URI = "http://matt.ucc.asn.au/dropbear/releases/dropbear-${PV}.tar.bz2 \
31 file://CVE-2021-36369.patch \ 31 file://CVE-2021-36369.patch \
32 file://CVE-2023-36328.patch \ 32 file://CVE-2023-36328.patch \
33 file://CVE-2023-48795.patch \ 33 file://CVE-2023-48795.patch \
34 file://0001-Add-m_snprintf-that-won-t-return-negative.patch \
35 file://0001-Handle-arbitrary-length-paths-and-commands-in-multih.patch \
36 file://CVE-2025-47203.patch \
34 " 37 "
35 38
36PAM_SRC_URI = "file://0005-dropbear-enable-pam.patch \ 39PAM_SRC_URI = "file://0005-dropbear-enable-pam.patch \
diff --git a/meta/recipes-core/dropbear/dropbear/0001-Add-m_snprintf-that-won-t-return-negative.patch b/meta/recipes-core/dropbear/dropbear/0001-Add-m_snprintf-that-won-t-return-negative.patch
new file mode 100644
index 0000000000..ec75fcbc61
--- /dev/null
+++ b/meta/recipes-core/dropbear/dropbear/0001-Add-m_snprintf-that-won-t-return-negative.patch
@@ -0,0 +1,48 @@
1From ac2433cb8daa1279d14f8b2cd4c7e1f3405787d4 Mon Sep 17 00:00:00 2001
2From: Matt Johnston <matt@ucc.asn.au>
3Date: Fri, 1 Apr 2022 12:10:48 +0800
4Subject: [PATCH] Add m_snprintf() that won't return negative
5
6Origin: https://github.com/mkj/dropbear/commit/ac2433cb8daa1279d14f8b2cd4c7e1f3405787d4
7
8Upstream-Status: Backport [https://github.com/mkj/dropbear/commit/ac2433cb8daa1279d14f8b2cd4c7e1f3405787d4]
9Signed-off-by: Peter Marko <peter.marko@siemens.com>
10---
11 dbutil.c | 13 +++++++++++++
12 dbutil.h | 2 ++
13 2 files changed, 15 insertions(+)
14
15diff --git a/dbutil.c b/dbutil.c
16index 5af6330..d4c3298 100644
17--- a/dbutil.c
18+++ b/dbutil.c
19@@ -691,3 +691,16 @@ void fsync_parent_dir(const char* fn) {
20 m_free(fn_dir);
21 #endif
22 }
23+
24+int m_snprintf(char *str, size_t size, const char *format, ...) {
25+ va_list param;
26+ int ret;
27+
28+ va_start(param, format);
29+ ret = vsnprintf(str, size, format, param);
30+ va_end(param);
31+ if (ret < 0) {
32+ dropbear_exit("snprintf failed");
33+ }
34+ return ret;
35+}
36diff --git a/dbutil.h b/dbutil.h
37index 2a1c82c..71cffe8 100644
38--- a/dbutil.h
39+++ b/dbutil.h
40@@ -70,6 +70,8 @@ void m_close(int fd);
41 void setnonblocking(int fd);
42 void disallow_core(void);
43 int m_str_to_uint(const char* str, unsigned int *val);
44+/* The same as snprintf() but exits rather than returning negative */
45+int m_snprintf(char *str, size_t size, const char *format, ...);
46
47 /* Used to force mp_ints to be initialised */
48 #define DEF_MP_INT(X) mp_int X = {0, 0, 0, NULL}
diff --git a/meta/recipes-core/dropbear/dropbear/0001-Handle-arbitrary-length-paths-and-commands-in-multih.patch b/meta/recipes-core/dropbear/dropbear/0001-Handle-arbitrary-length-paths-and-commands-in-multih.patch
new file mode 100644
index 0000000000..dbc457209d
--- /dev/null
+++ b/meta/recipes-core/dropbear/dropbear/0001-Handle-arbitrary-length-paths-and-commands-in-multih.patch
@@ -0,0 +1,126 @@
1From fe15c36664a984de9e1b2386ac52d4b8577cac93 Mon Sep 17 00:00:00 2001
2From: Matt Johnston <matt@ucc.asn.au>
3Date: Mon, 1 Apr 2024 11:50:26 +0800
4Subject: [PATCH] Handle arbitrary length paths and commands in
5 multihop_passthrough_args()
6
7Origin: https://github.com/mkj/dropbear/commit/7894254afa9b1d3a836911b7ccea1fe18391b881
8Origin: https://github.com/mkj/dropbear/commit/2f1177e55f33afd676e08c9449ab7ab517fc3b30
9Origin: https://github.com/mkj/dropbear/commit/697b1f86c0b2b0caf12e9e32bab29161093ab5d4
10Origin: https://github.com/mkj/dropbear/commit/dd03da772bfad6174425066ff9752b60e25ed183
11Origin: https://github.com/mkj/dropbear/commit/d59436a4d56de58b856142a5d489a4a8fc7382ed
12
13Upstream-Status: Backport [see commits above]
14Signed-off-by: Peter Marko <peter.marko@siemens.com>
15---
16 cli-runopts.c | 63 +++++++++++++++++++++------------------------------
17 1 file changed, 26 insertions(+), 37 deletions(-)
18
19diff --git a/cli-runopts.c b/cli-runopts.c
20index 255b47e..9798f62 100644
21--- a/cli-runopts.c
22+++ b/cli-runopts.c
23@@ -523,61 +523,50 @@ static void loadidentityfile(const char* filename, int warnfail) {
24
25 #if DROPBEAR_CLI_MULTIHOP
26
27-static char*
28-multihop_passthrough_args() {
29- char *ret;
30- int total;
31- unsigned int len = 0;
32+/* Fill out -i, -y, -W options that make sense for all
33+ * the intermediate processes */
34+static char* multihop_passthrough_args(void) {
35+ char *args = NULL;
36+ unsigned int len, total;
37+#if DROPBEAR_CLI_PUBKEY_AUTH
38 m_list_elem *iter;
39- /* Fill out -i, -y, -W options that make sense for all
40- * the intermediate processes */
41+#endif
42+ /* Sufficient space for non-string args */
43+ len = 100;
44+
45+ /* String arguments have arbitrary length, so determine space required */
46 #if DROPBEAR_CLI_PUBKEY_AUTH
47 for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
48 {
49 sign_key * key = (sign_key*)iter->item;
50- len += 3 + strlen(key->filename);
51+ len += 4 + strlen(key->filename);
52 }
53-#endif /* DROPBEAR_CLI_PUBKEY_AUTH */
54+#endif
55
56- len += 30; /* space for -W <size>, terminator. */
57- ret = m_malloc(len);
58+ args = m_malloc(len);
59 total = 0;
60
61- if (cli_opts.no_hostkey_check)
62- {
63- int written = snprintf(ret+total, len-total, "-y -y ");
64- total += written;
65- }
66- else if (cli_opts.always_accept_key)
67- {
68- int written = snprintf(ret+total, len-total, "-y ");
69- total += written;
70+ /* Create new argument string */
71+
72+ if (cli_opts.no_hostkey_check) {
73+ total += m_snprintf(args+total, len-total, "-y -y ");
74+ } else if (cli_opts.always_accept_key) {
75+ total += m_snprintf(args+total, len-total, "-y ");
76 }
77
78- if (opts.recv_window != DEFAULT_RECV_WINDOW)
79- {
80- int written = snprintf(ret+total, len-total, "-W %u ", opts.recv_window);
81- total += written;
82+ if (opts.recv_window != DEFAULT_RECV_WINDOW) {
83+ total += m_snprintf(args+total, len-total, "-W %u ", opts.recv_window);
84 }
85
86 #if DROPBEAR_CLI_PUBKEY_AUTH
87 for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
88 {
89 sign_key * key = (sign_key*)iter->item;
90- const size_t size = len - total;
91- int written = snprintf(ret+total, size, "-i %s ", key->filename);
92- dropbear_assert((unsigned int)written < size);
93- total += written;
94+ total += m_snprintf(args+total, len-total, "-i %s ", key->filename);
95 }
96 #endif /* DROPBEAR_CLI_PUBKEY_AUTH */
97
98- /* if args were passed, total will be not zero, and it will have a space at the end, so remove that */
99- if (total > 0)
100- {
101- total--;
102- }
103-
104- return ret;
105+ return args;
106 }
107
108 /* Sets up 'onion-forwarding' connections. This will spawn
109@@ -608,7 +597,7 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
110 && strchr(cli_opts.username, '@')) {
111 unsigned int len = strlen(orighostarg) + strlen(cli_opts.username) + 2;
112 hostbuf = m_malloc(len);
113- snprintf(hostbuf, len, "%s@%s", cli_opts.username, orighostarg);
114+ m_snprintf(hostbuf, len, "%s@%s", cli_opts.username, orighostarg);
115 } else {
116 hostbuf = m_strdup(orighostarg);
117 }
118@@ -642,7 +631,7 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
119 + strlen(passthrough_args)
120 + 30;
121 cli_opts.proxycmd = m_malloc(cmd_len);
122- snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s %s",
123+ m_snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s %s",
124 argv0, cli_opts.remotehost, cli_opts.remoteport,
125 passthrough_args, remainder);
126 #ifndef DISABLE_ZLIB
diff --git a/meta/recipes-core/dropbear/dropbear/CVE-2025-47203.patch b/meta/recipes-core/dropbear/dropbear/CVE-2025-47203.patch
new file mode 100644
index 0000000000..3a51927cfe
--- /dev/null
+++ b/meta/recipes-core/dropbear/dropbear/CVE-2025-47203.patch
@@ -0,0 +1,344 @@
1From e5a0ef27c227f7ae69d9a9fec98a056494409b9b Mon Sep 17 00:00:00 2001
2From: Matt Johnston <matt@ucc.asn.au>
3Date: Mon, 5 May 2025 23:14:19 +0800
4Subject: [PATCH] Execute multihop commands directly, no shell
5
6This avoids problems with shell escaping if arguments contain special
7characters.
8
9Origin: https://github.com/mkj/dropbear/commit/e5a0ef27c227f7ae69d9a9fec98a056494409b9b
10Bug: https://www.openwall.com/lists/oss-security/2025/05/13/1
11Bug-Debian: https://deb.freexian.com/extended-lts/tracker/CVE-2025-47203
12
13CVE: CVE-2025-47203
14Upstream-Status: Backport [https://github.com/mkj/dropbear/commit/e5a0ef27c227f7ae69d9a9fec98a056494409b9b]
15Signed-off-by: Peter Marko <peter.marko@siemens.com>
16---
17 cli-main.c | 60 ++++++++++++++++++++++++++++--------------
18 cli-runopts.c | 84 +++++++++++++++++++++++++++++++++++------------------------
19 dbutil.c | 9 +++++--
20 dbutil.h | 1 +
21 runopts.h | 5 ++++
22 5 files changed, 104 insertions(+), 55 deletions(-)
23
24diff --git a/cli-main.c b/cli-main.c
25index 7f455d1..53c55c1 100644
26--- a/cli-main.c
27+++ b/cli-main.c
28@@ -73,9 +73,8 @@ int main(int argc, char ** argv) {
29
30 pid_t proxy_cmd_pid = 0;
31 #if DROPBEAR_CLI_PROXYCMD
32- if (cli_opts.proxycmd) {
33+ if (cli_opts.proxycmd || cli_opts.proxyexec) {
34 cli_proxy_cmd(&sock_in, &sock_out, &proxy_cmd_pid);
35- m_free(cli_opts.proxycmd);
36 if (signal(SIGINT, kill_proxy_sighandler) == SIG_ERR ||
37 signal(SIGTERM, kill_proxy_sighandler) == SIG_ERR ||
38 signal(SIGHUP, kill_proxy_sighandler) == SIG_ERR) {
39@@ -96,7 +95,8 @@ int main(int argc, char ** argv) {
40 }
41 #endif /* DBMULTI stuff */
42
43-static void exec_proxy_cmd(const void *user_data_cmd) {
44+#if DROPBEAR_CLI_PROXYCMD
45+static void shell_proxy_cmd(const void *user_data_cmd) {
46 const char *cmd = user_data_cmd;
47 char *usershell;
48
49@@ -105,40 +105,62 @@ static void exec_proxy_cmd(const void *user_data_cmd) {
50 dropbear_exit("Failed to run '%s'\n", cmd);
51 }
52
53-#if DROPBEAR_CLI_PROXYCMD
54+static void exec_proxy_cmd(const void *unused) {
55+ (void)unused;
56+ run_command(cli_opts.proxyexec[0], cli_opts.proxyexec, ses.maxfd);
57+ dropbear_exit("Failed to run '%s'\n", cli_opts.proxyexec[0]);
58+}
59+
60 static void cli_proxy_cmd(int *sock_in, int *sock_out, pid_t *pid_out) {
61- char * ex_cmd = NULL;
62- size_t ex_cmdlen;
63+ char * cmd_arg = NULL;
64+ void (*exec_fn)(const void *user_data) = NULL;
65 int ret;
66
67+ /* exactly one of cli_opts.proxycmd or cli_opts.proxyexec should be set */
68+
69 /* File descriptor "-j &3" */
70- if (*cli_opts.proxycmd == '&') {
71+ if (cli_opts.proxycmd && *cli_opts.proxycmd == '&') {
72 char *p = cli_opts.proxycmd + 1;
73 int sock = strtoul(p, &p, 10);
74 /* must be a single number, and not stdin/stdout/stderr */
75 if (sock > 2 && sock < 1024 && *p == '\0') {
76 *sock_in = sock;
77 *sock_out = sock;
78- return;
79+ goto cleanup;
80 }
81 }
82
83- /* Normal proxycommand */
84-
85- /* So that spawn_command knows which shell to run */
86- fill_passwd(cli_opts.own_user);
87-
88- ex_cmdlen = strlen(cli_opts.proxycmd) + 6; /* "exec " + command + '\0' */
89- ex_cmd = m_malloc(ex_cmdlen);
90- snprintf(ex_cmd, ex_cmdlen, "exec %s", cli_opts.proxycmd);
91+ if (cli_opts.proxycmd) {
92+ /* Normal proxycommand */
93+ size_t shell_cmdlen;
94+ /* So that spawn_command knows which shell to run */
95+ fill_passwd(cli_opts.own_user);
96+
97+ shell_cmdlen = strlen(cli_opts.proxycmd) + 6; /* "exec " + command + '\0' */
98+ cmd_arg = m_malloc(shell_cmdlen);
99+ snprintf(cmd_arg, shell_cmdlen, "exec %s", cli_opts.proxycmd);
100+ exec_fn = shell_proxy_cmd;
101+ } else {
102+ /* No shell */
103+ exec_fn = exec_proxy_cmd;
104+ }
105
106- ret = spawn_command(exec_proxy_cmd, ex_cmd,
107- sock_out, sock_in, NULL, pid_out);
108- m_free(ex_cmd);
109+ ret = spawn_command(exec_fn, cmd_arg, sock_out, sock_in, NULL, pid_out);
110 if (ret == DROPBEAR_FAILURE) {
111 dropbear_exit("Failed running proxy command");
112 *sock_in = *sock_out = -1;
113 }
114+
115+cleanup:
116+ m_free(cli_opts.proxycmd);
117+ m_free(cmd_arg);
118+ if (cli_opts.proxyexec) {
119+ char **a = NULL;
120+ for (a = cli_opts.proxyexec; *a; a++) {
121+ m_free_direct(*a);
122+ }
123+ m_free(cli_opts.proxyexec);
124+ }
125 }
126
127 static void kill_proxy_sighandler(int UNUSED(signo)) {
128diff --git a/cli-runopts.c b/cli-runopts.c
129index 9798f62..0f3dcd0 100644
130--- a/cli-runopts.c
131+++ b/cli-runopts.c
132@@ -525,47 +525,69 @@ static void loadidentityfile(const char* filename, int warnfail) {
133
134 /* Fill out -i, -y, -W options that make sense for all
135 * the intermediate processes */
136-static char* multihop_passthrough_args(void) {
137- char *args = NULL;
138- unsigned int len, total;
139+static char** multihop_args(const char* argv0, const char* prior_hops) {
140+ /* null terminated array */
141+ char **args = NULL;
142+ size_t max_args = 14, pos = 0, len;
143 #if DROPBEAR_CLI_PUBKEY_AUTH
144 m_list_elem *iter;
145 #endif
146- /* Sufficient space for non-string args */
147- len = 100;
148
149- /* String arguments have arbitrary length, so determine space required */
150 #if DROPBEAR_CLI_PUBKEY_AUTH
151 for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
152 {
153- sign_key * key = (sign_key*)iter->item;
154- len += 4 + strlen(key->filename);
155+ /* "-i file" for each */
156+ max_args += 2;
157 }
158 #endif
159
160- args = m_malloc(len);
161- total = 0;
162+ args = m_malloc(sizeof(char*) * max_args);
163+ pos = 0;
164
165- /* Create new argument string */
166+ args[pos] = m_strdup(argv0);
167+ pos++;
168
169 if (cli_opts.no_hostkey_check) {
170- total += m_snprintf(args+total, len-total, "-y -y ");
171+ args[pos] = m_strdup("-y");
172+ pos++;
173+ args[pos] = m_strdup("-y");
174+ pos++;
175 } else if (cli_opts.always_accept_key) {
176- total += m_snprintf(args+total, len-total, "-y ");
177+ args[pos] = m_strdup("-y");
178+ pos++;
179 }
180
181 if (opts.recv_window != DEFAULT_RECV_WINDOW) {
182- total += m_snprintf(args+total, len-total, "-W %u ", opts.recv_window);
183+ args[pos] = m_strdup("-W");
184+ pos++;
185+ args[pos] = m_malloc(11);
186+ m_snprintf(args[pos], 11, "%u", opts.recv_window);
187+ pos++;
188 }
189
190 #if DROPBEAR_CLI_PUBKEY_AUTH
191 for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
192 {
193 sign_key * key = (sign_key*)iter->item;
194- total += m_snprintf(args+total, len-total, "-i %s ", key->filename);
195+ args[pos] = m_strdup("-i");
196+ pos++;
197+ args[pos] = m_strdup(key->filename);
198+ pos++;
199 }
200 #endif /* DROPBEAR_CLI_PUBKEY_AUTH */
201
202+ /* last hop */
203+ args[pos] = m_strdup("-B");
204+ pos++;
205+ len = strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport) + 2;
206+ args[pos] = m_malloc(len);
207+ snprintf(args[pos], len, "%s:%s", cli_opts.remotehost, cli_opts.remoteport);
208+ pos++;
209+
210+ /* hostnames of prior hops */
211+ args[pos] = m_strdup(prior_hops);
212+ pos++;
213+
214 return args;
215 }
216
217@@ -585,7 +607,7 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
218 char *userhostarg = NULL;
219 char *hostbuf = NULL;
220 char *last_hop = NULL;
221- char *remainder = NULL;
222+ char *prior_hops = NULL;
223
224 /* both scp and rsync parse a user@host argument
225 * and turn it into "-l user host". This breaks
226@@ -603,6 +625,8 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
227 }
228 userhostarg = hostbuf;
229
230+ /* Split off any last hostname and use that as remotehost/remoteport.
231+ * That is used for authorized_keys checking etc */
232 last_hop = strrchr(userhostarg, ',');
233 if (last_hop) {
234 if (last_hop == userhostarg) {
235@@ -610,36 +634,28 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
236 }
237 *last_hop = '\0';
238 last_hop++;
239- remainder = userhostarg;
240+ prior_hops = userhostarg;
241 userhostarg = last_hop;
242 }
243
244+ /* Update cli_opts.remotehost and cli_opts.remoteport */
245 parse_hostname(userhostarg);
246
247- if (last_hop) {
248- /* Set up the proxycmd */
249- unsigned int cmd_len = 0;
250- char *passthrough_args = multihop_passthrough_args();
251+ /* Construct any multihop proxy command. Use proxyexec to
252+ * avoid worrying about shell escaping. */
253+ if (prior_hops) {
254+ cli_opts.proxyexec = multihop_args(argv0, prior_hops);
255+ /* Any -J argument has been copied to proxyexec */
256 if (cli_opts.proxycmd) {
257 dropbear_exit("-J can't be used with multihop mode");
258 }
259- if (cli_opts.remoteport == NULL) {
260- cli_opts.remoteport = "22";
261- }
262- cmd_len = strlen(argv0) + strlen(remainder)
263- + strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport)
264- + strlen(passthrough_args)
265- + 30;
266- cli_opts.proxycmd = m_malloc(cmd_len);
267- m_snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s %s",
268- argv0, cli_opts.remotehost, cli_opts.remoteport,
269- passthrough_args, remainder);
270+
271 #ifndef DISABLE_ZLIB
272- /* The stream will be incompressible since it's encrypted. */
273+ /* This outer stream will be incompressible since it's encrypted. */
274 opts.compress_mode = DROPBEAR_COMPRESS_OFF;
275 #endif
276- m_free(passthrough_args);
277 }
278+
279 m_free(hostbuf);
280 }
281 #endif /* !DROPBEAR_CLI_MULTIHOP */
282diff --git a/dbutil.c b/dbutil.c
283index d4c3298..a51c1f9 100644
284--- a/dbutil.c
285+++ b/dbutil.c
286@@ -347,7 +347,6 @@ int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data,
287 void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) {
288 char * argv[4];
289 char * baseshell = NULL;
290- unsigned int i;
291
292 baseshell = basename(usershell);
293
294@@ -369,6 +368,12 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) {
295 argv[1] = NULL;
296 }
297
298+ run_command(usershell, argv, maxfd);
299+}
300+
301+void run_command(const char* argv0, char** args, unsigned int maxfd) {
302+ unsigned int i;
303+
304 /* Re-enable SIGPIPE for the executed process */
305 if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) {
306 dropbear_exit("signal() error");
307@@ -380,7 +385,7 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) {
308 m_close(i);
309 }
310
311- execv(usershell, argv);
312+ execv(argv0, args);
313 }
314
315 #if DEBUG_TRACE
316diff --git a/dbutil.h b/dbutil.h
317index 71cffe8..5d86485 100644
318--- a/dbutil.h
319+++ b/dbutil.h
320@@ -60,6 +60,7 @@ char * stripcontrol(const char * text);
321 int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data,
322 int *writefd, int *readfd, int *errfd, pid_t *pid);
323 void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell);
324+void run_command(const char* argv0, char** args, unsigned int maxfd);
325 #if ENABLE_CONNECT_UNIX
326 int connect_unix(const char* addr);
327 #endif
328diff --git a/runopts.h b/runopts.h
329index 01201d2..b49dc13 100644
330--- a/runopts.h
331+++ b/runopts.h
332@@ -179,7 +179,12 @@ typedef struct cli_runopts {
333 unsigned int netcat_port;
334 #endif
335 #if DROPBEAR_CLI_PROXYCMD
336+ /* A proxy command to run via the user's shell */
337 char *proxycmd;
338+#endif
339+#if DROPBEAR_CLI_MULTIHOP
340+ /* Similar to proxycmd, but is arguments for execve(), not shell */
341+ char **proxyexec;
342 #endif
343 char *bind_address;
344 char *bind_port;