From 4d82423dd323978153fa7c4d21d7030643dd8efa Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 18 Mar 2026 13:24:07 +0100 Subject: [PATCH] delta: harden external command invocations By moving operations Perl-native (from shell and external commands), and passing arguments individually to external commands. Pointed out by Codex Security Closes #21104 --- scripts/delta | 104 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 38 deletions(-) diff --git a/scripts/delta b/scripts/delta index 02b0232c47..7a1793dbb3 100755 --- a/scripts/delta +++ b/scripts/delta @@ -34,8 +34,20 @@ use strict; use warnings; use POSIX; +use IPC::Open3; use Time::Piece; +sub cmd { + my @out; + my $as_string = shift if($_[0] eq '$'); # return as string (vs. list of lines) + my $hideerr = shift if($_[0] eq '2>'); # 2>/dev/null + my $pid = open3(my $in, my $out, $hideerr ? '>/dev/null' : '>&STDERR', @_); + close $in; + push @out, <$out>; + waitpid($pid, 0); + return $as_string ? join('', @out) : @out; +} + my $start = $ARGV[0] || ''; if($start eq "-h") { @@ -43,76 +55,92 @@ if($start eq "-h") { exit; } elsif($start eq "") { - $start = `git tag --sort=taggerdate | grep "^curl-" | tail -1`; + my @tags = cmd('git', 'tag', '--sort=taggerdate', '--no-column', '--list', 'curl-*'); + $start = $tags[-1]; # latest tag chomp $start; } -my $commits = `git log --oneline $start.. | wc -l`; -my $committers = `git shortlog -s $start.. | wc -l`; -my $bcommitters = `git shortlog -s $start | wc -l`; +my $commits = cmd('$', 'git', 'rev-list', '--count', "$start.."); +my $committers = cmd('git', 'shortlog', '-s', "$start.."); +my $bcommitters = cmd('git', 'shortlog', '-s', $start); -my $acommits = `git log --oneline | wc -l`; -my $acommitters = `git shortlog -s | wc -l`; +my $acommits = cmd('$', 'git', 'rev-list', '--count', 'HEAD'); +my $acommitters = cmd('git', 'shortlog', '-s', 'HEAD'); # delta from now compared to before my $ncommitters = $acommitters - $bcommitters; # number of contributors right now -my $acontribs = `./scripts/contrithanks.sh stdout | wc -l`; +my $acontribs = cmd('./scripts/contrithanks.sh', 'stdout'); # number when the tag was set -my $bcontribs = `git show $start:docs/THANKS | grep -c '^[^ ]'`; +my $bcontribs = cmd('$', 'git', 'grep', '-h', '-c', '^[^ ]', "$start:docs/THANKS"); # delta my $contribs = $acontribs - $bcontribs; # number of setops: sub setopts { - my ($f)=@_; - open(H, $f); - my $opts; - while() { - if(/^ CURLOPT(|DEPRECATED)\(/ && ($_ !~ /OBSOLETE/)) { - $opts++; + my $mode = shift; + my $opts = 0; + if(open(H, $mode, @_)) { + while() { + if(/^ CURLOPT(|DEPRECATED)\(/ && ($_ !~ /OBSOLETE/)) { + $opts++; + } } + close(H); + } + else { + die join(' ', @_) . ": $!\n"; } - close(H); return $opts; } -my $asetopts = setopts("/dev/null | grep -c '{ *"....--'`; -my $noptions=$aoptions - $boptions; +my $aoptions = cmd('$', '2>', 'git', 'grep', '-h', '-c', '{ *"....--', 'src/tool_listhelp.c'); +my $boptions = cmd('$', '2>', 'git', 'grep', '-h', '-c', '{ *"....--', "$start:src/tool_listhelp.c"); +my $noptions = $aoptions - $boptions; # current local branch -my $branch=`git rev-parse --abbrev-ref HEAD 2>/dev/null`; +my $branch = cmd('$', '2>', 'git', 'rev-parse', '--abbrev-ref', 'HEAD'); chomp $branch; # Number of files in git -my $afiles=`git ls-files | wc -l`; -my $deletes=`git diff-tree --diff-filter=A -r --summary origin/$branch $start 2>/dev/null | wc -l`; -my $creates=`git diff-tree --diff-filter=D -r --summary origin/$branch $start 2>/dev/null | wc -l`; +my $afiles = cmd('git', 'ls-files'); +my $deletes = cmd('2>', 'git', 'diff-tree', '--diff-filter=A', '-r', '--summary', "origin/$branch", $start); +my $creates = cmd('2>', 'git', 'diff-tree', '--diff-filter=D', '-r', '--summary', "origin/$branch", $start); # Time since that tag -my $tagged=`git for-each-ref --format="%(refname:short) | %(taggerdate:unix)" refs/tags/* | grep ^$start | cut '-d|' -f2`; # Unix timestamp -my $taggednice=`git for-each-ref --format="%(refname:short) | %(creatordate)" refs/tags/* | grep ^$start | cut '-d|' -f2`; # human readable time +sub tagstamp { + my $col = shift; + foreach my $line (@_) { + if(index($line, "$start ") == 0) { + my @cols = split(/\|/, $line); + return $cols[$col - 1]; + } + } +} +my @tagged = cmd('git', 'for-each-ref', '--format=%(refname:short) | %(taggerdate:unix) | %(creatordate)', 'refs/tags/*'); +my $tagged = tagstamp(2, @tagged); # Unix timestamp +my $taggednice = tagstamp(3, @tagged); # human readable time chomp $taggednice; -my $now=POSIX::strftime("%s", localtime()); -my $elapsed=$now - $tagged; # number of seconds since tag -my $total=$now - Time::Piece->strptime('19980320', '%Y%m%d')->epoch; -my $totalhttpget=$now - Time::Piece->strptime('19961111', '%Y%m%d')->epoch; +my $now = POSIX::strftime("%s", localtime()); +my $elapsed = $now - $tagged; # number of seconds since tag +my $total = $now - Time::Piece->strptime('19980320', '%Y%m%d')->epoch; +my $totalhttpget = $now - Time::Piece->strptime('19961111', '%Y%m%d')->epoch; # Number of public functions in libcurl -my $apublic=`git grep ^CURL_EXTERN -- include/curl | wc -l`; -my $bpublic=`git grep ^CURL_EXTERN $start -- include/curl | wc -l`; +my $apublic = cmd('git', 'grep', '^CURL_EXTERN', '--', 'include/curl'); +my $bpublic = cmd('git', 'grep', '^CURL_EXTERN', $start, '--', 'include/curl'); my $public = $apublic - $bpublic; # diffstat my ($fileschanged, $insertions, $deletions); -my $diffstat=`git diff --stat $start.. | tail -1`; +my @diffstat = cmd('2>', 'git', 'diff', '--stat', "$start.."); +my $diffstat = $diffstat[-1]; if($diffstat =~ /^ *(\d+) files changed, (\d+) insertions\(\+\), (\d+)/) { - ($fileschanged, $insertions, $deletions)=($1, $2, $3); + ($fileschanged, $insertions, $deletions) = ($1, $2, $3); } # Changes/bug-fixes currently logged @@ -123,16 +151,16 @@ open(F, ") { if($_ =~ /following changes:/) { - $mode=1; + $mode = 1; } elsif($_ =~ /following bugfixes:/) { - $mode=2; + $mode = 2; } elsif($_ =~ /known bugs:/) { - $mode=3; + $mode = 3; } elsif($_ =~ /like these:/) { - $mode=4; + $mode = 4; } if($_ =~ /^ o /) { if($mode == 1) {