Use Perl 5.22’s <<>> operator for safe command-line handling

We’ve had the three argument open since Perl 5.6. This allows you to separate the way you want to interact with the file from the filename.

Old Perl requires you to include the mode and filename together, giving Perl the opportunity to interpret what you mean:

open FILE, '> some_file';

You can write that in three arguments so Perl doesn’t get to guess what the filename argument means. Now it’s a literal filename and nothing else:

open FILE, '>', 'some_file';

This is important when you use a variable for the filename argument.
With two-argument open you probably expect to open a file for reading
because that’s the implied mode:

open FILE, $filename;

What if, however, $filename isn’t just a literal filename? open gets a string and acts on what’s in that string. If it sees > at the beginning or a | at the end, it opens the file or the process in a particular way. This “magic open” feature is Perl trying to be helpful.

I discussed this in “Secure Programming Techniques” in Mastering Perl, but there’s another place that has this problem. The diamond operator, <>, automatically goes through the values in @ARGV and tries to open them. If those values have the special mode characters, Perl treats them as special. This is the same as <ARGV>, since that’s the implied filehandle.

Here’s a short Perl program that acts like cat -n, which numbers the lines:

#!/usr/bin/perl
# single-diamond.pl

print "$. $_" while( <> );

If you call this with the name of a regular file, the <> opens that file and prints its contents line by line. It can print its own source code:

$ perl single-diamond.pl single-diamond.pl
1 #!/usr/bin/perl
2 
3 print while( <> );

However, if you create a filename with a pipe on the end, it uses the name for the program it should launch then redirects its output into ARGV. Here it happens with the date command:

$ perl single-diamond.pl 'date |'
1 Sat Oct  4 02:16:49 EDT 2014

What if you use the > to try to open a file for writing? That mode truncates a file before you can write to it. you see that you have lines in not_empty before you run the program but those lines disappear when you run the program:

$ cat not_empty.txt
this is a line
here's another line
this is the last line
$ perl5.20.0 single-diamond.pl '> not_empty.txt'
$ cat not_empty.txt
$

This is a problem. Perl provides this convenient idiom to open the files on the command line, but now it’s dangerous.

To get around this, Perl v5.22 adds a new form of the diamond operator. The double diamond <<>> interprets the command-line arguments as literal filenames.

#!/usr/bin/perl
# double-diamond.pl
use v5.22;

print while( <<>> );

When you try to fool this program with the pipe, you get an error that the file named date | does not exist:

$ perl5.22.0 double-diamond.pl 'date |'
Can't open date |: No such file or directory at double-diamond.pl line 5.

Similarly, the double diamond doesn’t truncate the not_empty.txt file:

$ cat not_empty.txt
this is a line
here's another line
this is the last line
$ perl5.22.0 double-diamond.pl '> not_empty.txt'
Can't open > not_empty.txt: No such file or directory at double-diamond.pl line 5.
$ cat not_empty.txt
this is a line
here's another line
this is the last line

But, what if you can’t use v5.22? You’re stuck with the dangerous single diamond. You could abandon the convenience altogether and handle it yourself:

#!/usr/bin/perl

FILE: while( my $file = shift @ARGV ) {
	open my $fh, '<', $file or next FILE;
	print while( <$fh> );
	}

That’s not satisfying though, but neither is where you prepend a < to make the mode read-only:

#!/usr/bin/perl
# escaped.pl
use v5.14;

@ARGV = map { "< $_" } @ARGV;

print while( <> ); 

That works, but it’s a little bit ugly and probably has problems of its own:

$ perl escaped.pl 'echo "foo" |' '> not empty'
Can't open < echo "foo" |: No such file or directory at escaped.pl line 7, <> line 7.
Can't open < > not empty: No such file or directory at escaped.pl line 7, <> line 7.

Instead of trying a workaround, perhaps you can use this extremely dangerous and perverse security issue to convince your bosses to upgrade Perl.

Further reading

I’ve written about these issues at Mastering Perl too.

Things to remember

  • The filename in the two argument open can contain the file open mode.
  • The diamond operator might use “magic open”
  • The double-diamond operator interprets the command-line arguments as literal filenames

5 thoughts on “Use Perl 5.22’s <<>> operator for safe command-line handling”

  1. I don’t understand the rationale for this implementation. How often did the programmer actually intend the dangerous flexibility of the current operator? Isn’t this design a bug? I’ll assume the
    answers to these questions are “insignificant” and “yes”.

    Why not change the default behavior of <> to that of the proposed <<>> and provide a pragma to allow the old dangerous behavior? That way 99.99% of programs using <> will continue to work as intended, but more safely. And the tiny number of very strange exceptions can still be made to continue working.

    (Another variation of this would be to make the <> be the old too-flexible operator.)

  2. The <> implementation implies that the ‘best’ (most safe) way requires the most typing.
    Why not make “use v5.22” change the behaviour of <<>> to <>?

  3. Johan is right and in addition it doesn’t allow a straightforward fix in the
    code to the issue that caused this fix in the language, when you use under CGI:

    my $file = $cgi->param('file');
    while ( <$file> ){ ...
    

    and you were able to smuggle “ARGV” into $file. When using with CGI, in @ARGV
    are all the value params including a bad string ending with ‘|’.

    Under current rules you have to fix it with:

    @ARGV = scalar $cgi->param('file');
    while ( <<>> ){ ...
    

    But that looks wonky.

Comments are closed.