Skip to content

Commit e5a0ef2

Browse files
committed
Execute multihop commands directly, no shell
This avoids problems with shell escaping if arguments contain special characters.
1 parent 753eefe commit e5a0ef2

File tree

5 files changed

+118
-62
lines changed

5 files changed

+118
-62
lines changed

src/cli-main.c

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,8 @@ int main(int argc, char ** argv) {
7777
}
7878

7979
#if DROPBEAR_CLI_PROXYCMD
80-
if (cli_opts.proxycmd) {
80+
if (cli_opts.proxycmd || cli_opts.proxyexec) {
8181
cli_proxy_cmd(&sock_in, &sock_out, &proxy_cmd_pid);
82-
m_free(cli_opts.proxycmd);
8382
if (signal(SIGINT, kill_proxy_sighandler) == SIG_ERR ||
8483
signal(SIGTERM, kill_proxy_sighandler) == SIG_ERR ||
8584
signal(SIGHUP, kill_proxy_sighandler) == SIG_ERR) {
@@ -101,7 +100,8 @@ int main(int argc, char ** argv) {
101100
}
102101
#endif /* DBMULTI stuff */
103102

104-
static void exec_proxy_cmd(const void *user_data_cmd) {
103+
#if DROPBEAR_CLI_PROXYCMD
104+
static void shell_proxy_cmd(const void *user_data_cmd) {
105105
const char *cmd = user_data_cmd;
106106
char *usershell;
107107

@@ -110,41 +110,62 @@ static void exec_proxy_cmd(const void *user_data_cmd) {
110110
dropbear_exit("Failed to run '%s'\n", cmd);
111111
}
112112

113-
#if DROPBEAR_CLI_PROXYCMD
113+
static void exec_proxy_cmd(const void *unused) {
114+
(void)unused;
115+
run_command(cli_opts.proxyexec[0], cli_opts.proxyexec, ses.maxfd);
116+
dropbear_exit("Failed to run '%s'\n", cli_opts.proxyexec[0]);
117+
}
118+
114119
static void cli_proxy_cmd(int *sock_in, int *sock_out, pid_t *pid_out) {
115-
char * ex_cmd = NULL;
116-
size_t ex_cmdlen;
120+
char * cmd_arg = NULL;
121+
void (*exec_fn)(const void *user_data) = NULL;
117122
int ret;
118123

124+
/* exactly one of cli_opts.proxycmd or cli_opts.proxyexec should be set */
125+
119126
/* File descriptor "-j &3" */
120-
if (*cli_opts.proxycmd == '&') {
127+
if (cli_opts.proxycmd && *cli_opts.proxycmd == '&') {
121128
char *p = cli_opts.proxycmd + 1;
122129
int sock = strtoul(p, &p, 10);
123130
/* must be a single number, and not stdin/stdout/stderr */
124131
if (sock > 2 && sock < 1024 && *p == '\0') {
125132
*sock_in = sock;
126133
*sock_out = sock;
127-
return;
134+
goto cleanup;
128135
}
129136
}
130137

131-
/* Normal proxycommand */
132-
133-
/* So that spawn_command knows which shell to run */
134-
fill_passwd(cli_opts.own_user);
135-
136-
ex_cmdlen = strlen(cli_opts.proxycmd) + 6; /* "exec " + command + '\0' */
137-
ex_cmd = m_malloc(ex_cmdlen);
138-
snprintf(ex_cmd, ex_cmdlen, "exec %s", cli_opts.proxycmd);
138+
if (cli_opts.proxycmd) {
139+
/* Normal proxycommand */
140+
size_t shell_cmdlen;
141+
/* So that spawn_command knows which shell to run */
142+
fill_passwd(cli_opts.own_user);
143+
144+
shell_cmdlen = strlen(cli_opts.proxycmd) + 6; /* "exec " + command + '\0' */
145+
cmd_arg = m_malloc(shell_cmdlen);
146+
snprintf(cmd_arg, shell_cmdlen, "exec %s", cli_opts.proxycmd);
147+
exec_fn = shell_proxy_cmd;
148+
} else {
149+
/* No shell */
150+
exec_fn = exec_proxy_cmd;
151+
}
139152

140-
ret = spawn_command(exec_proxy_cmd, ex_cmd,
141-
sock_out, sock_in, NULL, pid_out);
142-
DEBUG1(("cmd: %s pid=%d", ex_cmd,*pid_out))
143-
m_free(ex_cmd);
153+
ret = spawn_command(exec_fn, cmd_arg, sock_out, sock_in, NULL, pid_out);
144154
if (ret == DROPBEAR_FAILURE) {
145155
dropbear_exit("Failed running proxy command");
146156
*sock_in = *sock_out = -1;
147157
}
158+
159+
cleanup:
160+
m_free(cli_opts.proxycmd);
161+
m_free(cmd_arg);
162+
if (cli_opts.proxyexec) {
163+
char **a = NULL;
164+
for (a = cli_opts.proxyexec; *a; a++) {
165+
m_free_direct(*a);
166+
}
167+
m_free(cli_opts.proxyexec);
168+
}
148169
}
149170

150171
static void kill_proxy_sighandler(int UNUSED(signo)) {

src/cli-runopts.c

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -556,62 +556,88 @@ void loadidentityfile(const char* filename, int warnfail) {
556556

557557
/* Fill out -i, -y, -W options that make sense for all
558558
* the intermediate processes */
559-
static char* multihop_passthrough_args(void) {
560-
char *args = NULL;
561-
unsigned int len, total;
559+
static char** multihop_args(const char* argv0, const char* prior_hops) {
560+
/* null terminated array */
561+
char **args = NULL;
562+
size_t max_args = 14, pos = 0, len;
562563
#if DROPBEAR_CLI_PUBKEY_AUTH
563564
m_list_elem *iter;
564565
#endif
565-
/* Sufficient space for non-string args */
566-
len = 100;
567566

568-
/* String arguments have arbitrary length, so determine space required */
569-
if (cli_opts.proxycmd) {
570-
len += strlen(cli_opts.proxycmd);
571-
}
572567
#if DROPBEAR_CLI_PUBKEY_AUTH
573568
for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
574569
{
575-
sign_key * key = (sign_key*)iter->item;
576-
len += 4 + strlen(key->filename);
570+
/* "-i file" for each */
571+
max_args += 2;
577572
}
578573
#endif
579574

580-
args = m_malloc(len);
581-
total = 0;
575+
args = m_malloc(sizeof(char*) * max_args);
576+
pos = 0;
582577

583-
/* Create new argument string */
578+
args[pos] = m_strdup(argv0);
579+
pos++;
584580

585581
if (cli_opts.quiet) {
586-
total += m_snprintf(args+total, len-total, "-q ");
582+
args[pos] = m_strdup("-q");
583+
pos++;
587584
}
588585

589586
if (cli_opts.no_hostkey_check) {
590-
total += m_snprintf(args+total, len-total, "-y -y ");
587+
args[pos] = m_strdup("-y");
588+
pos++;
589+
args[pos] = m_strdup("-y");
590+
pos++;
591591
} else if (cli_opts.always_accept_key) {
592-
total += m_snprintf(args+total, len-total, "-y ");
592+
args[pos] = m_strdup("-y");
593+
pos++;
593594
}
594595

595596
if (cli_opts.batch_mode) {
596-
total += m_snprintf(args+total, len-total, "-o BatchMode=yes ");
597+
args[pos] = m_strdup("-o");
598+
pos++;
599+
args[pos] = m_strdup("BatchMode=yes");
600+
pos++;
597601
}
598602

599603
if (cli_opts.proxycmd) {
600-
total += m_snprintf(args+total, len-total, "-J '%s' ", cli_opts.proxycmd);
604+
args[pos] = m_strdup("-J");
605+
pos++;
606+
args[pos] = m_strdup(cli_opts.proxycmd);
607+
pos++;
601608
}
602609

603610
if (opts.recv_window != DEFAULT_RECV_WINDOW) {
604-
total += m_snprintf(args+total, len-total, "-W %u ", opts.recv_window);
611+
args[pos] = m_strdup("-W");
612+
pos++;
613+
args[pos] = m_malloc(11);
614+
m_snprintf(args[pos], 11, "%u", opts.recv_window);
615+
pos++;
605616
}
606617

607618
#if DROPBEAR_CLI_PUBKEY_AUTH
608619
for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
609620
{
610621
sign_key * key = (sign_key*)iter->item;
611-
total += m_snprintf(args+total, len-total, "-i %s ", key->filename);
622+
args[pos] = m_strdup("-i");
623+
pos++;
624+
args[pos] = m_strdup(key->filename);
625+
pos++;
612626
}
613627
#endif /* DROPBEAR_CLI_PUBKEY_AUTH */
614628

629+
/* last hop */
630+
args[pos] = m_strdup("-B");
631+
pos++;
632+
len = strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport) + 2;
633+
args[pos] = m_malloc(len);
634+
snprintf(args[pos], len, "%s:%s", cli_opts.remotehost, cli_opts.remoteport);
635+
pos++;
636+
637+
/* hostnames of prior hops */
638+
args[pos] = m_strdup(prior_hops);
639+
pos++;
640+
615641
return args;
616642
}
617643

@@ -626,15 +652,15 @@ static char* multihop_passthrough_args(void) {
626652
* etc for as many hosts as we want.
627653
*
628654
* Note that "-J" arguments aren't actually used, instead
629-
* below sets cli_opts.proxycmd directly.
655+
* below sets cli_opts.proxyexec directly.
630656
*
631657
* Ports for hosts can be specified as host/port.
632658
*/
633659
static void parse_multihop_hostname(const char* orighostarg, const char* argv0) {
634660
char *userhostarg = NULL;
635661
char *hostbuf = NULL;
636662
char *last_hop = NULL;
637-
char *remainder = NULL;
663+
char *prior_hops = NULL;
638664

639665
/* both scp and rsync parse a user@host argument
640666
* and turn it into "-l user host". This breaks
@@ -652,39 +678,37 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
652678
}
653679
userhostarg = hostbuf;
654680

681+
/* Split off any last hostname and use that as remotehost/remoteport.
682+
* That is used for authorized_keys checking etc */
655683
last_hop = strrchr(userhostarg, ',');
656684
if (last_hop) {
657685
if (last_hop == userhostarg) {
658686
dropbear_exit("Bad multi-hop hostnames");
659687
}
660688
*last_hop = '\0';
661689
last_hop++;
662-
remainder = userhostarg;
690+
prior_hops = userhostarg;
663691
userhostarg = last_hop;
664692
}
665693

694+
/* Update cli_opts.remotehost and cli_opts.remoteport */
666695
parse_hostname(userhostarg);
667696

668-
if (last_hop) {
669-
/* Set up the proxycmd */
670-
unsigned int cmd_len = 0;
671-
char *passthrough_args = multihop_passthrough_args();
672-
cmd_len = strlen(argv0) + strlen(remainder)
673-
+ strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport)
674-
+ strlen(passthrough_args)
675-
+ 30;
676-
/* replace proxycmd. old -J arguments have been copied
677-
to passthrough_args */
678-
cli_opts.proxycmd = m_realloc(cli_opts.proxycmd, cmd_len);
679-
m_snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s %s",
680-
argv0, cli_opts.remotehost, cli_opts.remoteport,
681-
passthrough_args, remainder);
697+
/* Construct any multihop proxy command. Use proxyexec to
698+
* avoid worrying about shell escaping. */
699+
if (prior_hops) {
700+
cli_opts.proxyexec = multihop_args(argv0, prior_hops);
701+
/* Any -J argument has been copied to proxyexec */
702+
if (cli_opts.proxycmd) {
703+
m_free(cli_opts.proxycmd);
704+
}
705+
682706
#ifndef DISABLE_ZLIB
683-
/* The stream will be incompressible since it's encrypted. */
707+
/* This outer stream will be incompressible since it's encrypted. */
684708
opts.allow_compress = 0;
685709
#endif
686-
m_free(passthrough_args);
687710
}
711+
688712
m_free(hostbuf);
689713
}
690714
#endif /* DROPBEAR_CLI_MULTIHOP */

src/dbutil.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,6 @@ int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data,
371371
void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) {
372372
char * argv[4];
373373
char * baseshell = NULL;
374-
unsigned int i;
375374

376375
baseshell = basename(usershell);
377376

@@ -393,6 +392,12 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) {
393392
argv[1] = NULL;
394393
}
395394

395+
run_command(usershell, argv, maxfd);
396+
}
397+
398+
void run_command(const char* argv0, char** args, unsigned int maxfd) {
399+
unsigned int i;
400+
396401
/* Re-enable SIGPIPE for the executed process */
397402
if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) {
398403
dropbear_exit("signal() error");
@@ -404,7 +409,7 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) {
404409
m_close(i);
405410
}
406411

407-
execv(usershell, argv);
412+
execv(argv0, args);
408413
}
409414

410415
#if DEBUG_TRACE

src/dbutil.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ char * stripcontrol(const char * text);
6363
int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data,
6464
int *writefd, int *readfd, int *errfd, pid_t *pid);
6565
void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell);
66+
void run_command(const char* argv0, char** args, unsigned int maxfd);
6667
#if ENABLE_CONNECT_UNIX
6768
int connect_unix(const char* addr);
6869
#endif

src/runopts.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,12 @@ typedef struct cli_runopts {
194194
unsigned int netcat_port;
195195
#endif
196196
#if DROPBEAR_CLI_PROXYCMD
197+
/* A proxy command to run via the user's shell */
197198
char *proxycmd;
199+
#endif
200+
#if DROPBEAR_CLI_MULTIHOP
201+
/* Similar to proxycmd, but is arguments for execve(), not shell */
202+
char **proxyexec;
198203
#endif
199204
const char *bind_arg;
200205
char *bind_address;

0 commit comments

Comments
 (0)