Chrooting CVS Server on Solaris

a part of MarkD's Guide to CVS
last modified 12/9/03
These instructions were inspired by the server how-to page over at

The Problem

I wanted to set up CVS for both anonymous read-only access by anybody, as well as read-write access for anyone with proper authorization. I didn't set up seperate access controls (e.g. rolf can contribute the Casandrix project but not to the Portman project). With these instructions, rolf can contribute to both projects. I didn't want to cope with the added complexity a finer grain of control would entail.

I wanted something with a little more security than your standard CVS setup. Described here is a chroot'd environment, meaning that the cvs server runs in its own little protected "sandbox", which should keep most malicious goons at bay.

A Solution

These instructions are based on Solaris 2.6, but should be applicable to most unix variants.

  1. make a user to own everything. I used "cvsowner". Give it a home directory. I'm using /home/cvsowner. I also made a seperate group for them, cvs. For example purposes the uid for cvsowner is 6000, and the gid for cvs is 350.

    For the instructions that follow, I presume you're logged in as cvsowner

  2. Create the following directory structure:
  3. Obtain and compile cvs. I used version 1.10.7, available from Cyclic Software
    • unzip and untar the distribution
    • cd to the distribution directory
    • ./configure --prefix=/home/cvsowner/cvs-server-root
      gmake install

    The page said that they compiled it statically. I tried doing that, but was foiled several of the necessary Solaris libraries used symbols that only occured in shared libraries. oh well.

  4. Snarf run-cvs.c and compile it. This is a little program that Joseph Kizzier sent me that uses the chroot system call. Modify it to match the user ID and group ID for your cvsowner.
    Here's how I compiled it: gcc run-cvs.c -o run-cvs

    Then copy this somewhere where Ordinary People can't run it, since will end up being run as root by inetd
    In my case, I put it into /usr/local/sbin/run-cvs.

  5. Set up the chroot environment. This involves duplicating just enough of the unix directory hierarchy to satisfy the program. The chroot system call 'hoists' a given directory to act as /. IN our calse, /home/cvsowner/cvs-server-root will become / as far as CVS is concerned.

    I'm presuming you have sudo. If you don't have sudo, then consider everything done with command to have been done by root

    1. make the /dev entries:
      sudo mknod /home/cvsowner/cvs-server-root/dev/zero c 13 12
      sudo mknod /home/cvsowner/cvs-server-root/dev/null c 13 2

    2. Copy over any shared libraries and make symlinks:
      cd /home/cvsowner/cvs-server-root/lib
      cp /usr/lib/ .
      ln -s
      cp /usr/lib/ .
      ln -s
      cp /usr/lib/ .
      ln -s
      cp /usr/lib/ .
      ln -s
      cp /usr/lib/ .
      ln -s
      cp /usr/lib/ .
      ln -s
      cp /usr/lib/ .
    3. Symlink /usr/lib to point ot lib:
      cd /home/cvsowner/cvs-server-root/usr
      ln -s cd /home/cvsowner/cvs-server-root/lib .
    4. create /home/cvsowner/cvs-server-root/etc/group and put this in it:
    5. create /home/cvsowner/cvs-server-root/etc/passwd and put this in it:
    6. create /home/cvsowner/cvs-server-root/etc/nsswitch.conf and put this in it:
      passwd:     files
      group:      files
  6. Now that we're done with the chroot environment, It's time to configure other aspects of the machine

  7. Add this to /etc/services :
    cvspserver             2401/tcp
  8. Add this to /etc/inetd.conf :
    cvspserver	stream	tcp	nowait	root	/usr/local/sbin/run-cvs
    and then sudo kill -HUP your inetd.

  9. Now with all of that happy stuff done, it's time to actually configure your cvs installation:

    1. Create the repository:
      cvs -d /home/cvsowner/cvs-server-root/cvsweb init
      (we usually have our CVSROOTs living at /cvsweb, hence the use of the name)

    2. create the passwd file for CVS (which is different than the /etc/passwd file).
      Create /home/cvsowner/cvs-server-root/cvsweb/CVSROOT/passwd and put this in it:
      (this password is "anonymous")

    3. Check out the CVSROOT somewhere to work on configuration files:
      cd $HOME
      cvs -d /home/cvsowner/cvs-server-root/cvsweb checkout CVSROOT
    4. creat the file readers with this content:
      and check it in. This says that the CVS username 'anonymous' has read-only access. everyone else has read/write access.

      See the Random Notes section below for some notes on passwords and passwd

    5. (optional) edit cvswrappers to include the Usual Binary Files (this prevents keyword expansion inside of binary files):
      *.gif -k 'b'
      *.jpg -k 'b'
      *.png -k 'b'

      Be sure to check this back in so that it will update the CVS config databases

    6. Add any users who have read/write access to /home/cvsowner/cvs-server-root/etc/passwd file:

      If you don't have any access to a program that will create the encrypted password entries, you can snarf my pwcrypt.c. I had found a Perl script out of a book, but it didn't work correctly on Solaris.

  10. And then test your installation.
    cvs -d
    cvs login
      (enter password)
    cvs checkout CVSROOT
    If this works, you're golden.

Random Notes

Regarding passwords. CVS has two modes when checking usernames and passowrds. It looks in its local $CVSROOT/CVSROOT/passwd file. If it doesn't find the user in there (or the password doesn't authenticate), it can fall back and look in the system's /etc/passwd and compare the username/password against that, but only if the SystemAuth parameter is set to "yes".

I've decided to do it this way, and take advantage of this fall-back effect. Only anonymous lives in the CVS $CVSROOT/CVSROOT/passwd. All other cvs users live in the /etc/passwd (which is acutally /home/cvs-owner/cvs-server-root/etc/passwd).

One 'feature' of CVS I discovered is that anyone with anonymous access can get the CVSROOT project and see the configuration. There's not a whole lot that can be gleaned from this, except that the passwd file has encrypted passwords, making it an easy target for some L4M3 L3Et H4X0R, so with the authorized user passwords in the "/etc/passwd", the usernames and encrypted passwords are safely hidden away.

There's two handy tricks I used to determine which set of libraries to use. I kept doing
sudo chroot /home/cvsowner/cvs-server-root /bin/cvs
until it stopped complaining about not finding libraries.

I also had problems with cvs not being able to acquire the passwd entry for a given user. After a little grovelling around in the cvs sources, I discovered the system call getpwnam() was failing. I wrote a little getpwnam.c program, and ran it under the Solaris program truss, which shows a trace of all system calls. Using truss I was able to determine that it was missing yet another shared library.

RavenBlack dropped me an email with a handy Linux tip:

Under Linux (not sure about this under any other OS) you can do "ldd cvs" and get a list of the libraries cvs is going to require. I'm writing a setup script that makes a CVS repository in this chrooted manner (because I like the setup and it's a pain in the arse to do it again manually whenever I end up shifting to a different server), which sets up the proper chroot libraries thus:

for f in `ldd /usr/bin/cvs | cut -d' ' -f3`; do
 cp "$f" lib
Though that does rely on ldd having a very consistent output layout, and might be better done with a sed match rather than a cut.
$Id: chroot.html,v 1.3 2001/04/09 14:37:18 markd Exp $