Shell Elimination III

#!perl
...
my $cmd    = "btrfs subvolume snapshot -r '$subvolume' '$destination/$name'";
my $status = system $cmd;

The above command does route through the shell, and does have a few problems. Spoiler: avoid routing through the shell via something like:

my $status =
  system qw(btrfs subvolume snapshot -r), $subvolume, "$destination/$name";

This avoids word-splitting problems the POSIX shell is plagued with, because sh is (probably) not involved, and also avoids various security issues like what if untrusted users supply whatever is in those variables, or even if the variables somehow contain something that makes the shell go derp, e.g. a variable with a password that has (for "security" reasons) various fancy characters that the shell then turns into something wrong wrong wrong. (I had to explain all this to some folks at a Computer Science department, once. Sigh.) Also, it saves us a fork, as Perl should directly run btrfs not first sh and that then btrfs. But (almost) nobody cares about Wirth's Law.

Incoherent Internet Ramblings (IIR) should not be trusted; therefore, with a simpler command (the fact that I do not have anything with btrfs is also relevant)

#!/usr/bin/env perl
my $variable = shift;
system "printf '%s\n' '$variable'";

we can get down to testing variable inputs. (It's single quoted, what could possibly go wrong here, one might ask.)

% < file
#!/usr/bin/env perl
my $variable = shift;
system "printf '%s\n' '$variable'";
% perl file "goodbye cruel world"
goodbye cruel world

All well and good. Unless...

% perl file "';who am i;echo '"

jmates   ttys007  Aug  4 11:54

%

Whoops, shell injection. Let's try that with the not-through-the-blasted-sh version of the system command:

#!/usr/bin/env perl
my $variable = shift;
system printf => '%s\n', $variable;

which now results in:

% < file
#!/usr/bin/env perl
my $variable = shift;
system printf => '%s\n', $variable;
% perl file "';who am i;echo '"
';who am i;echo '

By the way I'm using ZSH here; other shells might have trouble with the bare < file form and may need to use something like cat file or some other such barbarism. And yes, my shell prompt is only %. Moving on...

One might wish to know where or if sh is being routed through; process tracing such as ktrace (OpenBSD) or strace (Linux) or so forth depending on the flavor of unix is a good way to reveal this, especially if you are really trying to prevent things from every going needlessly through the shell.

File brand X

# ktrace -id perl file foo
foo
# kdump -f ktrace.out | grep /bin
 16273 ktrace   NAMI  "/bin/perl"
 16273 ktrace   NAMI  "/usr/bin/perl"
       "#!/usr/bin/env perl
 27736 perl     NAMI  "/bin/printf"
 27736 perl     NAMI  "/usr/bin/printf"

File brand Y

# ktrace -id perl file foo
foo
# kdump -f ktrace.out | grep /bin
 12316 ktrace   NAMI  "/bin/perl"
 12316 ktrace   NAMI  "/usr/bin/perl"
       "#!/usr/bin/env perl
 82307 perl     NAMI  "/bin/sh"
 82307 sh       NAMI  "/bin/printf"
 82307 sh       NAMI  "/usr/bin/printf"
 82307 sh       NAMI  "/usr/bin/printf"
 29518 sh       NAMI  "/usr/bin/printf"

Which of these files do you think used the dangerous system "printf '%s\n' '$variable'"; form?

This will be pretty similar for strace or whatever. The major hurdle is figuring out what command flags you need to perform the trace, and then how to extract the information you want from the resulting output.

Anyways, eliminate the shell where you can. It is usually more efficient to do so, and helps avoid fun things like shell injection attacks.

root