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); }