📄 ch23_02.htm
字号:
by promising yourself you'll never operate on a filename more thanonce. As soon as you have the file opened, forget about the filename(except maybe for error messages), and operate only on the handlerepresenting the file. This is much safer because, even though someonecould play with your filenames, they can't play with your filehandles.(Or if they can, it's because you let them--see "Passing Filehandles" in<a href="ch16_01.htm">Chapter 16, "Interprocess Communication"</a>.)</p><p><a name="INDEX-4038"></a>Earlier in this chapter, we showed a<tt class="literal">handle_looks_safe</tt> function which called Perl's<tt class="literal">stat</tt> function on a filehandle (not a filename) tocheck its ownership and permissions. Using the filehandle is criticalto correctness--if we had used the name of the file, there would havebeen no guarantee that the file whose attributes we were inspectingwas the same one we just opened (or were about to open). Some peskyevil doer could have deleted our file and quickly replaced it with afile of nefarious design, sometime between the <tt class="literal">stat</tt>and the <tt class="literal">open</tt>. It wouldn't matter which was calledfirst; there'd still be the opportunity for foul play between the two.You may think that the risk is very small because the window is veryshort, but there are many cracking scripts out in the world that willbe perfectly happy to run your program thousands of times to catch itthe one time it wasn't careful enough. A smart cracking script caneven lower the priority of your program so it gets interrupted moreoften than usual, just to speed things up a little. People work hardon these things--that's why they're called<em class="emphasis">exploits</em>.</p><p>By calling <tt class="literal">stat</tt> on a filehandle that's alreadyopen, we only access the filename once and so avoid the racecondition. A good strategy for avoiding races between two events isto somehow combine both into one, making the operationatomic.<a href="#FOOTNOTE-9">[9]</a> Since we access the file by name onlyonce, there can't be any race condition between multiple accesses, soit doesn't matter whether the name changes. Even if our crackerdeletes the file we opened (yes, that can happen) and puts a differentone there to trick us with, we still have a handle to the real,original file.</p><blockquote class="footnote"><a name="FOOTNOTE-9"></a><p>[9] Yes, you may still perform atomic operationsin a nuclear-free zone. When Democritus gave the word "atom" to theindivisible bits of matter, he meant literally something that couldnot be cut: <em class="emphasis">a-</em> (not) + <em class="emphasis">tomos</em>(cuttable). An atomic operation is an action that can't beinterrupted. (Just you try interrupting an atomic bombsometime.)</p></blockquote><a name="INDEX-4039"></a><a name="INDEX-4040"></a><a name="INDEX-4041"></a><h3 class="sect2">23.2.3. Temporary Files</h3><p><a name="INDEX-4042"></a><a name="INDEX-4043"></a><a name="INDEX-4044"></a>Apart from allowing buffer overruns (which Perl scripts are virtually immuneto) and trusting untrustworthy input data (which taint mode guards against), creating temporaryfiles improperly is one of the most frequently exploited security holes. Fortunately,temp file attacks usually require crackers to have a valid useraccount on the system they're trying to crack, which drastically reducesthe number of potential bad guys.</p><p>Careless or casual programs use temporary files in all kinds ofunsafe ways, like placing them in world-writable directories, usingpredictable filenames, and not making sure the file doesn't alreadyexist. Whenever you find a program with code like this:<blockquote><pre class="programlisting">open(TMP, ">/tmp/foo.$$") or die "can't open /tmp/foo.$$: $!";</pre></blockquote>you've just found all three of those errors at once. That programis an accident waiting to happen.</p><p><a name="INDEX-4045"></a>The way the exploit plays out is that the cracker first plants afile with the same name as the one you'll use. Appending the PIDisn't enough for uniqueness; surprising though it may sound, guessingPIDs really isn't difficult.<a href="#FOOTNOTE-10">[10]</a>Now along comes the program with the careless <tt class="literal">open</tt> call, andinstead of creating a new temporary file for its own purposes, itoverwrites the cracker's file instead.</p><blockquote class="footnote"><a name="FOOTNOTE-10"></a><p>[10]Unless you're on a systemlike OpenBSD, which randomizes new PID assignments.</p></blockquote><p>So what harm can that do? A lot. The cracker's file isn't really aplain file, you see. It's a symbolic link (or sometimes a hard link),probably pointing to some critical file that crackers couldn'tnormally write to on their own, such as<em class="emphasis">/etc/passwd</em>. The program thought it opened abrand new file in <em class="emphasis">/tmp</em>, but it clobbered anexisting file somewhere else instead.</p><p><a name="INDEX-4046"></a><a name="INDEX-4047"></a>Perl provides two functions that address this issue, if properlyused. The first is <tt class="literal">POSIX::tmpnam</tt>, which just returns a filenamethat you're expected to open for yourself:<blockquote><pre class="programlisting"># Keep trying names until we get one that's brand new.use POSIX;do { $name = tmpnam();} until sysopen(TMP, $name, O_RDWR | O_CREAT | O_EXCL, 0600);# Now do I/O using TMP handle.</pre></blockquote>The second is <tt class="literal">IO::File::new_tmpfile</tt>, which gives you backan already opened handle:<blockquote><pre class="programlisting"># Or else let the module do that for us.use IO::File;my $fh = IO::File::new_tmpfile(); # this is POSIX's tmpfile(3)# Now do I/O using $fh handle.</pre></blockquote>Neither approach is perfect, but of the two, the first is the betterapproach. The major problem with the second one is that Perl issubject to the foibles of whatever implementation of<em class="emphasis">tmpfile</em>(3) happens to be in your system'sC library, and you have no guarantee that this function doesn't dosomething just as dangerous as the <tt class="literal">open</tt> we'retrying to fix. (And some, sadly enough, do.) A minor problem is thatit doesn't give you the name of the file at all. Although it's betterif you can handle a temp file without a name--because that way you'llnever provoke a race condition by trying to open it again--often youcan't.</p><p>The major problem with the first approach is that you have no controlover the location of the pathname, as you do with the C library's<em class="emphasis">mkstemp</em>(3) function. For one thing, younever want to put the file on an NFS-mounted filesystem. The<tt class="literal">O_EXCL</tt> flag is not guaranteed to work correctlyunder NFS, so multiple processes that request an exclusive create atnearly the same time might all succeed. For another, because the pathreturned is probably in a directory others can write to, someone couldplant a symbolic link pointing to a nonexistent file, forcing you tocreate your file in a location they prefer.<a href="#FOOTNOTE-11">[11]</a> If you have any say in it, don'tput temp files in a directory that anyone else can write to. If youmust, make sure to use the <tt class="literal">O_EXCL</tt> flag to<tt class="literal">sysopen</tt>, and try to use directories with theowner-delete-only flag (the sticky bit) set on them.</p><blockquote class="footnote"><a name="FOOTNOTE-11"></a><p>[11]A solutionto this, which works only under some operating systems, is to call<tt class="literal">sysopen</tt> and OR in the <tt class="literal">O_NOFOLLOW</tt>flag. This makes the function fail if the final component of the pathis a symbolic link.</p></blockquote><p><a name="INDEX-4048"></a>As of version 5.6.1 of Perl, there is a third way. The standard<tt class="literal">File::Temp</tt> module takes into account all thedifficulties we've mentioned. You might use the default options likethis:<blockquote><pre class="programlisting">use File::Temp "tempfile";$handle = tempfile();</pre></blockquote></p><p>Or you might specify some of the options like this:<blockquote><pre class="programlisting">use File::Temp "tempfile";($handle, $filename) = tempfile("plughXXXXXX", DIR => "/var/spool/adventure", SUFFIX = '.dat');</pre></blockquote></p><p>The <tt class="literal">File::Temp</tt> module also providessecurity-conscious emulations of the other functions we've mentioned(though the native interface is better because it gives you an openedfilehandle, not just a filename, which is subject to race conditions).See <a href="ch32_01.htm">Chapter 32, "Standard Modules"</a>, for a longerdescription of the options and semantics of this module.</p><p>Once you have your filehandle, you can do whatever you want with it.It's open for both reading and writing, so you can write to thehandle, <tt class="literal">seek</tt> back to the beginning, and then if youwant, overwrite what you'd just put there or read it back again. Thething you really, <em class="emphasis">really</em> want to avoid doing isever opening that filename again, because you can't know for sure thatit's really the same file you opened the first timearound.<a href="#FOOTNOTE-12">[12]</a></p><blockquote class="footnote"><a name="FOOTNOTE-12"></a><p>[12] Except afterwards by doing a<tt class="literal">stat</tt> on both filehandles and comparing the firsttwo return values of each (the device/inode pair). But it's too lateby then because the damage is already done. All you can do is detectthe damage and abort (and maybe sneakily send email to the systemadministrator).</p></blockquote><p><a name="INDEX-4049"></a>When you launch another program from within your script, Perl normallycloses all filehandles for you to avoid another vulnerability. If youuse <tt class="literal">fcntl</tt> to clear your close-on-exec flag (asdemonstrated at the end of the entry on <tt class="literal">open</tt> in<a href="ch29_01.htm">Chapter 29, "Functions"</a>), other programs you call willinherit this new, open file descriptor. On systems that support the<em class="emphasis">/dev/fd/</em> directory, you could provide anotherprogram with a filename that really means the file descriptor byconstructing it this way:<blockquote><pre class="programlisting">$virtname = "/dev/fd/" . fileno(TMP);</pre></blockquote>If you only needed to call a Perl subroutine or program that'sexpecting a filename as an argument, and you knew that subroutine orprogram used regular <tt class="literal">open</tt> for it, you could passthe handle using Perl's notation for indicating a filehandle:<blockquote><pre class="programlisting">$virtname = "=&" . fileno(TMP);</pre></blockquote>When that file "name" is passed with a regular Perl<tt class="literal">open</tt> of one or two arguments (not three, whichwould dispel this useful magic), you gain access to the duplicateddescriptor. In some ways, this is more portable than passing a filefrom <em class="emphasis">/dev/fd/</em>, because it works everywhere thatPerl works; not all systems have a <em class="emphasis">/dev/fd/</em>directory. On the other hand, the special Perl<tt class="literal">open</tt> syntax for accessing file descriptors bynumber works only with Perl programs, not with programs written inother languages.<a name="INDEX-4050"></a><a name="INDEX-4051"></a><a name="INDEX-4052"></a></p><a name="INDEX-4053"></a><a name="INDEX-4054"></a><!-- BOTTOM NAV BAR --><hr width="515" align="left"><div class="navbar"><table width="515" border="0"><tr><td align="left" valign="top" width="172"><a href="ch23_01.htm"><img src="../gifs/txtpreva.gif" alt="Previous" border="0"></a></td><td align="center" valign="top" width="171"><a href="index.htm"><img src="../gifs/txthome.gif" alt="Home" border="0"></a></td><td align="right" valign="top" width="172"><a href="ch23_03.htm"><img src="../gifs/txtnexta.gif" alt="Next" border="0"></a></td></tr><tr><td align="left" valign="top" width="172">23.1. Handling Insecure Data</td><td align="center" valign="top" width="171"><a href="index/index.htm"><img src="../gifs/index.gif" alt="Book Index" border="0"></a></td><td align="right" valign="top" width="172">23.3. Handling Insecure Code</td></tr></table></div><hr width="515" align="left"><!-- LIBRARY NAV BAR --><img src="../gifs/smnavbar.gif" usemap="#library-map" border="0" alt="Library Navigation Links"><p><font size="-1"><a href="copyrght.htm">Copyright © 2001</a> O'Reilly & Associates. All rights reserved.</font></p><map name="library-map"> <area shape="rect" coords="2,-1,79,99" href="../index.htm"><area shape="rect" coords="84,1,157,108" href="../perlnut/index.htm"><area shape="rect" coords="162,2,248,125" href="../prog/index.htm"><area shape="rect" coords="253,2,326,130" href="../advprog/index.htm"><area shape="rect" coords="332,1,407,112" href="../cookbook/index.htm"><area shape="rect" coords="414,2,523,103" href="../sysadmin/index.htm"></map><!-- END OF BODY --></body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -