svn_repos_trace_node_locations() reveals paths hidden by authz Summary ======= Subversion servers, both httpd and svnserve, will reveal some paths that should be hidden by path-based authz. When a node is copied from an unreadable location to a readable location the unreadable path may be revealed. This vulnerablity only reveals the path, it does not reveal the contents of the path. Known vulnerable ================ Subversion 1.8.0 to 1.8.13 Subversion 1.7.0 to 1.7.20 All older versions Known fixed =========== Subversion 1.8.14 Subversion 1.7.21 Details ======= The function svn_repos_trace_node_locations() follows the history of a node through earlier revisions and shows any copies. It should stop following history when an unreadable path is encountered but an implementation error causes the first unreadable path to be returned in some circumstances. This only reveals the unreadable path, it does not reveal the content of the file or directory at that path. Any attempts to obtain the content will fail with an authorization error. Examples: 1. After copying "unreadable-path" to "readable-path" then following the history of "readable-path" may reveal "unreadable-path". 2. After copying "unreadable/some-path" to "readable/other-path" then following the history of "readable/other-path" may reveal "unreadable/some-path". Severity ======== CVSSv2 Base Score: 3.5 CVSSv2 Base Vector: AV:N/AC:M/Au:S/C:P/I:N/A:N Recommendations =============== All Subversion servers using path-based authz to deny read access to sensitive paths should be upgraded. References ========== CVE-2015-3187 (Subversion) Reported by =========== C. Michael Pilato, CollabNet Patches ======= Patches: ======== Patch for Subversion 1.8.13: [[[ Index: subversion/libsvn_repos/rev_hunt.c =================================================================== --- subversion/libsvn_repos/rev_hunt.c (revision 1685077) +++ subversion/libsvn_repos/rev_hunt.c (working copy) @@ -726,23 +726,6 @@ svn_repos_trace_node_locations(svn_fs_t *fs, if (! prev_path) break; - if (authz_read_func) - { - svn_boolean_t readable; - svn_fs_root_t *tmp_root; - - SVN_ERR(svn_fs_revision_root(&tmp_root, fs, revision, currpool)); - SVN_ERR(authz_read_func(&readable, tmp_root, path, - authz_read_baton, currpool)); - if (! readable) - { - svn_pool_destroy(lastpool); - svn_pool_destroy(currpool); - - return SVN_NO_ERROR; - } - } - /* Assign the current path to all younger revisions until we reach the copy target rev. */ while ((revision_ptr < revision_ptr_end) @@ -765,6 +748,20 @@ svn_repos_trace_node_locations(svn_fs_t *fs, path = prev_path; revision = prev_rev; + if (authz_read_func) + { + svn_boolean_t readable; + SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool)); + SVN_ERR(authz_read_func(&readable, root, path, + authz_read_baton, currpool)); + if (!readable) + { + svn_pool_destroy(lastpool); + svn_pool_destroy(currpool); + return SVN_NO_ERROR; + } + } + /* Clear last pool and switch. */ svn_pool_clear(lastpool); tmppool = lastpool; Index: subversion/tests/cmdline/authz_tests.py =================================================================== --- subversion/tests/cmdline/authz_tests.py (revision 1685077) +++ subversion/tests/cmdline/authz_tests.py (working copy) @@ -609,8 +609,10 @@ def authz_log_and_tracing_test(sbox): ## cat + expected_err2 = ".*svn: E195012: Unable to find repository location.*" + # now see if we can look at the older version of rho - svntest.actions.run_and_verify_svn(None, None, expected_err, + svntest.actions.run_and_verify_svn(None, None, expected_err2, 'cat', '-r', '2', D_url+'/rho') if sbox.repo_url.startswith('http'): @@ -627,10 +629,11 @@ def authz_log_and_tracing_test(sbox): svntest.actions.run_and_verify_svn(None, None, expected_err, 'diff', '-r', 'HEAD', G_url+'/rho') - svntest.actions.run_and_verify_svn(None, None, expected_err, + # diff treats the unreadable path as indicating an add so no error + svntest.actions.run_and_verify_svn(None, None, [], 'diff', '-r', '2', D_url+'/rho') - svntest.actions.run_and_verify_svn(None, None, expected_err, + svntest.actions.run_and_verify_svn(None, None, [], 'diff', '-r', '2:4', D_url+'/rho') # test whether read access is correctly granted and denied Index: subversion/tests/libsvn_repos/repos-test.c =================================================================== --- subversion/tests/libsvn_repos/repos-test.c (revision 1685077) +++ subversion/tests/libsvn_repos/repos-test.c (working copy) @@ -3524,6 +3524,245 @@ test_load_r0_mergeinfo(const svn_test_opts_t *opts return SVN_NO_ERROR; } +static svn_error_t * +mkdir_delete_copy(svn_repos_t *repos, + const char *src, + const char *dst, + apr_pool_t *pool) +{ + svn_fs_t *fs = svn_repos_fs(repos); + svn_revnum_t youngest_rev; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root, *rev_root; + + SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool)); + + SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_make_dir(txn_root, "A/T", pool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); + + SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_delete(txn_root, "A/T", pool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); + + SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev - 1, pool)); + SVN_ERR(svn_fs_copy(rev_root, src, txn_root, dst, pool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); + + return SVN_NO_ERROR; +} + +struct authz_read_baton_t { + apr_hash_t *paths; + apr_pool_t *pool; + const char *deny; +}; + +static svn_error_t * +authz_read_func(svn_boolean_t *allowed, + svn_fs_root_t *root, + const char *path, + void *baton, + apr_pool_t *pool) +{ + struct authz_read_baton_t *b = baton; + + if (b->deny && !strcmp(b->deny, path)) + *allowed = FALSE; + else + *allowed = TRUE; + + svn_hash_sets(b->paths, apr_pstrdup(b->pool, path), (void*)1); + + return SVN_NO_ERROR; +} + +static svn_error_t * +verify_locations(apr_hash_t *actual, + apr_hash_t *expected, + apr_hash_t *checked, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, expected); hi; hi = apr_hash_next(hi)) + { + const svn_revnum_t *rev = svn__apr_hash_index_key(hi); + const char *path = apr_hash_get(actual, rev, sizeof(svn_revnum_t)); + + if (!path) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "expected %s for %d found (null)", + (char*)svn__apr_hash_index_val(hi), + (int)*rev); + else if (strcmp(path, svn__apr_hash_index_val(hi))) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "expected %s for %d found %s", + (char*)svn__apr_hash_index_val(hi), + (int)*rev, path); + + } + + for (hi = apr_hash_first(pool, actual); hi; hi = apr_hash_next(hi)) + { + const svn_revnum_t *rev = svn__apr_hash_index_key(hi); + const char *path = apr_hash_get(expected, rev, sizeof(svn_revnum_t)); + + if (!path) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "found %s for %d expected (null)", + (char*)svn__apr_hash_index_val(hi), + (int)*rev); + else if (strcmp(path, svn__apr_hash_index_val(hi))) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "found %s for %d expected %s", + (char*)svn__apr_hash_index_val(hi), + (int)*rev, path); + + if (!svn_hash_gets(checked, path)) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "did not check %s", path); + } + + return SVN_NO_ERROR; +} + +static void +set_expected(apr_hash_t *expected, + svn_revnum_t rev, + const char *path, + apr_pool_t *pool) +{ + svn_revnum_t *rp = apr_palloc(pool, sizeof(svn_revnum_t)); + *rp = rev; + apr_hash_set(expected, rp, sizeof(svn_revnum_t), path); +} + +static svn_error_t * +trace_node_locations_authz(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_repos_t *repos; + svn_fs_t *fs; + svn_revnum_t youngest_rev = 0; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + struct authz_read_baton_t arb; + apr_array_header_t *revs = apr_array_make(pool, 10, sizeof(svn_revnum_t)); + apr_hash_t *locations; + apr_hash_t *expected = apr_hash_make(pool); + int i; + + /* Create test repository. */ + SVN_ERR(svn_test__create_repos(&repos, "test-repo-trace-node-locations-authz", + opts, pool)); + fs = svn_repos_fs(repos); + + /* r1 create A */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_make_dir(txn_root, "A", pool)); + SVN_ERR(svn_fs_make_file(txn_root, "A/f", pool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "A/f", "foobar", pool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); + + /* r4 copy A to B */ + SVN_ERR(mkdir_delete_copy(repos, "A", "B", pool)); + + /* r7 copy B to C */ + SVN_ERR(mkdir_delete_copy(repos, "B", "C", pool)); + + /* r10 copy C to D */ + SVN_ERR(mkdir_delete_copy(repos, "C", "D", pool)); + + SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool)); + SVN_ERR_ASSERT(youngest_rev == 10); + + arb.paths = apr_hash_make(pool); + arb.pool = pool; + arb.deny = NULL; + + apr_array_clear(revs); + for (i = 0; i <= youngest_rev; ++i) + APR_ARRAY_PUSH(revs, svn_revnum_t) = i; + set_expected(expected, 10, "/D/f", pool); + set_expected(expected, 8, "/C/f", pool); + set_expected(expected, 7, "/C/f", pool); + set_expected(expected, 5, "/B/f", pool); + set_expected(expected, 4, "/B/f", pool); + set_expected(expected, 2, "/A/f", pool); + set_expected(expected, 1, "/A/f", pool); + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + apr_array_clear(revs); + for (i = 1; i <= youngest_rev; ++i) + APR_ARRAY_PUSH(revs, svn_revnum_t) = i; + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + apr_array_clear(revs); + for (i = 2; i <= youngest_rev; ++i) + APR_ARRAY_PUSH(revs, svn_revnum_t) = i; + set_expected(expected, 1, NULL, pool); + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + apr_array_clear(revs); + for (i = 3; i <= youngest_rev; ++i) + APR_ARRAY_PUSH(revs, svn_revnum_t) = i; + set_expected(expected, 2, NULL, pool); + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + apr_array_clear(revs); + for (i = 6; i <= youngest_rev; ++i) + APR_ARRAY_PUSH(revs, svn_revnum_t) = i; + set_expected(expected, 5, NULL, pool); + set_expected(expected, 4, NULL, pool); + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + arb.deny = "/B/f"; + apr_array_clear(revs); + for (i = 0; i <= youngest_rev; ++i) + APR_ARRAY_PUSH(revs, svn_revnum_t) = i; + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + apr_array_clear(revs); + for (i = 6; i <= youngest_rev; ++i) + APR_ARRAY_PUSH(revs, svn_revnum_t) = i; + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + APR_ARRAY_PUSH(revs, svn_revnum_t) = 0; + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + return SVN_NO_ERROR; +} + /* The test table. */ struct svn_test_descriptor_t test_funcs[] = @@ -3573,5 +3812,7 @@ struct svn_test_descriptor_t test_funcs[] = "test dumping with r0 mergeinfo"), SVN_TEST_OPTS_PASS(test_load_r0_mergeinfo, "test loading with r0 mergeinfo"), + SVN_TEST_OPTS_PASS(trace_node_locations_authz, + "authz for svn_repos_trace_node_locations"), SVN_TEST_NULL }; ]]] Patch for Subversion 1.7.20: [[[ Index: subversion/libsvn_repos/rev_hunt.c =================================================================== --- subversion/libsvn_repos/rev_hunt.c (revision 1685077) +++ subversion/libsvn_repos/rev_hunt.c (working copy) @@ -721,23 +721,6 @@ svn_repos_trace_node_locations(svn_fs_t *fs, if (! prev_path) break; - if (authz_read_func) - { - svn_boolean_t readable; - svn_fs_root_t *tmp_root; - - SVN_ERR(svn_fs_revision_root(&tmp_root, fs, revision, currpool)); - SVN_ERR(authz_read_func(&readable, tmp_root, path, - authz_read_baton, currpool)); - if (! readable) - { - svn_pool_destroy(lastpool); - svn_pool_destroy(currpool); - - return SVN_NO_ERROR; - } - } - /* Assign the current path to all younger revisions until we reach the copy target rev. */ while ((revision_ptr < revision_ptr_end) @@ -760,6 +743,20 @@ svn_repos_trace_node_locations(svn_fs_t *fs, path = prev_path; revision = prev_rev; + if (authz_read_func) + { + svn_boolean_t readable; + SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool)); + SVN_ERR(authz_read_func(&readable, root, path, + authz_read_baton, currpool)); + if (!readable) + { + svn_pool_destroy(lastpool); + svn_pool_destroy(currpool); + return SVN_NO_ERROR; + } + } + /* Clear last pool and switch. */ svn_pool_clear(lastpool); tmppool = lastpool; Index: subversion/tests/cmdline/authz_tests.py =================================================================== --- subversion/tests/cmdline/authz_tests.py (revision 1685077) +++ subversion/tests/cmdline/authz_tests.py (working copy) @@ -608,8 +608,10 @@ def authz_log_and_tracing_test(sbox): ## cat + expected_err2 = ".*svn: E195012: Unable to find repository location.*" + # now see if we can look at the older version of rho - svntest.actions.run_and_verify_svn(None, None, expected_err, + svntest.actions.run_and_verify_svn(None, None, expected_err2, 'cat', '-r', '2', D_url+'/rho') if sbox.repo_url.startswith('http'): @@ -626,10 +628,11 @@ def authz_log_and_tracing_test(sbox): svntest.actions.run_and_verify_svn(None, None, expected_err, 'diff', '-r', 'HEAD', G_url+'/rho') - svntest.actions.run_and_verify_svn(None, None, expected_err, + # diff treats the unreadable path as indicating an add so no error + svntest.actions.run_and_verify_svn(None, None, [], 'diff', '-r', '2', D_url+'/rho') - svntest.actions.run_and_verify_svn(None, None, expected_err, + svntest.actions.run_and_verify_svn(None, None, [], 'diff', '-r', '2:4', D_url+'/rho') # test whether read access is correctly granted and denied Index: subversion/tests/libsvn_repos/repos-test.c =================================================================== --- subversion/tests/libsvn_repos/repos-test.c (revision 1685077) +++ subversion/tests/libsvn_repos/repos-test.c (working copy) @@ -2586,6 +2586,246 @@ test_dump_r0_mergeinfo(const svn_test_opts_t *opts return SVN_NO_ERROR; } +static svn_error_t * +mkdir_delete_copy(svn_repos_t *repos, + const char *src, + const char *dst, + apr_pool_t *pool) +{ + svn_fs_t *fs = svn_repos_fs(repos); + svn_revnum_t youngest_rev; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root, *rev_root; + + SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool)); + + SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_make_dir(txn_root, "A/T", pool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); + + SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_delete(txn_root, "A/T", pool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); + + SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev - 1, pool)); + SVN_ERR(svn_fs_copy(rev_root, src, txn_root, dst, pool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); + + return SVN_NO_ERROR; +} + +struct authz_read_baton_t { + apr_hash_t *paths; + apr_pool_t *pool; + const char *deny; +}; + +static svn_error_t * +authz_read_func(svn_boolean_t *allowed, + svn_fs_root_t *root, + const char *path, + void *baton, + apr_pool_t *pool) +{ + struct authz_read_baton_t *b = baton; + + if (b->deny && !strcmp(b->deny, path)) + *allowed = FALSE; + else + *allowed = TRUE; + + apr_hash_set(b->paths, apr_pstrdup(b->pool, path), APR_HASH_KEY_STRING, + (void*)1); + + return SVN_NO_ERROR; +} + +static svn_error_t * +verify_locations(apr_hash_t *actual, + apr_hash_t *expected, + apr_hash_t *checked, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, expected); hi; hi = apr_hash_next(hi)) + { + const svn_revnum_t *rev = svn__apr_hash_index_key(hi); + const char *path = apr_hash_get(actual, rev, sizeof(svn_revnum_t)); + + if (!path) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "expected %s for %d found (null)", + (char*)svn__apr_hash_index_val(hi), + (int)*rev); + else if (strcmp(path, svn__apr_hash_index_val(hi))) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "expected %s for %d found %s", + (char*)svn__apr_hash_index_val(hi), + (int)*rev, path); + + } + + for (hi = apr_hash_first(pool, actual); hi; hi = apr_hash_next(hi)) + { + const svn_revnum_t *rev = svn__apr_hash_index_key(hi); + const char *path = apr_hash_get(expected, rev, sizeof(svn_revnum_t)); + + if (!path) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "found %s for %d expected (null)", + (char*)svn__apr_hash_index_val(hi), + (int)*rev); + else if (strcmp(path, svn__apr_hash_index_val(hi))) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "found %s for %d expected %s", + (char*)svn__apr_hash_index_val(hi), + (int)*rev, path); + + if (!apr_hash_get(checked, path, APR_HASH_KEY_STRING)) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "did not check %s", path); + } + + return SVN_NO_ERROR; +} + +static void +set_expected(apr_hash_t *expected, + svn_revnum_t rev, + const char *path, + apr_pool_t *pool) +{ + svn_revnum_t *rp = apr_palloc(pool, sizeof(svn_revnum_t)); + *rp = rev; + apr_hash_set(expected, rp, sizeof(svn_revnum_t), path); +} + +static svn_error_t * +trace_node_locations_authz(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_repos_t *repos; + svn_fs_t *fs; + svn_revnum_t youngest_rev = 0; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + struct authz_read_baton_t arb; + apr_array_header_t *revs = apr_array_make(pool, 10, sizeof(svn_revnum_t)); + apr_hash_t *locations; + apr_hash_t *expected = apr_hash_make(pool); + int i; + + /* Create test repository. */ + SVN_ERR(svn_test__create_repos(&repos, "test-repo-trace-node-locations-authz", + opts, pool)); + fs = svn_repos_fs(repos); + + /* r1 create A */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_make_dir(txn_root, "A", pool)); + SVN_ERR(svn_fs_make_file(txn_root, "A/f", pool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "A/f", "foobar", pool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); + + /* r4 copy A to B */ + SVN_ERR(mkdir_delete_copy(repos, "A", "B", pool)); + + /* r7 copy B to C */ + SVN_ERR(mkdir_delete_copy(repos, "B", "C", pool)); + + /* r10 copy C to D */ + SVN_ERR(mkdir_delete_copy(repos, "C", "D", pool)); + + SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool)); + SVN_ERR_ASSERT(youngest_rev == 10); + + arb.paths = apr_hash_make(pool); + arb.pool = pool; + arb.deny = NULL; + + apr_array_clear(revs); + for (i = 0; i <= youngest_rev; ++i) + APR_ARRAY_PUSH(revs, svn_revnum_t) = i; + set_expected(expected, 10, "/D/f", pool); + set_expected(expected, 8, "/C/f", pool); + set_expected(expected, 7, "/C/f", pool); + set_expected(expected, 5, "/B/f", pool); + set_expected(expected, 4, "/B/f", pool); + set_expected(expected, 2, "/A/f", pool); + set_expected(expected, 1, "/A/f", pool); + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + apr_array_clear(revs); + for (i = 1; i <= youngest_rev; ++i) + APR_ARRAY_PUSH(revs, svn_revnum_t) = i; + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + apr_array_clear(revs); + for (i = 2; i <= youngest_rev; ++i) + APR_ARRAY_PUSH(revs, svn_revnum_t) = i; + set_expected(expected, 1, NULL, pool); + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + apr_array_clear(revs); + for (i = 3; i <= youngest_rev; ++i) + APR_ARRAY_PUSH(revs, svn_revnum_t) = i; + set_expected(expected, 2, NULL, pool); + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + apr_array_clear(revs); + for (i = 6; i <= youngest_rev; ++i) + APR_ARRAY_PUSH(revs, svn_revnum_t) = i; + set_expected(expected, 5, NULL, pool); + set_expected(expected, 4, NULL, pool); + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + arb.deny = "/B/f"; + apr_array_clear(revs); + for (i = 0; i <= youngest_rev; ++i) + APR_ARRAY_PUSH(revs, svn_revnum_t) = i; + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + apr_array_clear(revs); + for (i = 6; i <= youngest_rev; ++i) + APR_ARRAY_PUSH(revs, svn_revnum_t) = i; + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + APR_ARRAY_PUSH(revs, svn_revnum_t) = 0; + apr_hash_clear(arb.paths); + SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs, + authz_read_func, &arb, pool)); + SVN_ERR(verify_locations(locations, expected, arb.paths, pool)); + + return SVN_NO_ERROR; +} + /* The test table. */ struct svn_test_descriptor_t test_funcs[] = @@ -2623,5 +2863,7 @@ struct svn_test_descriptor_t test_funcs[] = "test issue 4060"), SVN_TEST_OPTS_PASS(test_dump_r0_mergeinfo, "test dumping with r0 mergeinfo"), + SVN_TEST_OPTS_PASS(trace_node_locations_authz, + "authz for svn_repos_trace_node_locations"), SVN_TEST_NULL }; ]]]