gnupatch: Don't fetch from cgit URLs with unstable hashes

cgit cannot serve patches with stable hashes, so store these patches
in-tree. cgit community discussion about this problem:
https://lists.zx2c4.com/pipermail/cgit/2017-February/003470.html

We pull the patches in-tree rather than strip cgit footers with fetchpatch
because per https://github.com/NixOS/nixpkgs/pull/61471#issuecomment-493218587
dependencies of fetchpatch cannot use fetchpatch.

Verification that the only difference between the live page, the
patch committed here, and the version cached under the old hash at
tarballs.nixos.org is the cgit version footer:

$ curl -s -L http://tarballs.nixos.org/sha256/"$(nix-hash --type sha256 --to-base16 0iw0lk0yhnhvfjzal48ij6zdr92mgb84jq7fwryy1hdhi47hhq64)" > Allow_input_files_to_be_missing_for_ed-style_patches.patch
$ diff -U0 --label cgit-live <( curl -s -L https://git.savannah.gnu.org/cgit/patch.git/patch/?id=b5a91a01e5d0897facdd0f49d64b76b0f02b43e1 ) Allow_input_files_to_be_missing_for_ed-style_patches.patch
--- cgit-live
+++ Allow_input_files_to_be_missing_for_ed-style_patches.patch 2020-01-29 17:22:00.077312937 -0800
@@ -32 +32 @@
-cgit v1.2.1
+cgit v1.0-41-gc330

$ curl -s -L http://tarballs.nixos.org/sha256/"$(nix-hash --type sha256 --to-base16 1bpy16n3hm5nv9xkrn6c4wglzsdzj3ss1biq16w9kfv48p4hx2vg)" > CVE-2018-1000156.patch
$ diff -U0 --label cgit-live <( curl -s -L https://git.savannah.gnu.org/cgit/patch.git/patch/?id=123eaff0d5d1aebe128295959435b9ca5909c26d ) CVE-2018-1000156.patch
--- cgit-live
+++ CVE-2018-1000156.patch 2020-01-29 17:23:41.021116969 -0800
@@ -210 +210 @@
-cgit v1.2.1
+cgit v1.0-41-gc330

Chuck 2327204a 3eb6e3f5

+251 -10
+33
pkgs/tools/text/gnupatch/Allow_input_files_to_be_missing_for_ed-style_patches.patch
···
··· 1 + From b5a91a01e5d0897facdd0f49d64b76b0f02b43e1 Mon Sep 17 00:00:00 2001 2 + From: Andreas Gruenbacher <agruen@gnu.org> 3 + Date: Fri, 6 Apr 2018 11:34:51 +0200 4 + Subject: Allow input files to be missing for ed-style patches 5 + 6 + * src/pch.c (do_ed_script): Allow input files to be missing so that new 7 + files will be created as with non-ed-style patches. 8 + --- 9 + src/pch.c | 8 +++++--- 10 + 1 file changed, 5 insertions(+), 3 deletions(-) 11 + 12 + diff --git a/src/pch.c b/src/pch.c 13 + index bc6278c..0c5cc26 100644 14 + --- a/src/pch.c 15 + +++ b/src/pch.c 16 + @@ -2394,9 +2394,11 @@ do_ed_script (char const *inname, char const *outname, 17 + 18 + if (! dry_run && ! skip_rest_of_patch) { 19 + int exclusive = *outname_needs_removal ? 0 : O_EXCL; 20 + - assert (! inerrno); 21 + - *outname_needs_removal = true; 22 + - copy_file (inname, outname, 0, exclusive, instat.st_mode, true); 23 + + if (inerrno != ENOENT) 24 + + { 25 + + *outname_needs_removal = true; 26 + + copy_file (inname, outname, 0, exclusive, instat.st_mode, true); 27 + + } 28 + sprintf (buf, "%s %s%s", editor_program, 29 + verbosity == VERBOSE ? "" : "- ", 30 + outname); 31 + -- 32 + cgit v1.0-41-gc330 33 +
+211
pkgs/tools/text/gnupatch/CVE-2018-1000156.patch
···
··· 1 + From 123eaff0d5d1aebe128295959435b9ca5909c26d Mon Sep 17 00:00:00 2001 2 + From: Andreas Gruenbacher <agruen@gnu.org> 3 + Date: Fri, 6 Apr 2018 12:14:49 +0200 4 + Subject: Fix arbitrary command execution in ed-style patches 5 + (CVE-2018-1000156) 6 + 7 + * src/pch.c (do_ed_script): Write ed script to a temporary file instead 8 + of piping it to ed: this will cause ed to abort on invalid commands 9 + instead of rejecting them and carrying on. 10 + * tests/ed-style: New test case. 11 + * tests/Makefile.am (TESTS): Add test case. 12 + --- 13 + src/pch.c | 91 ++++++++++++++++++++++++++++++++++++++++--------------- 14 + tests/Makefile.am | 1 + 15 + tests/ed-style | 41 +++++++++++++++++++++++++ 16 + 3 files changed, 108 insertions(+), 25 deletions(-) 17 + create mode 100644 tests/ed-style 18 + 19 + diff --git a/src/pch.c b/src/pch.c 20 + index 0c5cc26..4fd5a05 100644 21 + --- a/src/pch.c 22 + +++ b/src/pch.c 23 + @@ -33,6 +33,7 @@ 24 + # include <io.h> 25 + #endif 26 + #include <safe.h> 27 + +#include <sys/wait.h> 28 + 29 + #define INITHUNKMAX 125 /* initial dynamic allocation size */ 30 + 31 + @@ -2389,24 +2390,28 @@ do_ed_script (char const *inname, char const *outname, 32 + static char const editor_program[] = EDITOR_PROGRAM; 33 + 34 + file_offset beginning_of_this_line; 35 + - FILE *pipefp = 0; 36 + size_t chars_read; 37 + + FILE *tmpfp = 0; 38 + + char const *tmpname; 39 + + int tmpfd; 40 + + pid_t pid; 41 + + 42 + + if (! dry_run && ! skip_rest_of_patch) 43 + + { 44 + + /* Write ed script to a temporary file. This causes ed to abort on 45 + + invalid commands such as when line numbers or ranges exceed the 46 + + number of available lines. When ed reads from a pipe, it rejects 47 + + invalid commands and treats the next line as a new command, which 48 + + can lead to arbitrary command execution. */ 49 + + 50 + + tmpfd = make_tempfile (&tmpname, 'e', NULL, O_RDWR | O_BINARY, 0); 51 + + if (tmpfd == -1) 52 + + pfatal ("Can't create temporary file %s", quotearg (tmpname)); 53 + + tmpfp = fdopen (tmpfd, "w+b"); 54 + + if (! tmpfp) 55 + + pfatal ("Can't open stream for file %s", quotearg (tmpname)); 56 + + } 57 + 58 + - if (! dry_run && ! skip_rest_of_patch) { 59 + - int exclusive = *outname_needs_removal ? 0 : O_EXCL; 60 + - if (inerrno != ENOENT) 61 + - { 62 + - *outname_needs_removal = true; 63 + - copy_file (inname, outname, 0, exclusive, instat.st_mode, true); 64 + - } 65 + - sprintf (buf, "%s %s%s", editor_program, 66 + - verbosity == VERBOSE ? "" : "- ", 67 + - outname); 68 + - fflush (stdout); 69 + - pipefp = popen(buf, binary_transput ? "wb" : "w"); 70 + - if (!pipefp) 71 + - pfatal ("Can't open pipe to %s", quotearg (buf)); 72 + - } 73 + for (;;) { 74 + char ed_command_letter; 75 + beginning_of_this_line = file_tell (pfp); 76 + @@ -2417,14 +2422,14 @@ do_ed_script (char const *inname, char const *outname, 77 + } 78 + ed_command_letter = get_ed_command_letter (buf); 79 + if (ed_command_letter) { 80 + - if (pipefp) 81 + - if (! fwrite (buf, sizeof *buf, chars_read, pipefp)) 82 + + if (tmpfp) 83 + + if (! fwrite (buf, sizeof *buf, chars_read, tmpfp)) 84 + write_fatal (); 85 + if (ed_command_letter != 'd' && ed_command_letter != 's') { 86 + p_pass_comments_through = true; 87 + while ((chars_read = get_line ()) != 0) { 88 + - if (pipefp) 89 + - if (! fwrite (buf, sizeof *buf, chars_read, pipefp)) 90 + + if (tmpfp) 91 + + if (! fwrite (buf, sizeof *buf, chars_read, tmpfp)) 92 + write_fatal (); 93 + if (chars_read == 2 && strEQ (buf, ".\n")) 94 + break; 95 + @@ -2437,13 +2442,49 @@ do_ed_script (char const *inname, char const *outname, 96 + break; 97 + } 98 + } 99 + - if (!pipefp) 100 + + if (!tmpfp) 101 + return; 102 + - if (fwrite ("w\nq\n", sizeof (char), (size_t) 4, pipefp) == 0 103 + - || fflush (pipefp) != 0) 104 + + if (fwrite ("w\nq\n", sizeof (char), (size_t) 4, tmpfp) == 0 105 + + || fflush (tmpfp) != 0) 106 + write_fatal (); 107 + - if (pclose (pipefp) != 0) 108 + - fatal ("%s FAILED", editor_program); 109 + + 110 + + if (lseek (tmpfd, 0, SEEK_SET) == -1) 111 + + pfatal ("Can't rewind to the beginning of file %s", quotearg (tmpname)); 112 + + 113 + + if (! dry_run && ! skip_rest_of_patch) { 114 + + int exclusive = *outname_needs_removal ? 0 : O_EXCL; 115 + + *outname_needs_removal = true; 116 + + if (inerrno != ENOENT) 117 + + { 118 + + *outname_needs_removal = true; 119 + + copy_file (inname, outname, 0, exclusive, instat.st_mode, true); 120 + + } 121 + + sprintf (buf, "%s %s%s", editor_program, 122 + + verbosity == VERBOSE ? "" : "- ", 123 + + outname); 124 + + fflush (stdout); 125 + + 126 + + pid = fork(); 127 + + if (pid == -1) 128 + + pfatal ("Can't fork"); 129 + + else if (pid == 0) 130 + + { 131 + + dup2 (tmpfd, 0); 132 + + execl ("/bin/sh", "sh", "-c", buf, (char *) 0); 133 + + _exit (2); 134 + + } 135 + + else 136 + + { 137 + + int wstatus; 138 + + if (waitpid (pid, &wstatus, 0) == -1 139 + + || ! WIFEXITED (wstatus) 140 + + || WEXITSTATUS (wstatus) != 0) 141 + + fatal ("%s FAILED", editor_program); 142 + + } 143 + + } 144 + + 145 + + fclose (tmpfp); 146 + + safe_unlink (tmpname); 147 + 148 + if (ofp) 149 + { 150 + diff --git a/tests/Makefile.am b/tests/Makefile.am 151 + index 6b6df63..16f8693 100644 152 + --- a/tests/Makefile.am 153 + +++ b/tests/Makefile.am 154 + @@ -32,6 +32,7 @@ TESTS = \ 155 + crlf-handling \ 156 + dash-o-append \ 157 + deep-directories \ 158 + + ed-style \ 159 + empty-files \ 160 + false-match \ 161 + fifo \ 162 + diff --git a/tests/ed-style b/tests/ed-style 163 + new file mode 100644 164 + index 0000000..d8c0689 165 + --- /dev/null 166 + +++ b/tests/ed-style 167 + @@ -0,0 +1,41 @@ 168 + +# Copyright (C) 2018 Free Software Foundation, Inc. 169 + +# 170 + +# Copying and distribution of this file, with or without modification, 171 + +# in any medium, are permitted without royalty provided the copyright 172 + +# notice and this notice are preserved. 173 + + 174 + +. $srcdir/test-lib.sh 175 + + 176 + +require cat 177 + +use_local_patch 178 + +use_tmpdir 179 + + 180 + +# ============================================================== 181 + + 182 + +cat > ed1.diff <<EOF 183 + +0a 184 + +foo 185 + +. 186 + +EOF 187 + + 188 + +check 'patch -e foo -i ed1.diff' <<EOF 189 + +EOF 190 + + 191 + +check 'cat foo' <<EOF 192 + +foo 193 + +EOF 194 + + 195 + +cat > ed2.diff <<EOF 196 + +1337a 197 + +r !echo bar 198 + +,p 199 + +EOF 200 + + 201 + +check 'patch -e foo -i ed2.diff 2> /dev/null || echo "Status: $?"' <<EOF 202 + +? 203 + +Status: 2 204 + +EOF 205 + + 206 + +check 'cat foo' <<EOF 207 + +foo 208 + +EOF 209 + -- 210 + cgit v1.0-41-gc330 211 +
+7 -10
pkgs/tools/text/gnupatch/default.nix
··· 13 patches = [ 14 # https://git.savannah.gnu.org/cgit/patch.git/patch/?id=f290f48a621867084884bfff87f8093c15195e6a 15 ./CVE-2018-6951.patch 16 - (fetchurl { 17 - url = https://git.savannah.gnu.org/cgit/patch.git/patch/?id=b5a91a01e5d0897facdd0f49d64b76b0f02b43e1; 18 - name = "Allow_input_files_to_be_missing_for_ed-style_patches.patch"; 19 - sha256 = "0iw0lk0yhnhvfjzal48ij6zdr92mgb84jq7fwryy1hdhi47hhq64"; 20 - }) 21 - (fetchurl { 22 - url = https://git.savannah.gnu.org/cgit/patch.git/patch/?id=123eaff0d5d1aebe128295959435b9ca5909c26d; 23 - name = "CVE-2018-1000156.patch"; 24 - sha256 = "1bpy16n3hm5nv9xkrn6c4wglzsdzj3ss1biq16w9kfv48p4hx2vg"; 25 - }) 26 # https://git.savannah.gnu.org/cgit/patch.git/commit/?id=9c986353e420ead6e706262bf204d6e03322c300 27 ./CVE-2018-6952.patch 28
··· 13 patches = [ 14 # https://git.savannah.gnu.org/cgit/patch.git/patch/?id=f290f48a621867084884bfff87f8093c15195e6a 15 ./CVE-2018-6951.patch 16 + 17 + # https://git.savannah.gnu.org/cgit/patch.git/patch/?id=b5a91a01e5d0897facdd0f49d64b76b0f02b43e1 18 + ./Allow_input_files_to_be_missing_for_ed-style_patches.patch 19 + 20 + # https://git.savannah.gnu.org/cgit/patch.git/patch/?id=123eaff0d5d1aebe128295959435b9ca5909c26d 21 + ./CVE-2018-1000156.patch 22 + 23 # https://git.savannah.gnu.org/cgit/patch.git/commit/?id=9c986353e420ead6e706262bf204d6e03322c300 24 ./CVE-2018-6952.patch 25