mod_dav_svn denial-of-service via control characters in paths Summary: ======== It has been discovered that the patch for CVE-2013-1968 was incomplete and unintentionally left mod_dav_svn vulnerable to control characters in filenames. If a path or a revision-property which contains control characters is committed to a repository then SVN operations served by mod_dav_svn can be disrupted. Known vulnerable: ================= Subversion mod_dav_svn servers through 1.14.4 (inclusive). Known fixed: ============ Servers running Subversion 1.14.5 Details: ======== If a path which contains control characters is committed to a repository then SVN operations served by mod_dav_svn can be disrupted by encoding errors raised from the XML library. This leads to disruption for users accessing the repository via HTTP. Affected repositories can be repaired (see "Recommendations" below). However, restoring proper operation might take some time because a full dump/load cycle may be required. Local repositories and svnserve repository servers (accessed via a file://, svn://, or svn+ssh:// URL) are not affected. In these cases, control characters have been rejected since CVE-2013-1968 was patched in Subversion 1.6.21 and Subversion 1.7.9. Known symptoms of the problem include: 1) 'svn checkout', 'svnsync', and other operations that attempt to read the affected revision may produce errors like: svn: E175009: The XML response contains invalid XML svn: E130003: Malformed XML: not well-formed (invalid token) 2) Attempts to browse affected files or directories via the web interface will cause the server to return: 500 Internal Server Error Apache Subversion clients have always rejected filenames with control characters, so control characters cannot be introduced with stock Subversion clients. They could, however, be triggered by custom malicious Subversion clients or by third-party client implementations. Servers updated to Subversion 1.14.5 will reject control characters in all cases. Severity: ========= CVSSv3.1 Base Score: 3.1 CVSSv3.1 Base Vector: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L A remote authenticated attacker with commit access may be able to corrupt repositories on a Subversion server and cause disruption for other users. Configurations that allow anonymous write access to the repository will be vulnerable to this without authentication. Recommendations: ================ We recommend all users to upgrade their servers to a known fixed release of Subversion. Users who are unable to upgrade may apply the patch included below. New Subversion packages can be found at: http://subversion.apache.org/packages.html Repositories affected by this problem can be repaired manually: Bad revision properties can be repaired by using svn propedit over the file://, svn:// or svn+ssh:// protocols. Bad paths which have entered a repository need to be removed from history with a dump/load cycle, using svnadmin dump --exclude to filter out the bad paths, and loading the result into a fresh repository with svnadmin load. References: =========== CVE-2024-46901 (Subversion) CVE-2013-1968 (Subversion) XML Characters: https://www.w3.org/TR/xml/#charsets Reported by: ============ HaoZi, WordPress China Patches: ======== Patch against Subversion 1.14.4: [[[ Index: subversion/include/private/svn_repos_private.h =================================================================== --- subversion/include/private/svn_repos_private.h (revision 1921550) +++ subversion/include/private/svn_repos_private.h (working copy) @@ -390,6 +390,14 @@ svn_repos__get_dump_editor(const svn_delta_editor_ const char *update_anchor_relpath, apr_pool_t *pool); +/* Validate that the given PATH is a valid pathname that can be stored in + * a Subversion repository, according to the name constraints used by the + * svn_repos_* layer. + */ +svn_error_t * +svn_repos__validate_new_path(const char *path, + apr_pool_t *scratch_pool); + #ifdef __cplusplus } #endif /* __cplusplus */ Index: subversion/libsvn_repos/commit.c =================================================================== --- subversion/libsvn_repos/commit.c (revision 1921550) +++ subversion/libsvn_repos/commit.c (working copy) @@ -308,8 +308,7 @@ add_file_or_directory(const char *path, svn_boolean_t was_copied = FALSE; const char *full_path, *canonicalized_path; - /* Reject paths which contain control characters (related to issue #4340). */ - SVN_ERR(svn_path_check_valid(path, pool)); + SVN_ERR(svn_repos__validate_new_path(path, pool)); SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path, pool, pool)); Index: subversion/libsvn_repos/repos.c =================================================================== --- subversion/libsvn_repos/repos.c (revision 1921550) +++ subversion/libsvn_repos/repos.c (working copy) @@ -2092,3 +2092,13 @@ svn_repos__fs_type(const char **fs_type, svn_dirent_join(repos_path, SVN_REPOS__DB_DIR, pool), pool); } + +svn_error_t * +svn_repos__validate_new_path(const char *path, + apr_pool_t *scratch_pool) +{ + /* Reject paths which contain control characters (related to issue #4340). */ + SVN_ERR(svn_path_check_valid(path, scratch_pool)); + + return SVN_NO_ERROR; +} Index: subversion/mod_dav_svn/lock.c =================================================================== --- subversion/mod_dav_svn/lock.c (revision 1921550) +++ subversion/mod_dav_svn/lock.c (working copy) @@ -36,6 +36,7 @@ #include "svn_pools.h" #include "svn_props.h" #include "private/svn_log.h" +#include "private/svn_repos_private.h" #include "dav_svn.h" @@ -717,6 +718,12 @@ append_locks(dav_lockdb *lockdb, /* Commit a 0-byte file: */ + if ((serr = svn_repos__validate_new_path(resource->info->repos_path, + resource->pool))) + return dav_svn__convert_err(serr, HTTP_BAD_REQUEST, + "Request specifies an invalid path.", + resource->pool); + if ((serr = dav_svn__get_youngest_rev(&rev, repos, resource->pool))) return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "Could not determine youngest revision", Index: subversion/mod_dav_svn/repos.c =================================================================== --- subversion/mod_dav_svn/repos.c (revision 1921550) +++ subversion/mod_dav_svn/repos.c (working copy) @@ -2928,6 +2928,16 @@ open_stream(const dav_resource *resource, if (kind == svn_node_none) /* No existing file. */ { + serr = svn_repos__validate_new_path(resource->info->repos_path, + resource->pool); + + if (serr != NULL) + { + return dav_svn__convert_err(serr, HTTP_BAD_REQUEST, + "Request specifies an invalid path.", + resource->pool); + } + serr = svn_fs_make_file(resource->info->root.root, resource->info->repos_path, resource->pool); @@ -4120,6 +4130,14 @@ create_collection(dav_resource *resource) return err; } + if ((serr = svn_repos__validate_new_path(resource->info->repos_path, + resource->pool)) != NULL) + { + return dav_svn__convert_err(serr, HTTP_BAD_REQUEST, + "Request specifies an invalid path.", + resource->pool); + } + if ((serr = svn_fs_make_dir(resource->info->root.root, resource->info->repos_path, resource->pool)) != NULL) @@ -4194,6 +4212,12 @@ copy_resource(const dav_resource *src, return err; } + serr = svn_repos__validate_new_path(dst->info->repos_path, dst->pool); + if (serr) + return dav_svn__convert_err(serr, HTTP_BAD_REQUEST, + "Request specifies an invalid path.", + dst->pool); + src_repos_path = svn_repos_path(src->info->repos->repos, src->pool); dst_repos_path = svn_repos_path(dst->info->repos->repos, dst->pool); @@ -4430,6 +4454,12 @@ move_resource(dav_resource *src, if (err) return err; + serr = svn_repos__validate_new_path(dst->info->repos_path, dst->pool); + if (serr) + return dav_svn__convert_err(serr, HTTP_BAD_REQUEST, + "Request specifies an invalid path.", + dst->pool); + /* Copy the src to the dst. */ serr = svn_fs_copy(src->info->root.root, /* the root object of src rev*/ src->info->repos_path, /* the relative path of src */ ]]]