Hi all,

Currently, `httpd.h` redefines the `apr_filepath_merge()` function on Win32
as follows:
```
#ifdef WIN32
#define apr_filepath_merge  ap_filepath_merge
#endif
```

The intent behind this redefinition seems to be to retroactively apply
the recently introduced UNC path checks to any existing calls of
apr_filepath_merge(), including those in third-party module code.

However, this approach has several downsides, because the security-related
behavior of the code is now tied to the inclusion of <httpd.h>:

- Utility layers that do not include httpd.h but use apr_filepath_merge()
  will need to include this header to ensure appropriate security behavior.

- Refactorings that involve adding or removing #include <httpd.h> can
  inadvertently change the security properties of the code.

- It becomes challenging to understand the code behavior by inspection alone.

Personally, I find these issues quite significant.

An alternative would be to explicitly use ap_filepath_merge() everywhere and
remove the platform-specific define.  This alternative is demonstrated in the
attached patch:
[[[
* include/httpd.h
  (apr_filepath_merge): Remove this Win32-specific #define, and …

* modules/*, server/*:
  …replace all its usages with ap_filepath_merge().  This ensures explicit and
  consistent security-related behavior that doesn't depend on the inclusion
  of the <httpd.h> header.
]]]

What do you think?


Thanks,
Evgeny Kotkov
Index: include/httpd.h
===================================================================
--- include/httpd.h     (revision 1918820)
+++ include/httpd.h     (working copy)
@@ -2875,10 +2875,6 @@
                                              apr_int32_t flags,
                                              apr_pool_t *p);
 
-#ifdef WIN32
-#define apr_filepath_merge  ap_filepath_merge
-#endif
-
 /* Win32/NetWare/OS2 need to check for both forward and back slashes
  * in ap_normalize_path() and ap_escape_url().
  */
Index: modules/arch/win32/mod_isapi.c
===================================================================
--- modules/arch/win32/mod_isapi.c      (revision 1918820)
+++ modules/arch/win32/mod_isapi.c      (working copy)
@@ -990,7 +990,7 @@
 
 #ifdef WIN32
         /* We need to make this a real Windows path name */
-        apr_filepath_merge(&file, "", file, APR_FILEPATH_NATIVE, r->pool);
+        ap_filepath_merge(&file, "", file, APR_FILEPATH_NATIVE, r->pool);
 #endif
 
         *buf_size = apr_cpystrn(buf_data, file, *buf_size) - buf_data;
Index: modules/dav/fs/quota.c
===================================================================
--- modules/dav/fs/quota.c      (revision 1918820)
+++ modules/dav/fs/quota.c      (working copy)
@@ -98,10 +98,10 @@
             break;
 
         case APR_DIR:
-            rv = apr_filepath_merge(&newpath, path, finfo.name, 0, r->pool);
+            rv = ap_filepath_merge(&newpath, path, finfo.name, 0, r->pool);
             if (rv != APR_SUCCESS) {
                 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
-                              "apr_filepath_merge \"%s\" \"%s\" failed",
+                              "ap_filepath_merge \"%s\" \"%s\" failed",
                               path, finfo.name);
                 goto out;
             }
Index: modules/filters/mod_include.c
===================================================================
--- modules/filters/mod_include.c       (revision 1918820)
+++ modules/filters/mod_include.c       (working copy)
@@ -1697,9 +1697,9 @@
         char *newpath;
 
         /* be safe; only files in this directory or below allowed */
-        rv = apr_filepath_merge(&newpath, NULL, tag_val,
-                                APR_FILEPATH_SECUREROOTTEST |
-                                APR_FILEPATH_NOTABSOLUTE, r->pool);
+        rv = ap_filepath_merge(&newpath, NULL, tag_val,
+                               APR_FILEPATH_SECUREROOTTEST |
+                               APR_FILEPATH_NOTABSOLUTE, r->pool);
 
         if (rv != APR_SUCCESS) {
             error_fmt = APLOGNO(02668) "unable to access file \"%s\" "
@@ -1834,9 +1834,9 @@
             char *newpath;
 
             /* be safe; only files in this directory or below allowed */
-            rv = apr_filepath_merge(&newpath, NULL, parsed_string,
-                                    APR_FILEPATH_SECUREROOTTEST |
-                                    APR_FILEPATH_NOTABSOLUTE, ctx->dpool);
+            rv = ap_filepath_merge(&newpath, NULL, parsed_string,
+                                   APR_FILEPATH_SECUREROOTTEST |
+                                   APR_FILEPATH_NOTABSOLUTE, ctx->dpool);
 
             if (rv != APR_SUCCESS) {
                 error_fmt = "unable to include file \"%s\" in parsed file %s";
Index: modules/lua/mod_lua.c
===================================================================
--- modules/lua/mod_lua.c       (revision 1918820)
+++ modules/lua/mod_lua.c       (working copy)
@@ -196,8 +196,8 @@
     
     if (filename) {
         char *file;
-        apr_filepath_merge(&file, server_cfg->root_path,
-                           filename, APR_FILEPATH_NOTRELATIVE, r->pool);
+        ap_filepath_merge(&file, server_cfg->root_path,
+                          filename, APR_FILEPATH_NOTRELATIVE, r->pool);
         spec->file = file;
     }
     else {
@@ -1541,11 +1541,11 @@
         ap_get_module_config(cmd->server->module_config, &lua_module);
 
     char *fixed_filename;
-    rv = apr_filepath_merge(&fixed_filename, 
-                            server_cfg->root_path, 
-                            arg,
-                            APR_FILEPATH_NOTRELATIVE, 
-                            cmd->pool);
+    rv = ap_filepath_merge(&fixed_filename, 
+                           server_cfg->root_path, 
+                           arg,
+                           APR_FILEPATH_NOTRELATIVE, 
+                           cmd->pool);
 
     if (rv != APR_SUCCESS) {
         return apr_psprintf(cmd->pool,
Index: modules/mappers/mod_alias.c
===================================================================
--- modules/mappers/mod_alias.c (revision 1918820)
+++ modules/mappers/mod_alias.c (working copy)
@@ -585,7 +585,7 @@
                     char *fake = r->uri + l;
 
                     /*
-                     * For the apr_filepath_merge below we need a relative path
+                     * For the ap_filepath_merge below we need a relative path
                      * Hence skip all leading '/'
                      */
                     while (*fake == '/') {
@@ -594,9 +594,9 @@
 
                     /* Merge if there is something left to merge */
                     if (*fake) {
-                        if ((rv = apr_filepath_merge(&found, alias->real, fake,
-                                                     APR_FILEPATH_TRUENAME
-                                                   | APR_FILEPATH_SECUREROOT, 
r->pool))
+                        if ((rv = ap_filepath_merge(&found, alias->real, fake,
+                                                    APR_FILEPATH_TRUENAME
+                                                  | APR_FILEPATH_SECUREROOT, 
r->pool))
                                     != APR_SUCCESS) {
                             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, 
APLOGNO(10297)
                                           "Cannot map %s to file", 
r->the_request);
Index: modules/mappers/mod_rewrite.c
===================================================================
--- modules/mappers/mod_rewrite.c       (revision 1918820)
+++ modules/mappers/mod_rewrite.c       (working copy)
@@ -1004,16 +1004,16 @@
      * enough.
      */
     if ((slash = ap_strchr_c(curpath, '/')) != NULL) {
-        rv = apr_filepath_merge(&statpath, root,
-                                apr_pstrndup(pool, curpath,
-                                             (apr_size_t)(slash - curpath)),
-                                APR_FILEPATH_NOTABOVEROOT |
-                                APR_FILEPATH_NOTRELATIVE, pool);
+        rv = ap_filepath_merge(&statpath, root,
+                               apr_pstrndup(pool, curpath,
+                                            (apr_size_t)(slash - curpath)),
+                               APR_FILEPATH_NOTABOVEROOT |
+                               APR_FILEPATH_NOTRELATIVE, pool);
     }
     else {
-        rv = apr_filepath_merge(&statpath, root, curpath,
-                                APR_FILEPATH_NOTABOVEROOT |
-                                APR_FILEPATH_NOTRELATIVE, pool);
+        rv = ap_filepath_merge(&statpath, root, curpath,
+                               APR_FILEPATH_NOTABOVEROOT |
+                               APR_FILEPATH_NOTRELATIVE, pool);
     }
 
     if (rv == APR_SUCCESS) {
Index: modules/md/md_util.c
===================================================================
--- modules/md/md_util.c        (revision 1918820)
+++ modules/md/md_util.c        (working copy)
@@ -422,7 +422,7 @@
     va_start(ap, p);
     path = va_arg(ap, char *);
     while (path && APR_SUCCESS == rv && (segment = va_arg(ap, char *))) {
-        rv = apr_filepath_merge((char **)&path, path, segment, 
APR_FILEPATH_SECUREROOT , p);
+        rv = ap_filepath_merge((char **)&path, path, segment, 
APR_FILEPATH_SECUREROOT , p);
     }
     va_end(ap);
     
Index: modules/proxy/proxy_util.c
===================================================================
--- modules/proxy/proxy_util.c  (revision 1918820)
+++ modules/proxy/proxy_util.c  (working copy)
@@ -4895,9 +4895,9 @@
                                   "search for temporary directory failed");
                     return HTTP_INTERNAL_SERVER_ERROR;
                 }
-                apr_filepath_merge(&template, temp_dir,
-                                   "modproxy.tmp.XXXXXX",
-                                   APR_FILEPATH_NATIVE, p);
+                ap_filepath_merge(&template, temp_dir,
+                                  "modproxy.tmp.XXXXXX",
+                                  APR_FILEPATH_NATIVE, p);
                 status = apr_file_mktemp(&tmpfile, template, 0, p);
                 if (status != APR_SUCCESS) {
                     ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, 
APLOGNO(01090)
Index: modules/ssl/ssl_ct_util.c
===================================================================
--- modules/ssl/ssl_ct_util.c   (revision 1918820)
+++ modules/ssl/ssl_ct_util.c   (working copy)
@@ -30,7 +30,7 @@
 {
     apr_status_t rv;
 
-    rv = apr_filepath_merge(out, dirname, basename, 0, p);
+    rv = ap_filepath_merge(out, dirname, basename, 0, p);
     if (rv != APR_SUCCESS) {
         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                      APLOGNO(02776) "can't build filename from %s and %s",
Index: server/config.c
===================================================================
--- server/config.c     (revision 1918820)
+++ server/config.c     (working copy)
@@ -1597,8 +1597,8 @@
 {
     char *newpath = NULL;
     apr_status_t rv;
-    rv = apr_filepath_merge(&newpath, ap_server_root, file,
-                            APR_FILEPATH_TRUENAME, p);
+    rv = ap_filepath_merge(&newpath, ap_server_root, file,
+                           APR_FILEPATH_TRUENAME, p);
     if (newpath && (rv == APR_SUCCESS || APR_STATUS_IS_EPATHWILD(rv)
                                       || APR_STATUS_IS_ENOENT(rv)
                                       || APR_STATUS_IS_ENOTDIR(rv))) {
@@ -1615,8 +1615,8 @@
     apr_status_t rv;
     const char *runtime_dir = ap_runtime_dir ? ap_runtime_dir : 
ap_server_root_relative(p, DEFAULT_REL_RUNTIMEDIR);
 
-    rv = apr_filepath_merge(&newpath, runtime_dir, file,
-                            APR_FILEPATH_TRUENAME, p);
+    rv = ap_filepath_merge(&newpath, runtime_dir, file,
+                           APR_FILEPATH_TRUENAME, p);
     if (newpath && (rv == APR_SUCCESS || APR_STATUS_IS_EPATHWILD(rv)
                                       || APR_STATUS_IS_ENOENT(rv)
                                       || APR_STATUS_IS_ENOTDIR(rv))) {
Index: server/core.c
===================================================================
--- server/core.c       (revision 1918820)
+++ server/core.c       (working copy)
@@ -1694,8 +1694,8 @@
         return "DocumentRoot must be a directory";
     }
 
-    if (apr_filepath_merge((char**)&conf->ap_document_root, NULL, arg,
-                           APR_FILEPATH_TRUENAME, cmd->pool) != APR_SUCCESS
+    if (ap_filepath_merge((char**)&conf->ap_document_root, NULL, arg,
+                          APR_FILEPATH_TRUENAME, cmd->pool) != APR_SUCCESS
         || !ap_is_directory(cmd->temp_pool, arg)) {
         if (cmd->server->is_virtual) {
             ap_log_perror(APLOG_MARK, APLOG_STARTUP, 0,
@@ -2570,8 +2570,8 @@
         /*
          * Ensure that the pathname is canonical, and append the trailing /
          */
-        rv = apr_filepath_merge(&newpath, NULL, cmd->path,
-                                APR_FILEPATH_TRUENAME, cmd->pool);
+        rv = ap_filepath_merge(&newpath, NULL, cmd->path,
+                               APR_FILEPATH_TRUENAME, cmd->pool);
         if (rv != APR_SUCCESS && rv != APR_EPATHWILD) {
             return apr_pstrcat(cmd->pool, "<Directory \"", cmd->path,
                                "\"> path is invalid.", NULL);
@@ -2759,8 +2759,8 @@
 
         /* Ensure that the pathname is canonical, but we
          * can't test the case/aliases without a fixed path */
-        if (apr_filepath_merge(&newpath, "", cmd->path,
-                               0, cmd->pool) != APR_SUCCESS)
+        if (ap_filepath_merge(&newpath, "", cmd->path,
+                              0, cmd->pool) != APR_SUCCESS)
                 return apr_pstrcat(cmd->pool, "<Files \"", cmd->path,
                                "\"> is invalid.", NULL);
         cmd->path = newpath;
@@ -3303,8 +3303,8 @@
         return err;
     }
 
-    if ((apr_filepath_merge((char**)&ap_server_root, NULL, arg,
-                            APR_FILEPATH_TRUENAME, cmd->pool) != APR_SUCCESS)
+    if ((ap_filepath_merge((char**)&ap_server_root, NULL, arg,
+                           APR_FILEPATH_TRUENAME, cmd->pool) != APR_SUCCESS)
         || !ap_is_directory(cmd->temp_pool, ap_server_root)) {
         return "ServerRoot must be a valid directory";
     }
@@ -3320,9 +3320,9 @@
         return err;
     }
 
-    if ((apr_filepath_merge((char**)&ap_runtime_dir, NULL,
-                            ap_server_root_relative(cmd->temp_pool, arg),
-                            APR_FILEPATH_TRUENAME, cmd->pool) != APR_SUCCESS)
+    if ((ap_filepath_merge((char**)&ap_runtime_dir, NULL,
+                           ap_server_root_relative(cmd->temp_pool, arg),
+                           APR_FILEPATH_TRUENAME, cmd->pool) != APR_SUCCESS)
         || !ap_is_directory(cmd->temp_pool, ap_runtime_dir)) {
         return "DefaultRuntimeDir must be a valid directory, absolute or 
relative to ServerRoot";
     }
@@ -3338,9 +3338,9 @@
         return err;
     }
 
-    if ((apr_filepath_merge((char**)&core_state_dir, NULL,
-                            ap_server_root_relative(cmd->temp_pool, arg),
-                            APR_FILEPATH_TRUENAME, cmd->pool) != APR_SUCCESS)
+    if ((ap_filepath_merge((char**)&core_state_dir, NULL,
+                           ap_server_root_relative(cmd->temp_pool, arg),
+                           APR_FILEPATH_TRUENAME, cmd->pool) != APR_SUCCESS)
         || !ap_is_directory(cmd->temp_pool, core_state_dir)) {
         return "DefaultStateDir must be a valid directory, absolute or 
relative to ServerRoot";
     }
@@ -5055,9 +5055,9 @@
     while (*path == '/') {
         ++path;
     }
-    if ((rv = apr_filepath_merge(&r->filename, ap_document_root(r), path,
-                                 APR_FILEPATH_TRUENAME
-                               | APR_FILEPATH_SECUREROOT, r->pool))
+    if ((rv = ap_filepath_merge(&r->filename, ap_document_root(r), path,
+                                APR_FILEPATH_TRUENAME
+                              | APR_FILEPATH_SECUREROOT, r->pool))
                 != APR_SUCCESS) {
         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00127)
                      "Cannot map %s to file", r->the_request);
@@ -5741,7 +5741,7 @@
         ? core_state_dir
         : ap_server_root_relative(p, DEFAULT_REL_STATEDIR);
 
-    rv = apr_filepath_merge(&newpath, state_dir, file, APR_FILEPATH_TRUENAME, 
p);
+    rv = ap_filepath_merge(&newpath, state_dir, file, APR_FILEPATH_TRUENAME, 
p);
     if (newpath && (rv == APR_SUCCESS || APR_STATUS_IS_EPATHWILD(rv)
                                       || APR_STATUS_IS_ENOENT(rv)
                                       || APR_STATUS_IS_ENOTDIR(rv))) {
@@ -6093,12 +6093,8 @@
     if (APR_SUCCESS != (rv = check_unc(addpath, p))) {
         return rv;
     }
-#undef apr_filepath_merge
 #endif
     return apr_filepath_merge(newpath, rootpath, addpath, flags, p);
-#ifdef WIN32
-#define apr_filepath_merge ap_filepath_merge
-#endif
 }
 
 
Index: server/mpm/netware/mpm_netware.c
===================================================================
--- server/mpm/netware/mpm_netware.c    (revision 1918820)
+++ server/mpm/netware/mpm_netware.c    (working copy)
@@ -1134,7 +1134,7 @@
             for (i=len; i; i--) {
                 if (s[i] == '\\' || s[i] == '/') {
                     s[i] = '\0';
-                    apr_filepath_merge(&def_server_root, NULL, s,
+                    ap_filepath_merge(&def_server_root, NULL, s,
                         APR_FILEPATH_TRUENAME, process->pool);
                     break;
                 }
Index: server/mpm/winnt/mpm_winnt.c
===================================================================
--- server/mpm/winnt/mpm_winnt.c        (revision 1918820)
+++ server/mpm/winnt/mpm_winnt.c        (working copy)
@@ -1140,8 +1140,8 @@
         if (!strcasecmp(def_server_root, "bin"))
             *(def_server_root - 1) = '\0';
     }
-    apr_filepath_merge(&def_server_root, NULL, binpath,
-                       APR_FILEPATH_TRUENAME, process->pool);
+    ap_filepath_merge(&def_server_root, NULL, binpath,
+                      APR_FILEPATH_TRUENAME, process->pool);
 
     /* Use process->pool so that the rewritten argv
      * lasts for the lifetime of the server process,
Index: server/request.c
===================================================================
--- server/request.c    (revision 1918820)
+++ server/request.c    (working copy)
@@ -708,8 +708,8 @@
      * so we can begin by checking the cache for a recent directory walk.
      * This call will ensure we have an absolute path in the same pass.
      */
-    if ((rv = apr_filepath_merge(&entry_dir, NULL, r->filename,
-                                 APR_FILEPATH_NOTRELATIVE, r->pool))
+    if ((rv = ap_filepath_merge(&entry_dir, NULL, r->filename,
+                                APR_FILEPATH_NOTRELATIVE, r->pool))
                   != APR_SUCCESS) {
         ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00030)
                       "Module bug?  Request filename path %s is invalid or "
@@ -898,9 +898,9 @@
          */
         if ((r->finfo.filetype == APR_DIR) && r->path_info && *r->path_info)
         {
-            if ((rv = apr_filepath_merge(&r->path_info, r->filename,
-                                         r->path_info,
-                                         APR_FILEPATH_NOTABOVEROOT, r->pool))
+            if ((rv = ap_filepath_merge(&r->path_info, r->filename,
+                                        r->path_info,
+                                        APR_FILEPATH_NOTABOVEROOT, r->pool))
                 != APR_SUCCESS) {
                 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00033)
                               "dir_walk error, path_info %s is not relative "
@@ -2483,8 +2483,8 @@
         rnew->canonical_filename = (char*)(1);
     }
 
-    if (apr_filepath_merge(&rnew->filename, fdir, new_file,
-                           APR_FILEPATH_TRUENAME, rnew->pool) != APR_SUCCESS) {
+    if (ap_filepath_merge(&rnew->filename, fdir, new_file,
+                          APR_FILEPATH_TRUENAME, rnew->pool) != APR_SUCCESS) {
         rnew->status = HTTP_FORBIDDEN;
         return rnew;
     }
Index: server/util.c
===================================================================
--- server/util.c       (revision 1918820)
+++ server/util.c       (working copy)
@@ -226,7 +226,7 @@
 /* We actually compare the canonical root to this root, (but we don't
  * waste time checking the case), since every use of this function in
  * httpd-2.1 tests if the path is 'proper', meaning we've already passed
- * it through apr_filepath_merge, or we haven't.
+ * it through ap_filepath_merge, or we haven't.
  */
 AP_DECLARE(int) ap_os_is_path_absolute(apr_pool_t *p, const char *dir)
 {
Index: server/util_script.c
===================================================================
--- server/util_script.c        (revision 1918820)
+++ server/util_script.c        (working copy)
@@ -441,7 +441,7 @@
                                   NULL);
 #ifdef WIN32
             /* We need to make this a real Windows path name */
-            apr_filepath_merge(&pt, "", pt, APR_FILEPATH_NATIVE, r->pool);
+            ap_filepath_merge(&pt, "", pt, APR_FILEPATH_NATIVE, r->pool);
 #endif
             apr_table_setn(e, "PATH_TRANSLATED", pt);
         }

Reply via email to