cgi-wrap.c allows to execute CGI-scripts under a special uid/gid in a chroot environment. Unlike suexec, it does not execute arbitrary (user-)scripts but only programs which are installed and configured by the system-administrator. This allows to remove some restrictions of suexec without lowering security.

The following checks/actions will be done by cgi-wrap.c:
  1. The configuration file will be checked if it is owned by root and non-writable by group or world
  2. The configuration file will be read to get information about Any UIDs/GIDs must be given in a numeric form; user-/groupnames are not allowed.
  3. The directory will be changed and the chroot entered
  4. The UID and GID will be changed
  5. The environment will be cleaned up
  6. Environment variables like USER will be set
  7. It will be gone into the directory of the CGI-scripts
  8. The basename under which cgi-wrap.c was called (argv[0]) will be checked for strange characters (non-alpha,...)
  9. It will be checked if the CGI-script with this basename is owned by root, is not writable by group or world and does not have a set setuid bit.
  10. The CGI-script will be executed
When any check fails, the execution will be aborted.

cgi-wrap.c is a single C file and can be downloaded here


02 Jul 2003
Version 0.2 released
This version fixes a major security hole allowing the injection of arbitrary environment variables into the called program.
17 Apr 2003
Version 0.1 released


The program was compiled and is running successfully in the following environment:
It is highly probable, that it works with other Unices, compilers and libraries also.


cgi-wrap.c requires an individual configuration file for each cgi-bin directory and must be recompiled for each project. Therefore, a general installation-guide is very difficultly. Instead of, an example-installation for viewcvs is given.

Installation of the target-project

In the first step, the target-project (viewcvs) should be installed accordingly its documentation but without modifying the ScriptAlias related http-configuration. Non-public files (e.g. such ones with database passwords) should be readable by the service group or user only but not by the user running the webserver. In our example, the service user would be the user "viewcvs" (see below).

It is recommended to do the same with the CGI-scripts; e.g. to make them readable/executable by the service group only. In any case, the scripts must be owned by root and not group/word-writable.

$ ls -la /usr/share/viewcvs/cgi
drwxr-xr-x    2 root     root         1024 Apr 11 22:37 .
drwxr-xr-x    6 root     root         1024 Apr 11 22:37 ..
-rwxr-x---    1 root     viewcvs      1496 Apr 11 22:26 query.cgi
-rwxr-x---    1 root     viewcvs      1633 Apr 11 22:26 viewcvs.cgi

The build of cgi-wrap.c

Then a name for the cgi-wrap.c configuration file must be choosen which should be outside of the www-root in a non-world writable directory and owned by root. A good name might be /etc/httpd/conf/viewcvs-wrap.conf which must be told to cgi-wrap.c in the compilation stage:
diet gcc cgi-wrap.c -DCGI_USERFILE=\"/etc/httpd/conf/viewcvs-wrap.conf\" -o viewcvs-wrap
(additional flags like '-Os' after diet or '-DNDEBUG' may be useful)

The configuration file

Now, /etc/httpd/conf/viewcvs-wrap.conf can be populated with
60030 60009

The first line "60030" specifies the UID (on my system, this is the user 'viewcvs'). The second line "60030 60009" means that the script will be executed with a group-id of 60030 (group 'viewcvs') and the supplementary group 60009 (group 'cvsmgr').

"viewcvs" in the third line means that the USER environment variable should be set to this string. This value is necessarily because a name-resolution is often impossible in a minimal chroot environment and tools like rcs are failing when the username can not be determined.

The fourth line "/" specifies the chroot-directory and the fifth one the directory of the real CGI-scripts. In this case, viewcvs is not running in a chroot-environment, but there are existing real configurations (e.g. for a Wiki-system or a MRTG statistic webfrontend) where real chroot's will be used. How to create such a chroot environment is beyond the scope of this document.

The cgi-script directory

After having the configuration file, a directory like '/usr/local/lib/httpd/viewcvs' can be created where the executable viewcvs-wrap will be installed with an ownership of root.apache and a set SUID bit:
  1. mkdir -p /usr/local/lib/httpd/viewcvs
  2. cp viewcvs-wrap /usr/local/lib/httpd/viewcvs/
  3. chown root.apache /usr/local/lib/httpd/viewcvs/viewcvs-wrap
  4. chmod 04510 /usr/local/lib/httpd/viewcvs/viewcvs-wrap
or, when using install
  1. install -d /usr/local/lib/httpd/viewcvs
  2. install -m4510 -o root -g apache viewcvs-wrap /usr/local/lib/httpd/viewcvs/
For each real CGI-script a link with the basename must be created to viewcvs-wrap. In the case of viewcvs, the real scripts are named viewcvs.cgi and query.cgi:
The directory should now look like:
$ ls -l /usr/local/lib/httpd/viewcvs/
lrwxrwxrwx    1 root     root           14 Apr 11 17:41 query.cgi -> ./viewcvs-wrap
-r-s--x---    1 root     apache       8928 Apr 17 11:51 viewcvs-wrap
lrwxrwxrwx    1 root     root           14 Apr 11 17:41 viewcvs.cgi -> ./viewcvs-wrap
When you encounter messages like
... mod_mime_magic: can't read `/usr/local/lib/httpd/viewcvs/query.cgi'
in your logfiles, the viewcvs-wrap executable can be made group-readable.

The httpd-configuration

After doing this, lines like
ScriptAlias /viewcvs/cgi-bin /usr/local/lib/httpd/viewcvs
<Directory /usr/local/lib/httpd/viewcvs>
    Options +ExecCGI +SymLinksIfOwnerMatch
can be added to the httpd-configuration file and the webserver can be restarted.

Clients can access the content through the URL http://<HOSTNAME>/viewcvs/cgi-bin/viewcvs.cgi

Enrico Scholz
Last modified: Thu Jul 3 00:18:07 CEST 2003
Valid XHTML 1.0! Valid CSS!