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