Subject: Perl's alleged tempfile vulnerabilities
From: Tom Christiansen (tchrist@CHTHON.PERL.COM)
Date: Fri Feb 04 2000 - 14:48:37 CET
[Message CC'd to bugtraq per Theo's request]
theo>Check this out.
>> Date: Wed, 2 Feb 2000 12:50:14 +0200
>> Sender: Bugtraq List <BUGTRAQ@SECURITYFOCUS.COM>
>> From: Neil Blakey-Milner <nbm@MITHRANDR.MORIA.ORG>
>> To: BUGTRAQ@SECURITYFOCUS.COM
>> Organization: Rhodes University Computer Users' Society
>> Subject: Re: Tempfile vulnerabilities
>> X-To: Grant Taylor <gtaylor+bugtraq_hcdbb013100@PICANTE.COM>
>> In-Reply-To: <200002010455.XAA20677@pace.picante.com>
>>
>> On Mon 2000-01-31 (23:55), Grant Taylor wrote:
>> >
>> > sub get_tmpfile {
>> > my $file;
>> > do {
>> > open RAN, "/dev/random" || die;
>> > read(RAN,$foo,16);
>> > close RAN;
>> > $file = '/tmp/autobuse' . unpack('H16',$foo);
>> > } while (-e $file || -l $file);
>> >
>> > return $file;
>> > }
>> >
>> > This method is Linux-specific, but that's all I need. The fixed
>> > autobuse is available at http://www.picante.com/~gtaylor/autobuse/
>> >
>> > Note that Autobuse has, as far as I know, zero users (including me).
>> > If I am wrong about this, please let me know!
>>
>> I was about to suggest using mkstemp() from File::MkTemp, available
>> on CPAN, until I noticed that this mkstemp doesn't seem to use
>> O_CREAT and O_EXCL from Fcntl nor does it chmod 600 or similar.
>>
>> It uses:
>>
>> $template = mktemp(@_);
>>
>> $openup = File::Spec->catfile($_[1], $template);
>>
>> $fh = new FileHandle ">$openup"; #and say ahhh.
>>
>> croak("Could not open file: $openup")
>> unless(defined $fh);
>>
>> return($fh);
>>
>> Which seems to be just as bad as using mktemp, and then opening a
>> file, with the usual race conditions. Considering the general
>> feeling about the "mkstemp" concept, this implementation isn't
>> quite there.
>>
>> Neil
>> --
>> Neil Blakey-Milner
>> nbm@rucus.ru.ac.za
>Is there a correct mkstemp(3) in perl?
>THERE REALLY SHOULD BE. Can you give me details?
I have always advocated one of two approaches. These are published.
I can't help it if people do stupid things. :-(
1) use POSIX;
do {
$name = tmpnam();
} until sysopen(FH, $name, O_RDWR|O_CREAT|O_EXCL, 0666);
2) use IO::File;
$handle = IO::File->new_tmpfile(); # this is POSIX's tmpfile(3)
The second case is what you're told to use if if you ask for
POSIX::tmpfile, which has always struck me as a bit as awkward at
best. I think one should just do it instead of blowing up as
POSIX::tmpfile current does. This may be an issue of FDs versus
FPs; Perl I/O operators/functions expect to deal with (FILE *)FPs,
not (int)FDs. So you'd have to fdopen the FD you got back anyway
to do much with it.
Anyway, here's the IO::File description:
new_tmpfile
Creates an IO::File opened for read/write on a newly created
temporary file. On systems where this is possible, the
temporary file is anonymous (ie. it is unlinked after
creation, but held open). If the temporary file cannot be
created or opened, the IO::File object is destroyed. Otherwise,
it is returned to the caller.
In the XS code[see footnote #0], we have the following:
MODULE = IO PACKAGE = IO::File PREFIX = f
SV *
new_tmpfile(packname = "IO::File")
char * packname
PREINIT:
OutputStream fp;
GV *gv;
CODE:
#ifdef PerlIO
fp = PerlIO_tmpfile();
#else
fp = tmpfile();
#endif
gv = (GV*)SvREFCNT_inc(newGVgen(packname));
hv_delete(GvSTASH(gv), GvNAME(gv), GvNAMELEN(gv), G_DISCARD);
if (do_open(gv, "+>&", 3, FALSE, 0, 0, fp)) {
ST(0) = sv_2mortal(newRV((SV*)gv));
sv_bless(ST(0), gv_stashpv(packname, TRUE));
SvREFCNT_dec(gv); /* undo increment in newRV() */
}
else {
ST(0) = &PL_sv_undef;
SvREFCNT_dec(gv);
}
Which is just calling the standard POSIX tmpfile() function. Well,
sometimes. If you use the Perl I/O abstraction, then you get either
the real tmpfile(), or under sfio, some sftmp(0) thingie that I
know nothing about. The only hole I see is in my ignorance of the
possible sfio-related sftmp(0) call. Enlightenment in this area
is welcome. I also don't know what sub-Unix systems do about the
unlink issue, since their primitive or alien filesystems are
notoriously too paltry to support that approach, since they can't
bring themselves to talk about a file as something distinct from a
filename. In that case, I don't know what happens to the file
created by IO::File::new_tmpfile. Perhaps it has a destructor to
unlink it eventually, but this isn't in any code I could find.
There's been some discussion of having Perl automatically
call tmpfile() in the event that its filename were undefined.
For example:
# get an anon filehandle (ie. stdio/sfio stream object)
open(TMPFH, undef) || die "can't get tmpfilehandle: $!";
# or using monadic open on an undefined variable
undef $tmpfh; # initial state of all variables, actually
open($tmpfh) || die "can't get tmpfilehandle: $!";
In fact, Perl hero Nick Ing-Simmons once offered up the code to
make open(F, undef) automatically call tmpfile(). Perhaps in
retrospect lamentably, this proposal was shot down due to its ugly
interface. It may well be that we want to resurrect that notion,
perhaps amending it to produce a form more acceptable to the interface
lawyers.
I do not dispute that there's a great deal of code out there
that for a temporary file, blindly does an
open(TMPOUT, "> /tmp/foo.$$")
in order to get its temporary file. I do feel that Perl has
facilities to create safe temporary files, but that, for various
reasons, people are largely unaware of them.
So they end up doing the /tmp/foo.$$ thing, and thus open themselves
up to a large host of maladies. That "strategy", to use the term
generously, is vulnerable to pid prediction, which, while reasonably
stymied by OpenBSD's randomized pid assignment, is of no help to
people on systems that don't do that. Worse, it is subject to
someone else pre-creating that file in a world-writable directory.
Consider what hitting a named pipe instead of a regular file could
do to your program. Now, accessing open(2) instead of fopen(3) and
then using O_EXCL|O_CREAT would help, but does not suffice for all
possible cases. Someone could have created a symbolic link from
that file to elsewhere--or to nowhere. If the file did exist, then
without the O_EXCL|O_CREAT check it would blindly overwrite it, as
we so often saw in the execve() symlink/suid-script bug targetting
important files like /etc/passwd with the link. But because a
symlink can point to nowhere, the O_EXCL|O_CREAT test does not
suffice: you might still end up making a "new" file, even one that
you own, that's somewhere else than you think it is. Who knows
what nastiness the nefarious crackers could possibly make of that?
It isn't clear how best to address the issues raised in the original
bug report. We *have* the technology now to take care of most if
not all the problems, but inadequate user education remains the
bottleneck. Quite bluntly, people make poor decisions, and I don't
see how to stop this from happening: "The poor we shall have with
us always." :-(
[Back to Theo, from private mail]
theo>In the last 6 months, I think that about 25 /tmp races in perl
theo>programs have hit it. That number is going to go way up.
theo>
theo>A proactive approach to education could squish that now, instead
theo>of reacting 3 years from now when everyone has done it wrong.
Perl has two things you can do, neither of which is "automatic" in
the sense that it's part of the normal Perl open() function.
1) You *can* use POSIX::tmpnam(), and this seems to behave
reasonably well on most systems. However, you still have
to know to do the right thing (the sysopen loop previous
demonstrated), and you have no control over *where* the
file is placed, which means it can very well land in a
directory that's mode 0777 not 01777 (or be run on a system
that doesn't honor sticky directories as owner-delete-only),
just to mention one potential problem.
2) On the other hand, IO::File::new_tmpfile (which IM!HO should
really be merely POSIX::tmpfile per expectations) dodges
at least one bullet by making the proper open call for you.
However, it gives you no access to the filename created;
in fact, there often isn't one, due to unlink magic. This
is less error prone, but still imperfect.
We could adopt some of the "automatic tmpfile" strategies involving
an undefined filename, but that's not the whole of the issue. To
answer your original question, no, Perl does *not* currently support
the mk*temp*(3) family of functions (meaning mktemp(3), mkstemp(3),
mkstemps(3), and mkdtemp(3)). Apparently, there's something on
CPAN called File::Temp, but it looks like it's not doing the right
thing and just calling the real functions[FN#1]. These would allow
you to supply a full template and thus place your temporary files
in the directory that you want them in. We could add those trivially
enough. But this still can do nothing to make people use them. In
fact, nothing can. :-(
I think that the easiest but perhaps not the most effective "solution"
I can offer you is to add a notice in the Perl documentation on the
open and sysopen functions, and also in the security section, that
mentions the use of POSIX::tmpnam() and IO::File::new_tmpfile().
This might have more effect than adding the mk*temp*(3) family.
But we could do that, too.
There *is* one more thing we might be able to do, one that's a bit
more proactive. We might augment Perl's open function so that it
would emit a warning when run with warnings[FN#2] and/or that it
would raise an exception when run in taint mode[FN#3].
This would mean checking for using the perilously simplistic
open(FH, ">filename") style of open (which, being fopen(path, "w"),
is really O_TRUNC|O_CREAT not O_EXCL|O_CREAT) on a file whose name
looks like a tempfile.
Now, just how could you ever tell that? Well, one could watch for
"$$" at the end of the filename. Maybe this would be only in
conjunction with a "/tmp/" component, but not necessarily rooted
at slash, so that /usr/tmp and /var/tmp would show up, too. Perl
could, upon finding this situation (however we define it), emit a
warning or raise an exception to the effect that this operation is
insecure because it's subject to a race condition, and suggest using
one of POSIX::tmpnam() and IO::File::new_tmpfile() instead.
I will forward this message to p5p, Perl's release and development
team for further discussion. Interested and creative parties should
feel free to *constructively* kibitz there. :-)
--tom
Footnotes:
[0] XS is C-like glue code that gets massaged by Perl's xsubpp
(the external subroutine preprocessor) into legit C,
somewhat akin to Sun RPC xdr .x files.
[1] The "real" mk*temp*(3) functions are described at
http://www.openbsd.org/cgi-bin/man.cgi?query=mktemp&apropos=0&sektion=3&m
[2] Warnings are enabled via the -w/-W command line switches,
or else under the "use warnings" pragma.
[3] "taintperl" security checks are enabled either optionally
through the -T command line switch, or else automatically
when running with differing real vs. effective user and/or
group IDs.
-- Tom Christiansen Perl Documentation Pumpking tchrist@perl.com
This archive was generated by hypermail 2b25 : Sat Feb 05 2000 - 09:40:19 CET