Bug 38116 - Chroot-Patch for SuExec
Summary: Chroot-Patch for SuExec
Status: RESOLVED LATER
Alias: None
Product: Apache httpd-2
Classification: Unclassified
Component: mod_suexec (show other bugs)
Version: 2.0.55
Hardware: Other Linux
: P2 enhancement (vote)
Target Milestone: ---
Assignee: Apache HTTPD Bugs Mailing List
URL:
Keywords: MassUpdate
Depends on:
Blocks:
 
Reported: 2006-01-04 10:12 UTC by Florian Doersch
Modified: 2018-11-07 21:09 UTC (History)
1 user (show)



Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Florian Doersch 2006-01-04 10:12:08 UTC
I created a SuExec Modification, which chroots first to the User-Homedir and 
then executes the cgi-script (kind of "mpm_peruser", but faster on machines 
with low memory)

The Interpreter(s) are needed to be in the jail too, otherwise it can't 
execute the script then.

I created it with httpd 2.0.55, but it should work on other versions too.

It works for example if you have such a directory structure:

/srv/www
/srv/www/vhosts
/srv/www/vhosts/user1
/srv/www/vhosts/user1/htdocs
/srv/www/vhosts/user2
/srv/www/vhosts/user2/htdocs

So a request for "user1" will be chrooted first to /srv/www/vhosts/user1, and 
then it will execute the specified script in that directory.

Sincerely
F. D.

---

diff -urN httpd-2.0.55-orig/support/suexec.c httpd-2.0.55/support/suexec.c
--- httpd-2.0.55-orig/support/suexec.c	2005-02-04 21:21:18.000000000 +0100
+++ httpd-2.0.55/support/suexec.c	2006-01-04 10:02:21.017195966 +0100
@@ -1,34 +1,34 @@
 /* Copyright 1999-2005 The Apache Software Foundation or its licensors, as
- * applicable.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+* applicable.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
 
 /*
- * suexec.c -- "Wrapper" support program for suEXEC behaviour for Apache
- *
- ***********************************************************************
- *
- * NOTE! : DO NOT edit this code!!!  Unless you know what you are doing,
- *         editing this code might open up your system in unexpected 
- *         ways to would-be crackers.  Every precaution has been taken 
- *         to make this code as safe as possible; alter it at your own
- *         risk.
- *
- ***********************************************************************
- *
- *
- */
+* suexec.c -- "Wrapper" support program for suEXEC behaviour for Apache
+*
+***********************************************************************
+*
+* NOTE! : DO NOT edit this code!!!  Unless you know what you are doing,
+*         editing this code might open up your system in unexpected 
+*         ways to would-be crackers.  Every precaution has been taken 
+*         to make this code as safe as possible; alter it at your own
+*         risk.
+*
+***********************************************************************
+*
+*
+*/
 
 #include "apr.h"
 #include "ap_config.h"
@@ -55,21 +55,166 @@
 #include <grp.h>
 #endif
 
+
+/**********************/
+/* CHROOT Data, Begin */
+
+#ifdef MAXPATHLEN
+#define PATH_LEN MAXPATHLEN
+#else
+#define PATH_LEN PATH_MAX
+#endif
+
+#define PASSWD_FILE     "/etc/passwd"
+#define LINE_SZ         256
+
+struct passwd_data_s {
+  char *login;
+  char *pass;
+  int  id;
+  int  gid;
+  char *gecos;
+  char *dir;
+  char *shell;
+};
+
+typedef struct passwd_data_s passwd_data;
+
+/**********************/
+
+void free_passwd_data(passwd_data *p) {
+
+  if (p == NULL)
+    return;
+
+  if (p->login!=NULL) free(p->login);
+  if (p->pass!=NULL)  free(p->pass);
+  if (p->gecos!=NULL) free(p->gecos);
+  if (p->dir!=NULL)   free(p->dir);
+  if (p->shell!=NULL) free(p->shell);
+
+  free(p);
+}
+
+int fill_from_string(char *str, passwd_data *data) {
+  char *p = str;
+  char buff[LINE_SZ];
+  int n = 0;
+  int state = 0;
+
+  memset(buff,'\0',LINE_SZ);
+
+  while (*p != '\0') {
+
+    if ((*p == ':') || (*p == '\n')) {
+      switch (state) {
+      case 0: 
+        data->login = (char *)malloc(strlen(buff)+1);
+        memset(data->login,'\0',strlen(buff)+1);
+        strcpy(data->login,buff);
+        break;
+      case 1: 
+        data->pass = (char *)malloc(strlen(buff)+1);
+        memset(data->pass,'\0',strlen(buff)+1);
+        strcpy(data->pass,buff);
+        break;
+      case 2: 
+        data->id = atoi(buff);
+        break;
+      case 3: 
+        data->gid = atoi(buff);
+        break;
+      case 4: 
+        data->gecos = (char *)malloc(strlen(buff)+1);
+        memset(data->gecos,'\0',strlen(buff)+1);
+        strcpy(data->gecos,buff);
+        break;
+      case 5: 
+        data->dir = (char *)malloc(strlen(buff)+1);
+        memset(data->dir,'\0',strlen(buff)+1);
+        strcpy(data->dir,buff);
+        break;
+      case 6: 
+        data->shell = (char *)malloc(strlen(buff)+1);
+        memset(data->shell,'\0',strlen(buff)+1);
+        strncpy(data->shell,buff,strlen(buff));
+        break;
+      }
+
+      memset(buff,'\0',LINE_SZ);
+      n=0;
+      state++;
+    }
+    else {
+      buff[n] = *p;
+      n++;
+    }
+
+    p++;
+  }
+
+  if (state > 6) 
+    return(1);
+
+  return(0);
+}
+
+passwd_data *getpasswddata(int uid) {
+  char line[LINE_SZ];
+  passwd_data *pd = NULL;
+  FILE *f = NULL;
+
+  f = fopen(PASSWD_FILE,"r");
+
+  if (!f) 
+    return(pd);
+
+  while (!feof(f)) {
+
+    memset(line,'\0',LINE_SZ);
+
+    if (fgets(line,LINE_SZ,f)==NULL) {
+      pd = NULL;
+      break;
+    }
+
+    pd = (passwd_data *)malloc(sizeof(passwd_data));
+    memset(pd,'\0',sizeof(passwd_data));
+
+    if (!fill_from_string(line,pd)) {
+      free_passwd_data(pd);
+      continue;
+    }
+
+    if (pd->id == uid) 
+      break;
+
+    free_passwd_data(pd);
+  } 
+
+  fclose(f);
+  return(pd);
+
+};
+
+/* CHROOT Data, End   */
+/**********************/
+
 /*
- ***********************************************************************
- * There is no initgroups() in QNX, so I believe this is safe :-)
- * Use cc -osuexec -3 -O -mf -DQNX suexec.c to compile.
- *
- * May 17, 1997.
- * Igor N. Kovalenko -- infoh@mail.wplus.net
- ***********************************************************************
- */
+***********************************************************************
+* There is no initgroups() in QNX, so I believe this is safe :-)
+* Use cc -osuexec -3 -O -mf -DQNX suexec.c to compile.
+*
+* May 17, 1997.
+* Igor N. Kovalenko -- infoh@mail.wplus.net
+***********************************************************************
+*/
 
 #if defined(NEED_INITGROUPS)
 int initgroups(const char *name, gid_t basegid)
 {
-    /* QNX and MPE do not appear to support supplementary groups. */
-    return 0;
+  /* QNX and MPE do not appear to support supplementary groups. */
+  return 0;
 }
 #endif
 
@@ -93,527 +238,583 @@
 
 char *safe_env_lst[] =
 {
-    /* variable name starts with */
-    "HTTP_",
-    "SSL_",
-
-    /* variable name is */
-    "AUTH_TYPE=",
-    "CONTENT_LENGTH=",
-    "CONTENT_TYPE=",
-    "DATE_GMT=",
-    "DATE_LOCAL=",
-    "DOCUMENT_NAME=",
-    "DOCUMENT_PATH_INFO=",
-    "DOCUMENT_ROOT=",
-    "DOCUMENT_URI=",
-    "FILEPATH_INFO=",
-    "GATEWAY_INTERFACE=",
-    "HTTPS=",
-    "LAST_MODIFIED=",
-    "PATH_INFO=",
-    "PATH_TRANSLATED=",
-    "QUERY_STRING=",
-    "QUERY_STRING_UNESCAPED=",
-    "REMOTE_ADDR=",
-    "REMOTE_HOST=",
-    "REMOTE_IDENT=",
-    "REMOTE_PORT=",
-    "REMOTE_USER=",
-    "REDIRECT_QUERY_STRING=",
-    "REDIRECT_REMOTE_USER=",
-    "REDIRECT_STATUS=",
-    "REDIRECT_URL=",
-    "REQUEST_METHOD=",
-    "REQUEST_URI=",
-    "SCRIPT_FILENAME=",
-    "SCRIPT_NAME=",
-    "SCRIPT_URI=",
-    "SCRIPT_URL=",
-    "SERVER_ADMIN=",
-    "SERVER_NAME=",
-    "SERVER_ADDR=",
-    "SERVER_PORT=",
-    "SERVER_PROTOCOL=",
-    "SERVER_SIGNATURE=",
-    "SERVER_SOFTWARE=",
-    "UNIQUE_ID=",
-    "USER_NAME=",
-    "TZ=",
-    NULL
+  /* variable name starts with */
+  "HTTP_",
+  "SSL_",
+
+  /* variable name is */
+  "AUTH_TYPE=",
+  "CONTENT_LENGTH=",
+  "CONTENT_TYPE=",
+  "DATE_GMT=",
+  "DATE_LOCAL=",
+  "DOCUMENT_NAME=",
+  "DOCUMENT_PATH_INFO=",
+  "DOCUMENT_ROOT=",
+  "DOCUMENT_URI=",
+  "FILEPATH_INFO=",
+  "GATEWAY_INTERFACE=",
+  "HTTPS=",
+  "LAST_MODIFIED=",
+  "PATH_INFO=",
+  "PATH_TRANSLATED=",
+  "QUERY_STRING=",
+  "QUERY_STRING_UNESCAPED=",
+  "REMOTE_ADDR=",
+  "REMOTE_HOST=",
+  "REMOTE_IDENT=",
+  "REMOTE_PORT=",
+  "REMOTE_USER=",
+  "REDIRECT_QUERY_STRING=",
+  "REDIRECT_REMOTE_USER=",
+  "REDIRECT_STATUS=",
+  "REDIRECT_URL=",
+  "REQUEST_METHOD=",
+  "REQUEST_URI=",
+  "SCRIPT_FILENAME=",
+  "SCRIPT_NAME=",
+  "SCRIPT_URI=",
+  "SCRIPT_URL=",
+  "SERVER_ADMIN=",
+  "SERVER_NAME=",
+  "SERVER_ADDR=",
+  "SERVER_PORT=",
+  "SERVER_PROTOCOL=",
+  "SERVER_SIGNATURE=",
+  "SERVER_SOFTWARE=",
+  "UNIQUE_ID=",
+  "USER_NAME=",
+  "TZ=",
+  NULL
 };
 
 
 static void err_output(const char *fmt, va_list ap)
 {
 #ifdef AP_LOG_EXEC
-    time_t timevar;
-    struct tm *lt;
+  time_t timevar;
+  struct tm *lt;
 
-    if (!log) {
-        if ((log = fopen(AP_LOG_EXEC, "a")) == NULL) {
-            fprintf(stderr, "failed to open log file\n");
-            perror("fopen");
-            exit(1);
-        }
+  if (!log) {
+    if ((log = fopen(AP_LOG_EXEC, "a")) == NULL) {
+      fprintf(stderr, "failed to open log file %s\n",AP_LOG_EXEC);
+      perror("fopen");
+      exit(1);
     }
+  }
 
-    time(&timevar);
-    lt = localtime(&timevar);
+  time(&timevar);
+  lt = localtime(&timevar);
 
-    fprintf(log, "[%d-%.2d-%.2d %.2d:%.2d:%.2d]: ",
-            lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
-            lt->tm_hour, lt->tm_min, lt->tm_sec);
+  fprintf(log, "[%d-%.2d-%.2d %.2d:%.2d:%.2d]: ",
+    lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
+    lt->tm_hour, lt->tm_min, lt->tm_sec);
 
-    vfprintf(log, fmt, ap);
+  vfprintf(log, fmt, ap);
 
-    fflush(log);
+  fflush(log);
 #endif /* AP_LOG_EXEC */
-    return;
+  return;
 }
 
 static void log_err(const char *fmt,...)
 {
 #ifdef AP_LOG_EXEC
-    va_list ap;
+  va_list ap;
 
-    va_start(ap, fmt);
-    err_output(fmt, ap);
-    va_end(ap);
+  va_start(ap, fmt);
+  err_output(fmt, ap);
+  va_end(ap);
 #endif /* AP_LOG_EXEC */
-    return;
+  return;
 }
 
 static void clean_env(void)
 {
-    char pathbuf[512];
-    char **cleanenv;
-    char **ep;
-    int cidx = 0;
-    int idx;
-
-    /* While cleaning the environment, the environment should be clean.
-     * (e.g. malloc() may get the name of a file for writing debugging info.
-     * Bad news if MALLOC_DEBUG_FILE is set to /etc/passwd.  Sprintf() may be
-     * susceptible to bad locale settings....)
-     * (from PR 2790)
-     */
-    char **envp = environ;
-    char *empty_ptr = NULL;
- 
-    environ = &empty_ptr; /* VERY safe environment */
-
-    if ((cleanenv = (char **) calloc(AP_ENVBUF, sizeof(char *))) == NULL) {
-        log_err("failed to malloc memory for environment\n");
-        exit(120);
-    }
-
-    sprintf(pathbuf, "PATH=%s", AP_SAFE_PATH);
-    cleanenv[cidx] = strdup(pathbuf);
-    cidx++;
-
-    for (ep = envp; *ep && cidx < AP_ENVBUF-1; ep++) {
-        for (idx = 0; safe_env_lst[idx]; idx++) {
-            if (!strncmp(*ep, safe_env_lst[idx],
-                         strlen(safe_env_lst[idx]))) {
-                cleanenv[cidx] = *ep;
-                cidx++;
-                break;
-            }
-        }
+  char pathbuf[512];
+  char **cleanenv;
+  char **ep;
+  int cidx = 0;
+  int idx;
+
+  /* While cleaning the environment, the environment should be clean.
+  * (e.g. malloc() may get the name of a file for writing debugging info.
+  * Bad news if MALLOC_DEBUG_FILE is set to /etc/passwd.  Sprintf() may be
+  * susceptible to bad locale settings....)
+  * (from PR 2790)
+  */
+  char **envp = environ;
+  char *empty_ptr = NULL;
+
+  environ = &empty_ptr; /* VERY safe environment */
+
+  if ((cleanenv = (char **) calloc(AP_ENVBUF, sizeof(char *))) == NULL) {
+    log_err("failed to malloc memory for environment\n");
+    exit(120);
+  }
+
+  sprintf(pathbuf, "PATH=%s", AP_SAFE_PATH);
+  cleanenv[cidx] = strdup(pathbuf);
+  cidx++;
+
+  for (ep = envp; *ep && cidx < AP_ENVBUF-1; ep++) {
+    for (idx = 0; safe_env_lst[idx]; idx++) {
+      if (!strncmp(*ep, safe_env_lst[idx],
+        strlen(safe_env_lst[idx]))) {
+          cleanenv[cidx] = *ep;
+          cidx++;
+          break;
+      }
     }
+  }
 
-    cleanenv[cidx] = NULL;
+  cleanenv[cidx] = NULL;
 
-    environ = cleanenv;
+  environ = cleanenv;
 }
 
 int main(int argc, char *argv[])
 {
-    int userdir = 0;        /* ~userdir flag             */
-    uid_t uid;              /* user information          */
-    gid_t gid;              /* target group placeholder  */
-    char *target_uname;     /* target user name          */
-    char *target_gname;     /* target group name         */
-    char *target_homedir;   /* target home directory     */
-    char *actual_uname;     /* actual user name          */
-    char *actual_gname;     /* actual group name         */
-    char *prog;             /* name of this program      */
-    char *cmd;              /* command to be executed    */
-    char cwd[AP_MAXPATH];   /* current working directory */
-    char dwd[AP_MAXPATH];   /* docroot working directory */
-    struct passwd *pw;      /* password entry holder     */
-    struct group *gr;       /* group entry holder        */
-    struct stat dir_info;   /* directory info holder     */
-    struct stat prg_info;   /* program info holder       */
-
-    /*
-     * Start with a "clean" environment
-     */
-    clean_env();
-
-    prog = argv[0];
-    /*
-     * Check existence/validity of the UID of the user
-     * running this program.  Error out if invalid.
-     */
-    uid = getuid();
-    if ((pw = getpwuid(uid)) == NULL) {
-        log_err("crit: invalid uid: (%ld)\n", uid);
-        exit(102);
-    }
-    /*
-     * See if this is a 'how were you compiled' request, and
-     * comply if so.
-     */
-    if ((argc > 1)
-        && (! strcmp(argv[1], "-V"))
-        && ((uid == 0)
+  /**********************/
+  /* CHROOT Vars, Begin */
+  int ret;
+  passwd_data *pwdent;
+  char *tmp_str;
+  /* CHROOT Vars, End   */
+  /**********************/
+
+
+  int userdir = 0;        /* ~userdir flag             */
+  uid_t uid;              /* user information          */
+  gid_t gid;              /* target group placeholder  */
+  char *target_uname;     /* target user name          */
+  char *target_gname;     /* target group name         */
+  char *target_homedir;   /* target home directory     */
+  char *actual_uname;     /* actual user name          */
+  char *actual_gname;     /* actual group name         */
+  char *prog;             /* name of this program      */
+  char *cmd;              /* command to be executed    */
+  char cwd[AP_MAXPATH];   /* current working directory */
+  char dwd[AP_MAXPATH];   /* docroot working directory */
+  struct passwd *pw;      /* password entry holder     */
+  struct group *gr;       /* group entry holder        */
+  struct stat dir_info;   /* directory info holder     */
+  struct stat prg_info;   /* program info holder       */
+
+  /*
+  * Start with a "clean" environment
+  */
+  clean_env();
+
+  prog = argv[0];
+  /*
+  * Check existence/validity of the UID of the user
+  * running this program.  Error out if invalid.
+  */
+  uid = getuid();
+  if ((pw = getpwuid(uid)) == NULL) {
+    log_err("crit: invalid uid: (%ld)\n", uid);
+    exit(102);
+  }
+  /*
+  * See if this is a 'how were you compiled' request, and
+  * comply if so.
+  */
+  if ((argc > 1)
+    && (! strcmp(argv[1], "-V"))
+    && ((uid == 0)
 #ifdef _OSD_POSIX
-        /* User name comparisons are case insensitive on BS2000/OSD */
-            || (! strcasecmp(AP_HTTPD_USER, pw->pw_name)))
+    /* User name comparisons are case insensitive on BS2000/OSD */
+    || (! strcasecmp(AP_HTTPD_USER, pw->pw_name)))
 #else  /* _OSD_POSIX */
-            || (! strcmp(AP_HTTPD_USER, pw->pw_name)))
+    || (! strcmp(AP_HTTPD_USER, pw->pw_name)))
 #endif /* _OSD_POSIX */
-        ) {
+    ) {
 #ifdef AP_DOC_ROOT
-        fprintf(stderr, " -D AP_DOC_ROOT=\"%s\"\n", AP_DOC_ROOT);
+      fprintf(stderr, " -D AP_DOC_ROOT=\"%s\"\n", AP_DOC_ROOT);
 #endif
 #ifdef AP_GID_MIN
-        fprintf(stderr, " -D AP_GID_MIN=%d\n", AP_GID_MIN);
+      fprintf(stderr, " -D AP_GID_MIN=%d\n", AP_GID_MIN);
 #endif
 #ifdef AP_HTTPD_USER
-        fprintf(stderr, " -D AP_HTTPD_USER=\"%s\"\n", AP_HTTPD_USER);
+      fprintf(stderr, " -D AP_HTTPD_USER=\"%s\"\n", AP_HTTPD_USER);
 #endif
 #ifdef AP_LOG_EXEC
-        fprintf(stderr, " -D AP_LOG_EXEC=\"%s\"\n", AP_LOG_EXEC);
+      fprintf(stderr, " -D AP_LOG_EXEC=\"%s\"\n", AP_LOG_EXEC);
 #endif
 #ifdef AP_SAFE_PATH
-        fprintf(stderr, " -D AP_SAFE_PATH=\"%s\"\n", AP_SAFE_PATH);
+      fprintf(stderr, " -D AP_SAFE_PATH=\"%s\"\n", AP_SAFE_PATH);
 #endif
 #ifdef AP_SUEXEC_UMASK
-        fprintf(stderr, " -D AP_SUEXEC_UMASK=%03o\n", AP_SUEXEC_UMASK);
+      fprintf(stderr, " -D AP_SUEXEC_UMASK=%03o\n", AP_SUEXEC_UMASK);
 #endif
 #ifdef AP_UID_MIN
-        fprintf(stderr, " -D AP_UID_MIN=%d\n", AP_UID_MIN);
+      fprintf(stderr, " -D AP_UID_MIN=%d\n", AP_UID_MIN);
 #endif
 #ifdef AP_USERDIR_SUFFIX
-        fprintf(stderr, " -D AP_USERDIR_SUFFIX=\"%s\"\n", AP_USERDIR_SUFFIX);
+      fprintf(stderr, " -D AP_USERDIR_SUFFIX=\"%s\"\n", AP_USERDIR_SUFFIX);
 #endif
-        exit(0);
-    }
-    /*
-     * If there are a proper number of arguments, set
-     * all of them to variables.  Otherwise, error out.
-     */
-    if (argc < 4) {
-        log_err("too few arguments\n");
-        exit(101);
-    }
-    target_uname = argv[1];
-    target_gname = argv[2];
-    cmd = argv[3];
-
-    /*
-     * Check to see if the user running this program
-     * is the user allowed to do so as defined in
-     * suexec.h.  If not the allowed user, error out.
-     */
+      exit(0);
+  }
+  /*
+  * If there are a proper number of arguments, set
+  * all of them to variables.  Otherwise, error out.
+  */
+  if (argc < 4) {
+    log_err("too few arguments\n");
+    exit(101);
+  }
+  target_uname = argv[1];
+  target_gname = argv[2];
+  cmd = argv[3];
+
+  /*
+  * Check to see if the user running this program
+  * is the user allowed to do so as defined in
+  * suexec.h.  If not the allowed user, error out.
+  */
 #ifdef _OSD_POSIX
-    /* User name comparisons are case insensitive on BS2000/OSD */
-    if (strcasecmp(AP_HTTPD_USER, pw->pw_name)) {
-        log_err("user mismatch (%s instead of %s)\n", pw->pw_name, 
AP_HTTPD_USER);
-        exit(103);
-    }
+  /* User name comparisons are case insensitive on BS2000/OSD */
+  if (strcasecmp(AP_HTTPD_USER, pw->pw_name)) {
+    log_err("user mismatch (%s instead of %s)\n", pw->pw_name, AP_HTTPD_USER);
+    exit(103);
+  }
 #else  /*_OSD_POSIX*/
-    if (strcmp(AP_HTTPD_USER, pw->pw_name)) {
-        log_err("user mismatch (%s instead of %s)\n", pw->pw_name, 
AP_HTTPD_USER);
-        exit(103);
-    }
+  if (strcmp(AP_HTTPD_USER, pw->pw_name)) {
+    log_err("user mismatch (%s instead of %s)\n", pw->pw_name, AP_HTTPD_USER);
+    exit(103);
+  }
 #endif /*_OSD_POSIX*/
 
-    /*
-     * Check for a leading '/' (absolute path) in the command to be executed,
-     * or attempts to back up out of the current directory,
-     * to protect against attacks.  If any are
-     * found, error out.  Naughty naughty crackers.
-     */
-    if ((cmd[0] == '/') || (!strncmp(cmd, "../", 3))
-        || (strstr(cmd, "/../") != NULL)) {
-        log_err("invalid command (%s)\n", cmd);
-        exit(104);
-    }
-
-    /*
-     * Check to see if this is a ~userdir request.  If
-     * so, set the flag, and remove the '~' from the
-     * target username.
-     */
-    if (!strncmp("~", target_uname, 1)) {
-        target_uname++;
-        userdir = 1;
-    }
-
-    /*
-     * Error out if the target username is invalid.
-     */
-    if (strspn(target_uname, "1234567890") != strlen(target_uname)) {
-        if ((pw = getpwnam(target_uname)) == NULL) {
-            log_err("invalid target user name: (%s)\n", target_uname);
-            exit(105);
-        }
-    }
-    else {
-        if ((pw = getpwuid(atoi(target_uname))) == NULL) {
-            log_err("invalid target user id: (%s)\n", target_uname);
-            exit(121);
-        }
-    }
-
-    /*
-     * Error out if the target group name is invalid.
-     */
-    if (strspn(target_gname, "1234567890") != strlen(target_gname)) {
-        if ((gr = getgrnam(target_gname)) == NULL) {
-            log_err("invalid target group name: (%s)\n", target_gname);
-            exit(106);
-        }
-        gid = gr->gr_gid;
-        actual_gname = strdup(gr->gr_name);
-    }
-    else {
-        gid = atoi(target_gname);
-        actual_gname = strdup(target_gname);
-    }
+  /*
+  * Check for a leading '/' (absolute path) in the command to be executed,
+  * or attempts to back up out of the current directory,
+  * to protect against attacks.  If any are
+  * found, error out.  Naughty naughty crackers.
+  */
+  if ((cmd[0] == '/') || (!strncmp(cmd, "../", 3))
+    || (strstr(cmd, "/../") != NULL)) {
+      log_err("invalid command (%s)\n", cmd);
+      exit(104);
+  }
+
+  /*
+  * Check to see if this is a ~userdir request.  If
+  * so, set the flag, and remove the '~' from the
+  * target username.
+  */
+  if (!strncmp("~", target_uname, 1)) {
+    target_uname++;
+    userdir = 1;
+  }
+
+  /*
+  * Error out if the target username is invalid.
+  */
+  if (strspn(target_uname, "1234567890") != strlen(target_uname)) {
+    if ((pw = getpwnam(target_uname)) == NULL) {
+      log_err("invalid target user name: (%s)\n", target_uname);
+      exit(105);
+    }
+  }
+  else {
+    if ((pw = getpwuid(atoi(target_uname))) == NULL) {
+      log_err("invalid target user id: (%s)\n", target_uname);
+      exit(121);
+    }
+  }
+
+  /*
+  * Error out if the target group name is invalid.
+  */
+  if (strspn(target_gname, "1234567890") != strlen(target_gname)) {
+    if ((gr = getgrnam(target_gname)) == NULL) {
+      log_err("invalid target group name: (%s)\n", target_gname);
+      exit(106);
+    }
+    gid = gr->gr_gid;
+    actual_gname = strdup(gr->gr_name);
+  }
+  else {
+    gid = atoi(target_gname);
+    actual_gname = strdup(target_gname);
+  }
 
 #ifdef _OSD_POSIX
-    /*
-     * Initialize BS2000 user environment
-     */
-    {
-        pid_t pid;
-        int status;
-
-        switch (pid = ufork(target_uname)) {
-        case -1:    /* Error */
-            log_err("failed to setup bs2000 environment for user %s: %s\n",
-                    target_uname, strerror(errno));
-            exit(150);
-        case 0:     /* Child */
-            break;
-        default:    /* Father */
-            while (pid != waitpid(pid, &status, 0))
-                ;
-            /* @@@ FIXME: should we deal with STOP signals as well? */
-            if (WIFSIGNALED(status)) {
-                kill (getpid(), WTERMSIG(status));
-            }
-            exit(WEXITSTATUS(status));
-        }
+  /*
+  * Initialize BS2000 user environment
+  */
+  {
+    pid_t pid;
+    int status;
+
+    switch (pid = ufork(target_uname)) {
+  case -1:    /* Error */
+    log_err("failed to setup bs2000 environment for user %s: %s\n",
+      target_uname, strerror(errno));
+    exit(150);
+  case 0:     /* Child */
+    break;
+  default:    /* Father */
+    while (pid != waitpid(pid, &status, 0))
+      ;
+    /* @@@ FIXME: should we deal with STOP signals as well? */
+    if (WIFSIGNALED(status)) {
+      kill (getpid(), WTERMSIG(status));
     }
-#endif /*_OSD_POSIX*/
-    
-    /*
-     * Save these for later since initgroups will hose the struct
-     */
-    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 
-     * before we setuid().
-     */
-    log_err("uid: (%s/%s) gid: (%s/%s) cmd: %s\n",
-            target_uname, actual_uname,
-            target_gname, actual_gname,
-            cmd);
-
-    /*
-     * Error out if attempt is made to execute as root or as
-     * a UID less than AP_UID_MIN.  Tsk tsk.
-     */
-    if ((uid == 0) || (uid < AP_UID_MIN)) {
-        log_err("cannot run as forbidden uid (%d/%s)\n", uid, cmd);
-        exit(107);
-    }
-
-    /*
-     * Error out if attempt is made to execute as root group
-     * or as a GID less than AP_GID_MIN.  Tsk tsk.
-     */
-    if ((gid == 0) || (gid < AP_GID_MIN)) {
-        log_err("cannot run as forbidden gid (%d/%s)\n", gid, cmd);
-        exit(108);
-    }
-
-    /*
-     * 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)) {
-        log_err("failed to setgid (%ld: %s)\n", gid, cmd);
-        exit(109);
-    }
-
-    /*
-     * setuid() to the target user.  Error out on fail.
-     */
-    if ((setuid(uid)) != 0) {
-        log_err("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
-     * ~userdir request).  Error out if we cannot get either one,
-     * or if the current working directory is not in the docroot.
-     * Use chdir()s and getcwd()s to avoid problems with symlinked
-     * directories.  Yuck.
-     */
-    if (getcwd(cwd, AP_MAXPATH) == NULL) {
-        log_err("cannot get current working directory\n");
-        exit(111);
-    }
-
-    if (userdir) {
-        if (((chdir(target_homedir)) != 0) ||
-            ((chdir(AP_USERDIR_SUFFIX)) != 0) ||
-            ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
-            ((chdir(cwd)) != 0)) {
-            log_err("cannot get docroot information (%s)\n", target_homedir);
-            exit(112);
-        }
-    }
-    else {
-        if (((chdir(AP_DOC_ROOT)) != 0) ||
-            ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
-            ((chdir(cwd)) != 0)) {
-            log_err("cannot get docroot information (%s)\n", AP_DOC_ROOT);
-            exit(113);
-        }
-    }
-
-    if ((strncmp(cwd, dwd, strlen(dwd))) != 0) {
-        log_err("command not in docroot (%s/%s)\n", cwd, cmd);
-        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("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("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("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("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("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("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
-     * "[error] Premature end of script headers: ..."
-     */
-    if (!(prg_info.st_mode & S_IXUSR)) {
-        log_err("file has no execute permission: (%s/%s)\n", cwd, cmd);
-        exit(121);
+    exit(WEXITSTATUS(status));
     }
+  }
+#endif /*_OSD_POSIX*/
+
+  /*
+  * Save these for later since initgroups will hose the struct
+  */
+  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 
+  * before we setuid().
+  */
+  log_err("uid: (%s/%s) gid: (%s/%s) cmd: %s\n",
+    target_uname, actual_uname,
+    target_gname, actual_gname,
+    cmd);
+
+  /*
+  * Error out if attempt is made to execute as root or as
+  * a UID less than AP_UID_MIN.  Tsk tsk.
+  */
+  if ((uid == 0) || (uid < AP_UID_MIN)) {
+    log_err("cannot run as forbidden uid (%d/%s)\n", uid, cmd);
+    exit(107);
+  }
+
+  /*
+  * Error out if attempt is made to execute as root group
+  * or as a GID less than AP_GID_MIN.  Tsk tsk.
+  */
+  if ((gid == 0) || (gid < AP_GID_MIN)) {
+    log_err("cannot run as forbidden gid (%d/%s)\n", gid, cmd);
+    exit(108);
+  }
+
+  /********************************/
+  /* CHROOT Implementation, Begin */
+
+  /* First, we have to be root */
+  if (setgid(0) || setuid(0) || getuid() != geteuid() || getgid() != getegid
()) {
+    log_err("suexec: can't setgid(0). (Is suexec owned by root and has setuid 
permissions?)\n");
+    exit(126);
+  }
+
+  /* Now, read the userdata of the specified user */
+  pwdent = getpasswddata(uid);
+  if (pwdent == NULL) {
+    log_err("suexec: can't get passwd info for uid %d\n",uid);
+    exit(122);
+  }
+
+  /* Get Full-Path of the user-homedir */
+  tmp_str = (char *)malloc(PATH_LEN);
+  if (!realpath(pwdent->dir,tmp_str)) {
+    log_err("suexec: can't canonize path \"%s\". Bad path?\n", pwdent->dir);
+    exit(123);
+  }
+  free(pwdent->dir);
+  pwdent->dir = tmp_str;
+
+  /* We have to read the current directory here, won't work after chroot */
+  if (getcwd(cwd, AP_MAXPATH) == NULL) {
+    log_err("cannot get current working directory\n");
+    exit(111);
+  }
+
+  /* Changing to Home-Dir of user */
+  ret = chdir(pwdent->dir);	
+  if (ret) {
+    log_err("suexec: can't chdir to path \"%s\". Bad path?\n", pwdent->dir);
+    exit(124);
+  }
+
+  /* Now we change the root */
+  ret = chroot(pwdent->dir);
+  if (ret) { 
+    log_err("suexec: can't chroot to path \"%s\"\n", pwdent->dir);
+    exit(125);
+  }
+
+  /*
+  * 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)) {
+    log_err("failed to setgid (%ld: %s)\n", gid, cmd);
+    exit(109);
+  }
+
+  /*
+  * setuid() to the target user.  Error out on fail.
+  */
+  if ((setuid(uid)) != 0) {
+    log_err("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
+  * ~userdir request).  Error out if we cannot get either one,
+  * or if the current working directory is not in the docroot.
+  * Use chdir()s and getcwd()s to avoid problems with symlinked
+  * directories.  Yuck.
+  */
+
+  if (userdir) {
+    if (((chdir(target_homedir)) != 0) ||
+      ((chdir(AP_USERDIR_SUFFIX)) != 0) ||
+      ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
+      ((chdir(cwd)) != 0)) {
+        log_err("cannot get docroot information (%s)\n", target_homedir);
+        exit(112);
+    }
+  }
+  else {
+    if (((chdir(AP_DOC_ROOT)) != 0) ||
+      ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
+      ((chdir(cwd)) != 0)) {
+        log_err("cannot get docroot information (%s)\n", AP_DOC_ROOT);
+        exit(113);
+    }
+  }
+
+  if ((strncmp(cwd, dwd, strlen(dwd))) != 0) {
+    log_err("command not in docroot (%s - %s - %s)\n", cwd, cmd, dwd);
+    exit(114);
+  }
+
+  /* free the password-data */
+  free_passwd_data(pwdent);
+
+  /* CHROOT Implementation, End   */
+  /********************************/
+  
+  /*
+  * 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("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("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("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("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("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("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
+  * "[error] Premature end of script headers: ..."
+  */
+  if (!(prg_info.st_mode & S_IXUSR)) {
+    log_err("file has no execute permission: (%s/%s)\n", cwd, cmd);
+    exit(121);
+  }
 
 #ifdef AP_SUEXEC_UMASK
-    /*
-     * umask() uses inverse logic; bits are CLEAR for allowed access.
-     */
-    if ((~AP_SUEXEC_UMASK) & 0022) {
-        log_err("notice: AP_SUEXEC_UMASK of %03o allows "
-                "write permission to group and/or other\n", AP_SUEXEC_UMASK);
-    }
-    umask(AP_SUEXEC_UMASK);
+  /*
+  * umask() uses inverse logic; bits are CLEAR for allowed access.
+  */
+  if ((~AP_SUEXEC_UMASK) & 0022) {
+    log_err("notice: AP_SUEXEC_UMASK of %03o allows "
+      "write permission to group and/or other\n", AP_SUEXEC_UMASK);
+  }
+  umask(AP_SUEXEC_UMASK);
 #endif /* AP_SUEXEC_UMASK */
 
-    /* 
-     * Be sure to close the log file so the CGI can't
-     * mess with it.  If the exec fails, it will be reopened 
-     * automatically when log_err is called.  Note that the log
-     * might not actually be open if AP_LOG_EXEC isn't defined.
-     * However, the "log" cell isn't ifdef'd so let's be defensive
-     * and assume someone might have done something with it
-     * outside an ifdef'd AP_LOG_EXEC block.
-     */
-    if (log != NULL) {
-        fclose(log);
-        log = NULL;
-    }
-
-    /*
-     * Execute the command, replacing our image with its own.
-     */
+  /* 
+  * Be sure to close the log file so the CGI can't
+  * mess with it.  If the exec fails, it will be reopened 
+  * automatically when log_err is called.  Note that the log
+  * might not actually be open if AP_LOG_EXEC isn't defined.
+  * However, the "log" cell isn't ifdef'd so let's be defensive
+  * and assume someone might have done something with it
+  * outside an ifdef'd AP_LOG_EXEC block.
+  */
+  if (log != NULL) {
+    fclose(log);
+    log = NULL;
+  }
+
+  /*
+  * Execute the command, replacing our image with its own.
+  */
 #ifdef NEED_HASHBANG_EMUL
-    /* We need the #! emulation when we want to execute scripts */
-    {
-        extern char **environ;
+  /* We need the #! emulation when we want to execute scripts */
+  {
+    extern char **environ;
 
-        ap_execve(cmd, &argv[3], environ);
-    }
+    ap_execve(cmd, &argv[3], environ);
+  }
 #else /*NEED_HASHBANG_EMUL*/
-    execv(cmd, &argv[3]);
+  execv(cmd, &argv[3]);
 #endif /*NEED_HASHBANG_EMUL*/
 
-    /*
-     * (I can't help myself...sorry.)
-     *
-     * Uh oh.  Still here.  Where's the kaboom?  There was supposed to be an
-     * EARTH-shattering kaboom!
-     *
-     * Oh well, log the failure and error out.
-     */
-    log_err("(%d)%s: exec failed (%s)\n", errno, strerror(errno), cmd);
-    exit(255);
+  /*
+  * (I can't help myself...sorry.)
+  *
+  * Uh oh.  Still here.  Where's the kaboom?  There was supposed to be an
+  * EARTH-shattering kaboom!
+  *
+  * Oh well, log the failure and error out.
+  */
+  log_err("(%d)%s: exec failed (%s)\n", errno, strerror(errno), cmd);
+  exit(255);
 }
Comment 1 Marvin Gülker 2013-09-19 11:22:58 UTC
Wow. What is the status of this? Googling around tells us that there’s a great number of people who have manually patched chroot support into suexec, showing that there’s clearly a demand for this feature in suexec. Nevertheless it has never made it into upstream suexec. Is there any reason for this? Is this ever planned to be included?

Some examples of people who wrote patches apart from the guy reporting this years ago:

* http://e.metaclarity.org/268/httpdsuexecchrootfastcgiphp/
* http://www.marco-gatti.com/2012/11/06/stuff/apache-suexec-custom/
* http://sourceforge.net/projects/chroot-suexec/
* http://blog.ivanristic.com/2006/06/apache-suexec-chroot-patch.html

Debian seems to have a patched version of suexec as a package "custom-suexec", but as I’m not running a debian-based distro this does not really help. Any chance to have this in upstream so one can rely on this feature being maintained?

Valete,
Marvin
Comment 2 William A. Rowe Jr. 2018-11-07 21:09:40 UTC
Please help us to refine our list of open and current defects; this is a mass update of old and inactive Bugzilla reports which reflect user error, already resolved defects, and still-existing defects in httpd.

As repeatedly announced, the Apache HTTP Server Project has discontinued all development and patch review of the 2.2.x series of releases. The final release 2.2.34 was published in July 2017, and no further evaluation of bug reports or security risks will be considered or published for 2.2.x releases. All reports older than 2.4.x have been updated to status RESOLVED/LATER; no further action is expected unless the report still applies to a current version of httpd.

If your report represented a question or confusion about how to use an httpd feature, an unexpected server behavior, problems building or installing httpd, or working with an external component (a third party module, browser etc.) we ask you to start by bringing your question to the User Support and Discussion mailing list, see [https://httpd.apache.org/lists.html#http-users] for details. Include a link to this Bugzilla report for completeness with your question.

If your report was clearly a defect in httpd or a feature request, we ask that you retest using a modern httpd release (2.4.33 or later) released in the past year. If it can be reproduced, please reopen this bug and change the Version field above to the httpd version you have reconfirmed with.

Your help in identifying defects or enhancements still applicable to the current httpd server software release is greatly appreciated.