From 47e4b1e2f1d90ab98e6992502b7dd5924bac2cb8 Mon Sep 17 00:00:00 2001 From: Arturo Bernal Date: Sun, 11 Oct 2026 15:57:23 +0200 Subject: [PATCH] mod_rewrite: Check UNCList before prefix_stat() probes a UNC path On Windows, prefix_stat() calls apr_filepath_root() with APR_FILEPATH_TRUENAME on the substitution result before any UNC check. For a UNC path this contacts the host, so a RewriteRule producing one could cause a network lookup even though UNCList denies it. Call ap_stat_check() first, like the file tests of RewriteCond do. PR 70210. --- changes-entries/rewrite-unc-prefix-stat.txt | 2 + modules/mappers/mod_rewrite.c | 8 ++++ test/modules/core/test_011_rewrite_unc.py | 46 +++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 changes-entries/rewrite-unc-prefix-stat.txt create mode 100644 test/modules/core/test_011_rewrite_unc.py diff --git a/changes-entries/rewrite-unc-prefix-stat.txt b/changes-entries/rewrite-unc-prefix-stat.txt new file mode 100644 index 00000000000..3a5b3ab7ee6 --- /dev/null +++ b/changes-entries/rewrite-unc-prefix-stat.txt @@ -0,0 +1,2 @@ + *) mod_rewrite: Check UNCList before probing the first path segment of a + RewriteRule substitution on Windows. PR 70210. diff --git a/modules/mappers/mod_rewrite.c b/modules/mappers/mod_rewrite.c index 9891545238c..c895c31a654 100644 --- a/modules/mappers/mod_rewrite.c +++ b/modules/mappers/mod_rewrite.c @@ -1001,6 +1001,14 @@ static int prefix_stat(request_rec *r, const char *path, apr_pool_t *pool, rewri char *statpath; apr_status_t rv; + /* On Windows, apr_filepath_root() with APR_FILEPATH_TRUENAME contacts the + * host of a UNC path. Check UNCList first, like for the other file checks. + */ + if (ap_stat_check(path, pool) != APR_SUCCESS) { + rewritelog(r, 4, NULL, "prefix_stat: refusing to stat path='%s'", path); + return 0; + } + rv = apr_filepath_root(&root, &curpath, APR_FILEPATH_TRUENAME, pool); if (rv != APR_SUCCESS) { diff --git a/test/modules/core/test_011_rewrite_unc.py b/test/modules/core/test_011_rewrite_unc.py new file mode 100644 index 00000000000..3c61eb33304 --- /dev/null +++ b/test/modules/core/test_011_rewrite_unc.py @@ -0,0 +1,46 @@ +import re +import sys + +import pytest + +from pyhttpd.conf import HttpdConf + +# UNCList only exists on Windows +pytestmark = pytest.mark.skipif( + sys.platform != "win32", reason="UNCList is a Windows feature") + + +class TestRewriteUnc: + + @pytest.fixture(autouse=True, scope='class') + def _class_scope(self, env): + conf = HttpdConf(env, extras={ + f"test1.{env.http_tld}": [ + "RewriteEngine On", + 'RewriteRule ^/v/(.*)$ "//$1"', + 'RewriteRule ^/vunc/(.*)$ "//$1" [UNC]', + ], + }) + conf.add_vhost_test1() + conf.install() + assert env.apache_restart() == 0 + + # No UNCList, all UNC paths are refused. A UNC path made by a + # RewriteRule must be refused as well, and before the host in it is + # contacted, which takes a long time for one that does not answer. + # The addresses are not routed (RFC 5737), a different one for + # each request as failed lookups are cached. + @pytest.mark.parametrize("path, host", [ + ["/v", "203.0.113.61"], + ["/vunc", "203.0.113.62"], + ]) + def test_core_011_01(self, env, path, host): + pos = env.httpd_error_log.current_pos() + url = env.mkurl("http", "test1", f"{path}/{host}/share/f.txt") + r = env.curl_get(url, options=["--max-time", "5"]) + assert r.response, f"no timely response, UNC host contacted? {r.stderr}" + assert r.response["status"] == 404 + assert env.httpd_error_log.wait_for( + re.compile(r".*AH10504: check_unc: UNC path //" + host), pos), \ + "UNCList was not consulted" + env.httpd_error_log.ignore_recent(lognos=["AH10504"])