Apache+suexec Resource Limits

(See important updates at bottom from 9/28/2005!)

The company I work for maintains a hosting facility, and we use the Apache webserver on FreeBSD.

Back in April 2000, I realized that Apache's suexec module was not enforcing any sort of resource limits on the processes that it forked. I submitted this as Apache PR#6017. The issue was marked as non-critical with a "medium" priority, and I haven't yet received any response from the Apache team on this issue. I'm setting up this page as a resource for others who might be in the same boat, to share what I've since learned.

The technical issue seems to be that resource limitations are handled differently on various different platforms, and so suexec doesn't have a good uniform way to enforce these genericly. As well, resource limitations in Apache, even without suexec, seem to be somewhat unreliable, as has been mentioned in several other problem reports: 5901, 4551, 3482.

Some progress with the issue has been made with FreeBSD, the operating system I happened to be running in the scenario where I needed the limitations. FreeBSD PR#13606 deals with this issue a bit. There is a patch submitted there that mostly works, except leaves out the note that Scot Hetzel made, "You will also need to add "-lutil" to the suexec entry in src/support/Makefile.tmpl".

Note that in October of 2002, the FreeBSD team marked PR#13606 as closed with the note "alternative patch committed." What exactly that means, I'm not sure (what source tree, which patch, etc).

I've posted the unified patch here: suexec_rsrclimit.patch. I've verified that this patch applies cleanly to the Apache 1.3.22 release source tree.

There's another patch out there that does the same thing in a slightly different way, and hard-codes the resource class that's used, instead of basing it on the one specified per user in the password database.

That gets us to a point where, when the login.conf setup is correctly enabled, suexec will impose limitations on things like memory and CPU time for forked CGI scripts. However, the limitations do not appear to work for things like "max number of processes". I think this is related to the fact that, by the time suexec is working, telling a process it doesn't have the resources available to fork isn't an option - but that's where my understanding of the code stops at the moment.

I was hoping that the recent release of Apache 2.0.35 would include some sort of note or fix for these issues, but I don't see any major differences in the suexec source in the 2.0 codeline. Sigh.

So, I'm looking for feedback from other folks who may have worked in this same problem space, or who have suggestions to make about possible solutions. E-mail me or post below. Thanks!

UPDATE on Sep 28, 2005: As Peter Brezny noted in an e-mail earlier this year (okay, um, February), the FreeBSD port for Apache 1.3.x has had patches in place to add some resource limiting for some time now - in particular, patch-ak, patch-ba, and patch-bb gave me what I was interested in. The 1.3.33 tree compiled fine with these in place. I verified that suexec'ed Perl processes were indeed killed off for very low values of the resource settings in /etc/login.conf, but the only visible output was a "premature end of script headers" in the http error log, which was not so useful. I also couldn't get limits like "max processes" or "cpu time" to have an effect, so there's more exploring to do there.


7 most recent User Comments on this Page      [view all comments] [all comments on this site] [posting policy]
(powered by SSIComment)


suexec.patch posted by dard on Dec 11, 2003 at 07:39:50 AM

--- suexec.c.orig Wed Mar 5 18:50:29 2003
+++ suexec.c Thu Dec 11 20:51:55 2003
@@ -91,6 +91,18 @@

#include

+/* patch activates rlimits via login.conf including maxproc entry
+ * do not forget add -lutil -lkmv to Makefile
+ * any questions and suggestions dard at mail.ru
+ * credits: sg at di-net.ru
+ */
+
+#include
+#include
+#include
+#include
+#include
+
#include "suexec.h"

/*
@@ -280,6 +292,11 @@
struct group *gr; /* group entry holder */
struct stat dir_info; /* directory info holder */
struct stat prg_info; /* program info holder */
+ struct login_cap_t *lc;
+ kvm_t *kd;
+ static struct kinfo_proc *pbase;
+ static int nproc=0; /* number of user running processes */
+ rlim_t max_proc=1000; /* maximum allowed processes from login.conf */

/*
* Start with a "clean" environment
@@ -447,9 +464,74 @@
/*
* Save these for later since initgroups will hose the struct
*/
+ target_homedir = strdup(pw->pw_dir);
+
+ /*
+ * Stat the cwd and verify it is a directory, or error out.
+ */
+ if (getcwd(cwd, AP_MAXPATH) == NULL) {
+ log_err("emerg: cannot get current working directory\n");
+ exit(111);
+ }
+
+ if (((lstat(cwd, &dir_info)) != 0) || !(S_ISDIR(dir_info.st_mode))) {
+ log_err("error: cannot stat directory: (%s)\n", cwd);
+ exit(115);
+ }
+
+ /*
+ * Error out if cwd is writable by others.
+ */
+ if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) {
+ log_err("error: directory is writable by others: (%s)\n", cwd);
+ exit(116);
+ }
+
+ /*
+ * Error out if we cannot stat the program.
+ */
+ if (((lstat(cmd, &prg_info)) != 0) || (S_ISLNK(prg_info.st_mode))) {
+ log_err("error: cannot stat program: (%s)\n", cmd);
+ exit(117);
+ }
+
+ /*
+ * Error out if the program is writable by others.
+ */
+ if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP)) {
+ log_err("error: file is writable by others: (%s/%s)\n", cwd, cmd);
+ exit(118);
+ }
+
+ /*
+ * Error out if the file is setuid or setgid.
+ */
+ if ((prg_info.st_mode & S_ISUID) || (prg_info.st_mode & S_ISGID)) {
+ log_err("error: file is either setuid or setgid: (%s/%s)\n", cwd, cmd);
+ exit(119);
+ }
+
+ /*
+ * Error out if the target name/group is different from
+ * the name/group of the cwd or the program.
+ */
+ if ((prg_info.st_uid != dir_info.st_uid) ||
+ (prg_info.st_gid != dir_info.st_gid)) {
+ log_err("error: program uid/gid (%ld/%ld) mismatch "
+ "with directory (%ld/%ld)\n",
+ prg_info.st_uid, prg_info.st_gid,
+ dir_info.st_uid, dir_info.st_gid);
+ exit(120);
+ }
+
+ pw=getpwuid(prg_info.st_uid);
+ if (pw==NULL) {
+ log_err("error: invalid uid %ld\n",prg_info.st_uid);
+ exit(105);
+ }
+
uid = pw->pw_uid;
actual_uname = strdup(pw->pw_name);
- target_homedir = strdup(pw->pw_dir);

/*
* Log the transaction here to be sure we have an open log
@@ -478,25 +560,44 @@
exit(108);
}

+ if ((kd = kvm_open(NULL,NULL,NULL,O_RDONLY,"kvm_open")) == NULL) {
+ log_err("crit: Error opening kernel memory.\n");
+ exit(199);
+ }
+ pbase = kvm_getprocs(kd, KERN_PROC_UID, uid, &nproc);
+ kvm_close(kd);
+
+ lc=login_getpwclass(pw);
+ if ((max_proc = login_getcapnum(lc, "maxproc", max_proc, max_proc)) == RLIM_INFINITY) {
+ log_err("info: maxproc is infinity for user id %d\n",uid);
+ }
+ login_close(lc);
+
+ if (((rlim_t)nproc) > max_proc) {
+ log_err("crit: maxproc limit exceeded by uid %d\n",uid);
+ exit(199);
+ }
+ setusercontext(lc,pw,pw->pw_uid,LOGIN_SETALL&~LOGIN_SETLOGIN);
+
/*
* Change UID/GID here so that the following tests work over NFS.
*
* Initialize the group access list for the target user,
* and setgid() to the target group. If unsuccessful, error out.
*/
- if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) {
+/* if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) {
log_err("emerg: failed to setgid (%ld: %s)\n", gid, cmd);
exit(109);
}
-
+*/
/*
* setuid() to the target user. Error out on fail.
*/
- if ((setuid(uid)) != 0) {
+/* if ((setuid(uid)) != 0) {
log_err("emerg: failed to setuid (%ld: %s)\n", uid, cmd);
exit(110);
}
-
+*/
/*
* Get the current working directory, as well as the proper
* document root (dependant upon whether or not it is a
@@ -505,10 +606,6 @@
* Use chdir()s and getcwd()s to avoid problems with symlinked
* directories. Yuck.
*/
- if (getcwd(cwd, AP_MAXPATH) == NULL) {
- log_err("emerg: cannot get current working directory\n");
- exit(111);
- }

if (userdir) {
if (((chdir(target_homedir)) != 0) ||
@@ -534,61 +631,6 @@
exit(114);
}

- /*
- * Stat the cwd and verify it is a directory, or error out.
- */
- if (((lstat(cwd, &dir_info)) != 0) || !(S_ISDIR(dir_info.st_mode))) {
- log_err("error: cannot stat directory: (%s)\n", cwd);
- exit(115);
- }
-
- /*
- * Error out if cwd is writable by others.
- */
- if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) {
- log_err("error: directory is writable by others: (%s)\n", cwd);
- exit(116);
- }
-
- /*
- * Error out if we cannot stat the program.
- */
- if (((lstat(cmd, &prg_info)) != 0) || (S_ISLNK(prg_info.st_mode))) {
- log_err("error: cannot stat program: (%s)\n", cmd);
- exit(117);
- }
-
- /*
- * Error out if the program is writable by others.
- */
- if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP)) {
- log_err("error: file is writable by others: (%s/%s)\n", cwd, cmd);
- exit(118);
- }
-
- /*
- * Error out if the file is setuid or setgid.
- */
- if ((prg_info.st_mode & S_ISUID) || (prg_info.st_mode & S_ISGID)) {
- log_err("error: file is either setuid or setgid: (%s/%s)\n", cwd, cmd);
- exit(119);
- }
-
- /*
- * Error out if the target name/group is different from
- * the name/group of the cwd or the program.
- */
- if ((uid != dir_info.st_uid) ||
- (gid != dir_info.st_gid) ||
- (uid != prg_info.st_uid) ||
- (gid != prg_info.st_gid)) {
- log_err("error: target uid/gid (%ld/%ld) mismatch "
- "with directory (%ld/%ld) or program (%ld/%ld)\n",
- uid, gid,
- dir_info.st_uid, dir_info.st_gid,
- prg_info.st_uid, prg_info.st_gid);
- exit(120);
- }
/*
* Error out if the program is not executable for the user.
* Otherwise, she won't find any error in the logs except for


apache posted by eric clope on Jul 05, 2003 at 02:40:46 AM

you know that resource limiting has been in the ports tree for some time now... it's one of the apache13 patches that's applied when installing from the ports tree..

just fyi,
thanks..and great site


Post your comment (all fields but email are required):   

E-mail (will be spam-proofed)
Name
Subject
Comments
You may use the following HTML tags in your comments:
<B><I><P><A><LI><OL><UL><EM><BR><STRONG><BLOCKQUOTE><HR><DIV><TT>
 

 

 


Last modified: Wednesday, 28-Sep-2005 17:32:43 EDT