From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: X-Spam-Status: No, score=-4.0 required=3.0 tests=ALL_TRUSTED,BAYES_00 shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id BDF021F5A1 for ; Wed, 1 Jan 2020 10:39:00 +0000 (UTC) From: Eric Wong To: meta@public-inbox.org Subject: [PATCH 5/6] wwwstatic: avoid TOCTTOU for FIFO check Date: Wed, 1 Jan 2020 10:38:58 +0000 Message-Id: <20200101103859.15401-6-e@80x24.org> In-Reply-To: <20200101103859.15401-1-e@80x24.org> References: <20200101103859.15401-1-e@80x24.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: We can use Perl's sysopen function to pass O_NONBLOCK to open(2) and avoid blocking on FIFOs. This avoids a TOCTTOU race where somebody can change a regular to FIFO in between the stat(2) and open(2) syscalls. --- lib/PublicInbox/WwwStatic.pm | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/PublicInbox/WwwStatic.pm b/lib/PublicInbox/WwwStatic.pm index 093a7920..ce4bfe9b 100644 --- a/lib/PublicInbox/WwwStatic.pm +++ b/lib/PublicInbox/WwwStatic.pm @@ -4,9 +4,10 @@ package PublicInbox::WwwStatic; use strict; use parent qw(Exporter); -use Fcntl qw(:seek); +use Fcntl qw(SEEK_SET O_RDONLY O_NONBLOCK); use HTTP::Date qw(time2str); use HTTP::Status qw(status_message); +use Errno qw(EACCES ENOTDIR ENOENT); our @EXPORT_OK = qw(@NO_CACHE r); our @NO_CACHE = ('Expires', 'Fri, 01 Jan 1980 00:00:00 GMT', @@ -70,15 +71,19 @@ sub prepare_range { sub response { my ($env, $h, $path, $type) = @_; - return r(404) unless -f $path && -r _; # just in case it's a FIFO :P - my ($size, $in); + my $in; if ($env->{REQUEST_METHOD} eq 'HEAD') { - $size = -s _; + return r(404) unless -f $path && -r _; # in case it's a FIFO :P } else { # GET, callers should've already filtered out other methods - open $in, '<', $path or return r(403); - $size = -s $in; + if (!sysopen($in, $path, O_RDONLY|O_NONBLOCK)) { + return r(404) if $! == ENOENT || $! == ENOTDIR; + return r(403) if $! == EACCES; + return r(500); + } + return r(404) unless -f $in; } + my $size = -s _; # bare "_" reuses "struct stat" from "-f" above my $mtime = time2str((stat(_))[9]); if (my $ims = $env->{HTTP_IF_MODIFIED_SINCE}) {