Opening files securely in Perl

Opening files is a common operation in any programming language,
and opening files in Perl is particularly easy. This is a good
thing -- beginning programmers can learn the language more easily,
and experienced programmers can get on with their job with less
fuss and effort.

Perl's open function is extremely feature-rich, and it has the
longest description of any function in Perl's perlfunc
documentation.

However, when programming in a security-sensitive context, the
ease in which Perl allows us to open files can be a hindrance,
rather than a help. When a process is running with elevated
privileges, we usually wish to be specific about how files are
opened, and we need to be particularly careful to avoid opening
files the wrong way.

Clobbering files and executing code with two-argument open.

If the mode (reading, writing, appending) is not supplied to
the two-argument version of open, then the file is opened
for reading. Here's an example:

open(my $fh,$filename) or die "Cannot read from $filename - $!";

Because we haven't told Perl how we wish to open the file, it is
opened for reading by default. But what happens if our $filename
starts with a >? Then our file isn't opened for reading at all,
instead we open the file for writing instead, clobbering its contents
if it already exists. This may just be an inconvenience, or it
could be a disaster, especially if our $filename looks something
like >/etc/passwd.

An even more serious problem arises when our $filename contains
the pipe (|) character. When this appears at the start or end
of the filename, Perl will execute a command instead of opening
a file. That could be used to spawn a shell, send out spam, or
do all manner of undesirable things.

The solution to these problems is simple. Always specify the
file operation when opening a file. In the above example, we can
explicitly open the file for reading by putting a < at
the start of the filename:

open(my $fh,"<$filename") or die "Cannot read from $filename - $!";

Many readers will feel safe and secure in the knowledge that they've
been doing this for years. However, that doesn't shield us from
all of open's magical features.

Hijacking streams with two-argument open.

An often-forgotten feature of Perl's two-argument open is that it
can be used to duplicate streams, as well as opening regular files.

Put very simply, open can be used to gain access to other file handles
that are already open. These can include files, sockets, and the
special file handles STDIN, STDOUT, and STDERR.

The syntax for duplicating a stream is rarely seen, but it should
not be overlooked:

open(my $fh,"<&=0") or die "Cannot dup STDIN - $!";

The <&=N syntax (where N is a number) returns
another file handle corresponding to the file descriptor numberN. In Perl you can find the file descriptor number of a file handle
using the fileno function, but the standard file handles
STDIN, STDOUT, and STDERR are always assigned the special
numbers 0, 1 and 2 respectively. The equivalent construct can be
used to duplicate a file handle for writing:

open(my $fh,">&=1") or die "Cannot dup STDOUT - $!";

The risk of duplicating file-descriptors is that it may allow
a clever attacker to unexpectedly read or write to another file handle
that's already been opened by the process. These may include
system files, configuration files, or even network sockets.

Perl has another shortcut for duplicating the file handles
STDIN and STDOUT, and that's the special filename
minus (-):

Avoiding problems by using three-argument open

Perl has a three-argument version of open, which is much more strict
when it comes to processing its arguments. Using the 3-argument
open, the mode with which to open the file is passed as a
separate argument.

Using our previous example of opening a file for reading, our
code would now look like:

open(my $fh,"<",$filename) or die "Cannot read from $filename - $!"

When using three-argument open, the filename is treated literally.
This means that if it contains leading or trailing spaces, or any
other strange characters, Perl will try to open a file with exactly
that name.

Using three-argument open means you won't have any surprises when
you try to open a file, and as such it's highly recommended that
you make use of three-argument open when working in a security
sensitive context.

Race conditions

Using the three-argument version of open isn't itself sufficient to
ensure your code is secure. You should always check input from unsafe
sources (typically anything outside of your program's control) before
passing it to open. Taint, covered last week, is invaluable in this
area.

Even so, your programs may be subject to race conditions. For example,
in old-style locking code some programs do the following: if a lock file
already exists, the program exits. If it does not, the lock file is
created and the program runs on. If the test for existence and the file
creation occur in two steps then there is a chance that the lock file will
be created by another invocation between those two steps and two copies of
the program will run together.

These issues and some methods to combat there will be covered further in
the next installment.