COMMAND
make
SYSTEMS AFFECTED
All FreeBSD versions before the correction date (2000-01-16),
NetBSD, OpenBSD
PROBLEM
Following is based on FreeBSD-SA-00:01 Security Advisory FreeBSD.
The make(1) program is typically used to schedule building of
source code. It has a switch ('-j') to allow parallel building
by spawning multiple child processes.
The -j option to make(1) uses temporary files in /tmp to
communicate with its child processes by storing the shell command
the child should execute. This is useful on multi-processor
architectures for making use of all of the available CPUs, and is
also widely used on uniprocessor systems to minimize the
scheduling latency of the build process.
However make(1) uses the temporary file in an insecure way,
repeatedly deleting and reusing the same file name for the entire
life of the program. This makes it vulnerable to a race condition
wherein a malicious user could observe the name of the temporary
file being used, and replace the contents of a later instance of
the file with her desired commands after the legitimate commands
have been written.
This vulnerability was discovered as part of the FreeBSD Auditing
Project, an ongoing effort to identify and correct security
vulnerabilities in the FreeBSD operating system.
All versions of NetBSD and OpenBSD are also believed to be
vulnerable to this problem. Other systems using a BSD-derived
make(1) binary may also be vulnerable. Local users could execute
arbitrary shell commands as part of the build process scheduled by
"make -j" by another user.
SOLUTION
Avoid using the '-j' flag to make(1). Upgrade your system to one
that is listed above as having the problem resolved, or patch your
present system. To patch your present system: save the patch
below into a file, and execute the following commands as root:
cd /usr/src/usr.bin/make
patch < /path/to/patch/file
make all
make install
Patches for 3.4-STABLE and 4.0-CURRENT systems before the
resolution date:
Index: job.c
===================================================================
RCS file: /home/ncvs/src/usr.bin/make/job.c,v
retrieving revision 1.16
diff -u -r1.16 job.c
--- job.c 1999/09/11 13:08:01 1.16
+++ job.c 2000/01/17 01:42:57
@@ -163,14 +163,6 @@
#define JOB_STOPPED 3 /* The job is stopped */
/*
- * tfile is the name of a file into which all shell commands are put. It is
- * used over by removing it before the child shell is executed. The XXXXXXXXXX
- * in the string are replaced by mkstemp(3).
- */
-static char tfile[sizeof(TMPPAT)];
-
-
-/*
* Descriptions for various shells.
*/
static Shell shells[] = {
@@ -993,7 +985,7 @@
/*
* If we are aborting and the job table is now empty, we finish.
*/
- (void) eunlink(tfile);
+ (void) eunlink(job->tfile);
Finish(errors);
}
}
@@ -1668,6 +1660,7 @@
Boolean cmdsOK; /* true if the nodes commands were all right */
Boolean local; /* Set true if the job was run locally */
Boolean noExec; /* Set true if we decide not to run the job */
+ int tfd; /* File descriptor for temp file */
if (previous != NULL) {
previous->flags &= ~(JOB_FIRST|JOB_IGNERR|JOB_SILENT|JOB_REMOTE);
@@ -1697,6 +1690,12 @@
}
job->flags |= flags;
+ (void) strcpy(job->tfile, TMPPAT);
+ if ((tfd = mkstemp(job->tfile)) == -1)
+ Punt("cannot create temp file: %s", strerror(errno));
+ else
+ (void) close(tfd);
+
/*
* Check the commands now so any attributes from .DEFAULT have a chance
* to migrate to the node
@@ -1722,9 +1721,9 @@
DieHorribly();
}
- job->cmdFILE = fopen(tfile, "w+");
+ job->cmdFILE = fopen(job->tfile, "w+");
if (job->cmdFILE == NULL) {
- Punt("Could not open %s", tfile);
+ Punt("Could not open %s", job->tfile);
}
(void) fcntl(FILENO(job->cmdFILE), F_SETFD, 1);
/*
@@ -1830,7 +1829,7 @@
* Unlink and close the command file if we opened one
*/
if (job->cmdFILE != stdout) {
- (void) eunlink(tfile);
+ (void) eunlink(job->tfile);
if (job->cmdFILE != NULL)
(void) fclose(job->cmdFILE);
} else {
@@ -1859,7 +1858,7 @@
}
} else {
(void) fflush(job->cmdFILE);
- (void) eunlink(tfile);
+ (void) eunlink(job->tfile);
}
/*
@@ -2403,13 +2402,6 @@
* be running at once. */
{
GNode *begin; /* node for commands to do at the very start */
- int tfd;
-
- (void) strcpy(tfile, TMPPAT);
- if ((tfd = mkstemp(tfile)) == -1)
- Punt("cannot create temp file: %s", strerror(errno));
- else
- (void) close(tfd);
jobs = Lst_Init(FALSE);
stoppedJobs = Lst_Init(FALSE);
@@ -2914,7 +2906,7 @@
}
}
}
- (void) eunlink(tfile);
+ (void) eunlink(job->tfile);
}
/*
@@ -2948,7 +2940,6 @@
}
}
}
- (void) eunlink(tfile);
return(errors);
}
@@ -3024,6 +3015,7 @@
KILL(job->pid, SIGINT);
KILL(job->pid, SIGKILL);
#endif /* RMT_WANTS_SIGNALS */
+ (void) eunlink(job->tfile);
}
}
@@ -3032,7 +3024,6 @@
*/
while (waitpid((pid_t) -1, &foo, WNOHANG) > 0)
continue;
- (void) eunlink(tfile);
}
#ifdef REMOTE
Index: job.h
===================================================================
RCS file: /home/ncvs/src/usr.bin/make/job.h,v
retrieving revision 1.10
diff -u -r1.10 job.h
--- job.h 1999/08/28 01:03:31 1.10
+++ job.h 2000/01/17 01:42:31
@@ -93,6 +93,8 @@
#define JOB_BUFSIZE 1024
typedef struct Job {
int pid; /* The child's process ID */
+ char tfile[sizeof(TMPPAT)];
+ /* Temporary file to use for job */
GNode *node; /* The target the child is making */
LstNode tailCmds; /* The node of the first command to be
* saved when the job has been run */