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 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