]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
delta: harden external command invocations
authorViktor Szakats <commit@vsz.me>
Wed, 18 Mar 2026 12:24:07 +0000 (13:24 +0100)
committerViktor Szakats <commit@vsz.me>
Mon, 11 May 2026 20:05:29 +0000 (22:05 +0200)
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

index 02b0232c47c23ed6ab39d70ced64e845fe86874e..7a1793dbb36a013c5de6d9b5bccd36793bab24a6 100755 (executable)
@@ -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(<H>) {
-        if(/^  CURLOPT(|DEPRECATED)\(/ && ($_ !~ /OBSOLETE/)) {
-            $opts++;
+    my $mode = shift;
+    my $opts = 0;
+    if(open(H, $mode, @_)) {
+        while(<H>) {
+            if(/^  CURLOPT(|DEPRECATED)\(/ && ($_ !~ /OBSOLETE/)) {
+                $opts++;
+            }
         }
+        close(H);
+    }
+    else {
+        die join(' ', @_) . ": $!\n";
     }
-    close(H);
     return $opts;
 }
-my $asetopts = setopts("<include/curl/curl.h");
-my $bsetopts = setopts("git show $start:include/curl/curl.h|");
+my $asetopts = setopts('<', 'include/curl/curl.h');
+my $bsetopts = setopts('-|', 'git', 'show', "$start:include/curl/curl.h");
 my $nsetopts = $asetopts - $bsetopts;
 
 # Number of command line options:
-my $aoptions=`grep -c '{ *"....--' src/tool_listhelp.c`;
-my $boptions=`git show $start:src/tool_listhelp.c 2>/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, "<RELEASE-NOTES");
 my $mode = 0;
 while(<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) {