Perl's alleged tempfile vulnerabilities

From: Tom Christiansen (tchristat_private)
Date: Fri Feb 04 2000 - 05:48:37 PST

  • Next message: Tim Hollebeek: "recent 'cross site scripting' CERT advisory"

    [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 <BUGTRAQat_private>
    >> From: Neil Blakey-Milner <nbmat_private>
    >> To: BUGTRAQat_private
    >> Organization: Rhodes University Computer Users' Society
    >> Subject: Re: Tempfile vulnerabilities
    >> X-To: Grant Taylor <gtaylor+bugtraq_hcdbb013100at_private>
    >> In-Reply-To:  <200002010455.XAA20677at_private>
    >>
    >> 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
    >> nbmat_private
    
    >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
    tchristat_private
    



    This archive was generated by hypermail 2b30 : Fri Apr 13 2001 - 15:33:33 PDT