From ebd74eaecae25cb4bbfae1e73e80b41d3300169a Mon Sep 17 00:00:00 2001 From: Graham Knop Date: Fri, 3 Mar 2017 18:51:34 +0100 Subject: [PATCH] parse versions statically when possible Parsing version declarations by evaling them is rather unsafe. It would be possible to improve the security of that by using a Safe container. However, Safe.pm is an XS module and EUMM has to work without any XS. Safe.pm should only be unavailable when compiling perl core though, and the versions in perl core can all be parsed statically. This patch takes the first step, by implementing static parsing of versions that follow common patterns. Later, a Safe container can be added for the eval code path. We may consider adding a warning for the more exotic forms, and possibly rejecting them in the far future. --- lib/ExtUtils/MM_Unix.pm | 71 +++++++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/lib/ExtUtils/MM_Unix.pm b/lib/ExtUtils/MM_Unix.pm index 21abeb8fa..e7376513c 100644 --- a/lib/ExtUtils/MM_Unix.pm +++ b/lib/ExtUtils/MM_Unix.pm @@ -2904,23 +2904,66 @@ sub parse_version { return $result; } +my $v = qr{v?[0-9._]+}; +my $_quoted_version = qr{ + \s* + (?: + (['"]?) ($v) \1 + | qq? \s* (?: + | ([^\s\w]) ($v) \3 + | \s ([\w]) ($v) \5 + | \( ($v) \) + | \< ($v) \> + | \[ ($v) \] + | \{ ($v) \} + ) + ) + \s* +}x; + sub get_version { my ($self, $parsefile, $sigil, $name) = @_; my $line = $_; # from the while() loop in parse_version - { - package ExtUtils::MakeMaker::_version; - undef *version; # in case of unexpected version() sub - eval { - require version; - version::->import; - }; - no strict; - local *{$name}; - local $^W = 0; - $line = $1 if $line =~ m{^(.+)}s; - eval($line); ## no critic - return ${$name}; - } + + if ($line =~ m{^\s* + (use \s+ version \s* ;)? + \s* (?:our)? \s* \Q${sigil}${name}\E \s* = (.+?) (?:;|$) + }x) { + my ($used_version, $assign) = ($1, $2); + my @match; + @match = $assign =~ m{^$_quoted_version$} + or @match = $assign =~ m{^\s* + version (?: ::qv | ->(?:parse|declare) ) \s* \( + $_quoted_version + \) \s* + $}x + or $used_version && (@match = $assign =~ m{^\s* + qv \s* \( + $_quoted_version + \) \s* + $}x); + # there will be either one or two defined matches. if there are two, + # the first is the quote character + return $_ for grep defined, reverse @match; + } + + $self->_eval_version($parsefile, $sigil, $name, $line); +} + +sub _eval_version { + my ($self, $parsefile, $sigil, $name, $line) = @_; + package ExtUtils::MakeMaker::_version; + undef *version; # in case of unexpected version() sub + eval { + require version; + version::->import; + }; + no strict; + local *{$name}; + local $^W = 0; + $line = $1 if $line =~ m{^(.+)}s; + eval($line); ## no critic + return ${$name}; } =item pasthru (o)