/* 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 #include #include #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