Skip Menu |

This queue is for tickets about the File-Temp CPAN distribution.

Report information
The Basics
Id: 122820
Status: new
Priority: 0/
Queue: File-Temp

People
Owner: Nobody in particular
Requestors: jkeenan [...] pobox.com
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: (no value)
Fixed in: (no value)



Subject: Change in tempdir behavior requires change in documentation
Date: Wed, 16 Aug 2017 10:39:01 -0400
To: bug-File-Temp [...] rt.cpan.org
From: James E Keenan <jkeenan [...] pobox.com>
Summary: Because of a change in the behavior of File::Path::rmtree() for security reasons, the behavior of File::Temp::tempdir() has also changed. This change of behavior should be documented and illustrated with tests. A. Why Is File::Temp Affected by a Security Vulnerability in File::Path? File::Temp::tempdir()'s 'CLEANUP => 1' option is implemented with internal calls to File::Path::rmtree() (https://metacpan.org/source/DAGOLDEN/File-Temp-0.2304/lib/File/Temp.pm): ##### eval { rmtree($dir, $DEBUG, 0); }; # line 784 eval { rmtree($self->{REALNAME}, $File::Temp::DEBUG, 0); }; # line 1616 ##### The Perl-false value in the third positional argument means, in effect, "Do everything you can to remove all directories under the temporary directory, including directories on which you don't have normal permissions." The "everything you can" includes a lot of filesystem checks and attempts to chmod directories with fairly exotic permissions such as 0000 and 0200 -- directories on which the user lacks both 'read' and 'execute' permissions. This stands in contrast to a rmtree() call with a Perl-true value in the third parameter: ##### eval { rmtree($self->{REALNAME}, $File::Temp::DEBUG, 1); }; ##### In this case, rmtree() is documented to "skip the files for which the process lacks the required privileges needed to delete files .... In other words, the code will make no attempt to alter file permissions." (https://metacpan.org/pod/File::Path) In practice, this means that rmtree() with a true value in the third parameter will not remove a directory unless its permissions are something like 0700. Unfortunately, the hoops which rmtree() must internally jump through to remove directories regardless of permissions require many filesystem checks which introduce the possibility of "Time of Check to Time of Use" (TOCTTOU: https://en.wikipedia.org/wiki/Time_of_check_to_time_of_use) situations which, in turn, introduce security vulnerabilities due to race conditions. These vulnerabilities are sufficiently serious that a CVE has been filed and the behavior of rmtree() in File::Path versions 2.14 and later has been changed (https://rt.cpan.org/Ticket/Display.html?id=121951). (These changes have been merged into Perl 5 blead.) The practical impact of this change is that rmtree() will no longer be able to directly remove directories on which the user lacks both 'read' and 'execute' permissions. Any code, including File::Temp::tempdir(), which uses rmtree() for recursive removal of directories will be similarly affected. Hence, the need to modify File::Temp::tempdir()'s documentation. B. Example of Change in tempdir()'s Behavior and How to Address An example of such code would be: ##### my $tdir = tempdir(CLEANUP => 1); my $subdir = File::Spec->catdir($tdir, 'abc'); mkdir $subdir; chmod 0000, $subdir; ... # do something with $subdir # CLEANUP triggered, making internal call to File::Path::rmtree() ##### Up through File::Path version 2.12 -- or up through perl-5.26.0 -- 'CLEANUP' would remove $subdir even though the user no longer has any permissions on it. Very convenient, but (as discussed above) performed via filesystem checks which open up security vulnerabilities. From File::Path version 2.14 forward -- or from perl 5.27.3 forward -- the rmtree() call inside the 'CLEANUP' option will no longer remove $subdir. The user will get warnings like this: ##### cannot chdir to child for /tmp/caIID_gLXE/abc: Permission denied at /home/jkeenan/testing/blead/lib/perl5/5.27.3/File/Temp.pm line 784. cannot remove directory for /tmp/caIID_gLXE: Directory not empty at /home/jkeenan/testing/blead/lib/perl5/5.27.3/File/Temp.pm line 784. ##### Manual inspection of /tmp shows that access was denied to the second-level temporary directory, preventing its removal and the removal of its parent directory. ##### $ ls -ld /tmp/caIID_gLXE/ drwx------ 3 jkeenan jkeenan 4096 Aug 16 09:40 /tmp/caIID_gLXE/ $ ls -l /tmp/caIID_gLXE/ total 4 d--------- 2 jkeenan jkeenan 4096 Aug 16 09:40 abc $ ls -l /tmp/caIID_gLXE/abc ls: cannot open directory '/tmp/caIID_gLXE/abc': Permission denied ##### But the code sample can easily be modified to restore reasonable permissions before tempdir() triggers cleanup. Under File::Path 2.14 (and perl 5.27.3) and later, we call: ##### my $tdir = tempdir(CLEANUP => 1); my $subdir = File::Spec->catdir($tdir, 'abc'); mkdir $subdir; chmod 0000, $subdir; ... # do something with $subdir chmod 0700, $subdir; # CLEANUP triggered, making internal call to File::Path::rmtree() ##### $subdir's permissions now permit File::Path::rmtree() to remove it. This in turns permit's tempdir()'s 'CLEANUP' option to remove it. All the tempdirs are removed and the user is no longer presented with warnings. (See attachments 'tempdir-cleanup.pl' and 'adjusted-tempdir-cleanup.pl' for program exemplars.) C. Scope of Impact I anticipate that the maintainers and users of File::Temp will have concerns about this change in tempdir()'s behavior. tempdir() is undoubtedly used extensively in production code, but since we have no access to such code, we have to treat the entirety of Perl code on CPAN as a proxy. Over the past several weeks, I conducted a two-stage study to address these concerns. 1. I grepped CPAN for code which creates directories without both 'read' and 'execute' permissions or so modifies permissions. That includes code like this: ##### mkdir $some_directory, 0000 mkdir $some_directory, 0200 mkdir $some_directory, 0 chmod 0000, $some_directory, $some_other_directory chmod 0200, $some_directory, $some_other_directory chmod 0, $some_directory, $some_other_directory File::Path::mkpath('foo/bar/baz', '/zug/zwang', { verbose => 1, mode => 0000 }) File::Path::mkpath('/foo/bar/baz', 1, 0000) File::Path::make_path('foo/bar/baz', '/zug/zwang', { mode => 0000 } ##### While I make no claim that this search was exhaustive, I located enough examples to have confidence in my inferences. (See https://metacpan.org/source/JKEENAN/Regexp-Functions-chmod_et_al-0.02/t/002-chmod-et-al.t for the full list of code strings searched for). 2. I then grepped the files identified in step #1 for 'tempdir' and 'newdir'. (In File::Temp's object-oriented interface $t->newdir is equivalent to 'tempdir(CLEANUP => 1)'.) I manually inspected those files for locations where the function calls discussed in step #1 fell within the scope of tempdir(). In all of CPAN I could locate only six files which needed patching. I created and submitted patches or pull requests for all six. ##### G/GR/GRAY|IO-AIO-Util-0.09.tar.gz|t/01_mkpath.t M/MA/MANWAR|Filesys-DiskUsage-0.10.tar.gz|t/02.warnings.t P/PE/PERLANCAR|File-MoreUtil-0.61.tar.gz|t/01-basics.t P/PE/PETDANCE|ack-2.18.tar.gz|t/ack-s.t S/SN/SNOWHARE|Tie-DB_File-SplitHash-1.05.tar.gz|t/01_Tie-DB_File-SplitHash.t X/XA/XAN|Archive-Tar-Builder-2.5002.tar.gz|t/lib-Archive-Tar-Builder.t ##### I am fairly confident that this substantially addresses the problem, as six is one more than the number of problematic files separately identified by experienced CPAN tester Slaven Rezic earlier this year (https://rt.cpan.org/Ticket/Display.html?id=122255#txn-1735608). D. Next Steps I can prepare a pull request for (a) changes in File::Path's documentation discussing this change in behavior; and (b) additional unit tests illustrating the change in behavior. But I would like to get some feedback from File::Temp's maintainers first. Thank you very much. Jim Keenan (maintainer of File::Path)

Message body is not shown because sender requested not to inline it.

Message body is not shown because sender requested not to inline it.