Home page logo
/

bugtraq logo Bugtraq mailing list archives

Perl's alleged tempfile vulnerabilities
From: tchrist () CHTHON PERL COM (Tom Christiansen)
Date: Fri, 4 Feb 2000 06:48:37 -0700


[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



  By Date           By Thread  

Current thread:
[ Nmap | Sec Tools | Mailing Lists | Site News | About/Contact | Advertising | Privacy ]