diff options
Diffstat (limited to 'subversion/tests/libsvn_fs_fs/fs-fs-fuzzy-test.c')
-rw-r--r-- | subversion/tests/libsvn_fs_fs/fs-fs-fuzzy-test.c | 393 |
1 files changed, 393 insertions, 0 deletions
diff --git a/subversion/tests/libsvn_fs_fs/fs-fs-fuzzy-test.c b/subversion/tests/libsvn_fs_fs/fs-fs-fuzzy-test.c new file mode 100644 index 0000000..818c1e0 --- /dev/null +++ b/subversion/tests/libsvn_fs_fs/fs-fs-fuzzy-test.c @@ -0,0 +1,393 @@ +/* fs-fs-fuzzy-test.c --- fuzzing tests for the FSFS filesystem + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <stdlib.h> +#include <string.h> +#include <apr_pools.h> + +#include "../svn_test.h" +#include "../../libsvn_fs_fs/fs.h" +#include "../../libsvn_fs_fs/fs_fs.h" +#include "../../libsvn_fs_fs/rev_file.h" + +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_props.h" +#include "svn_fs.h" +#include "private/svn_string_private.h" +#include "private/svn_string_private.h" + +#include "../svn_test_fs.h" + + + +/*** Helper Functions ***/ + +/* We won't log or malfunction() upon errors. */ +static void +dont_filter_warnings(void *baton, svn_error_t *err) +{ + return; +} + + +/*** Test core code ***/ + +/* Verify that a modification of any single byte in REVISION of FS at + * REPO_NAME using MODIFIER with BATON will be detected. */ +static svn_error_t * +fuzzing_1_byte_1_rev(const char *repo_name, + svn_fs_t *fs, + svn_revnum_t revision, + unsigned char (* modifier)(unsigned char c, void *baton), + void *baton, + apr_pool_t *pool) +{ + svn_repos_t *repos; + apr_hash_t *fs_config; + svn_fs_fs__revision_file_t *rev_file; + apr_off_t filesize = 0, offset; + apr_off_t i; + unsigned char footer_len; + + apr_pool_t *iterpool = svn_pool_create(pool); + + /* Open the revision file for modification. */ + SVN_ERR(svn_fs_fs__open_pack_or_rev_file_writable(&rev_file, fs, revision, + pool, iterpool)); + SVN_ERR(svn_fs_fs__auto_read_footer(rev_file)); + SVN_ERR(svn_io_file_seek(rev_file->file, APR_END, &filesize, iterpool)); + + offset = filesize - 1; + SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &offset, iterpool)); + SVN_ERR(svn_io_file_getc((char *)&footer_len, rev_file->file, iterpool)); + + /* We want all the caching we can get. More importantly, we want to + change the cache namespace before each test iteration. */ + fs_config = apr_hash_make(pool); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, "1"); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, "1"); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, "2"); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ, "0"); + + /* Manipulate all bytes one at a time. */ + for (i = 0; i < filesize; ++i) + { + svn_error_t *err = SVN_NO_ERROR; + + /* Read byte */ + unsigned char c_old, c_new; + SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &i, iterpool)); + SVN_ERR(svn_io_file_getc((char *)&c_old, rev_file->file, iterpool)); + + /* What to replace it with. Skip if there is no change. */ + c_new = modifier(c_old, baton); + if (c_new == c_old) + continue; + + /* Modify / corrupt the data. */ + SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &i, iterpool)); + SVN_ERR(svn_io_file_putc((char)c_new, rev_file->file, iterpool)); + SVN_ERR(svn_io_file_flush(rev_file->file, iterpool)); + + /* Make sure we use a different namespace for the caches during + this iteration. */ + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS, + svn_uuid_generate(iterpool)); + SVN_ERR(svn_repos_open3(&repos, repo_name, fs_config, iterpool, iterpool)); + svn_fs_set_warning_func(svn_repos_fs(repos), dont_filter_warnings, NULL); + + /* This shall detect the corruption and return an error. */ + err = svn_repos_verify_fs3(repos, revision, revision, FALSE, FALSE, + NULL, NULL, NULL, NULL, NULL, NULL, + iterpool); + + /* Case-only changes in checksum digests are not an error. + * We allow upper case chars to be used in MD5 checksums in all other + * places, thus restricting them here would be inconsistent. */ + if ( i >= filesize - footer_len /* Within footer */ + && c_old >= 'a' && c_old <= 'f' /* 'a' to 'f', only appear + in checksum digests */ + && c_new == c_old - 'a' + 'A') /* respective upper case */ + { + if (err) + { + /* Let us know where we were too strict ... */ + printf("Detected case change in checksum digest at offset 0x%" + APR_UINT64_T_HEX_FMT " (%" APR_OFF_T_FMT ") in r%ld: " + "%c -> %c\n", (apr_uint64_t)i, i, revision, c_old, c_new); + + SVN_ERR(err); + } + } + else if (!err) + { + /* Let us know where we miss changes ... */ + printf("Undetected mod at offset 0x%"APR_UINT64_T_HEX_FMT + " (%"APR_OFF_T_FMT") in r%ld: 0x%02x -> 0x%02x\n", + (apr_uint64_t)i, i, revision, c_old, c_new); + + SVN_TEST_ASSERT(err); + } + + svn_error_clear(err); + + /* Undo the corruption. */ + SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &i, iterpool)); + SVN_ERR(svn_io_file_putc((char)c_old, rev_file->file, iterpool)); + + svn_pool_clear(iterpool); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Create a greek repo with OPTS at REPO_NAME. Verify that a modification + * of any single byte using MODIFIER with BATON will be detected. */ +static svn_error_t * +fuzzing_1_byte_test(const svn_test_opts_t *opts, + const char *repo_name, + unsigned char (* modifier)(unsigned char c, void *baton), + void *baton, + apr_pool_t *pool) +{ + svn_repos_t *repos; + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + svn_revnum_t rev; + svn_revnum_t i; + + apr_pool_t *iterpool = svn_pool_create(pool); + + /* Bail (with success) on known-untestable scenarios */ + if (strcmp(opts->fs_type, "fsfs") != 0) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "this will test FSFS repositories only"); + /* Create a filesystem */ + SVN_ERR(svn_test__create_repos(&repos, repo_name, opts, pool)); + fs = svn_repos_fs(repos); + + /* Revision 1 (one and only revision): the Greek tree */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(rev)); + + for (i = 0; i <= rev; ++i) + { + svn_pool_clear(iterpool); + SVN_ERR(fuzzing_1_byte_1_rev(repo_name, fs, i, modifier, baton, + iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Modifier function to be used with fuzzing_set_byte_test. + * We return the fixed char value given as *BATON. */ +static unsigned char +set_byte(unsigned char c, void *baton) +{ + return *(const unsigned char *)baton; +} + +/* Run the fuzzing test setting any byte in the repo to all values MIN to + * MAX-1. */ +static svn_error_t * +fuzzing_set_byte_test(const svn_test_opts_t *opts, + int min, + int max, + apr_pool_t *pool) +{ + apr_pool_t *iterpool = svn_pool_create(pool); + unsigned i = 0; + for (i = min; i < max; ++i) + { + unsigned char c = i; + const char *repo_name; + svn_pool_clear(iterpool); + + repo_name = apr_psprintf(iterpool, "test-repo-fuzzing_set_byte_%d_%d", + min, max); + SVN_ERR(fuzzing_1_byte_test(opts, repo_name, set_byte, &c, iterpool)); + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + + + +/*** Tests ***/ + +/* ------------------------------------------------------------------------ */ + +static unsigned char +invert_byte(unsigned char c, void *baton) +{ + return ~c; +} + +static svn_error_t * +fuzzing_invert_byte_test(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + SVN_ERR(fuzzing_1_byte_test(opts, "test-repo-fuzzing_invert_byte", + invert_byte, NULL, pool)); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +static unsigned char +increment_byte(unsigned char c, void *baton) +{ + return c + 1; +} + +static svn_error_t * +fuzzing_increment_byte_test(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + SVN_ERR(fuzzing_1_byte_test(opts, "test-repo-fuzzing_increment_byte", + increment_byte, NULL, pool)); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +static unsigned char +decrement_byte(unsigned char c, void *baton) +{ + return c - 1; +} + +static svn_error_t * +fuzzing_decrement_byte_test(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + SVN_ERR(fuzzing_1_byte_test(opts, "test-repo-fuzzing_decrement_byte", + decrement_byte, NULL, pool)); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +static unsigned char +null_byte(unsigned char c, void *baton) +{ + return 0; +} + +static svn_error_t * +fuzzing_null_byte_test(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + SVN_ERR(fuzzing_1_byte_test(opts, "test-repo-fuzzing_null_byte", + null_byte, NULL, pool)); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +/* Generator macro: define a test function covering byte values N to M-1 */ +#define FUZZING_SET_BYTE_TEST_N(N,M)\ + static svn_error_t * \ + fuzzing_set_byte_test_ ##N(const svn_test_opts_t *opts, \ + apr_pool_t *pool) \ + { \ + return svn_error_trace(fuzzing_set_byte_test(opts, N, M, pool)); \ + } + +/* Add the test function declared above to the test_funcs array. */ +#define TEST_FUZZING_SET_BYTE_TEST_N(N,M)\ + SVN_TEST_OPTS_PASS(fuzzing_set_byte_test_ ##N, \ + "set any byte to any value between " #N " and " #M) + +/* Declare tests that will cover all possible byte values. */ +FUZZING_SET_BYTE_TEST_N(0,16) +FUZZING_SET_BYTE_TEST_N(16,32) +FUZZING_SET_BYTE_TEST_N(32,48) +FUZZING_SET_BYTE_TEST_N(48,64) +FUZZING_SET_BYTE_TEST_N(64,80) +FUZZING_SET_BYTE_TEST_N(80,96) +FUZZING_SET_BYTE_TEST_N(96,112) +FUZZING_SET_BYTE_TEST_N(112,128) +FUZZING_SET_BYTE_TEST_N(128,144) +FUZZING_SET_BYTE_TEST_N(144,160) +FUZZING_SET_BYTE_TEST_N(160,176) +FUZZING_SET_BYTE_TEST_N(176,192) +FUZZING_SET_BYTE_TEST_N(192,208) +FUZZING_SET_BYTE_TEST_N(208,224) +FUZZING_SET_BYTE_TEST_N(224,240) +FUZZING_SET_BYTE_TEST_N(240,256) + + +/* The test table. */ + +/* Allow for any number of tests to run in parallel. */ +static int max_threads = 0; + +static struct svn_test_descriptor_t test_funcs[] = + { + SVN_TEST_NULL, + SVN_TEST_OPTS_PASS(fuzzing_invert_byte_test, + "fuzzing: invert any byte"), + SVN_TEST_OPTS_PASS(fuzzing_increment_byte_test, + "fuzzing: increment any byte"), + SVN_TEST_OPTS_PASS(fuzzing_decrement_byte_test, + "fuzzing: decrement any byte"), + SVN_TEST_OPTS_PASS(fuzzing_null_byte_test, + "fuzzing: set any byte to 0"), + + /* Register generated tests. */ + TEST_FUZZING_SET_BYTE_TEST_N(0,16), + TEST_FUZZING_SET_BYTE_TEST_N(16,32), + TEST_FUZZING_SET_BYTE_TEST_N(32,48), + TEST_FUZZING_SET_BYTE_TEST_N(48,64), + TEST_FUZZING_SET_BYTE_TEST_N(64,80), + TEST_FUZZING_SET_BYTE_TEST_N(80,96), + TEST_FUZZING_SET_BYTE_TEST_N(96,112), + TEST_FUZZING_SET_BYTE_TEST_N(112,128), + TEST_FUZZING_SET_BYTE_TEST_N(128,144), + TEST_FUZZING_SET_BYTE_TEST_N(144,160), + TEST_FUZZING_SET_BYTE_TEST_N(160,176), + TEST_FUZZING_SET_BYTE_TEST_N(176,192), + TEST_FUZZING_SET_BYTE_TEST_N(192,208), + TEST_FUZZING_SET_BYTE_TEST_N(208,224), + TEST_FUZZING_SET_BYTE_TEST_N(224,240), + TEST_FUZZING_SET_BYTE_TEST_N(240,256), + + SVN_TEST_NULL + }; + +SVN_TEST_MAIN |