Some notes on Apache's suexec

November 26, 2015

We've recently been wrestling with suexec in an attempt to get it to do something that it seemed that suexec would do. As a result of that learning experience, I feel like writing down some things about suexec. You may wish to also read the official Apache documentation on suexec, but note that you may have to pay close attention to some of the things that it says (and a few things appear to be outright wrong).

Suexec has two modes:

  1. Running /~<user>/... CGIs as the particular user involved. This needs no special extra configuration for suexec and simply just happens. Per-user CGIs must be located under a specific subdirectory in the user's Unix home directory, by default public_html; suexec documentation calls this subdirectory name the userdir.

  2. Running CGIs for a virtual host as a particular user and group. This must be configured with the SuexecUserGroup directive. All virtual host CGIs must be located under a specific top level directory, by default often /var/www; suexec documentation calls this directory the docroot.

(Suexec also does various ownership and permissions checks on the CGIs and the directory they are directly in. Those are beyond the scope of these notes.)

The first important thing here is that the suexec docroot and userdir are not taken from the Apache DocumentRoot and UserDir settings; instead, they're hard coded into suexec itself. Any time that suexec logs errors like 'command not in docroot', the docroot it means is not the Apache DocumentRoot you've configured. It pretty much follows that if your Apache settings do not match the hardcoded suexec settings, suexec will thumb its nose at you.

(Also, the only form of UserDir directive that will ever work with suexec is 'UserDir somename'. You cannot use either 'UserDir /some/dir' or 'UserDir /some/*/subdir' with suexec. The suexec documentation notes this.)

The second important thing is that Apache and suexec explicitly distinguish between the two modes based on the incoming request itself, not the final paths involved, and these two modes are exclusive. If you make a request for a CGI via a /~user/... URL, the only thing that matters is if the eventual path is under the user's home directory plus the suexec userdir. If you make a request to a virtual host with a SuexecUserGroup directive, the only thing that matters is if the eventual path is under the suexec docroot. In particular, you cannot configure a virtual host for a user, point its DocumentRoot to that user's userdir, and have suexec run CGIs. This path would be perfectly acceptable if the CGIs were invoked via /~user/... URLs, but when invoked for a plain virtual host, suexec will reject these requests because the paths aren't under its docroot.

(Mechanically, Apache prefixes the user name it passes to the suexec binary with a ~ if it is a UserDir request. This is undocumented behavior reverse engineered from the code, so you shouldn't count on it.)

The third important thing is that suexec ignores symlinks in all of this checking; it uses only the 'real' physical paths, after symlinks have been traversed. As a result you cannot fool suexec by, for example, putting symlinks to elsewhere under what it considers its docroot. However it is fine for user /etc/passwd entries to include symlinks (as we do); suexec will not be upset by that.

Normally the suexec docroot and userdir are set when suexec is compiled and are fixed afterwards, which obviously creates some problems if you need something different. Debian and Ubuntu provide a second version of suexec that can look these up at runtime from a configuration file (this is the apache2-suexec-custom package). Failing this, well, you'll be arranging (somehow) for all of your virtual hosts to appear under /var/www (or at least all of the ones that need CGIs).

(You can determine the userdir and docroot settings for your suexec with 'suexec -V' as root. You want AP_DOC_ROOT and AP_USERDIR_SUFFIX.)

Sidebar: what 'command not in docroot' really means

The suexec error 'command not in docroot' is actually generic and is used for both modes of requests. So what suexec means by 'docroot' here is either the actual docroot, for a virtual host request, or the user's home directory plus the userdir subdirectory, for a /~user/... request. Unfortunately you cannot tell from suexec's log messages whether it was invoked for what it thought was a user home directory request or for a virtual host request; that has to be obtained from the Apache logs.

The check is done by a simple brute force method: first, chdir() to the CGI's directory and do a getcwd(). Then chdir() to either the docroot or the user's home directory plus the userdir and do another getcwd(). Compare the two directory paths and fail if the first is not underneath the second. Because it uses getcwd(), all symlinks involved in either path will wind up getting fully expanded.

Written on 26 November 2015.
« Why speeding up (C)Python matters
A thought on the apparent popularity of new static typing languages »

Page tools: View Source, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Thu Nov 26 01:02:43 2015
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.