139 lines
5.9 KiB
Diff
139 lines
5.9 KiB
Diff
Description: Subversion servers reveal 'copyfrom' paths that should be hidden
|
|
according to configured path-based authorization (authz) rules. When a node
|
|
has been copied from a protected location, users with access to the copy can
|
|
see the 'copyfrom' path of the original. This also reveals the fact that the
|
|
node was copied. Only the 'copyfrom' path is revealed; not its contents. Both
|
|
httpd and svnserve servers are vulnerable.
|
|
Author: Stefan Sperling <stsp@apache.org>
|
|
Origin: upstream
|
|
Last-Update: 2022-04-04
|
|
---
|
|
This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
|
|
--- a/subversion/libsvn_repos/log.c
|
|
+++ b/subversion/libsvn_repos/log.c
|
|
@@ -337,42 +337,36 @@ detect_changed(svn_repos_revision_access
|
|
if ( (change->change_kind == svn_fs_path_change_add)
|
|
|| (change->change_kind == svn_fs_path_change_replace))
|
|
{
|
|
- const char *copyfrom_path = change->copyfrom_path;
|
|
- svn_revnum_t copyfrom_rev = change->copyfrom_rev;
|
|
-
|
|
/* the following is a potentially expensive operation since on FSFS
|
|
we will follow the DAG from ROOT to PATH and that requires
|
|
actually reading the directories along the way. */
|
|
if (!change->copyfrom_known)
|
|
{
|
|
- SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path,
|
|
+ SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev, &change->copyfrom_path,
|
|
root, path, iterpool));
|
|
change->copyfrom_known = TRUE;
|
|
}
|
|
|
|
- if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
|
|
+ if (change->copyfrom_path && SVN_IS_VALID_REVNUM(change->copyfrom_rev))
|
|
{
|
|
- svn_boolean_t readable = TRUE;
|
|
-
|
|
if (callbacks->authz_read_func)
|
|
{
|
|
svn_fs_root_t *copyfrom_root;
|
|
+ svn_boolean_t readable;
|
|
|
|
SVN_ERR(svn_fs_revision_root(©from_root, fs,
|
|
- copyfrom_rev, iterpool));
|
|
+ change->copyfrom_rev, iterpool));
|
|
SVN_ERR(callbacks->authz_read_func(&readable,
|
|
copyfrom_root,
|
|
- copyfrom_path,
|
|
+ change->copyfrom_path,
|
|
callbacks->authz_read_baton,
|
|
iterpool));
|
|
if (! readable)
|
|
- found_unreadable = TRUE;
|
|
- }
|
|
-
|
|
- if (readable)
|
|
- {
|
|
- change->copyfrom_path = copyfrom_path;
|
|
- change->copyfrom_rev = copyfrom_rev;
|
|
+ {
|
|
+ found_unreadable = TRUE;
|
|
+ change->copyfrom_path = NULL;
|
|
+ change->copyfrom_rev = SVN_INVALID_REVNUM;
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
--- subversion-1.13.0.orig/subversion/tests/cmdline/authz_tests.py
|
|
+++ subversion-1.13.0/subversion/tests/cmdline/authz_tests.py
|
|
@@ -1524,6 +1524,61 @@ def authz_del_from_subdir(sbox):
|
|
'rm', sbox.repo_url + '/A/mu',
|
|
'-m', '')
|
|
|
|
+# test for the bug also known as CVE-2021-28544
|
|
+@Skip(svntest.main.is_ra_type_file)
|
|
+def log_inaccessible_copyfrom(sbox):
|
|
+ "log doesn't leak inaccessible copyfrom paths"
|
|
+
|
|
+ sbox.build(empty=True)
|
|
+ sbox.simple_add_text('secret', 'private')
|
|
+ sbox.simple_commit(message='log message for r1')
|
|
+ sbox.simple_copy('private', 'public')
|
|
+ sbox.simple_commit(message='log message for r2')
|
|
+
|
|
+ svntest.actions.enable_revprop_changes(sbox.repo_dir)
|
|
+ # Remove svn:date and svn:author for predictable output.
|
|
+ svntest.actions.run_and_verify_svn(None, [], 'propdel', '--revprop',
|
|
+ '-r2', 'svn:date', sbox.repo_url)
|
|
+ svntest.actions.run_and_verify_svn(None, [], 'propdel', '--revprop',
|
|
+ '-r2', 'svn:author', sbox.repo_url)
|
|
+
|
|
+ write_restrictive_svnserve_conf(sbox.repo_dir)
|
|
+
|
|
+ # First test with blanket access.
|
|
+ write_authz_file(sbox,
|
|
+ {"/" : "* = rw"})
|
|
+ expected_output = svntest.verify.ExpectedOutput([
|
|
+ "------------------------------------------------------------------------\n",
|
|
+ "r2 | (no author) | (no date) | 1 line\n",
|
|
+ "Changed paths:\n",
|
|
+ " A /public (from /private:1)\n",
|
|
+ "\n",
|
|
+ "log message for r2\n",
|
|
+ "------------------------------------------------------------------------\n",
|
|
+ ])
|
|
+ svntest.actions.run_and_verify_svn(expected_output, [],
|
|
+ 'log', '-r2', '-v',
|
|
+ sbox.repo_url)
|
|
+
|
|
+ # Now test with an inaccessible copy source (/private).
|
|
+ write_authz_file(sbox,
|
|
+ {"/" : "* = rw"},
|
|
+ {"/private" : "* ="})
|
|
+ expected_output = svntest.verify.ExpectedOutput([
|
|
+ "------------------------------------------------------------------------\n",
|
|
+ "r2 | (no author) | (no date) | 1 line\n",
|
|
+ "Changed paths:\n",
|
|
+ # The copy is shown as a plain add with no copyfrom info.
|
|
+ " A /public\n",
|
|
+ "\n",
|
|
+ # No log message, as the revision is only partially visible.
|
|
+ "\n",
|
|
+ "------------------------------------------------------------------------\n",
|
|
+ ])
|
|
+ svntest.actions.run_and_verify_svn(expected_output, [],
|
|
+ 'log', '-r2', '-v',
|
|
+ sbox.repo_url)
|
|
+
|
|
|
|
@SkipUnless(svntest.main.is_ra_type_dav) # dontdothat is dav only
|
|
def log_diff_dontdothat(sbox):
|
|
@@ -1771,6 +1826,7 @@ test_list = [ None,
|
|
inverted_group_membership,
|
|
group_member_empty_string,
|
|
empty_group,
|
|
+ log_inaccessible_copyfrom,
|
|
]
|
|
serial_only = True
|
|
|