diff options
-rw-r--r-- | meta/recipes-core/dropbear/dropbear/CVE-2025-47203.patch | 373 | ||||
-rw-r--r-- | meta/recipes-core/dropbear/dropbear_2024.86.bb | 1 |
2 files changed, 374 insertions, 0 deletions
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..9ce0f10588 --- /dev/null +++ b/meta/recipes-core/dropbear/dropbear/CVE-2025-47203.patch | |||
@@ -0,0 +1,373 @@ | |||
1 | From e5a0ef27c227f7ae69d9a9fec98a056494409b9b Mon Sep 17 00:00:00 2001 | ||
2 | From: Matt Johnston <matt@ucc.asn.au> | ||
3 | Date: Mon, 5 May 2025 23:14:19 +0800 | ||
4 | Subject: [PATCH] Execute multihop commands directly, no shell | ||
5 | |||
6 | This avoids problems with shell escaping if arguments contain special | ||
7 | characters. | ||
8 | |||
9 | CVE: CVE-2025-47203 | ||
10 | Upstream-Status: Backport [https://github.com/mkj/dropbear/commit/e5a0ef27c227f7ae69d9a9fec98a056494409b9b] | ||
11 | Signed-off-by: Peter Marko <peter.marko@siemens.com> | ||
12 | --- | ||
13 | src/cli-main.c | 59 +++++++++++++++++--------- | ||
14 | src/cli-runopts.c | 104 ++++++++++++++++++++++++++++------------------ | ||
15 | src/dbutil.c | 9 +++- | ||
16 | src/dbutil.h | 1 + | ||
17 | src/runopts.h | 5 +++ | ||
18 | 5 files changed, 117 insertions(+), 61 deletions(-) | ||
19 | |||
20 | diff --git a/src/cli-main.c b/src/cli-main.c | ||
21 | index 065fd76..2fafa88 100644 | ||
22 | --- a/src/cli-main.c | ||
23 | +++ b/src/cli-main.c | ||
24 | @@ -77,9 +77,8 @@ int main(int argc, char ** argv) { | ||
25 | } | ||
26 | |||
27 | #if DROPBEAR_CLI_PROXYCMD | ||
28 | - if (cli_opts.proxycmd) { | ||
29 | + if (cli_opts.proxycmd || cli_opts.proxyexec) { | ||
30 | cli_proxy_cmd(&sock_in, &sock_out, &proxy_cmd_pid); | ||
31 | - m_free(cli_opts.proxycmd); | ||
32 | if (signal(SIGINT, kill_proxy_sighandler) == SIG_ERR || | ||
33 | signal(SIGTERM, kill_proxy_sighandler) == SIG_ERR || | ||
34 | signal(SIGHUP, kill_proxy_sighandler) == SIG_ERR) { | ||
35 | @@ -101,7 +100,8 @@ int main(int argc, char ** argv) { | ||
36 | } | ||
37 | #endif /* DBMULTI stuff */ | ||
38 | |||
39 | -static void exec_proxy_cmd(const void *user_data_cmd) { | ||
40 | +#if DROPBEAR_CLI_PROXYCMD | ||
41 | +static void shell_proxy_cmd(const void *user_data_cmd) { | ||
42 | const char *cmd = user_data_cmd; | ||
43 | char *usershell; | ||
44 | |||
45 | @@ -110,41 +110,62 @@ static void exec_proxy_cmd(const void *user_data_cmd) { | ||
46 | dropbear_exit("Failed to run '%s'\n", cmd); | ||
47 | } | ||
48 | |||
49 | -#if DROPBEAR_CLI_PROXYCMD | ||
50 | +static void exec_proxy_cmd(const void *unused) { | ||
51 | + (void)unused; | ||
52 | + run_command(cli_opts.proxyexec[0], cli_opts.proxyexec, ses.maxfd); | ||
53 | + dropbear_exit("Failed to run '%s'\n", cli_opts.proxyexec[0]); | ||
54 | +} | ||
55 | + | ||
56 | static void cli_proxy_cmd(int *sock_in, int *sock_out, pid_t *pid_out) { | ||
57 | - char * ex_cmd = NULL; | ||
58 | - size_t ex_cmdlen; | ||
59 | + char * cmd_arg = NULL; | ||
60 | + void (*exec_fn)(const void *user_data) = NULL; | ||
61 | int ret; | ||
62 | |||
63 | + /* exactly one of cli_opts.proxycmd or cli_opts.proxyexec should be set */ | ||
64 | + | ||
65 | /* File descriptor "-j &3" */ | ||
66 | - if (*cli_opts.proxycmd == '&') { | ||
67 | + if (cli_opts.proxycmd && *cli_opts.proxycmd == '&') { | ||
68 | char *p = cli_opts.proxycmd + 1; | ||
69 | int sock = strtoul(p, &p, 10); | ||
70 | /* must be a single number, and not stdin/stdout/stderr */ | ||
71 | if (sock > 2 && sock < 1024 && *p == '\0') { | ||
72 | *sock_in = sock; | ||
73 | *sock_out = sock; | ||
74 | - return; | ||
75 | + goto cleanup; | ||
76 | } | ||
77 | } | ||
78 | |||
79 | - /* Normal proxycommand */ | ||
80 | + if (cli_opts.proxycmd) { | ||
81 | + /* Normal proxycommand */ | ||
82 | + size_t shell_cmdlen; | ||
83 | + /* So that spawn_command knows which shell to run */ | ||
84 | + fill_passwd(cli_opts.own_user); | ||
85 | |||
86 | - /* So that spawn_command knows which shell to run */ | ||
87 | - fill_passwd(cli_opts.own_user); | ||
88 | + shell_cmdlen = strlen(cli_opts.proxycmd) + 6; /* "exec " + command + '\0' */ | ||
89 | + cmd_arg = m_malloc(shell_cmdlen); | ||
90 | + snprintf(cmd_arg, shell_cmdlen, "exec %s", cli_opts.proxycmd); | ||
91 | + exec_fn = shell_proxy_cmd; | ||
92 | + } else { | ||
93 | + /* No shell */ | ||
94 | + exec_fn = exec_proxy_cmd; | ||
95 | + } | ||
96 | |||
97 | - ex_cmdlen = strlen(cli_opts.proxycmd) + 6; /* "exec " + command + '\0' */ | ||
98 | - ex_cmd = m_malloc(ex_cmdlen); | ||
99 | - snprintf(ex_cmd, ex_cmdlen, "exec %s", cli_opts.proxycmd); | ||
100 | - | ||
101 | - ret = spawn_command(exec_proxy_cmd, ex_cmd, | ||
102 | - sock_out, sock_in, NULL, pid_out); | ||
103 | - DEBUG1(("cmd: %s pid=%d", ex_cmd,*pid_out)) | ||
104 | - m_free(ex_cmd); | ||
105 | + ret = spawn_command(exec_fn, cmd_arg, sock_out, sock_in, NULL, pid_out); | ||
106 | if (ret == DROPBEAR_FAILURE) { | ||
107 | dropbear_exit("Failed running proxy command"); | ||
108 | *sock_in = *sock_out = -1; | ||
109 | } | ||
110 | + | ||
111 | +cleanup: | ||
112 | + m_free(cli_opts.proxycmd); | ||
113 | + m_free(cmd_arg); | ||
114 | + if (cli_opts.proxyexec) { | ||
115 | + char **a = NULL; | ||
116 | + for (a = cli_opts.proxyexec; *a; a++) { | ||
117 | + m_free_direct(*a); | ||
118 | + } | ||
119 | + m_free(cli_opts.proxyexec); | ||
120 | + } | ||
121 | } | ||
122 | |||
123 | static void kill_proxy_sighandler(int UNUSED(signo)) { | ||
124 | diff --git a/src/cli-runopts.c b/src/cli-runopts.c | ||
125 | index b664293..a21b7a2 100644 | ||
126 | --- a/src/cli-runopts.c | ||
127 | +++ b/src/cli-runopts.c | ||
128 | @@ -556,62 +556,88 @@ void loadidentityfile(const char* filename, int warnfail) { | ||
129 | |||
130 | /* Fill out -i, -y, -W options that make sense for all | ||
131 | * the intermediate processes */ | ||
132 | -static char* multihop_passthrough_args(void) { | ||
133 | - char *args = NULL; | ||
134 | - unsigned int len, total; | ||
135 | +static char** multihop_args(const char* argv0, const char* prior_hops) { | ||
136 | + /* null terminated array */ | ||
137 | + char **args = NULL; | ||
138 | + size_t max_args = 14, pos = 0, len; | ||
139 | #if DROPBEAR_CLI_PUBKEY_AUTH | ||
140 | m_list_elem *iter; | ||
141 | #endif | ||
142 | - /* Sufficient space for non-string args */ | ||
143 | - len = 100; | ||
144 | |||
145 | - /* String arguments have arbitrary length, so determine space required */ | ||
146 | - if (cli_opts.proxycmd) { | ||
147 | - len += strlen(cli_opts.proxycmd); | ||
148 | - } | ||
149 | #if DROPBEAR_CLI_PUBKEY_AUTH | ||
150 | for (iter = cli_opts.privkeys->first; iter; iter = iter->next) | ||
151 | { | ||
152 | - sign_key * key = (sign_key*)iter->item; | ||
153 | - len += 4 + strlen(key->filename); | ||
154 | + /* "-i file" for each */ | ||
155 | + max_args += 2; | ||
156 | } | ||
157 | #endif | ||
158 | |||
159 | - args = m_malloc(len); | ||
160 | - total = 0; | ||
161 | + args = m_malloc(sizeof(char*) * max_args); | ||
162 | + pos = 0; | ||
163 | |||
164 | - /* Create new argument string */ | ||
165 | + args[pos] = m_strdup(argv0); | ||
166 | + pos++; | ||
167 | |||
168 | if (cli_opts.quiet) { | ||
169 | - total += m_snprintf(args+total, len-total, "-q "); | ||
170 | + args[pos] = m_strdup("-q"); | ||
171 | + pos++; | ||
172 | } | ||
173 | |||
174 | if (cli_opts.no_hostkey_check) { | ||
175 | - total += m_snprintf(args+total, len-total, "-y -y "); | ||
176 | + args[pos] = m_strdup("-y"); | ||
177 | + pos++; | ||
178 | + args[pos] = m_strdup("-y"); | ||
179 | + pos++; | ||
180 | } else if (cli_opts.always_accept_key) { | ||
181 | - total += m_snprintf(args+total, len-total, "-y "); | ||
182 | + args[pos] = m_strdup("-y"); | ||
183 | + pos++; | ||
184 | } | ||
185 | |||
186 | if (cli_opts.batch_mode) { | ||
187 | - total += m_snprintf(args+total, len-total, "-o BatchMode=yes "); | ||
188 | + args[pos] = m_strdup("-o"); | ||
189 | + pos++; | ||
190 | + args[pos] = m_strdup("BatchMode=yes"); | ||
191 | + pos++; | ||
192 | } | ||
193 | |||
194 | if (cli_opts.proxycmd) { | ||
195 | - total += m_snprintf(args+total, len-total, "-J '%s' ", cli_opts.proxycmd); | ||
196 | + args[pos] = m_strdup("-J"); | ||
197 | + pos++; | ||
198 | + args[pos] = m_strdup(cli_opts.proxycmd); | ||
199 | + pos++; | ||
200 | } | ||
201 | |||
202 | if (opts.recv_window != DEFAULT_RECV_WINDOW) { | ||
203 | - total += m_snprintf(args+total, len-total, "-W %u ", opts.recv_window); | ||
204 | + args[pos] = m_strdup("-W"); | ||
205 | + pos++; | ||
206 | + args[pos] = m_malloc(11); | ||
207 | + m_snprintf(args[pos], 11, "%u", opts.recv_window); | ||
208 | + pos++; | ||
209 | } | ||
210 | |||
211 | #if DROPBEAR_CLI_PUBKEY_AUTH | ||
212 | for (iter = cli_opts.privkeys->first; iter; iter = iter->next) | ||
213 | { | ||
214 | sign_key * key = (sign_key*)iter->item; | ||
215 | - total += m_snprintf(args+total, len-total, "-i %s ", key->filename); | ||
216 | + args[pos] = m_strdup("-i"); | ||
217 | + pos++; | ||
218 | + args[pos] = m_strdup(key->filename); | ||
219 | + pos++; | ||
220 | } | ||
221 | #endif /* DROPBEAR_CLI_PUBKEY_AUTH */ | ||
222 | |||
223 | + /* last hop */ | ||
224 | + args[pos] = m_strdup("-B"); | ||
225 | + pos++; | ||
226 | + len = strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport) + 2; | ||
227 | + args[pos] = m_malloc(len); | ||
228 | + snprintf(args[pos], len, "%s:%s", cli_opts.remotehost, cli_opts.remoteport); | ||
229 | + pos++; | ||
230 | + | ||
231 | + /* hostnames of prior hops */ | ||
232 | + args[pos] = m_strdup(prior_hops); | ||
233 | + pos++; | ||
234 | + | ||
235 | return args; | ||
236 | } | ||
237 | |||
238 | @@ -626,7 +652,7 @@ static char* multihop_passthrough_args(void) { | ||
239 | * etc for as many hosts as we want. | ||
240 | * | ||
241 | * Note that "-J" arguments aren't actually used, instead | ||
242 | - * below sets cli_opts.proxycmd directly. | ||
243 | + * below sets cli_opts.proxyexec directly. | ||
244 | * | ||
245 | * Ports for hosts can be specified as host/port. | ||
246 | */ | ||
247 | @@ -634,7 +660,7 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0) | ||
248 | char *userhostarg = NULL; | ||
249 | char *hostbuf = NULL; | ||
250 | char *last_hop = NULL; | ||
251 | - char *remainder = NULL; | ||
252 | + char *prior_hops = NULL; | ||
253 | |||
254 | /* both scp and rsync parse a user@host argument | ||
255 | * and turn it into "-l user host". This breaks | ||
256 | @@ -652,6 +678,8 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0) | ||
257 | } | ||
258 | userhostarg = hostbuf; | ||
259 | |||
260 | + /* Split off any last hostname and use that as remotehost/remoteport. | ||
261 | + * That is used for authorized_keys checking etc */ | ||
262 | last_hop = strrchr(userhostarg, ','); | ||
263 | if (last_hop) { | ||
264 | if (last_hop == userhostarg) { | ||
265 | @@ -659,32 +687,28 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0) | ||
266 | } | ||
267 | *last_hop = '\0'; | ||
268 | last_hop++; | ||
269 | - remainder = userhostarg; | ||
270 | + prior_hops = userhostarg; | ||
271 | userhostarg = last_hop; | ||
272 | } | ||
273 | |||
274 | + /* Update cli_opts.remotehost and cli_opts.remoteport */ | ||
275 | parse_hostname(userhostarg); | ||
276 | |||
277 | - if (last_hop) { | ||
278 | - /* Set up the proxycmd */ | ||
279 | - unsigned int cmd_len = 0; | ||
280 | - char *passthrough_args = multihop_passthrough_args(); | ||
281 | - cmd_len = strlen(argv0) + strlen(remainder) | ||
282 | - + strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport) | ||
283 | - + strlen(passthrough_args) | ||
284 | - + 30; | ||
285 | - /* replace proxycmd. old -J arguments have been copied | ||
286 | - to passthrough_args */ | ||
287 | - cli_opts.proxycmd = m_realloc(cli_opts.proxycmd, cmd_len); | ||
288 | - m_snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s %s", | ||
289 | - argv0, cli_opts.remotehost, cli_opts.remoteport, | ||
290 | - passthrough_args, remainder); | ||
291 | + /* Construct any multihop proxy command. Use proxyexec to | ||
292 | + * avoid worrying about shell escaping. */ | ||
293 | + if (prior_hops) { | ||
294 | + cli_opts.proxyexec = multihop_args(argv0, prior_hops); | ||
295 | + /* Any -J argument has been copied to proxyexec */ | ||
296 | + if (cli_opts.proxycmd) { | ||
297 | + m_free(cli_opts.proxycmd); | ||
298 | + } | ||
299 | + | ||
300 | #ifndef DISABLE_ZLIB | ||
301 | - /* The stream will be incompressible since it's encrypted. */ | ||
302 | + /* This outer stream will be incompressible since it's encrypted. */ | ||
303 | opts.compress_mode = DROPBEAR_COMPRESS_OFF; | ||
304 | #endif | ||
305 | - m_free(passthrough_args); | ||
306 | } | ||
307 | + | ||
308 | m_free(hostbuf); | ||
309 | } | ||
310 | #endif /* DROPBEAR_CLI_MULTIHOP */ | ||
311 | diff --git a/src/dbutil.c b/src/dbutil.c | ||
312 | index 2b44921..a70025e 100644 | ||
313 | --- a/src/dbutil.c | ||
314 | +++ b/src/dbutil.c | ||
315 | @@ -371,7 +371,6 @@ int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data, | ||
316 | void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) { | ||
317 | char * argv[4]; | ||
318 | char * baseshell = NULL; | ||
319 | - unsigned int i; | ||
320 | |||
321 | baseshell = basename(usershell); | ||
322 | |||
323 | @@ -393,6 +392,12 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) { | ||
324 | argv[1] = NULL; | ||
325 | } | ||
326 | |||
327 | + run_command(usershell, argv, maxfd); | ||
328 | +} | ||
329 | + | ||
330 | +void run_command(const char* argv0, char** args, unsigned int maxfd) { | ||
331 | + unsigned int i; | ||
332 | + | ||
333 | /* Re-enable SIGPIPE for the executed process */ | ||
334 | if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) { | ||
335 | dropbear_exit("signal() error"); | ||
336 | @@ -404,7 +409,7 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) { | ||
337 | m_close(i); | ||
338 | } | ||
339 | |||
340 | - execv(usershell, argv); | ||
341 | + execv(argv0, args); | ||
342 | } | ||
343 | |||
344 | #if DEBUG_TRACE | ||
345 | diff --git a/src/dbutil.h b/src/dbutil.h | ||
346 | index 05fc50c..bfbed73 100644 | ||
347 | --- a/src/dbutil.h | ||
348 | +++ b/src/dbutil.h | ||
349 | @@ -63,6 +63,7 @@ char * stripcontrol(const char * text); | ||
350 | int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data, | ||
351 | int *writefd, int *readfd, int *errfd, pid_t *pid); | ||
352 | void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell); | ||
353 | +void run_command(const char* argv0, char** args, unsigned int maxfd); | ||
354 | #if ENABLE_CONNECT_UNIX | ||
355 | int connect_unix(const char* addr); | ||
356 | #endif | ||
357 | diff --git a/src/runopts.h b/src/runopts.h | ||
358 | index c4061a0..f255882 100644 | ||
359 | --- a/src/runopts.h | ||
360 | +++ b/src/runopts.h | ||
361 | @@ -197,7 +197,12 @@ typedef struct cli_runopts { | ||
362 | unsigned int netcat_port; | ||
363 | #endif | ||
364 | #if DROPBEAR_CLI_PROXYCMD | ||
365 | + /* A proxy command to run via the user's shell */ | ||
366 | char *proxycmd; | ||
367 | +#endif | ||
368 | +#if DROPBEAR_CLI_MULTIHOP | ||
369 | + /* Similar to proxycmd, but is arguments for execve(), not shell */ | ||
370 | + char **proxyexec; | ||
371 | #endif | ||
372 | const char *bind_arg; | ||
373 | char *bind_address; | ||
diff --git a/meta/recipes-core/dropbear/dropbear_2024.86.bb b/meta/recipes-core/dropbear/dropbear_2024.86.bb index be246a0ccd..10b7cb5c03 100644 --- a/meta/recipes-core/dropbear/dropbear_2024.86.bb +++ b/meta/recipes-core/dropbear/dropbear_2024.86.bb | |||
@@ -21,6 +21,7 @@ SRC_URI = "http://matt.ucc.asn.au/dropbear/releases/dropbear-${PV}.tar.bz2 \ | |||
21 | file://dropbear.default \ | 21 | file://dropbear.default \ |
22 | ${@bb.utils.contains('DISTRO_FEATURES', 'pam', '${PAM_SRC_URI}', '', d)} \ | 22 | ${@bb.utils.contains('DISTRO_FEATURES', 'pam', '${PAM_SRC_URI}', '', d)} \ |
23 | ${@bb.utils.contains('PACKAGECONFIG', 'disable-weak-ciphers', 'file://dropbear-disable-weak-ciphers.patch', '', d)} \ | 23 | ${@bb.utils.contains('PACKAGECONFIG', 'disable-weak-ciphers', 'file://dropbear-disable-weak-ciphers.patch', '', d)} \ |
24 | file://CVE-2025-47203.patch \ | ||
24 | " | 25 | " |
25 | 26 | ||
26 | SRC_URI[sha256sum] = "e78936dffc395f2e0db099321d6be659190966b99712b55c530dd0a1822e0a5e" | 27 | SRC_URI[sha256sum] = "e78936dffc395f2e0db099321d6be659190966b99712b55c530dd0a1822e0a5e" |