Subversion mod_dav_svn and svnserve are vulnerable to a remotely triggerable assertion DoS vulnerability for certain requests with dynamically evaluated revision numbers. Summary: ======== Subversion's mod_dav_svn and svnserve servers will trigger an assertion while processing some requests with special parameters, which are evaluated on the server side. Assertion will cause svnserve process or the process hosting mod_dav_svn module (Apache) to abort. This can lead to a DoS. There are no known instances of this problem being exploited in the wild, but an exploit has been tested. Known vulnerable: ================= Subversion servers 1.6.0 through 1.7.19 (inclusive) Subversion servers 1.8.0 through 1.8.11 (inclusive) Known fixed: ============ Subversion 1.7.20 Subversion 1.8.13 Subversion 1.8.12 was not publicly released. Details: ======== Subversion's http:// and svn:// protocol support includes certain request types with parameters, which are evaluated on the server side. As an example, sometimes clients need to trace the history of the object to its origin, while not knowing the exact value of the origin (revision number) prior to issuing the request. Certain parameter combinations can exploit this behavior and force a server into attempting an operation with invalid arguments. Subversion servers guard against these situations with assertion statements, and the default behavior for a failed assertion is to abort the current process. Severity: ========= CVSSv2 Base Score: 5.0 CVSSv2 Base Vector: AV:N/AC:L/Au:N/C:N/I:N/A:P We consider this to be a medium risk vulnerability. Apache HTTPD servers with repositories that allow anonymous reads will be vulnerable without authentication. Many Apache servers will respawn the listener processes, but a determined attacker will be able to crash these processes as they appear, denying service to legitimate users. Servers using threaded MPMs will close the connection on other clients being served by the same process that services the request from the attacker. In either case there is an increased processing impact of restarting a process and the cost of per process caches being lost. Exploiting this behavior against svnserve does not require an attacker to authenticate. A remote attacker can cause svnserve process to terminate and thus deny service to users of the server. Unfortunately, no special configuration is required and all mod_dav_svn and svnserve servers are vulnerable. Recommendations: ================ We recommend all users to upgrade to Subversion 1.8.13. Users of Subversion 1.7.x or 1.8.x who are unable to upgrade may apply the included patch. New Subversion packages can be found at: http://subversion.apache.org/packages.html No known workarounds are available. References: =========== CVE-2015-0248 (Subversion) Reported by: ============ Evgeny Kotkov, VisualSVN Patches: ======== Patch against 1.7.19: [[[ Index: subversion/mod_dav_svn/reports/get-location-segments.c =================================================================== --- subversion/mod_dav_svn/reports/get-location-segments.c (revision 1658197) +++ subversion/mod_dav_svn/reports/get-location-segments.c (working copy) @@ -181,17 +181,36 @@ dav_svn__get_location_segments_report(const dav_re "Not all parameters passed.", SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG); - if (SVN_IS_VALID_REVNUM(start_rev) - && SVN_IS_VALID_REVNUM(end_rev) - && (end_rev > start_rev)) + + /* No START_REV or PEG_REVISION? We'll use HEAD. */ + if (!SVN_IS_VALID_REVNUM(start_rev) || !SVN_IS_VALID_REVNUM(peg_revision)) + { + svn_revnum_t youngest; + + serr = svn_fs_youngest_rev(&youngest, resource->info->repos->fs, + resource->pool); + if (serr != NULL) + return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, + "Could not determine youngest revision", + resource->pool); + + if (!SVN_IS_VALID_REVNUM(start_rev)) + start_rev = youngest; + if (!SVN_IS_VALID_REVNUM(peg_revision)) + peg_revision = youngest; + } + + /* No END_REV? We'll use 0. */ + if (!SVN_IS_VALID_REVNUM(end_rev)) + end_rev = 0; + + if (end_rev > start_rev) return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0, "End revision must not be younger than " "start revision", SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG); - if (SVN_IS_VALID_REVNUM(peg_revision) - && SVN_IS_VALID_REVNUM(start_rev) - && (start_rev > peg_revision)) + if (start_rev > peg_revision) return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0, "Start revision must not be younger than " "peg revision", Index: subversion/svnserve/serve.c =================================================================== --- subversion/svnserve/serve.c (revision 1658197) +++ subversion/svnserve/serve.c (working copy) @@ -2266,10 +2266,31 @@ static svn_error_t *get_location_segments(svn_ra_s abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool); - if (SVN_IS_VALID_REVNUM(start_rev) - && SVN_IS_VALID_REVNUM(end_rev) - && (end_rev > start_rev)) + SVN_ERR(trivial_auth_request(conn, pool, b)); + SVN_ERR(log_command(baton, conn, pool, "%s", + svn_log__get_location_segments(abs_path, peg_revision, + start_rev, end_rev, + pool))); + + /* No START_REV or PEG_REVISION? We'll use HEAD. */ + if (!SVN_IS_VALID_REVNUM(start_rev) || !SVN_IS_VALID_REVNUM(peg_revision)) { + svn_revnum_t youngest; + + SVN_CMD_ERR(svn_fs_youngest_rev(&youngest, b->fs, pool)); + + if (!SVN_IS_VALID_REVNUM(start_rev)) + start_rev = youngest; + if (!SVN_IS_VALID_REVNUM(peg_revision)) + peg_revision = youngest; + } + + /* No END_REV? We'll use 0. */ + if (!SVN_IS_VALID_REVNUM(end_rev)) + end_rev = 0; + + if (end_rev > start_rev) + { err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, "Get-location-segments end revision must not be " "younger than start revision"); @@ -2276,9 +2297,7 @@ static svn_error_t *get_location_segments(svn_ra_s return log_fail_and_flush(err, b, conn, pool); } - if (SVN_IS_VALID_REVNUM(peg_revision) - && SVN_IS_VALID_REVNUM(start_rev) - && (start_rev > peg_revision)) + if (start_rev > peg_revision) { err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, "Get-location-segments start revision must not " @@ -2286,12 +2305,6 @@ static svn_error_t *get_location_segments(svn_ra_s return log_fail_and_flush(err, b, conn, pool); } - SVN_ERR(trivial_auth_request(conn, pool, b)); - SVN_ERR(log_command(baton, conn, pool, "%s", - svn_log__get_location_segments(abs_path, peg_revision, - start_rev, end_rev, - pool))); - /* All the parameters are fine - let's perform the query against the * repository. */ ]]] Patch against 1.8.11: [[[ Index: subversion/mod_dav_svn/reports/get-location-segments.c =================================================================== --- subversion/mod_dav_svn/reports/get-location-segments.c (revision 1658197) +++ subversion/mod_dav_svn/reports/get-location-segments.c (working copy) @@ -181,17 +181,36 @@ dav_svn__get_location_segments_report(const dav_re "Not all parameters passed.", SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG); - if (SVN_IS_VALID_REVNUM(start_rev) - && SVN_IS_VALID_REVNUM(end_rev) - && (end_rev > start_rev)) + + /* No START_REV or PEG_REVISION? We'll use HEAD. */ + if (!SVN_IS_VALID_REVNUM(start_rev) || !SVN_IS_VALID_REVNUM(peg_revision)) + { + svn_revnum_t youngest; + + serr = svn_fs_youngest_rev(&youngest, resource->info->repos->fs, + resource->pool); + if (serr != NULL) + return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, + "Could not determine youngest revision", + resource->pool); + + if (!SVN_IS_VALID_REVNUM(start_rev)) + start_rev = youngest; + if (!SVN_IS_VALID_REVNUM(peg_revision)) + peg_revision = youngest; + } + + /* No END_REV? We'll use 0. */ + if (!SVN_IS_VALID_REVNUM(end_rev)) + end_rev = 0; + + if (end_rev > start_rev) return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0, "End revision must not be younger than " "start revision", SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG); - if (SVN_IS_VALID_REVNUM(peg_revision) - && SVN_IS_VALID_REVNUM(start_rev) - && (start_rev > peg_revision)) + if (start_rev > peg_revision) return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0, "Start revision must not be younger than " "peg revision", Index: subversion/svnserve/serve.c =================================================================== --- subversion/svnserve/serve.c (revision 1658197) +++ subversion/svnserve/serve.c (working copy) @@ -2468,10 +2468,31 @@ static svn_error_t *get_location_segments(svn_ra_s abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool); - if (SVN_IS_VALID_REVNUM(start_rev) - && SVN_IS_VALID_REVNUM(end_rev) - && (end_rev > start_rev)) + SVN_ERR(trivial_auth_request(conn, pool, b)); + SVN_ERR(log_command(baton, conn, pool, "%s", + svn_log__get_location_segments(abs_path, peg_revision, + start_rev, end_rev, + pool))); + + /* No START_REV or PEG_REVISION? We'll use HEAD. */ + if (!SVN_IS_VALID_REVNUM(start_rev) || !SVN_IS_VALID_REVNUM(peg_revision)) { + svn_revnum_t youngest; + + SVN_CMD_ERR(svn_fs_youngest_rev(&youngest, b->fs, pool)); + + if (!SVN_IS_VALID_REVNUM(start_rev)) + start_rev = youngest; + if (!SVN_IS_VALID_REVNUM(peg_revision)) + peg_revision = youngest; + } + + /* No END_REV? We'll use 0. */ + if (!SVN_IS_VALID_REVNUM(end_rev)) + end_rev = 0; + + if (end_rev > start_rev) + { err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, "Get-location-segments end revision must not be " "younger than start revision"); @@ -2478,9 +2499,7 @@ static svn_error_t *get_location_segments(svn_ra_s return log_fail_and_flush(err, b, conn, pool); } - if (SVN_IS_VALID_REVNUM(peg_revision) - && SVN_IS_VALID_REVNUM(start_rev) - && (start_rev > peg_revision)) + if (start_rev > peg_revision) { err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, "Get-location-segments start revision must not " @@ -2488,12 +2507,6 @@ static svn_error_t *get_location_segments(svn_ra_s return log_fail_and_flush(err, b, conn, pool); } - SVN_ERR(trivial_auth_request(conn, pool, b)); - SVN_ERR(log_command(baton, conn, pool, "%s", - svn_log__get_location_segments(abs_path, peg_revision, - start_rev, end_rev, - pool))); - /* All the parameters are fine - let's perform the query against the * repository. */ ]]]