Fri Jul 24 18:56:00 2015 UTC ()
From Martin Natano @bitrig: Use execve(2) instead of system to apply patches
that require rcs command execution instead system(3) to avoid malicious
filenames in patches causing bad things to happen. In the process, lose SCCS
support. It is not like we are shipping sccs commands for that to work.


(christos)
diff -r1.23 -r1.24 src/usr.bin/patch/inp.c

cvs diff -r1.23 -r1.24 src/usr.bin/patch/inp.c (expand / switch to unified diff)

--- src/usr.bin/patch/inp.c 2009/10/21 17:16:11 1.23
+++ src/usr.bin/patch/inp.c 2015/07/24 18:56:00 1.24
@@ -1,17 +1,17 @@ @@ -1,17 +1,17 @@
1/* 1/*
2 * $OpenBSD: inp.c,v 1.34 2006/03/11 19:41:30 otto Exp $ 2 * $OpenBSD: inp.c,v 1.34 2006/03/11 19:41:30 otto Exp $
3 * $DragonFly: src/usr.bin/patch/inp.c,v 1.6 2007/09/29 23:11:10 swildner Exp $ 3 * $DragonFly: src/usr.bin/patch/inp.c,v 1.6 2007/09/29 23:11:10 swildner Exp $
4 * $NetBSD: inp.c,v 1.23 2009/10/21 17:16:11 joerg Exp $ 4 * $NetBSD: inp.c,v 1.24 2015/07/24 18:56:00 christos Exp $
5 */ 5 */
6 6
7/* 7/*
8 * patch - a program to apply diffs to original files 8 * patch - a program to apply diffs to original files
9 *  9 *
10 * Copyright 1986, Larry Wall 10 * Copyright 1986, Larry Wall
11 *  11 *
12 * Redistribution and use in source and binary forms, with or without 12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following condition is met: 13 * modification, are permitted provided that the following condition is met:
14 * 1. Redistributions of source code must retain the above copyright notice, 14 * 1. Redistributions of source code must retain the above copyright notice,
15 * this condition and the following disclaimer. 15 * this condition and the following disclaimer.
16 *  16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
@@ -21,34 +21,36 @@ @@ -21,34 +21,36 @@
21 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE. 27 * SUCH DAMAGE.
28 *  28 *
29 * -C option added in 1998, original code by Marc Espie, based on FreeBSD 29 * -C option added in 1998, original code by Marc Espie, based on FreeBSD
30 * behaviour 30 * behaviour
31 */ 31 */
32 32
33#include <sys/cdefs.h> 33#include <sys/cdefs.h>
34__RCSID("$NetBSD: inp.c,v 1.23 2009/10/21 17:16:11 joerg Exp $"); 34__RCSID("$NetBSD: inp.c,v 1.24 2015/07/24 18:56:00 christos Exp $");
35 35
36#include <sys/types.h> 36#include <sys/types.h>
37#include <sys/file.h> 37#include <sys/file.h>
38#include <sys/stat.h> 38#include <sys/stat.h>
39#include <sys/mman.h> 39#include <sys/mman.h>
 40#include <sys/wait.h>
40 41
41#include <ctype.h> 42#include <ctype.h>
 43#include <errno.h>
42#include <fcntl.h> 44#include <fcntl.h>
43#include <libgen.h> 45#include <libgen.h>
44#include <limits.h> 46#include <limits.h>
45#include <stddef.h> 47#include <stddef.h>
46#include <stdio.h> 48#include <stdio.h>
47#include <stdlib.h> 49#include <stdlib.h>
48#include <string.h> 50#include <string.h>
49#include <unistd.h> 51#include <unistd.h>
50 52
51#include "common.h" 53#include "common.h"
52#include "util.h" 54#include "util.h"
53#include "pch.h" 55#include "pch.h"
54#include "inp.h" 56#include "inp.h"
@@ -129,32 +131,34 @@ reallocate_lines(size_t *lines_allocated @@ -129,32 +131,34 @@ reallocate_lines(size_t *lines_allocated
129 *lines_allocated = 0; 131 *lines_allocated = 0;
130 return false; 132 return false;
131 } 133 }
132 *lines_allocated = new_size; 134 *lines_allocated = new_size;
133 i_ptr = p; 135 i_ptr = p;
134 return true; 136 return true;
135} 137}
136 138
137/* Try keeping everything in memory. */ 139/* Try keeping everything in memory. */
138 140
139static bool 141static bool
140plan_a(const char *filename) 142plan_a(const char *filename)
141{ 143{
142 int ifd, statfailed; 144 int ifd, statfailed, devnull, pstat;
143 char *p, *s, lbuf[MAXLINELEN]; 145 char *p, *s, lbuf[MAXLINELEN];
144 struct stat filestat; 146 struct stat filestat;
145 off_t i; 147 off_t i;
146 ptrdiff_t sz; 148 ptrdiff_t sz;
147 size_t iline, lines_allocated; 149 size_t iline, lines_allocated;
 150 pid_t pid;
 151 char *argp[4] = {NULL};
148 152
149#ifdef DEBUGGING 153#ifdef DEBUGGING
150 if (debug & 8) 154 if (debug & 8)
151 return false; 155 return false;
152#endif 156#endif
153 157
154 if (filename == NULL || *filename == '\0') 158 if (filename == NULL || *filename == '\0')
155 return false; 159 return false;
156 160
157 statfailed = stat(filename, &filestat); 161 statfailed = stat(filename, &filestat);
158 if (statfailed && ok_to_create_file) { 162 if (statfailed && ok_to_create_file) {
159 if (verbose) 163 if (verbose)
160 say("(Creating file %s...)\n", filename); 164 say("(Creating file %s...)\n", filename);
@@ -162,99 +166,120 @@ plan_a(const char *filename) @@ -162,99 +166,120 @@ plan_a(const char *filename)
162 /* 166 /*
163 * in check_patch case, we still display `Creating file' even 167 * in check_patch case, we still display `Creating file' even
164 * though we're not. The rule is that -C should be as similar 168 * though we're not. The rule is that -C should be as similar
165 * to normal patch behavior as possible 169 * to normal patch behavior as possible
166 */ 170 */
167 if (check_only) 171 if (check_only)
168 return true; 172 return true;
169 makedirs(filename, true); 173 makedirs(filename, true);
170 close(creat(filename, 0666)); 174 close(creat(filename, 0666));
171 statfailed = stat(filename, &filestat); 175 statfailed = stat(filename, &filestat);
172 } 176 }
173 if (statfailed && check_only) 177 if (statfailed && check_only)
174 fatal("%s not found, -C mode, can't probe further\n", filename); 178 fatal("%s not found, -C mode, can't probe further\n", filename);
175 /* For nonexistent or read-only files, look for RCS or SCCS versions. */ 179 /* For nonexistent or read-only files, look for RCS versions. */
176 if (statfailed || 180 if (statfailed ||
177 /* No one can write to it. */ 181 /* No one can write to it. */
178 (filestat.st_mode & 0222) == 0 || 182 (filestat.st_mode & 0222) == 0 ||
179 /* I can't write to it. */ 183 /* I can't write to it. */
180 ((filestat.st_mode & 0022) == 0 && filestat.st_uid != getuid())) { 184 ((filestat.st_mode & 0022) == 0 && filestat.st_uid != getuid())) {
181 const char *cs = NULL, *filebase, *filedir; 185 char *filebase, *filedir;
182 struct stat cstat; 186 struct stat cstat;
183 char *tmp_filename1, *tmp_filename2; 187 char *tmp_filename1, *tmp_filename2;
184 188
185 tmp_filename1 = strdup(filename); 189 tmp_filename1 = strdup(filename);
186 tmp_filename2 = strdup(filename); 190 tmp_filename2 = strdup(filename);
187 if (tmp_filename1 == NULL || tmp_filename2 == NULL) 191 if (tmp_filename1 == NULL || tmp_filename2 == NULL)
188 fatal("strdupping filename"); 192 fatal("strdupping filename");
189 filebase = basename(tmp_filename1); 193
190 filedir = dirname(tmp_filename2); 194 filebase = basename(tmp_filename1);
191 195 filedir = dirname(tmp_filename2);
192 /* Leave room in lbuf for the diff command. */ 196
193 s = lbuf + 20; 
194 
195#define try(f, a1, a2, a3) \ 197#define try(f, a1, a2, a3) \
196 (snprintf(s, sizeof lbuf - 20, f, a1, a2, a3), stat(s, &cstat) == 0) 198 (snprintf(lbuf, sizeof lbuf, f, a1, a2, a3), stat(lbuf, &cstat) == 0)
197 
198 if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) || 
199 try("%s/RCS/%s%s", filedir, filebase, "") || 
200 try("%s/%s%s", filedir, filebase, RCSSUFFIX)) { 
201 snprintf(buf, buf_len, CHECKOUT, filename); 
202 snprintf(lbuf, sizeof lbuf, RCSDIFF, filename); 
203 cs = "RCS"; 
204 } else if (try("%s/SCCS/%s%s", filedir, SCCSPREFIX, filebase) || 
205 try("%s/%s%s", filedir, SCCSPREFIX, filebase)) { 
206 snprintf(buf, buf_len, GET, s); 
207 snprintf(lbuf, sizeof lbuf, SCCSDIFF, s, filename); 
208 cs = "SCCS"; 
209 } else if (statfailed) 
210 fatal("can't find %s\n", filename); 
211 
212 free(tmp_filename1); 
213 free(tmp_filename2); 
214 199
215 /* 200 /*
216 * else we can't write to it but it's not under a version 201 * else we can't write to it but it's not under a version
217 * control system, so just proceed. 202 * control system, so just proceed.
218 */ 203 */
219 if (cs) { 204 if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) ||
 205 try("%s/RCS/%s%s", filedir, filebase, "") ||
 206 try("%s/%s%s", filedir, filebase, RCSSUFFIX)) {
220 if (!statfailed) { 207 if (!statfailed) {
221 if ((filestat.st_mode & 0222) != 0) 208 if ((filestat.st_mode & 0222) != 0)
222 /* The owner can write to it. */ 209 /* The owner can write to it. */
223 fatal("file %s seems to be locked " 210 fatal("file %s seems to be locked "
224 "by somebody else under %s\n", 211 "by somebody else under RCS\n",
225 filename, cs); 212 filename);
226 /* 213 /*
227 * It might be checked out unlocked. See if 214 * It might be checked out unlocked. See if
228 * it's safe to check out the default version 215 * it's safe to check out the default version
229 * locked. 216 * locked.
230 */ 217 */
231 if (verbose) 218 if (verbose)
232 say("Comparing file %s to default " 219 say("Comparing file %s to default "
233 "%s version...\n", 220 "RCS version...\n", filename);
234 filename, cs); 221
235 if (system(lbuf)) 222 switch (pid = fork()) {
 223 case -1:
 224 fatal("can't fork: %s\n",
 225 strerror(errno));
 226 case 0:
 227 devnull = open("/dev/null", O_RDONLY);
 228 if (devnull == -1) {
 229 fatal("can't open /dev/null: %s",
 230 strerror(errno));
 231 }
 232 (void)dup2(devnull, STDOUT_FILENO);
 233 argp[0] = __UNCONST(RCSDIFF);
 234 argp[1] = __UNCONST(filename);
 235 execv(RCSDIFF, argp);
 236 exit(127);
 237 }
 238 pid = waitpid(pid, &pstat, 0);
 239 if (pid == -1 || WEXITSTATUS(pstat) != 0) {
236 fatal("can't check out file %s: " 240 fatal("can't check out file %s: "
237 "differs from default %s version\n", 241 "differs from default RCS version\n",
238 filename, cs); 242 filename);
 243 }
239 } 244 }
 245
240 if (verbose) 246 if (verbose)
241 say("Checking out file %s from %s...\n", 247 say("Checking out file %s from RCS...\n",
242 filename, cs); 248 filename);
243 if (system(buf) || stat(filename, &filestat)) 249
244 fatal("can't check out file %s from %s\n", 250 switch (pid = fork()) {
245 filename, cs); 251 case -1:
 252 fatal("can't fork: %s\n", strerror(errno));
 253 case 0:
 254 argp[0] = __UNCONST(CHECKOUT);
 255 argp[1] = __UNCONST("-l");
 256 argp[2] = __UNCONST(filename);
 257 execv(CHECKOUT, argp);
 258 exit(127);
 259 }
 260 pid = waitpid(pid, &pstat, 0);
 261 if (pid == -1 || WEXITSTATUS(pstat) != 0 ||
 262 stat(filename, &filestat)) {
 263 fatal("can't check out file %s from RCS\n",
 264 filename);
 265 }
 266 } else if (statfailed) {
 267 fatal("can't find %s\n", filename);
246 } 268 }
 269 free(tmp_filename1);
 270 free(tmp_filename2);
247 } 271 }
 272
248 filemode = filestat.st_mode; 273 filemode = filestat.st_mode;
249 if (!S_ISREG(filemode)) 274 if (!S_ISREG(filemode))
250 fatal("%s is not a normal file--can't patch\n", filename); 275 fatal("%s is not a normal file--can't patch\n", filename);
251 i_size = filestat.st_size; 276 i_size = filestat.st_size;
252 if (out_of_mem) { 277 if (out_of_mem) {
253 set_hunkmax(); /* make sure dynamic arrays are allocated */ 278 set_hunkmax(); /* make sure dynamic arrays are allocated */
254 out_of_mem = false; 279 out_of_mem = false;
255 return false; /* force plan b because plan a bombed */ 280 return false; /* force plan b because plan a bombed */
256 } 281 }
257 if ((uintmax_t)i_size > (uintmax_t)SIZE_MAX) { 282 if ((uintmax_t)i_size > (uintmax_t)SIZE_MAX) {
258 say("block too large to mmap\n"); 283 say("block too large to mmap\n");
259 return false; 284 return false;
260 } 285 }