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