| // ftmutator.cc |
| // |
| // A custom fuzzer mutator to test for FreeType with libFuzzer. |
| // |
| // Copyright 2015-2017 by |
| // David Turner, Robert Wilhelm, and Werner Lemberg. |
| // |
| // This file is part of the FreeType project, and may only be used, |
| // modified, and distributed under the terms of the FreeType project |
| // license, LICENSE.TXT. By continuing to use, modify, or distribute |
| // this file you indicate that you have read the license and |
| // understand and accept it fully. |
| |
| |
| // Since `tar' is not a valid format for input to FreeType, treat any input |
| // that looks like `tar' as multiple files and mutate them separately. |
| // |
| // In the future, a variation of this may be used to guide mutation on a |
| // logically higher level. |
| |
| |
| // we use `unique_ptr', `decltype', and other gimmicks defined since C++11 |
| #if __cplusplus < 201103L |
| # error "a C++11 compiler is needed" |
| #endif |
| |
| #include <cstdint> |
| #include <cassert> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <cstddef> |
| #include <cstring> |
| #include <iostream> |
| |
| #include <memory> |
| #include <vector> |
| |
| #include <archive.h> |
| #include <archive_entry.h> |
| |
| #include "FuzzerInterface.h" |
| |
| |
| using namespace std; |
| |
| |
| // This function should be defined by `ftfuzzer.cc'. |
| extern "C" int |
| LLVMFuzzerTestOneInput( const uint8_t* Data, |
| size_t Size ); |
| |
| |
| static void |
| check_result( struct archive* a, |
| int r ) |
| { |
| if ( r == ARCHIVE_OK ) |
| return; |
| |
| const char* m = archive_error_string( a ); |
| write( 1, m, strlen( m ) ); |
| exit( 1 ); |
| } |
| |
| |
| static int |
| archive_read_entry_data( struct archive *ar, |
| vector<uint8_t> *vw ) |
| { |
| int r; |
| const uint8_t* buff; |
| size_t size; |
| int64_t offset; |
| |
| for (;;) |
| { |
| r = archive_read_data_block( ar, |
| reinterpret_cast<const void**>( &buff ), |
| &size, |
| &offset ); |
| if ( r == ARCHIVE_EOF ) |
| return ARCHIVE_OK; |
| if ( r != ARCHIVE_OK ) |
| return r; |
| |
| vw->insert( vw->end(), buff, buff + size ); |
| } |
| } |
| |
| |
| static vector<vector<uint8_t>> |
| parse_data( const uint8_t* data, |
| size_t size ) |
| { |
| struct archive_entry* entry; |
| int r; |
| vector<vector<uint8_t>> files; |
| |
| unique_ptr<struct archive, |
| decltype ( archive_read_free )*> a( archive_read_new(), |
| archive_read_free ); |
| |
| // activate reading of uncompressed tar archives |
| archive_read_support_format_tar( a.get() ); |
| |
| // the need for `const_cast' was removed with libarchive commit be4d4dd |
| if ( !( r = archive_read_open_memory( |
| a.get(), |
| const_cast<void*>(static_cast<const void*>( data ) ), |
| size ) ) ) |
| { |
| unique_ptr<struct archive, |
| decltype ( archive_read_close )*> a_open( a.get(), |
| archive_read_close ); |
| |
| // read files contained in archive |
| for (;;) |
| { |
| r = archive_read_next_header( a_open.get(), &entry ); |
| if ( r == ARCHIVE_EOF ) |
| break; |
| if ( r != ARCHIVE_OK ) |
| break; |
| |
| vector<uint8_t> entry_data; |
| r = archive_read_entry_data( a.get(), &entry_data ); |
| if ( entry_data.size() == 0 ) |
| continue; |
| |
| files.push_back( move( entry_data ) ); |
| if ( r != ARCHIVE_OK ) |
| break; |
| } |
| } |
| |
| return files; |
| } |
| |
| |
| class FTFuzzer |
| : public fuzzer::UserSuppliedFuzzer |
| { |
| |
| public: |
| FTFuzzer( fuzzer::FuzzerRandomBase* Rand ) |
| : fuzzer::UserSuppliedFuzzer( Rand ) {} |
| |
| |
| int |
| TargetFunction( const uint8_t* Data, |
| size_t Size ) |
| { |
| return LLVMFuzzerTestOneInput( Data, Size ); |
| } |
| |
| |
| // Custom mutator. |
| virtual size_t |
| Mutate( uint8_t* Data, |
| size_t Size, |
| size_t MaxSize ) |
| { |
| vector<vector<uint8_t>> files = parse_data( Data, Size ); |
| |
| // If the file was not recognized as a tar file, treat it as non-tar. |
| if ( files.size() == 0 ) |
| return fuzzer::UserSuppliedFuzzer::Mutate( Data, Size, MaxSize ); |
| |
| // This is somewhat `white box' on tar. The tar format uses 512 byte |
| // blocks. One block as header for each file, two empty blocks of 0's |
| // at the end. File data is padded to fill its last block. |
| size_t used_blocks = files.size() + 2; |
| for ( const auto& file : files ) |
| used_blocks += ( file.size() + 511 ) / 512; |
| |
| size_t max_blocks = MaxSize / 512; |
| |
| // If the input is big, it will need to be downsized. If the original |
| // tar file was too big, it may have been clipped to fit. In this |
| // case it may not be possible to properly write out the data, as |
| // there may not be enough space for the trailing two blocks. Start |
| // dropping file data or files from the end. |
| for ( size_t i = files.size(); |
| i-- > 1 && used_blocks > max_blocks; ) |
| { |
| size_t blocks_to_free = used_blocks - max_blocks; |
| size_t blocks_currently_used_by_file_data = |
| ( files[i].size() + 511 ) / 512; |
| |
| if ( blocks_currently_used_by_file_data >= blocks_to_free ) |
| { |
| files[i].resize( ( blocks_currently_used_by_file_data - |
| blocks_to_free ) * 512 ); |
| used_blocks -= blocks_to_free; |
| continue; |
| } |
| |
| files.pop_back(); |
| used_blocks -= blocks_currently_used_by_file_data + 1; |
| } |
| |
| // If we get down to one file, don't use tar. |
| if ( files.size() == 1 ) |
| { |
| memcpy( Data, files[0].data(), files[0].size() ); |
| return fuzzer::UserSuppliedFuzzer::Mutate( Data, |
| files[0].size(), |
| MaxSize ); |
| } |
| |
| size_t free_blocks = max_blocks - used_blocks; |
| |
| // Allow each file to use up as much of the currently available space |
| // it can. If it uses or gives up blocks, add them or remove them |
| // from the pool. |
| for ( auto&& file : files ) |
| { |
| size_t blocks_currently_used_by_file = ( file.size() + 511 ) / 512; |
| size_t blocks_available = blocks_currently_used_by_file + |
| free_blocks; |
| size_t max_size = blocks_available * 512; |
| size_t data_size = file.size(); |
| |
| file.resize( max_size ); |
| file.resize( fuzzer::UserSuppliedFuzzer::Mutate( file.data(), |
| data_size, |
| max_size ) ); |
| |
| size_t blocks_now_used_by_file = ( file.size() + 511 ) / 512; |
| free_blocks = free_blocks + |
| blocks_currently_used_by_file - |
| blocks_now_used_by_file; |
| } |
| |
| unique_ptr<struct archive, |
| decltype ( archive_write_free )*> a( archive_write_new(), |
| archive_write_free ); |
| |
| check_result( a.get(), archive_write_add_filter_none( a.get() ) ); |
| check_result( a.get(), archive_write_set_format_ustar( a.get() ) ); |
| |
| // `used' may not be correct until after the archive is closed. |
| size_t used = 0xbadbeef; |
| check_result( a.get(), archive_write_open_memory( a.get(), |
| Data, |
| MaxSize, |
| &used ) ); |
| |
| { |
| unique_ptr<struct archive, |
| decltype ( archive_write_close )*> a_open( a.get(), |
| archive_write_close ); |
| |
| int file_index = 0; |
| for ( const auto& file : files ) |
| { |
| unique_ptr<struct archive_entry, |
| decltype ( archive_entry_free )*> |
| e( archive_entry_new2( a_open.get() ), |
| archive_entry_free ); |
| |
| char name_buffer[100]; |
| snprintf( name_buffer, 100, "file%d", file_index++ ); |
| |
| archive_entry_set_pathname( e.get(), name_buffer ); |
| archive_entry_set_size( e.get(), file.size() ); |
| archive_entry_set_filetype( e.get(), AE_IFREG ); |
| archive_entry_set_perm( e.get(), 0644 ); |
| |
| check_result( a_open.get(), |
| archive_write_header( a_open.get(), e.get() ) ); |
| archive_write_data( a_open.get(), file.data(), file.size() ); |
| check_result( a_open.get(), |
| archive_write_finish_entry( a_open.get() ) ); |
| } |
| } |
| |
| return used; |
| } |
| |
| |
| // Cross `Data1' and `Data2', write up to `MaxOutSize' bytes into `Out', |
| // return the number of bytes written, which should be positive. |
| virtual size_t |
| CrossOver( const uint8_t* Data1, |
| size_t Size1, |
| const uint8_t* Data2, |
| size_t Size2, |
| uint8_t* Out, |
| size_t MaxOutSize ) |
| { |
| return fuzzer::UserSuppliedFuzzer::CrossOver( Data1, |
| Size1, |
| Data2, |
| Size2, |
| Out, |
| MaxOutSize ); |
| } |
| |
| }; // end of FTFuzzer class |
| |
| |
| int |
| main( int argc, |
| char* *argv ) |
| { |
| fuzzer::FuzzerRandomLibc Rand( 0 ); |
| FTFuzzer F( &Rand ); |
| |
| fuzzer::FuzzerDriver( argc, argv, F ); |
| } |
| |
| |
| // END |