Skip to content

Commit

Permalink
Fix shell injection vulnerability in patch(1) and drop SCCS
Browse files Browse the repository at this point in the history
support by replacing system() with execve().

Future revisions may remove the functionality completely.

Obtained from:	Bitrig
Security:	CVE-2015-1416
  • Loading branch information
delphij committed Jul 28, 2015
1 parent d572331 commit 33661d0
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 41 deletions.
6 changes: 2 additions & 4 deletions usr.bin/patch/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,10 @@
#define LINENUM_MAX LONG_MAX

#define SCCSPREFIX "s."
#define GET "get -e %s"
#define SCCSDIFF "get -p %s | diff - %s >/dev/null"

#define RCSSUFFIX ",v"
#define CHECKOUT "co -l %s"
#define RCSDIFF "rcsdiff %s > /dev/null"
#define CHECKOUT "/usr/bin/co"
#define RCSDIFF "/usr/bin/rcsdiff"

#define ORIGEXT ".orig"
#define REJEXT ".rej"
Expand Down
100 changes: 63 additions & 37 deletions usr.bin/patch/inp.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/wait.h>

#include <ctype.h>
#include <errno.h>
#include <libgen.h>
#include <stddef.h>
#include <stdint.h>
Expand Down Expand Up @@ -133,12 +135,14 @@ reallocate_lines(size_t *lines_allocated)
static bool
plan_a(const char *filename)
{
int ifd, statfailed;
int ifd, statfailed, devnull, pstat;
char *p, *s, lbuf[INITLINELEN];
struct stat filestat;
ptrdiff_t sz;
size_t i;
size_t iline, lines_allocated;
pid_t pid;
char *argp[4] = {NULL};

#ifdef DEBUGGING
if (debug & 8)
Expand Down Expand Up @@ -166,79 +170,101 @@ plan_a(const char *filename)
}
if (statfailed && check_only)
fatal("%s not found, -C mode, can't probe further\n", filename);
/* For nonexistent or read-only files, look for RCS or SCCS versions. */
/* For nonexistent or read-only files, look for RCS versions. */

if (statfailed ||
/* No one can write to it. */
(filestat.st_mode & 0222) == 0 ||
/* I can't write to it. */
((filestat.st_mode & 0022) == 0 && filestat.st_uid != getuid())) {
const char *cs = NULL, *filebase, *filedir;
char *filebase, *filedir;
struct stat cstat;
char *tmp_filename1, *tmp_filename2;

tmp_filename1 = strdup(filename);
tmp_filename2 = strdup(filename);
if (tmp_filename1 == NULL || tmp_filename2 == NULL)
fatal("strdupping filename");

filebase = basename(tmp_filename1);
filedir = dirname(tmp_filename2);

/* Leave room in lbuf for the diff command. */
s = lbuf + 20;

#define try(f, a1, a2, a3) \
(snprintf(s, buf_size - 20, f, a1, a2, a3), stat(s, &cstat) == 0)

if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) ||
try("%s/RCS/%s%s", filedir, filebase, "") ||
try("%s/%s%s", filedir, filebase, RCSSUFFIX)) {
snprintf(buf, buf_size, CHECKOUT, filename);
snprintf(lbuf, sizeof lbuf, RCSDIFF, filename);
cs = "RCS";
} else if (try("%s/SCCS/%s%s", filedir, SCCSPREFIX, filebase) ||
try("%s/%s%s", filedir, SCCSPREFIX, filebase)) {
snprintf(buf, buf_size, GET, s);
snprintf(lbuf, sizeof lbuf, SCCSDIFF, s, filename);
cs = "SCCS";
} else if (statfailed)
fatal("can't find %s\n", filename);

free(tmp_filename1);
free(tmp_filename2);
(snprintf(lbuf, sizeof(lbuf), f, a1, a2, a3), stat(lbuf, &cstat) == 0)

/*
* else we can't write to it but it's not under a version
* control system, so just proceed.
*/
if (cs) {
if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) ||
try("%s/RCS/%s%s", filedir, filebase, "") ||
try("%s/%s%s", filedir, filebase, RCSSUFFIX)) {
if (!statfailed) {
if ((filestat.st_mode & 0222) != 0)
/* The owner can write to it. */
fatal("file %s seems to be locked "
"by somebody else under %s\n",
filename, cs);
"by somebody else under RCS\n",
filename);
/*
* It might be checked out unlocked. See if
* it's safe to check out the default version
* locked.
*/
if (verbose)
say("Comparing file %s to default "
"%s version...\n",
filename, cs);
if (system(lbuf))
"RCS version...\n", filename);

switch (pid = fork()) {
case -1:
fatal("can't fork: %s\n",
strerror(errno));
case 0:
devnull = open("/dev/null", O_RDONLY);
if (devnull == -1) {
fatal("can't open /dev/null: %s",
strerror(errno));
}
(void)dup2(devnull, STDOUT_FILENO);
argp[0] = strdup(RCSDIFF);
argp[1] = strdup(filename);
execv(RCSDIFF, argp);
exit(127);
}
pid = waitpid(pid, &pstat, 0);
if (pid == -1 || WEXITSTATUS(pstat) != 0) {
fatal("can't check out file %s: "
"differs from default %s version\n",
filename, cs);
"differs from default RCS version\n",
filename);
}
}

if (verbose)
say("Checking out file %s from %s...\n",
filename, cs);
if (system(buf) || stat(filename, &filestat))
fatal("can't check out file %s from %s\n",
filename, cs);
say("Checking out file %s from RCS...\n",
filename);

switch (pid = fork()) {
case -1:
fatal("can't fork: %s\n", strerror(errno));
case 0:
argp[0] = strdup(CHECKOUT);
argp[1] = strdup("-l");
argp[2] = strdup(filename);
execv(CHECKOUT, argp);
exit(127);
}
pid = waitpid(pid, &pstat, 0);
if (pid == -1 || WEXITSTATUS(pstat) != 0 ||
stat(filename, &filestat)) {
fatal("can't check out file %s from RCS\n",
filename);
}
} else if (statfailed) {
fatal("can't find %s\n", filename);
}
free(tmp_filename1);
free(tmp_filename2);
}

filemode = filestat.st_mode;
if (!S_ISREG(filemode))
fatal("%s is not a normal file--can't patch\n", filename);
Expand Down

0 comments on commit 33661d0

Please sign in to comment.