Opening files securely in Perl

[ Perl tips index ]
[ Subscribe to Perl tips ]

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 number N. 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 (-):

        open(my $fh,">-") or die "Cannot dup STDOUT - $!";
        open(my $fh,"<-") or die "Cannot dup STDIN  - $!";

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.

[ Perl tips index ]
[ Subscribe to Perl tips ]

This Perl tip and associated text is copyright Perl Training Australia. You may freely distribute this text so long as it is distributed in full with this Copyright noticed attached.

If you have any questions please don't hesitate to contact us:

Phone: 03 9354 6001 (Australia)
International: +61 3 9354 6001

Valid XHTML 1.0 Valid CSS