8.17. Testing a File for Trustworthiness
8.17.1. Problem
You want to read
from a file, perhaps because it has configuration information. You
want to use the file only if it can''t be written to (or perhaps not
even be read from) by anyone else than its owner.
8.17.2. Solution
Use the stat function to retrieve ownership and
file permissions information. You can use the built-in version, which
returns a list:
( $dev, $ino, $mode, $nlink,
$uid, $gid, $rdev, $size,
$atime, $mtime, $ctime,
$blksize, $blocks ) = stat($filename)
or die "no $filename: $!";
$mode &= 07777; # discard file type info
Or you can use the by-name interface:
use File::stat;
$info = stat($filename) or die "no $filename: $!";
if ($info->uid = = 0) {
print "Superuser owns $filename\n";
}
if ($info->atime > $info->mtime) {
print "$filename has been read since it was written.\n";
}
8.17.3. Discussion
Usually you trust users to set file permissions as they wish. If they
want others to read their files, or even to write to them, that''s
their business. Applications such as editors, mailers, and shells are
often more discerning, though, refusing to evaluate code in
configuration files if anyone but the owner can write to them. This
helps avoid Trojan horse attacks. Security-minded programs such as
ftp and ssh may even reject
config files that can be read by anyone but their owner.If the file is writable by someone other than the owner or is owned
by someone other than the current user or the superuser, it shouldn''t
be trusted. To figure out file ownership and permissions, the
stat function is used. The following function
returns true if the file is deemed safe and false otherwise. If the
stat fails, undef is returned.
use File::stat;
sub is_safe {
my $path = shift;
my $info = stat($path);
return unless $info;
# owner neither superuser nor me
# the real uid is in stored in the $< variable
if (($info->uid != 0) && ($info->uid != $<)) {
return 0;
}
# check whether group or other can write file.
# use 066 to detect either reading or writing
if ($info->mode & 022) { # someone else can write this
return 0 unless -d _; # non-directories aren''t safe
# but directories with the sticky bit (01000) are
return 0 unless $info->mode & 01000;
}
return 1;
}
A directory is considered safe even if others can write to it,
provided its mode 01000 (owner delete only) bit is set.Careful programmers also ensure that no enclosing directory is
writable. This is due to systems with the "chown
giveaway" problem in which any user can give away a file they own to
make it owned by someone else. The following function handles that by
using the is_safe function to check every
enclosing directory up to the root if it detects that you have the
chown problem, for which it queries the
POSIX::sysconf. If you don''t have an unrestricted
version of chown, the
is_verysafe subroutine just calls
is_safe. If you do have the problem, it walks up
the filesystem tree until it reaches the
root.
use Cwd;
use POSIX qw(sysconf _PC_CHOWN_RESTRICTED);
sub is_verysafe {
my $path = shift;
return is_safe($path) if sysconf(_PC_CHOWN_RESTRICTED);
$path = getcwd( ) . "/" . $path if $path !~ m{^/};
do {
return unless is_safe($path);
$path =~ s#([^/]+|/)$##; # dirname
$path =~ s#/$## if length($path) > 1; # last slash
} while length $path;
return 1;
}
To use this in a program, try something like this:
$file = "$ENV{HOME}/.myprogrc";
readconfig($file) if is_safe($file);
This has potential for a race condition, because it''s presumed that
the hypothetical readconfig function will open the
file. Between the time when is_safe checks the
file''s stats and when readconfig opens it,
something wicked could theoretically occur. To avoid this, pass
is_safe the already open filehandle, which is set
up to handle this:
$file = "$ENV{HOME}/.myprogrc";
if (open(FILE, "<", $file)) {
readconfig(*FILE) if is_safe(*FILE);
}
You would still have to arrange for readconfig to
accept a filehandle instead of a filename, though.
8.17.4. See Also
The stat function in
perlfunc(1) and in Chapter 29 of
Programming Perl; documentation for the standard
POSIX and File::stat modules; Recipe 8.16