File 0014-Add-SymlinkProtect-and-SymlinkProtectRoot-functional.patch of Package ea-apache2

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Cory McIntire <cory@cpanel.net>
Date: Thu, 12 Jul 2018 15:50:51 -0500
Subject: [PATCH 14/21] Add SymlinkProtect and SymlinkProtectRoot functionality

---
 include/http_core.h             |  10 +++-
 modules/mappers/mod_userdir.c   |  31 +++++++---
 modules/mappers/mod_userdir.dep |   1 +
 server/core.c                   | 103 ++++++++++++++++++++++++++++++++
 4 files changed, 135 insertions(+), 10 deletions(-)

diff --git a/include/http_core.h b/include/http_core.h
index 948034f..8673506 100644
--- a/include/http_core.h
+++ b/include/http_core.h
@@ -509,7 +509,7 @@ typedef unsigned long etag_components_t;
 #define AP_CORE_MERGE_FLAG(field, to, base, over) to->field = \
                over->field != AP_CORE_CONFIG_UNSET            \
                ? over->field                                  \
-               : base->field                                   
+               : base->field
 
 /**
  * @brief Server Signature Enumeration
@@ -672,7 +672,7 @@ typedef struct {
 #define AP_CGI_PASS_AUTH_UNSET   (2)
     /** CGIPassAuth: Whether HTTP authorization headers will be passed to
      * scripts as CGI variables; affects all modules calling
-     * ap_add_common_vars(), as well as any others using this field as 
+     * ap_add_common_vars(), as well as any others using this field as
      * advice
      */
     unsigned int cgi_pass_auth : 2;
@@ -752,6 +752,12 @@ typedef struct {
 #define AP_HTTP_METHODS_REGISTERED    2
     char http_methods;
     unsigned int merge_slashes;
+    /* symlink protection */
+#define AP_SYMLINK_PROTECT_UNSET   0
+#define AP_SYMLINK_PROTECT_ENABLE  1
+#define AP_SYMLINK_PROTECT_DISABLE 2
+    int symlink_protect;
+    const char *symlink_protect_root;
  
     apr_size_t   flush_max_threshold;
     apr_int32_t  flush_max_pipelined;
diff --git a/modules/mappers/mod_userdir.c b/modules/mappers/mod_userdir.c
index 1ec0e90..6a0f44d 100644
--- a/modules/mappers/mod_userdir.c
+++ b/modules/mappers/mod_userdir.c
@@ -51,6 +51,7 @@
 
 #include "apr_strings.h"
 #include "apr_user.h"
+#include "apr_env.h"
 
 #define APR_WANT_STRFUNC
 #include "apr_want.h"
@@ -62,7 +63,9 @@
 #include "ap_config.h"
 #include "httpd.h"
 #include "http_config.h"
+#include "http_core.h"
 #include "http_request.h"
+#include "http_log.h"
 
 #if !defined(WIN32) && !defined(OS2) && !defined(NETWARE)
 #define HAVE_UNIX_SUEXEC
@@ -203,6 +206,7 @@ static int translate_userdir(request_rec *r)
     const char *user, *dname;
     char *redirect;
     apr_finfo_t statbuf;
+    core_server_config *sconf;
 
     /*
      * If the URI doesn't match our basic pattern, we've nothing to do with
@@ -259,6 +263,8 @@ static int translate_userdir(request_rec *r)
      * Special cases all checked, onward to normal substitution processing.
      */
 
+    sconf = ap_get_core_module_config(server_conf);
+
     while (*userdirs) {
         const char *userdir = ap_getword_conf(r->pool, &userdirs);
         char *filename = NULL, *prefix = NULL;
@@ -313,18 +319,20 @@ static int translate_userdir(request_rec *r)
         }
 
         /*
-         * Now see if it exists, or we're at the last entry. If we are at the
-         * last entry, then use the filename generated (if there is one)
-         * anyway, in the hope that some handler might handle it. This can be
-         * used, for example, to run a CGI script for the user.
-         */
+        * Now see if it exists, or we're at the last entry. If we are at the
+        * last entry, then use the filename generated (if there is one)
+        * anyway, in the hope that some handler might handle it. This can be
+        * used, for example, to run a CGI script for the user.
+        */
         if (filename && (!*userdirs
-                      || ((rv = apr_stat(&statbuf, filename, APR_FINFO_MIN,
+                      || ((rv = apr_stat(&statbuf, filename, (
+                           sconf->symlink_protect == AP_SYMLINK_PROTECT_ENABLE) ?
+                               APR_FINFO_NORM :
+                               APR_FINFO_MIN,
                                          r->pool)) == APR_SUCCESS
                                              || rv == APR_INCOMPLETE))) {
             r->filename = apr_pstrcat(r->pool, filename, dname, NULL);
-            ap_set_context_info(r, apr_pstrmemdup(r->pool, r->uri,
-                                                  dname - r->uri),
+            ap_set_context_info(r, apr_pstrmemdup(r->pool, r->uri, dname - r->uri),
                                 filename);
             /* XXX: Does this walk us around FollowSymLink rules?
              * When statbuf contains info on r->filename we can save a syscall
@@ -333,6 +341,13 @@ static int translate_userdir(request_rec *r)
             if (*userdirs && dname[0] == 0)
                 r->finfo = statbuf;
 
+            /* This is used later on to make sure the symlink exploit is not
+             * exploitable.
+             */
+            if (sconf->symlink_protect == AP_SYMLINK_PROTECT_ENABLE) {
+                apr_table_set(r->subprocess_env, "SPT_DOCROOT", filename);
+            }
+
             /* For use in the get_suexec_identity phase */
             apr_table_setn(r->notes, "mod_userdir_user", user);
 
diff --git a/modules/mappers/mod_userdir.dep b/modules/mappers/mod_userdir.dep
index 2aff834..fa229b1 100644
--- a/modules/mappers/mod_userdir.dep
+++ b/modules/mappers/mod_userdir.dep
@@ -12,6 +12,7 @@
 	"..\..\include\ap_regex.h"\
 	"..\..\include\ap_release.h"\
 	"..\..\include\apache_noprobes.h"\
+	"..\..\include\http_core.h"\
 	"..\..\include\http_config.h"\
 	"..\..\include\http_request.h"\
 	"..\..\include\httpd.h"\
diff --git a/server/core.c b/server/core.c
index 81f145f..4ee8905 100644
--- a/server/core.c
+++ b/server/core.c
@@ -21,6 +21,8 @@
 #include "apr_hash.h"
 #include "apr_thread_proc.h"    /* for RLIMIT stuff */
 #include "apr_random.h"
+#include "apr_env.h"
+#include "apr_file_io.h"
 
 #include "apr_version.h"
 #if APR_MAJOR_VERSION < 2
@@ -479,6 +481,7 @@ static void *create_core_server_config(apr_pool_t *a, server_rec *s)
 
     if (!is_virtual) {
         conf->ap_document_root = DOCUMENT_LOCATION;
+        conf->symlink_protect_root = "/var/www/html";
         conf->access_name = DEFAULT_ACCESS_FNAME;
 
         /* A mapping only makes sense in the global context */
@@ -543,6 +546,9 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv)
     if (virt->ap_document_root)
         conf->ap_document_root = virt->ap_document_root;
 
+    if (virt->symlink_protect_root)
+        conf->symlink_protect_root = virt->symlink_protect_root;
+
     if (virt->access_name)
         conf->access_name = virt->access_name;
 
@@ -589,6 +595,10 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv)
                            ? virt->merge_trailers
                            : base->merge_trailers;
 
+    conf->symlink_protect = (virt->symlink_protect != AP_SYMLINK_PROTECT_UNSET)
+                            ? virt->symlink_protect
+                            : base->symlink_protect;
+
     conf->protocols = ((virt->protocols->nelts > 0)? 
                        virt->protocols : base->protocols);
     conf->protocols_honor_order = ((virt->protocols_honor_order < 0)?
@@ -4430,6 +4440,30 @@ static const char *set_merge_trailers(cmd_parms *cmd, void *dummy, int arg)
     return NULL;
 }
 
+static const char *set_symlink_protect(cmd_parms *cmd, void *dummy, int arg)
+{
+    core_server_config *conf = ap_get_module_config(cmd->server->module_config,
+                                                    &core_module);
+    conf->symlink_protect = (arg ? AP_SYMLINK_PROTECT_ENABLE :
+                             AP_SYMLINK_PROTECT_DISABLE);
+    return NULL;
+}
+
+static const char *set_symlink_protect_root(cmd_parms *cmd, void *dummy,
+                                     const char *arg)
+{
+    char* value;
+    core_server_config *conf = ap_get_module_config(cmd->server->module_config,
+                                                    &core_module);
+    if (arg == NULL) {
+        return "SymlinkProtectRoot must have a value";
+    }
+    value = apr_pstrdup(cmd->pool, arg);
+    conf->symlink_protect_root = value;
+
+    return NULL;
+}
+
 /* Note --- ErrorDocument will now work from .htaccess files.
  * The AllowOverride of Fileinfo allows webmasters to turn it off
  */
@@ -4707,6 +4741,10 @@ AP_INIT_TAKE1("ThreadStackSize", ap_mpm_set_thread_stacksize, NULL, RSRC_CONF,
 AP_INIT_TAKE1("EnableExceptionHook", ap_mpm_set_exception_hook, NULL, RSRC_CONF,
               "Controls whether exception hook may be called after a crash"),
 #endif
+AP_INIT_FLAG("SymlinkProtect", set_symlink_protect, NULL, RSRC_CONF,
+             "Controls whether symlink protection will be active or not"),
+AP_INIT_TAKE1("SymlinkProtectRoot", set_symlink_protect_root, NULL, RSRC_CONF,
+             "Root directory of the symlink protect algorithm"),
 AP_INIT_TAKE1("TraceEnable", set_trace_enable, NULL, RSRC_CONF,
               "'on' (default), 'off' or 'extended' to trace request body content"),
 AP_INIT_FLAG("MergeTrailers", set_merge_trailers, NULL, RSRC_CONF,
@@ -4872,6 +4910,9 @@ static int default_handler(request_rec *r)
     int errstatus;
     apr_file_t *fd = NULL;
     apr_status_t status;
+    core_server_config *csconf;
+    apr_finfo_t post_open_dirstat;
+    apr_finfo_t post_open_finfo;
     /* XXX if/when somebody writes a content-md5 filter we either need to
      *     remove this support or coordinate when to use the filter vs.
      *     when to use this code
@@ -4882,6 +4923,13 @@ static int default_handler(request_rec *r)
     int bld_content_md5;
 
     d = (core_dir_config *)ap_get_core_module_config(r->per_dir_config);
+
+    /* must fetch global configuration.  First to determine if we are
+     * going to apply symlink protection, and second to check the document
+     * root user against the open file user if using symlink protection.
+     */
+    csconf = ap_get_module_config(r->server->module_config, &core_module);
+
     bld_content_md5 = (d->content_md5 == AP_CONTENT_MD5_ON)
                       && r->output_filters->frec->ftype != AP_FTYPE_RESOURCE;
 
@@ -4955,6 +5003,61 @@ static int default_handler(request_rec *r)
             return HTTP_FORBIDDEN;
         }
 
+        if (csconf->symlink_protect == AP_SYMLINK_PROTECT_ENABLE) {
+            /* This is where the magic is. If a user is trying to hit the apache
+             * symlink race condition, then we will know about it here.
+             */
+
+            const char *sp_docroot = apr_table_get(r->subprocess_env, "SPT_DOCROOT");
+            apr_status_t post_dirstat_rv;
+            apr_status_t post_fdstat_rv;
+
+            if (strcmp(csconf->ap_document_root, csconf->symlink_protect_root) == 0
+                && sp_docroot != NULL) {
+                /* This request is from mod_userdir. We need to stat what was stored in SPT_DOCROOT. */
+                post_dirstat_rv = apr_stat(&post_open_dirstat, sp_docroot,
+                                           APR_FINFO_USER | APR_FINFO_LINK, r->pool);
+            }
+            else {
+                /* This request matched a vhost. We need to stat ap_document_root. */
+                post_dirstat_rv = apr_stat(&post_open_dirstat, csconf->ap_document_root,
+                                           APR_FINFO_USER | APR_FINFO_LINK, r->pool);
+            }
+
+            post_fdstat_rv = apr_stat_fd(&post_open_finfo, fd, APR_FINFO_USER, r->pool);
+
+            if (((post_dirstat_rv != APR_SUCCESS && post_dirstat_rv != APR_INCOMPLETE)
+                || !(post_open_dirstat.valid & APR_FINFO_USER))
+                || ((post_fdstat_rv != APR_SUCCESS && post_fdstat_rv != APR_INCOMPLETE)
+                || !(post_open_finfo.valid & APR_FINFO_USER))) {
+                /* Then we couldn't stat either the directory root of the vhost
+                 * (very unlikely) or we couldn't stat the open file descriptor
+                 * (probably impossible).
+                 */
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
+                    "Could not stat directory root or open file. Aborting request.");
+                apr_file_close(fd);
+                return HTTP_NOT_FOUND;
+            }
+
+
+            if (apr_uid_compare(r->finfo.user, post_open_dirstat.user)
+                != APR_SUCCESS || apr_uid_compare(post_open_finfo.user, r->finfo.user)
+                != APR_SUCCESS) {
+                /* Then we've caught a race condition abuser. */
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
+                    "Caught race condition abuser. attacker: %i, victim: %i"
+                    " open file owner: %i, open file: %s", post_open_dirstat.user, r->finfo.user,
+                    post_open_finfo.user, r->filename);
+
+                apr_file_close(fd);
+                /* Return 404 because we don't want an attacker to be able to test
+                 * what files are where based on the return of an error.
+                 */
+                return HTTP_NOT_FOUND;
+            }
+        }
+
         ap_update_mtime(r, r->finfo.mtime);
         ap_set_last_modified(r);
         ap_set_etag_fd(r, fd);
openSUSE Build Service is sponsored by