summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--patches/0001-Avoid-set_file_attributes-sign-conversion-warnings.patch29
-rw-r--r--patches/0002-Test-suite-compatibility-fixes.patch132
-rw-r--r--patches/0003-Test-suite-fix-Korn-shell-incompatibility.patch26
-rw-r--r--patches/0004-Fix-segfault-with-mangled-rename-patch.patch29
-rw-r--r--patches/0005-Allow-input-files-to-be-missing-for-ed-style-patches.patch33
-rw-r--r--patches/0006-Fix-arbitrary-command-execution-in-ed-style-patches-.patch211
-rw-r--r--patches/0007-Invoke-ed-directly-instead-of-using-the-shell.patch38
-rw-r--r--patches/0008-Use-gnulib-execute-module.patch114
-rw-r--r--patches/0009-Minor-cleanups-in-do_ed_script.patch96
-rw-r--r--patches/0010-maint-avoid-warnings-from-GCC8.patch92
-rw-r--r--patches/0011-Fix-check-of-return-value-of-fwrite.patch81
-rw-r--r--patches/0012-Fix-ed-style-test-failure.patch27
-rw-r--r--patches/0013-Request-alloca-module-from-gnulib.patch33
-rw-r--r--patches/0014-Don-t-leak-temporary-file-on-failed-ed-style-patch.patch102
-rw-r--r--patches/0015-Don-t-leak-temporary-file-on-failed-multi-file-ed-st.patch78
-rw-r--r--patches/0016-Make-the-debug-2-output-more-useful.patch45
-rw-r--r--patches/0017-Fix-swapping-fake-lines-in-pch_swap.patch30
17 files changed, 1196 insertions, 0 deletions
diff --git a/patches/0001-Avoid-set_file_attributes-sign-conversion-warnings.patch b/patches/0001-Avoid-set_file_attributes-sign-conversion-warnings.patch
new file mode 100644
index 0000000..d9931c6
--- /dev/null
+++ b/patches/0001-Avoid-set_file_attributes-sign-conversion-warnings.patch
@@ -0,0 +1,29 @@
+From 3bbebbb29f6fbbf2988b9f2e75695b7c0b1f1c9b Mon Sep 17 00:00:00 2001
+From: Andreas Gruenbacher <agruen@gnu.org>
+Date: Wed, 7 Feb 2018 12:01:22 +0100
+Subject: [PATCH 01/17] Avoid set_file_attributes sign conversion warnings
+
+* src/util.c (set_file_attributes): Avoid sign conversion warnings when
+assigning -1 to uid_t / gid_t.
+---
+ src/util.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/util.c b/src/util.c
+index b1c7266..1cc08ba 100644
+--- a/src/util.c
++++ b/src/util.c
+@@ -256,8 +256,8 @@ set_file_attributes (char const *to, enum file_attributes attr,
+ }
+ if (attr & FA_IDS)
+ {
+- static uid_t euid = -1;
+- static gid_t egid = -1;
++ static uid_t euid = (uid_t)-1;
++ static gid_t egid = (gid_t)-1;
+ uid_t uid;
+ uid_t gid;
+
+--
+2.11.0
+
diff --git a/patches/0002-Test-suite-compatibility-fixes.patch b/patches/0002-Test-suite-compatibility-fixes.patch
new file mode 100644
index 0000000..425deb5
--- /dev/null
+++ b/patches/0002-Test-suite-compatibility-fixes.patch
@@ -0,0 +1,132 @@
+From f6bc5b14bd193859851d15a049bafb1007acd288 Mon Sep 17 00:00:00 2001
+From: Andreas Gruenbacher <agruen@gnu.org>
+Date: Wed, 7 Feb 2018 12:10:41 +0100
+Subject: [PATCH 02/17] Test suite compatibility fixes
+
+* tests/crlf-handling, tests/git-cleanup, tests/test-lib.sh: Use printf
+instead of echo -e / echo -n for compatibility with systems that don't
+support these echo options.
+* tests/merge: Minor other cleanups.
+---
+ tests/crlf-handling | 2 +-
+ tests/git-cleanup | 4 ++--
+ tests/merge | 18 ++++++++----------
+ tests/test-lib.sh | 21 +++++++--------------
+ 4 files changed, 18 insertions(+), 27 deletions(-)
+
+diff --git a/tests/crlf-handling b/tests/crlf-handling
+index 239149c..c192cac 100644
+--- a/tests/crlf-handling
++++ b/tests/crlf-handling
+@@ -14,7 +14,7 @@ use_local_patch
+ use_tmpdir
+
+ lf2crlf() {
+- while read l; do echo -e "$l\r"; done
++ while read l; do printf "%s\r\n" "$l"; done
+ }
+
+ echo 1 > a
+diff --git a/tests/git-cleanup b/tests/git-cleanup
+index 2e3e4c6..ca527a1 100644
+--- a/tests/git-cleanup
++++ b/tests/git-cleanup
+@@ -36,8 +36,8 @@ BAD PATCH
+ EOF
+
+ echo 1 > f
+-echo -n '' > g
+-echo -n '' > h
++printf '' > g
++printf '' > h
+
+ check 'patch -f -i 1.diff || echo status: $?' <<EOF
+ patching file f
+diff --git a/tests/merge b/tests/merge
+index 22d787b..b628891 100644
+--- a/tests/merge
++++ b/tests/merge
+@@ -30,30 +30,28 @@ x2() {
+ while test $# -gt 0 && test "$1" != -- ; do
+ echo "$1"
+ shift
+- done > a.sed
+- echo "$body" | sed -f a.sed > b
++ done > b.sed
++ echo "$body" | sed -f b.sed > b
+ shift
+ while test $# -gt 0 ; do
+ echo "$1"
+ shift
+- done > b.sed
+- echo "$body" | sed -f b.sed > c
+- rm -f a.sed b.sed
++ done > c.sed
++ echo "$body" | sed -f c.sed > c
++ rm -f b.sed c.sed
+ output=`diff -u a b | patch $ARGS -f c`
+ status=$?
+ echo "$output" | sed -e '/^$/d' -e '/^patching file c$/d'
+ cat c
+- test $status == 0 || echo "Status: $status"
++ test $status = 0 || echo "Status: $status"
+ }
+
+ x() {
+- ARGS="$ARGS --merge" x2 "$@"
++ ARGS="--merge" x2 "$@"
+ echo
+- ARGS="$ARGS --merge=diff3" x2 "$@"
++ ARGS="--merge=diff3" x2 "$@"
+ }
+
+-unset ARGS
+-
+ # ==============================================================
+
+ check 'x 3' <<EOF
+diff --git a/tests/test-lib.sh b/tests/test-lib.sh
+index be0d7e3..661da52 100644
+--- a/tests/test-lib.sh
++++ b/tests/test-lib.sh
+@@ -41,7 +41,7 @@ use_local_patch() {
+
+ eval 'patch() {
+ if test -n "$GDB" ; then
+- echo -e "\n" >&3
++ printf "\n\n" >&3
+ gdbserver localhost:53153 $PATCH "$@" 2>&3
+ else
+ $PATCH "$@"
+@@ -113,22 +113,15 @@ cleanup() {
+ exit $status
+ }
+
+-if test -z "`echo -n`"; then
+- if eval 'test -n "${BASH_LINENO[0]}" 2>/dev/null'; then
+- eval '
+- _start_test() {
+- echo -n "[${BASH_LINENO[2]}] $* -- "
+- }'
+- else
+- eval '
+- _start_test() {
+- echo -n "* $* -- "
+- }'
+- fi
++if eval 'test -n "${BASH_LINENO[0]}" 2>/dev/null'; then
++ eval '
++ _start_test() {
++ printf "[${BASH_LINENO[2]}] %s -- " "$*"
++ }'
+ else
+ eval '
+ _start_test() {
+- echo "* $*"
++ printf "* %s -- " "$*"
+ }'
+ fi
+
+--
+2.11.0
+
diff --git a/patches/0003-Test-suite-fix-Korn-shell-incompatibility.patch b/patches/0003-Test-suite-fix-Korn-shell-incompatibility.patch
new file mode 100644
index 0000000..49c37db
--- /dev/null
+++ b/patches/0003-Test-suite-fix-Korn-shell-incompatibility.patch
@@ -0,0 +1,26 @@
+From 074e2395f81d0ecaa66b71a6c228c70b49db72e5 Mon Sep 17 00:00:00 2001
+From: Andreas Gruenbacher <agruen@gnu.org>
+Date: Wed, 7 Feb 2018 17:05:00 +0100
+Subject: [PATCH 03/17] Test suite: fix Korn shell incompatibility
+
+tests/merge: In a Korn shell, shift apparently fails when $# is 0.
+---
+ tests/merge | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/tests/merge b/tests/merge
+index b628891..e950b92 100644
+--- a/tests/merge
++++ b/tests/merge
+@@ -32,7 +32,7 @@ x2() {
+ shift
+ done > b.sed
+ echo "$body" | sed -f b.sed > b
+- shift
++ test $# -eq 0 || shift
+ while test $# -gt 0 ; do
+ echo "$1"
+ shift
+--
+2.11.0
+
diff --git a/patches/0004-Fix-segfault-with-mangled-rename-patch.patch b/patches/0004-Fix-segfault-with-mangled-rename-patch.patch
new file mode 100644
index 0000000..c82df27
--- /dev/null
+++ b/patches/0004-Fix-segfault-with-mangled-rename-patch.patch
@@ -0,0 +1,29 @@
+From f290f48a621867084884bfff87f8093c15195e6a Mon Sep 17 00:00:00 2001
+From: Andreas Gruenbacher <agruen@gnu.org>
+Date: Mon, 12 Feb 2018 16:48:24 +0100
+Subject: [PATCH 04/17] Fix segfault with mangled rename patch
+
+http://savannah.gnu.org/bugs/?53132
+* src/pch.c (intuit_diff_type): Ensure that two filenames are specified
+for renames and copies (fix the existing check).
+---
+ src/pch.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/src/pch.c b/src/pch.c
+index ff9ed2c..bc6278c 100644
+--- a/src/pch.c
++++ b/src/pch.c
+@@ -974,7 +974,8 @@ intuit_diff_type (bool need_header, mode_t *p_file_type)
+ if ((pch_rename () || pch_copy ())
+ && ! inname
+ && ! ((i == OLD || i == NEW) &&
+- p_name[! reverse] &&
++ p_name[reverse] && p_name[! reverse] &&
++ name_is_valid (p_name[reverse]) &&
+ name_is_valid (p_name[! reverse])))
+ {
+ say ("Cannot %s file without two valid file names\n", pch_rename () ? "rename" : "copy");
+--
+2.11.0
+
diff --git a/patches/0005-Allow-input-files-to-be-missing-for-ed-style-patches.patch b/patches/0005-Allow-input-files-to-be-missing-for-ed-style-patches.patch
new file mode 100644
index 0000000..c58d80f
--- /dev/null
+++ b/patches/0005-Allow-input-files-to-be-missing-for-ed-style-patches.patch
@@ -0,0 +1,33 @@
+From b5a91a01e5d0897facdd0f49d64b76b0f02b43e1 Mon Sep 17 00:00:00 2001
+From: Andreas Gruenbacher <agruen@gnu.org>
+Date: Fri, 6 Apr 2018 11:34:51 +0200
+Subject: [PATCH 05/17] Allow input files to be missing for ed-style patches
+
+* src/pch.c (do_ed_script): Allow input files to be missing so that new
+files will be created as with non-ed-style patches.
+---
+ src/pch.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/src/pch.c b/src/pch.c
+index bc6278c..0c5cc26 100644
+--- a/src/pch.c
++++ b/src/pch.c
+@@ -2394,9 +2394,11 @@ do_ed_script (char const *inname, char const *outname,
+
+ if (! dry_run && ! skip_rest_of_patch) {
+ int exclusive = *outname_needs_removal ? 0 : O_EXCL;
+- assert (! inerrno);
+- *outname_needs_removal = true;
+- copy_file (inname, outname, 0, exclusive, instat.st_mode, true);
++ if (inerrno != ENOENT)
++ {
++ *outname_needs_removal = true;
++ copy_file (inname, outname, 0, exclusive, instat.st_mode, true);
++ }
+ sprintf (buf, "%s %s%s", editor_program,
+ verbosity == VERBOSE ? "" : "- ",
+ outname);
+--
+2.11.0
+
diff --git a/patches/0006-Fix-arbitrary-command-execution-in-ed-style-patches-.patch b/patches/0006-Fix-arbitrary-command-execution-in-ed-style-patches-.patch
new file mode 100644
index 0000000..900484a
--- /dev/null
+++ b/patches/0006-Fix-arbitrary-command-execution-in-ed-style-patches-.patch
@@ -0,0 +1,211 @@
+From 123eaff0d5d1aebe128295959435b9ca5909c26d Mon Sep 17 00:00:00 2001
+From: Andreas Gruenbacher <agruen@gnu.org>
+Date: Fri, 6 Apr 2018 12:14:49 +0200
+Subject: [PATCH 06/17] Fix arbitrary command execution in ed-style patches
+ (CVE-2018-1000156)
+
+* src/pch.c (do_ed_script): Write ed script to a temporary file instead
+of piping it to ed: this will cause ed to abort on invalid commands
+instead of rejecting them and carrying on.
+* tests/ed-style: New test case.
+* tests/Makefile.am (TESTS): Add test case.
+---
+ src/pch.c | 91 ++++++++++++++++++++++++++++++++++++++++---------------
+ tests/Makefile.am | 1 +
+ tests/ed-style | 41 +++++++++++++++++++++++++
+ 3 files changed, 108 insertions(+), 25 deletions(-)
+ create mode 100644 tests/ed-style
+
+diff --git a/src/pch.c b/src/pch.c
+index 0c5cc26..4fd5a05 100644
+--- a/src/pch.c
++++ b/src/pch.c
+@@ -33,6 +33,7 @@
+ # include <io.h>
+ #endif
+ #include <safe.h>
++#include <sys/wait.h>
+
+ #define INITHUNKMAX 125 /* initial dynamic allocation size */
+
+@@ -2389,24 +2390,28 @@ do_ed_script (char const *inname, char const *outname,
+ static char const editor_program[] = EDITOR_PROGRAM;
+
+ file_offset beginning_of_this_line;
+- FILE *pipefp = 0;
+ size_t chars_read;
++ FILE *tmpfp = 0;
++ char const *tmpname;
++ int tmpfd;
++ pid_t pid;
++
++ if (! dry_run && ! skip_rest_of_patch)
++ {
++ /* Write ed script to a temporary file. This causes ed to abort on
++ invalid commands such as when line numbers or ranges exceed the
++ number of available lines. When ed reads from a pipe, it rejects
++ invalid commands and treats the next line as a new command, which
++ can lead to arbitrary command execution. */
++
++ tmpfd = make_tempfile (&tmpname, 'e', NULL, O_RDWR | O_BINARY, 0);
++ if (tmpfd == -1)
++ pfatal ("Can't create temporary file %s", quotearg (tmpname));
++ tmpfp = fdopen (tmpfd, "w+b");
++ if (! tmpfp)
++ pfatal ("Can't open stream for file %s", quotearg (tmpname));
++ }
+
+- if (! dry_run && ! skip_rest_of_patch) {
+- int exclusive = *outname_needs_removal ? 0 : O_EXCL;
+- if (inerrno != ENOENT)
+- {
+- *outname_needs_removal = true;
+- copy_file (inname, outname, 0, exclusive, instat.st_mode, true);
+- }
+- sprintf (buf, "%s %s%s", editor_program,
+- verbosity == VERBOSE ? "" : "- ",
+- outname);
+- fflush (stdout);
+- pipefp = popen(buf, binary_transput ? "wb" : "w");
+- if (!pipefp)
+- pfatal ("Can't open pipe to %s", quotearg (buf));
+- }
+ for (;;) {
+ char ed_command_letter;
+ beginning_of_this_line = file_tell (pfp);
+@@ -2417,14 +2422,14 @@ do_ed_script (char const *inname, char const *outname,
+ }
+ ed_command_letter = get_ed_command_letter (buf);
+ if (ed_command_letter) {
+- if (pipefp)
+- if (! fwrite (buf, sizeof *buf, chars_read, pipefp))
++ if (tmpfp)
++ if (! fwrite (buf, sizeof *buf, chars_read, tmpfp))
+ write_fatal ();
+ if (ed_command_letter != 'd' && ed_command_letter != 's') {
+ p_pass_comments_through = true;
+ while ((chars_read = get_line ()) != 0) {
+- if (pipefp)
+- if (! fwrite (buf, sizeof *buf, chars_read, pipefp))
++ if (tmpfp)
++ if (! fwrite (buf, sizeof *buf, chars_read, tmpfp))
+ write_fatal ();
+ if (chars_read == 2 && strEQ (buf, ".\n"))
+ break;
+@@ -2437,13 +2442,49 @@ do_ed_script (char const *inname, char const *outname,
+ break;
+ }
+ }
+- if (!pipefp)
++ if (!tmpfp)
+ return;
+- if (fwrite ("w\nq\n", sizeof (char), (size_t) 4, pipefp) == 0
+- || fflush (pipefp) != 0)
++ if (fwrite ("w\nq\n", sizeof (char), (size_t) 4, tmpfp) == 0
++ || fflush (tmpfp) != 0)
+ write_fatal ();
+- if (pclose (pipefp) != 0)
+- fatal ("%s FAILED", editor_program);
++
++ if (lseek (tmpfd, 0, SEEK_SET) == -1)
++ pfatal ("Can't rewind to the beginning of file %s", quotearg (tmpname));
++
++ if (! dry_run && ! skip_rest_of_patch) {
++ int exclusive = *outname_needs_removal ? 0 : O_EXCL;
++ *outname_needs_removal = true;
++ if (inerrno != ENOENT)
++ {
++ *outname_needs_removal = true;
++ copy_file (inname, outname, 0, exclusive, instat.st_mode, true);
++ }
++ sprintf (buf, "%s %s%s", editor_program,
++ verbosity == VERBOSE ? "" : "- ",
++ outname);
++ fflush (stdout);
++
++ pid = fork();
++ if (pid == -1)
++ pfatal ("Can't fork");
++ else if (pid == 0)
++ {
++ dup2 (tmpfd, 0);
++ execl ("/bin/sh", "sh", "-c", buf, (char *) 0);
++ _exit (2);
++ }
++ else
++ {
++ int wstatus;
++ if (waitpid (pid, &wstatus, 0) == -1
++ || ! WIFEXITED (wstatus)
++ || WEXITSTATUS (wstatus) != 0)
++ fatal ("%s FAILED", editor_program);
++ }
++ }
++
++ fclose (tmpfp);
++ safe_unlink (tmpname);
+
+ if (ofp)
+ {
+diff --git a/tests/Makefile.am b/tests/Makefile.am
+index 6b6df63..16f8693 100644
+--- a/tests/Makefile.am
++++ b/tests/Makefile.am
+@@ -32,6 +32,7 @@ TESTS = \
+ crlf-handling \
+ dash-o-append \
+ deep-directories \
++ ed-style \
+ empty-files \
+ false-match \
+ fifo \
+diff --git a/tests/ed-style b/tests/ed-style
+new file mode 100644
+index 0000000..d8c0689
+--- /dev/null
++++ b/tests/ed-style
+@@ -0,0 +1,41 @@
++# Copyright (C) 2018 Free Software Foundation, Inc.
++#
++# Copying and distribution of this file, with or without modification,
++# in any medium, are permitted without royalty provided the copyright
++# notice and this notice are preserved.
++
++. $srcdir/test-lib.sh
++
++require cat
++use_local_patch
++use_tmpdir
++
++# ==============================================================
++
++cat > ed1.diff <<EOF
++0a
++foo
++.
++EOF
++
++check 'patch -e foo -i ed1.diff' <<EOF
++EOF
++
++check 'cat foo' <<EOF
++foo
++EOF
++
++cat > ed2.diff <<EOF
++1337a
++r !echo bar
++,p
++EOF
++
++check 'patch -e foo -i ed2.diff 2> /dev/null || echo "Status: $?"' <<EOF
++?
++Status: 2
++EOF
++
++check 'cat foo' <<EOF
++foo
++EOF
+--
+2.11.0
+
diff --git a/patches/0007-Invoke-ed-directly-instead-of-using-the-shell.patch b/patches/0007-Invoke-ed-directly-instead-of-using-the-shell.patch
new file mode 100644
index 0000000..4e04c85
--- /dev/null
+++ b/patches/0007-Invoke-ed-directly-instead-of-using-the-shell.patch
@@ -0,0 +1,38 @@
+From 3fcd042d26d70856e826a42b5f93dc4854d80bf0 Mon Sep 17 00:00:00 2001
+From: Andreas Gruenbacher <agruen@gnu.org>
+Date: Fri, 6 Apr 2018 19:36:15 +0200
+Subject: [PATCH 07/17] Invoke ed directly instead of using the shell
+
+* src/pch.c (do_ed_script): Invoke ed directly instead of using a shell
+command to avoid quoting vulnerabilities.
+---
+ src/pch.c | 6 ++----
+ 1 file changed, 2 insertions(+), 4 deletions(-)
+
+diff --git a/src/pch.c b/src/pch.c
+index 4fd5a05..16e001a 100644
+--- a/src/pch.c
++++ b/src/pch.c
+@@ -2459,9 +2459,6 @@ do_ed_script (char const *inname, char const *outname,
+ *outname_needs_removal = true;
+ copy_file (inname, outname, 0, exclusive, instat.st_mode, true);
+ }
+- sprintf (buf, "%s %s%s", editor_program,
+- verbosity == VERBOSE ? "" : "- ",
+- outname);
+ fflush (stdout);
+
+ pid = fork();
+@@ -2470,7 +2467,8 @@ do_ed_script (char const *inname, char const *outname,
+ else if (pid == 0)
+ {
+ dup2 (tmpfd, 0);
+- execl ("/bin/sh", "sh", "-c", buf, (char *) 0);
++ assert (outname[0] != '!' && outname[0] != '-');
++ execlp (editor_program, editor_program, "-", outname, (char *) NULL);
+ _exit (2);
+ }
+ else
+--
+2.11.0
+
diff --git a/patches/0008-Use-gnulib-execute-module.patch b/patches/0008-Use-gnulib-execute-module.patch
new file mode 100644
index 0000000..b609d41
--- /dev/null
+++ b/patches/0008-Use-gnulib-execute-module.patch
@@ -0,0 +1,114 @@
+From ff1d3a67da1e7f7af6a760ba5f0cee70763666da Mon Sep 17 00:00:00 2001
+From: Andreas Gruenbacher <agruen@gnu.org>
+Date: Fri, 6 Apr 2018 20:24:07 +0200
+Subject: [PATCH 08/17] Use gnulib execute module
+
+* bootstrap.conf (gnulib_modules): Add execute.
+* src/pch.c (do_ed_script): Switch from fork + execlp to execute.
+---
+ bootstrap.conf | 1 +
+ m4/.gitignore | 13 +++++++++++++
+ src/pch.c | 40 +++++++++++++++++++++-------------------
+ 3 files changed, 35 insertions(+), 19 deletions(-)
+
+diff --git a/bootstrap.conf b/bootstrap.conf
+index 47255fa..7c49a98 100644
+--- a/bootstrap.conf
++++ b/bootstrap.conf
+@@ -25,6 +25,7 @@ diffseq
+ dirname
+ dup2
+ errno
++execute
+ exitfail
+ extensions
+ faccessat
+diff --git a/m4/.gitignore b/m4/.gitignore
+index 9a94c0e..a84117a 100644
+--- a/m4/.gitignore
++++ b/m4/.gitignore
+@@ -275,3 +275,16 @@ xvasprintf.m4
+ /utime.m4
+ /utime_h.m4
+ /nstrftime.m4
++/execute.m4
++/fatal-signal.m4
++/posix_spawn.m4
++/rawmemchr.m4
++/sched_h.m4
++/sig_atomic_t.m4
++/sigaction.m4
++/signalblocking.m4
++/spawn_h.m4
++/strchrnul.m4
++/sys_wait_h.m4
++/wait-process.m4
++/waitpid.m4
+diff --git a/src/pch.c b/src/pch.c
+index 16e001a..1f14624 100644
+--- a/src/pch.c
++++ b/src/pch.c
+@@ -33,7 +33,8 @@
+ # include <io.h>
+ #endif
+ #include <safe.h>
+-#include <sys/wait.h>
++#include <alloca.h>
++#include "execute.h"
+
+ #define INITHUNKMAX 125 /* initial dynamic allocation size */
+
+@@ -2453,6 +2454,9 @@ do_ed_script (char const *inname, char const *outname,
+
+ if (! dry_run && ! skip_rest_of_patch) {
+ int exclusive = *outname_needs_removal ? 0 : O_EXCL;
++ char const **ed_argv;
++ int stdin_dup, status;
++
+ *outname_needs_removal = true;
+ if (inerrno != ENOENT)
+ {
+@@ -2461,24 +2465,22 @@ do_ed_script (char const *inname, char const *outname,
+ }
+ fflush (stdout);
+
+- pid = fork();
+- if (pid == -1)
+- pfatal ("Can't fork");
+- else if (pid == 0)
+- {
+- dup2 (tmpfd, 0);
+- assert (outname[0] != '!' && outname[0] != '-');
+- execlp (editor_program, editor_program, "-", outname, (char *) NULL);
+- _exit (2);
+- }
+- else
+- {
+- int wstatus;
+- if (waitpid (pid, &wstatus, 0) == -1
+- || ! WIFEXITED (wstatus)
+- || WEXITSTATUS (wstatus) != 0)
+- fatal ("%s FAILED", editor_program);
+- }
++ if ((stdin_dup = dup (0)) == -1
++ || dup2 (tmpfd, 0) == -1)
++ pfatal ("Failed to duplicate standard input");
++ assert (outname[0] != '!' && outname[0] != '-');
++ ed_argv = alloca (4 * sizeof * ed_argv);
++ ed_argv[0] = editor_program;
++ ed_argv[1] = "-";
++ ed_argv[2] = outname;
++ ed_argv[3] = (char *) NULL;
++ status = execute (editor_program, editor_program, (char **)ed_argv,
++ false, false, false, false, true, false, NULL);
++ if (status)
++ fatal ("%s FAILED", editor_program);
++ if (dup2 (stdin_dup, 0) == -1
++ || close (stdin_dup) == -1)
++ pfatal ("Failed to duplicate standard input");
+ }
+
+ fclose (tmpfp);
+--
+2.11.0
+
diff --git a/patches/0009-Minor-cleanups-in-do_ed_script.patch b/patches/0009-Minor-cleanups-in-do_ed_script.patch
new file mode 100644
index 0000000..57cec52
--- /dev/null
+++ b/patches/0009-Minor-cleanups-in-do_ed_script.patch
@@ -0,0 +1,96 @@
+From 2a32bf09f5e9572da4be183bb0dbde8164351474 Mon Sep 17 00:00:00 2001
+From: Andreas Gruenbacher <agruen@gnu.org>
+Date: Fri, 6 Apr 2018 20:32:46 +0200
+Subject: [PATCH 09/17] Minor cleanups in do_ed_script
+
+* src/pch.c (do_ed_script): Minor cleanups.
+---
+ src/pch.c | 57 +++++++++++++++++++++++++++------------------------------
+ 1 file changed, 27 insertions(+), 30 deletions(-)
+
+diff --git a/src/pch.c b/src/pch.c
+index 1f14624..1055542 100644
+--- a/src/pch.c
++++ b/src/pch.c
+@@ -2396,6 +2396,10 @@ do_ed_script (char const *inname, char const *outname,
+ char const *tmpname;
+ int tmpfd;
+ pid_t pid;
++ int exclusive = *outname_needs_removal ? 0 : O_EXCL;
++ char const **ed_argv;
++ int stdin_dup, status;
++
+
+ if (! dry_run && ! skip_rest_of_patch)
+ {
+@@ -2443,7 +2447,7 @@ do_ed_script (char const *inname, char const *outname,
+ break;
+ }
+ }
+- if (!tmpfp)
++ if (dry_run || skip_rest_of_patch)
+ return;
+ if (fwrite ("w\nq\n", sizeof (char), (size_t) 4, tmpfp) == 0
+ || fflush (tmpfp) != 0)
+@@ -2452,36 +2456,29 @@ do_ed_script (char const *inname, char const *outname,
+ if (lseek (tmpfd, 0, SEEK_SET) == -1)
+ pfatal ("Can't rewind to the beginning of file %s", quotearg (tmpname));
+
+- if (! dry_run && ! skip_rest_of_patch) {
+- int exclusive = *outname_needs_removal ? 0 : O_EXCL;
+- char const **ed_argv;
+- int stdin_dup, status;
+-
++ if (inerrno != ENOENT)
++ {
+ *outname_needs_removal = true;
+- if (inerrno != ENOENT)
+- {
+- *outname_needs_removal = true;
+- copy_file (inname, outname, 0, exclusive, instat.st_mode, true);
+- }
+- fflush (stdout);
+-
+- if ((stdin_dup = dup (0)) == -1
+- || dup2 (tmpfd, 0) == -1)
+- pfatal ("Failed to duplicate standard input");
+- assert (outname[0] != '!' && outname[0] != '-');
+- ed_argv = alloca (4 * sizeof * ed_argv);
+- ed_argv[0] = editor_program;
+- ed_argv[1] = "-";
+- ed_argv[2] = outname;
+- ed_argv[3] = (char *) NULL;
+- status = execute (editor_program, editor_program, (char **)ed_argv,
+- false, false, false, false, true, false, NULL);
+- if (status)
+- fatal ("%s FAILED", editor_program);
+- if (dup2 (stdin_dup, 0) == -1
+- || close (stdin_dup) == -1)
+- pfatal ("Failed to duplicate standard input");
+- }
++ copy_file (inname, outname, 0, exclusive, instat.st_mode, true);
++ }
++ fflush (stdout);
++
++ if ((stdin_dup = dup (0)) == -1
++ || dup2 (tmpfd, 0) == -1)
++ pfatal ("Failed to duplicate standard input");
++ assert (outname[0] != '!' && outname[0] != '-');
++ ed_argv = alloca (4 * sizeof * ed_argv);
++ ed_argv[0] = editor_program;
++ ed_argv[1] = "-";
++ ed_argv[2] = outname;
++ ed_argv[3] = (char *) NULL;
++ status = execute (editor_program, editor_program, (char **)ed_argv,
++ false, false, false, false, true, false, NULL);
++ if (status)
++ fatal ("%s FAILED", editor_program);
++ if (dup2 (stdin_dup, 0) == -1
++ || close (stdin_dup) == -1)
++ pfatal ("Failed to duplicate standard input");
+
+ fclose (tmpfp);
+ safe_unlink (tmpname);
+--
+2.11.0
+
diff --git a/patches/0010-maint-avoid-warnings-from-GCC8.patch b/patches/0010-maint-avoid-warnings-from-GCC8.patch
new file mode 100644
index 0000000..e1f9e4e
--- /dev/null
+++ b/patches/0010-maint-avoid-warnings-from-GCC8.patch
@@ -0,0 +1,92 @@
+From ae81be0024ea4eaf139b7ba57e9a8ce9e4a163ec Mon Sep 17 00:00:00 2001
+From: Jim Meyering <jim@meyering.net>
+Date: Fri, 6 Apr 2018 17:17:11 -0700
+Subject: [PATCH 10/17] maint: avoid warnings from GCC8
+
+Hi Andreas,
+
+I configured with --enable-gcc-warnings and bleeding-edge gcc
+(version 8.0.1 20180406) and hit some warning-escalated-to-errors.
+This fixes them:
+
+>From a71ddb200dbe7ac0f9258796b5a51979b2740e88 Mon Sep 17 00:00:00 2001
+From: Jim Meyering <meyering@fb.com>
+Date: Fri, 6 Apr 2018 16:47:00 -0700
+Subject: [PATCH] maint: avoid warnings from GCC8
+
+* src/common.h (FALLTHROUGH): Define.
+* src/patch.c (abort_hunk_context): Use FALLTHROUGH macro in place of
+a comment. This avoids a warning from -Wimplicit-fallthrough=.
+* src/pch.c (do_ed_script): Add otherwise unnecessary initialization
+to avoid warning from -Wmaybe-uninitialized.
+(another_hunk): Use FALLTHROUGH macro here, too, twice.
+---
+ src/common.h | 8 ++++++++
+ src/patch.c | 2 +-
+ src/pch.c | 7 +++----
+ 3 files changed, 12 insertions(+), 5 deletions(-)
+
+diff --git a/src/common.h b/src/common.h
+index ec50b40..904a3f8 100644
+--- a/src/common.h
++++ b/src/common.h
+@@ -218,3 +218,11 @@ bool merge_hunk (int hunk, struct outstate *, lin where, bool *);
+ #else
+ # define merge_hunk(hunk, outstate, where, somefailed) false
+ #endif
++
++#ifndef FALLTHROUGH
++# if __GNUC__ < 7
++# define FALLTHROUGH ((void) 0)
++# else
++# define FALLTHROUGH __attribute__ ((__fallthrough__))
++# endif
++#endif
+diff --git a/src/patch.c b/src/patch.c
+index 0fe6d72..1ae91d9 100644
+--- a/src/patch.c
++++ b/src/patch.c
+@@ -1381,7 +1381,7 @@ abort_hunk_context (bool header, bool reverse)
+ break;
+ case ' ': case '-': case '+': case '!':
+ fprintf (rejfp, "%c ", pch_char (i));
+- /* fall into */
++ FALLTHROUGH;
+ case '\n':
+ pch_write_line (i, rejfp);
+ break;
+diff --git a/src/pch.c b/src/pch.c
+index 1055542..cda3dfa 100644
+--- a/src/pch.c
++++ b/src/pch.c
+@@ -1735,7 +1735,7 @@ another_hunk (enum diff difftype, bool rev)
+ break;
+ case '=':
+ ch = ' ';
+- /* FALL THROUGH */
++ FALLTHROUGH;
+ case ' ':
+ if (fillsrc > p_ptrn_lines) {
+ free(s);
+@@ -1756,7 +1756,7 @@ another_hunk (enum diff difftype, bool rev)
+ p_end = fillsrc-1;
+ return -1;
+ }
+- /* FALL THROUGH */
++ FALLTHROUGH;
+ case '+':
+ if (filldst > p_end) {
+ free(s);
+@@ -2394,8 +2394,7 @@ do_ed_script (char const *inname, char const *outname,
+ size_t chars_read;
+ FILE *tmpfp = 0;
+ char const *tmpname;
+- int tmpfd;
+- pid_t pid;
++ int tmpfd = -1; /* placate gcc's -Wmaybe-uninitialized */
+ int exclusive = *outname_needs_removal ? 0 : O_EXCL;
+ char const **ed_argv;
+ int stdin_dup, status;
+--
+2.11.0
+
diff --git a/patches/0011-Fix-check-of-return-value-of-fwrite.patch b/patches/0011-Fix-check-of-return-value-of-fwrite.patch
new file mode 100644
index 0000000..022a90d
--- /dev/null
+++ b/patches/0011-Fix-check-of-return-value-of-fwrite.patch
@@ -0,0 +1,81 @@
+From 1e9104c18019e7dc6b5590aea4b1d4f9d8ecfd56 Mon Sep 17 00:00:00 2001
+From: Bruno Haible <bruno@clisp.org>
+Date: Sat, 7 Apr 2018 12:21:04 +0200
+Subject: [PATCH 11/17] Fix check of return value of fwrite().
+
+* src/patch.c (copy_till): Consider incomplete fwrite() write as an error.
+* src/pch.c (pch_write_line, do_ed_script): Likewise.
+---
+ src/patch.c | 4 ++--
+ src/pch.c | 14 +++++++++-----
+ 2 files changed, 11 insertions(+), 7 deletions(-)
+
+diff --git a/src/patch.c b/src/patch.c
+index 1ae91d9..3fcaec5 100644
+--- a/src/patch.c
++++ b/src/patch.c
+@@ -2,7 +2,7 @@
+
+ /* Copyright (C) 1984, 1985, 1986, 1987, 1988 Larry Wall
+
+- Copyright (C) 1989-1993, 1997-1999, 2002-2003, 2006, 2009-2012 Free Software
++ Copyright (C) 1989-1993, 1997-1999, 2002-2003, 2006, 2009-2018 Free Software
+ Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+@@ -1641,7 +1641,7 @@ copy_till (struct outstate *outstate, lin lastline)
+ if (size)
+ {
+ if ((! outstate->after_newline && putc ('\n', fp) == EOF)
+- || ! fwrite (s, sizeof *s, size, fp))
++ || fwrite (s, sizeof *s, size, fp) < size)
+ write_fatal ();
+ outstate->after_newline = s[size - 1] == '\n';
+ outstate->zero_output = false;
+diff --git a/src/pch.c b/src/pch.c
+index cda3dfa..79a3c99 100644
+--- a/src/pch.c
++++ b/src/pch.c
+@@ -2279,8 +2279,11 @@ pfetch (lin line)
+ bool
+ pch_write_line (lin line, FILE *file)
+ {
+- bool after_newline = (p_len[line] > 0) && (p_line[line][p_len[line] - 1] == '\n');
+- if (! fwrite (p_line[line], sizeof (*p_line[line]), p_len[line], file))
++ bool after_newline =
++ (p_len[line] > 0) && (p_line[line][p_len[line] - 1] == '\n');
++
++ if (fwrite (p_line[line], sizeof (*p_line[line]), p_len[line], file)
++ < p_len[line])
+ write_fatal ();
+ return after_newline;
+ }
+@@ -2427,13 +2430,14 @@ do_ed_script (char const *inname, char const *outname,
+ ed_command_letter = get_ed_command_letter (buf);
+ if (ed_command_letter) {
+ if (tmpfp)
+- if (! fwrite (buf, sizeof *buf, chars_read, tmpfp))
++ if (fwrite (buf, sizeof *buf, chars_read, tmpfp) < chars_read)
+ write_fatal ();
+ if (ed_command_letter != 'd' && ed_command_letter != 's') {
+ p_pass_comments_through = true;
+ while ((chars_read = get_line ()) != 0) {
+ if (tmpfp)
+- if (! fwrite (buf, sizeof *buf, chars_read, tmpfp))
++ if (fwrite (buf, sizeof *buf, chars_read, tmpfp)
++ < chars_read)
+ write_fatal ();
+ if (chars_read == 2 && strEQ (buf, ".\n"))
+ break;
+@@ -2448,7 +2452,7 @@ do_ed_script (char const *inname, char const *outname,
+ }
+ if (dry_run || skip_rest_of_patch)
+ return;
+- if (fwrite ("w\nq\n", sizeof (char), (size_t) 4, tmpfp) == 0
++ if (fwrite ("w\nq\n", sizeof (char), (size_t) 4, tmpfp) < (size_t) 4
+ || fflush (tmpfp) != 0)
+ write_fatal ();
+
+--
+2.11.0
+
diff --git a/patches/0012-Fix-ed-style-test-failure.patch b/patches/0012-Fix-ed-style-test-failure.patch
new file mode 100644
index 0000000..2ab28df
--- /dev/null
+++ b/patches/0012-Fix-ed-style-test-failure.patch
@@ -0,0 +1,27 @@
+From 458ac51a05426c1af9aa6bf1342ecf60728c19b4 Mon Sep 17 00:00:00 2001
+From: Bruno Haible <bruno@clisp.org>
+Date: Sat, 7 Apr 2018 12:34:03 +0200
+Subject: [PATCH 12/17] Fix 'ed-style' test failure.
+
+* tests/ed-style: Remove '?' line from expected output.
+---
+ tests/ed-style | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/tests/ed-style b/tests/ed-style
+index d8c0689..6b6ef9d 100644
+--- a/tests/ed-style
++++ b/tests/ed-style
+@@ -31,8 +31,7 @@ r !echo bar
+ ,p
+ EOF
+
+-check 'patch -e foo -i ed2.diff 2> /dev/null || echo "Status: $?"' <<EOF
+-?
++check 'patch -e foo -i ed2.diff > /dev/null 2> /dev/null || echo "Status: $?"' <<EOF
+ Status: 2
+ EOF
+
+--
+2.11.0
+
diff --git a/patches/0013-Request-alloca-module-from-gnulib.patch b/patches/0013-Request-alloca-module-from-gnulib.patch
new file mode 100644
index 0000000..0fc258c
--- /dev/null
+++ b/patches/0013-Request-alloca-module-from-gnulib.patch
@@ -0,0 +1,33 @@
+From f322a7e0e55924e043b2d9b0d9249b86fb7c271a Mon Sep 17 00:00:00 2001
+From: Bruno Haible <bruno@clisp.org>
+Date: Sat, 7 Apr 2018 12:39:03 +0200
+Subject: [PATCH 13/17] Request 'alloca' module from gnulib.
+
+* bootstrap.conf (gnulib_modules): Add 'alloca'.
+---
+ bootstrap.conf | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/bootstrap.conf b/bootstrap.conf
+index 7c49a98..68cddd7 100644
+--- a/bootstrap.conf
++++ b/bootstrap.conf
+@@ -1,6 +1,6 @@
+ # Bootstrap configuration.
+
+-# Copyright (C) 2006-2007, 2009-2012 Free Software Foundation, Inc.
++# Copyright (C) 2006-2007, 2009-2018 Free Software Foundation, Inc.
+
+ # This program is free software: you can redistribute it and/or modify
+ # it under the terms of the GNU General Public License as published by
+@@ -18,6 +18,7 @@
+
+ # gnulib modules used by this package.
+ gnulib_modules="
++alloca
+ argmatch
+ backupfile
+ clock-time
+--
+2.11.0
+
diff --git a/patches/0014-Don-t-leak-temporary-file-on-failed-ed-style-patch.patch b/patches/0014-Don-t-leak-temporary-file-on-failed-ed-style-patch.patch
new file mode 100644
index 0000000..eeb9aab
--- /dev/null
+++ b/patches/0014-Don-t-leak-temporary-file-on-failed-ed-style-patch.patch
@@ -0,0 +1,102 @@
+From 19599883ffb6a450d2884f081f8ecf68edbed7ee Mon Sep 17 00:00:00 2001
+From: Jean Delvare <jdelvare@suse.de>
+Date: Thu, 3 May 2018 14:31:55 +0200
+Subject: [PATCH 14/17] Don't leak temporary file on failed ed-style patch
+
+Now that we write ed-style patches to a temporary file before we
+apply them, we need to ensure that the temporary file is removed
+before we leave, even on fatal error.
+
+* src/pch.c (do_ed_script): Use global TMPEDNAME instead of local
+ tmpname. Don't unlink the file directly, instead tag it for removal
+ at exit time.
+* src/patch.c (cleanup): Unlink TMPEDNAME at exit.
+
+This closes bug #53820:
+https://savannah.gnu.org/bugs/index.php?53820
+
+Fixes: 123eaff0d5d1 ("Fix arbitrary command execution in ed-style patches (CVE-2018-1000156)")
+---
+ src/common.h | 2 ++
+ src/patch.c | 1 +
+ src/pch.c | 11 +++++------
+ 3 files changed, 8 insertions(+), 6 deletions(-)
+
+diff --git a/src/common.h b/src/common.h
+index 904a3f8..53c5e32 100644
+--- a/src/common.h
++++ b/src/common.h
+@@ -94,10 +94,12 @@ XTERN char const *origsuff;
+ XTERN char const * TMPINNAME;
+ XTERN char const * TMPOUTNAME;
+ XTERN char const * TMPPATNAME;
++XTERN char const * TMPEDNAME;
+
+ XTERN bool TMPINNAME_needs_removal;
+ XTERN bool TMPOUTNAME_needs_removal;
+ XTERN bool TMPPATNAME_needs_removal;
++XTERN bool TMPEDNAME_needs_removal;
+
+ #ifdef DEBUGGING
+ XTERN int debug;
+diff --git a/src/patch.c b/src/patch.c
+index 3fcaec5..9146597 100644
+--- a/src/patch.c
++++ b/src/patch.c
+@@ -1999,6 +1999,7 @@ cleanup (void)
+ remove_if_needed (TMPINNAME, &TMPINNAME_needs_removal);
+ remove_if_needed (TMPOUTNAME, &TMPOUTNAME_needs_removal);
+ remove_if_needed (TMPPATNAME, &TMPPATNAME_needs_removal);
++ remove_if_needed (TMPEDNAME, &TMPEDNAME_needs_removal);
+ remove_if_needed (TMPREJNAME, &TMPREJNAME_needs_removal);
+ output_files (NULL);
+ }
+diff --git a/src/pch.c b/src/pch.c
+index 79a3c99..1bb3153 100644
+--- a/src/pch.c
++++ b/src/pch.c
+@@ -2396,7 +2396,6 @@ do_ed_script (char const *inname, char const *outname,
+ file_offset beginning_of_this_line;
+ size_t chars_read;
+ FILE *tmpfp = 0;
+- char const *tmpname;
+ int tmpfd = -1; /* placate gcc's -Wmaybe-uninitialized */
+ int exclusive = *outname_needs_removal ? 0 : O_EXCL;
+ char const **ed_argv;
+@@ -2411,12 +2410,13 @@ do_ed_script (char const *inname, char const *outname,
+ invalid commands and treats the next line as a new command, which
+ can lead to arbitrary command execution. */
+
+- tmpfd = make_tempfile (&tmpname, 'e', NULL, O_RDWR | O_BINARY, 0);
++ tmpfd = make_tempfile (&TMPEDNAME, 'e', NULL, O_RDWR | O_BINARY, 0);
+ if (tmpfd == -1)
+- pfatal ("Can't create temporary file %s", quotearg (tmpname));
++ pfatal ("Can't create temporary file %s", quotearg (TMPEDNAME));
++ TMPEDNAME_needs_removal = true;
+ tmpfp = fdopen (tmpfd, "w+b");
+ if (! tmpfp)
+- pfatal ("Can't open stream for file %s", quotearg (tmpname));
++ pfatal ("Can't open stream for file %s", quotearg (TMPEDNAME));
+ }
+
+ for (;;) {
+@@ -2457,7 +2457,7 @@ do_ed_script (char const *inname, char const *outname,
+ write_fatal ();
+
+ if (lseek (tmpfd, 0, SEEK_SET) == -1)
+- pfatal ("Can't rewind to the beginning of file %s", quotearg (tmpname));
++ pfatal ("Can't rewind to the beginning of file %s", quotearg (TMPEDNAME));
+
+ if (inerrno != ENOENT)
+ {
+@@ -2484,7 +2484,6 @@ do_ed_script (char const *inname, char const *outname,
+ pfatal ("Failed to duplicate standard input");
+
+ fclose (tmpfp);
+- safe_unlink (tmpname);
+
+ if (ofp)
+ {
+--
+2.11.0
+
diff --git a/patches/0015-Don-t-leak-temporary-file-on-failed-multi-file-ed-st.patch b/patches/0015-Don-t-leak-temporary-file-on-failed-multi-file-ed-st.patch
new file mode 100644
index 0000000..20df406
--- /dev/null
+++ b/patches/0015-Don-t-leak-temporary-file-on-failed-multi-file-ed-st.patch
@@ -0,0 +1,78 @@
+From 369dcccdfa6336e5a873d6d63705cfbe04c55727 Mon Sep 17 00:00:00 2001
+From: Jean Delvare <jdelvare@suse.de>
+Date: Mon, 7 May 2018 15:14:45 +0200
+Subject: [PATCH 15/17] Don't leak temporary file on failed multi-file ed-style
+ patch
+
+The previous fix worked fine with single-file ed-style patches, but
+would still leak temporary files in the case of multi-file ed-style
+patch. Fix that case as well, and extend the test case to check for
+it.
+
+* src/patch.c (main): Unlink TMPEDNAME if needed before moving to
+ the next file in a patch.
+
+This closes bug #53820:
+https://savannah.gnu.org/bugs/index.php?53820
+
+Fixes: 123eaff0d5d1 ("Fix arbitrary command execution in ed-style patches (CVE-2018-1000156)")
+Fixes: 19599883ffb6 ("Don't leak temporary file on failed ed-style patch")
+---
+ src/patch.c | 1 +
+ tests/ed-style | 31 +++++++++++++++++++++++++++++++
+ 2 files changed, 32 insertions(+)
+
+diff --git a/src/patch.c b/src/patch.c
+index 9146597..81c7a02 100644
+--- a/src/patch.c
++++ b/src/patch.c
+@@ -236,6 +236,7 @@ main (int argc, char **argv)
+ }
+ remove_if_needed (TMPOUTNAME, &TMPOUTNAME_needs_removal);
+ }
++ remove_if_needed (TMPEDNAME, &TMPEDNAME_needs_removal);
+
+ if (! skip_rest_of_patch && ! file_type)
+ {
+diff --git a/tests/ed-style b/tests/ed-style
+index 6b6ef9d..504e6e5 100644
+--- a/tests/ed-style
++++ b/tests/ed-style
+@@ -38,3 +38,34 @@ EOF
+ check 'cat foo' <<EOF
+ foo
+ EOF
++
++# Test the case where one ed-style patch modifies several files
++
++cat > ed3.diff <<EOF
++--- foo
+++++ foo
++1c
++bar
++.
++--- baz
+++++ baz
++0a
++baz
++.
++EOF
++
++# Apparently we can't create a file with such a patch, while it works fine
++# when the file name is provided on the command line
++cat > baz <<EOF
++EOF
++
++check 'patch -e -i ed3.diff' <<EOF
++EOF
++
++check 'cat foo' <<EOF
++bar
++EOF
++
++check 'cat baz' <<EOF
++baz
++EOF
+--
+2.11.0
+
diff --git a/patches/0016-Make-the-debug-2-output-more-useful.patch b/patches/0016-Make-the-debug-2-output-more-useful.patch
new file mode 100644
index 0000000..c44d8fe
--- /dev/null
+++ b/patches/0016-Make-the-debug-2-output-more-useful.patch
@@ -0,0 +1,45 @@
+From ff81775f4eb6ab9a91b75e4031e8216654c0c76a Mon Sep 17 00:00:00 2001
+From: Andreas Gruenbacher <agruen@gnu.org>
+Date: Fri, 17 Aug 2018 10:31:22 +0200
+Subject: [PATCH 16/17] Make the (debug & 2) output more useful
+
+* src/pch.c (another_hunk): In the (debug & 2) output, fix how empty
+lines that are not part of the patch context are printed. Also, add
+newlines to lines that are missing them to keep the output readable.
+---
+ src/pch.c | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/src/pch.c b/src/pch.c
+index 1bb3153..e92bc64 100644
+--- a/src/pch.c
++++ b/src/pch.c
+@@ -1916,8 +1916,13 @@ another_hunk (enum diff difftype, bool rev)
+ lin i;
+
+ for (i = 0; i <= p_end + 1; i++) {
+- fprintf (stderr, "%s %c",
+- format_linenum (numbuf0, i),
++ fputs (format_linenum (numbuf0, i), stderr);
++ if (p_Char[i] == '\n')
++ {
++ fputc('\n', stderr);
++ continue;
++ }
++ fprintf (stderr, " %c",
+ p_Char[i]);
+ if (p_Char[i] == '*')
+ fprintf (stderr, " %s,%s\n",
+@@ -1930,7 +1935,8 @@ another_hunk (enum diff difftype, bool rev)
+ else if (p_Char[i] != '^')
+ {
+ fputs(" |", stderr);
+- pch_write_line (i, stderr);
++ if (! pch_write_line (i, stderr))
++ fputc('\n', stderr);
+ }
+ else
+ fputc('\n', stderr);
+--
+2.11.0
+
diff --git a/patches/0017-Fix-swapping-fake-lines-in-pch_swap.patch b/patches/0017-Fix-swapping-fake-lines-in-pch_swap.patch
new file mode 100644
index 0000000..a41a102
--- /dev/null
+++ b/patches/0017-Fix-swapping-fake-lines-in-pch_swap.patch
@@ -0,0 +1,30 @@
+From 9c986353e420ead6e706262bf204d6e03322c300 Mon Sep 17 00:00:00 2001
+From: Andreas Gruenbacher <agruen@gnu.org>
+Date: Fri, 17 Aug 2018 13:35:40 +0200
+Subject: [PATCH 17/17] Fix swapping fake lines in pch_swap
+
+* src/pch.c (pch_swap): Fix swapping p_bfake and p_efake when there is a
+blank line in the middle of a context-diff hunk: that empty line stays
+in the middle of the hunk and isn't swapped.
+
+Fixes: https://savannah.gnu.org/bugs/index.php?53133
+---
+ src/pch.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/pch.c b/src/pch.c
+index e92bc64..a500ad9 100644
+--- a/src/pch.c
++++ b/src/pch.c
+@@ -2122,7 +2122,7 @@ pch_swap (void)
+ }
+ if (p_efake >= 0) { /* fix non-freeable ptr range */
+ if (p_efake <= i)
+- n = p_end - i + 1;
++ n = p_end - p_ptrn_lines;
+ else
+ n = -i;
+ p_efake += n;
+--
+2.11.0
+