From 241989d49b5aedcde4bfd2a5e8257ea2c2fd252b Mon Sep 17 00:00:00 2001 From: Olaf Alders Date: Thu, 14 May 2026 00:09:58 +0000 Subject: [PATCH] Fix CVE-2026-8450: send_file() honoured 2-arg open() shell-magic HTTP::Daemon::ClientConn::send_file() used the 2-arg form open(FILE, $file), which interprets shell-magic prefixes in the path argument: '| cmd' (write pipe -- RCE), 'cmd |' (read pipe -- RCE plus response-body exfiltration via the sysread / print loop below), '> path' (write-truncate -- arbitrary file write), and '>> path', '+< path', '<&fd', and leading-whitespace variants of the above. Any HTTP::Daemon-based application that passed attacker-influenced bytes to send_file($string) -- for example, a download endpoint that derived the filename from a query parameter -- granted command execution and/or arbitrary file write at the daemon's UID. Switch to 3-arg open(my $fh, '<', $file): the explicit '<' mode makes the path argument a literal filename, so every magic shape above is opened (and fails, returning undef) as an ordinary file by that exact name. The localized typeglob is no longer needed and is replaced with a lexical filehandle. Two collateral hardening changes ride along: - binmode() failure now closes the handle and returns undef, rather than streaming the file with a wrong PerlIO layer. - send_file() returns '0E0' (true zero) on a successful zero-byte transfer so callers using "send_file or die" can distinguish open failure (undef) from an empty-but-successful copy. The POD now documents the new return-value contract and spells out that the fix only neutralises 2-arg open() shell-magic; callers remain responsible for validating attacker-influenced paths against symlinks, character/block devices (e.g. /dev/zero), named pipes, and document-root escapes. Reported and patched by Stig Palmquist (stigtsp). Co-Authored-By: Claude Opus 4.7 --- lib/HTTP/Daemon.pm | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/lib/HTTP/Daemon.pm b/lib/HTTP/Daemon.pm index 216c73f..a715159 100644 --- a/lib/HTTP/Daemon.pm +++ b/lib/HTTP/Daemon.pm @@ -617,12 +617,11 @@ sub send_file { my($self, $file) = @_; my $opened = 0; - local(*FILE); if (!ref($file)) { - open(FILE, $file) || return undef; - binmode(FILE); - $file = \*FILE; - $opened++; + open(my $fh, '<', $file) || return undef; + binmode($fh) || do { close($fh); return undef }; + $file = $fh; + $opened++; } my $cnt = 0; my $buf = ""; @@ -633,7 +632,11 @@ sub send_file print $self $buf; } close($file) if $opened; - $cnt; + + # Return a "true zero" for empty-but-successful copies so callers + # using `send_file or die` can distinguish open failure (undef) + # from a successful zero-byte transfer. + $cnt || '0E0'; } @@ -917,6 +920,28 @@ Copy the file to the client. The file can be a string (which will be interpreted as a filename) or a reference to an C or glob. +Returns the number of bytes copied on success, or C if the +filename form failed to open. An empty file returns the string +C<'0E0'> (zero numerically, true in boolean context) so that callers +using C<< send_file or die >> can distinguish open failure from a +successful zero-byte transfer. + +The filename form uses Perl's 3-argument C with an explicit C<< +< >> mode, so the path is no longer interpreted as a 2-argument +C shell-magic shape such as C<< | cmd >>, C<< cmd | >>, or +C<< > path >>. See +L for +the prior 2-argument C behaviour this replaces. + +Note that this fix only neutralises 2-argument C shell-magic. +Callers remain responsible for validating attacker-influenced paths: +C will still happily open symlinks, character/block devices +(e.g. C, C), named pipes (which may block the +worker), and files outside an intended document root. If C<$filename> +can be derived from request input, validate it (canonicalise, reject +C<..> segments, require C<-f _> and a vetted prefix) before passing it +in. + =item $c->daemon Return a reference to the corresponding C object. -- 2.52.0