btrbk: quote unsafe characters in shell commands

As filenames can contain meta characters like '$', we can't just put
ssh commands in double quotes. E.g. the following would trigger
variable expansion on local shell:

    ssh example.com "ls -l 'evil$x'"

Instead, we quote the ssh using single quotes (adding the need to
escape single quotes), while also quoting unsafe filenames using
single quotes. The above becomes:

    ssh example.com 'ls -l '\''evil$x'\'''

Or more complex, for a file named "file with'single quotes'":

    ssh example.com 'ls -l '\''file with'\''\'\'''\''single quotes'\''\'\'''\'''\'''

On the remote shell, this will expand to:

    ls -l 'file with'\''single quotes'\'''
unsafe-filenames
Axel Burri 2021-08-15 12:27:54 +02:00
parent d7f6d5fecf
commit acc7f9fc83
1 changed files with 10 additions and 3 deletions

13
btrbk
View File

@ -773,18 +773,24 @@ sub _piped_cmd_txt($)
return $cmd; return $cmd;
} }
sub quoteshell(@) {
# replace ' -> '\''
join ' ', map { "'" . s/'/'\\''/gr . "'" } @_
}
sub _safe_cmd($$) sub _safe_cmd($$)
{ {
# NOTE: this function alters $aref: hashes of form: "{ unsafe => 'string' }" get translated to "string" # NOTE: this function alters $aref: hashes of form: "{ unsafe => 'string' }" get translated to "'string'"
my $aref = shift; my $aref = shift;
my $offending = shift; my $offending = shift;
foreach(@$aref) { foreach(@$aref) {
if(ref($_) eq 'HASH') { if(ref($_) eq 'HASH') {
$_ = $_->{unsafe}; # replace in-place $_ = $_->{unsafe}; # replace in-place
# NOTE: all files must be absolute # NOTE: all files must be absolute (if not, check for leading dash '-' here!)
unless(defined(check_file($_, { absolute => 1 }))) { unless(defined(check_file($_, { absolute => 1 }))) {
push @$offending, "\"$_\""; push @$offending, "\"$_\"";
} }
$_ = quoteshell($_);
} }
} }
return join(' ', @$aref); return join(' ', @$aref);
@ -900,7 +906,8 @@ sub run_cmd(@)
my $rsh_text = _safe_cmd($href->{rsh}, \@unsafe_cmd); my $rsh_text = _safe_cmd($href->{rsh}, \@unsafe_cmd);
return undef unless(defined($rsh_text)); return undef unless(defined($rsh_text));
$href->{cmd_text} = $rsh_text . " '" . _piped_cmd_txt(\@rsh_cmd_pipe) . "'";
$href->{cmd_text} = $rsh_text . ' ' . quoteshell(_piped_cmd_txt(\@rsh_cmd_pipe));
} }
# local stream_buffer, rate_limit and show_progress in front of stream sink # local stream_buffer, rate_limit and show_progress in front of stream sink