Merge pull request #1773 from kevinbackhouse/fuzz

Add fuzz target
main
Kevin Backhouse 4 years ago committed by GitHub
commit 0208b508a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,30 @@
# Builds and runs the fuzz target for a short amount of time. This is
# mainly to protect the fuzz target from bitrot, but hopefully will
# also help to quickly catch some bugs before the PR is merged.
name: Linux-Ubuntu Quick Fuzz on PRs
on:
pull_request:
workflow_dispatch:
jobs:
Linux:
name: 'Ubuntu 20.04 - clang/libFuzzer'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: install dependencies
run: sudo ./ci/install_dependencies.sh
- name: build and compile
run: |
mkdir build && cd build
cmake -DEXIV2_ENABLE_PNG=ON -DEXIV2_ENABLE_WEBREADY=ON -DEXIV2_ENABLE_CURL=ON -DEXIV2_ENABLE_BMFF=ON -DEXIV2_TEAM_WARNINGS_AS_ERRORS=ON -DCMAKE_CXX_COMPILER=$(which clang++) -DEXIV2_BUILD_FUZZ_TESTS=ON -DEXIV2_TEAM_USE_SANITIZERS=ON ..
make -j $(nproc)
- name: Fuzz
run: |
cd build
mkdir corpus
./bin/fuzz-read-print-write corpus ../test/data/ -jobs=$(nproc) -workers=$(nproc) -max_total_time=120 -max_len=4096

@ -35,6 +35,7 @@ option( EXIV2_ENABLE_BMFF "Build with BMFF support"
option( EXIV2_BUILD_SAMPLES "Build sample applications" ON ) option( EXIV2_BUILD_SAMPLES "Build sample applications" ON )
option( EXIV2_BUILD_EXIV2_COMMAND "Build exiv2 command-line executable" ON ) option( EXIV2_BUILD_EXIV2_COMMAND "Build exiv2 command-line executable" ON )
option( EXIV2_BUILD_UNIT_TESTS "Build unit tests" OFF ) option( EXIV2_BUILD_UNIT_TESTS "Build unit tests" OFF )
option( EXIV2_BUILD_FUZZ_TESTS "Build fuzz tests (libFuzzer)" OFF )
option( EXIV2_BUILD_DOC "Add 'doc' target to generate documentation" OFF ) option( EXIV2_BUILD_DOC "Add 'doc' target to generate documentation" OFF )
# Only intended to be used by Exiv2 developers/contributors # Only intended to be used by Exiv2 developers/contributors
@ -91,6 +92,14 @@ if( EXIV2_BUILD_UNIT_TESTS )
add_subdirectory ( unitTests ) add_subdirectory ( unitTests )
endif() endif()
if( EXIV2_BUILD_FUZZ_TESTS )
if ((NOT COMPILER_IS_CLANG) OR (NOT EXIV2_TEAM_USE_SANITIZERS))
message(FATAL_ERROR "You need to build with Clang and sanitizers for the fuzzers to work. "
"Use Clang and -DEXIV2_TEAM_USE_SANITIZERS=ON")
endif()
add_subdirectory ( fuzz )
endif()
if( EXIV2_BUILD_SAMPLES ) if( EXIV2_BUILD_SAMPLES )
add_subdirectory( samples ) add_subdirectory( samples )
get_directory_property(SAMPLES DIRECTORY samples DEFINITION APPLICATIONS) get_directory_property(SAMPLES DIRECTORY samples DEFINITION APPLICATIONS)

@ -64,6 +64,7 @@ The file ReadMe.txt in a build bundle describes how to install the library on th
3. [Unit tests](#4-3) 3. [Unit tests](#4-3)
4. [Python tests](#4-4) 4. [Python tests](#4-4)
5. [Test Summary](#4-5) 5. [Test Summary](#4-5)
6. [Fuzzing](#4-6)
5. [Platform Notes](#5) 5. [Platform Notes](#5)
1. [Linux](#5-1) 1. [Linux](#5-1)
2. [macOS](#5-2) 2. [macOS](#5-2)
@ -1039,6 +1040,30 @@ $ cd <exiv2dir>/build
$ make python_tests 2>&1 | grep FAIL $ make python_tests 2>&1 | grep FAIL
``` ```
### 4.6 Fuzzing
The code for the fuzzers is in `exiv2dir/fuzz`
To build the fuzzers, use the *cmake* option `-DEXIV2_BUILD_FUZZ_TESTS=ON` and `-DEXIV2_TEAM_USE_SANITIZERS=ON`.
Note that it only works with clang compiler as libFuzzer is integrate with clang > 6.0
To build the fuzzers:
```bash
$ cd <exiv2dir>
$ rm -rf build-fuzz ; mkdir build-fuzz ; cd build-fuzz
$ cmake .. -DCMAKE_CXX_COMPILER=$(which clang++) -DEXIV2_BUILD_FUZZ_TESTS=ON -DEXIV2_TEAM_USE_SANITIZERS=ON
$ cmake --build .
```
To execute a fuzzer:
```bash
cd <exiv2dir>/build-fuzz
mkdir corpus
./bin/fuzz-read-print-write corpus ../test/data/ -jobs=$(nproc) -workers=$(nproc) -max_len=4096
```
[TOC](#TOC) [TOC](#TOC)
<div id="4-5"> <div id="4-5">

@ -82,7 +82,9 @@ if ( MINGW OR UNIX OR MSYS ) # MINGW, Linux, APPLE, CYGWIN
set(SANITIZER_FLAGS "-fno-omit-frame-pointer -fsanitize=address") set(SANITIZER_FLAGS "-fno-omit-frame-pointer -fsanitize=address")
endif() endif()
elseif( COMPILER_IS_CLANG ) elseif( COMPILER_IS_CLANG )
if ( CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.9 ) if ( EXIV2_BUILD_FUZZ_TESTS )
set(SANITIZER_FLAGS "-fsanitize=fuzzer-no-link")
elseif ( CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.9 )
set(SANITIZER_FLAGS "-fno-omit-frame-pointer -fsanitize=address,undefined -fno-sanitize-recover=all") set(SANITIZER_FLAGS "-fno-omit-frame-pointer -fsanitize=address,undefined -fno-sanitize-recover=all")
elseif ( CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.4 ) elseif ( CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.4 )
set(SANITIZER_FLAGS "-fno-omit-frame-pointer -fsanitize=address,undefined") set(SANITIZER_FLAGS "-fno-omit-frame-pointer -fsanitize=address,undefined")

@ -66,6 +66,7 @@ endif()
OptionOutput( "Building exiv2 command: " EXIV2_BUILD_EXIV2_COMMAND ) OptionOutput( "Building exiv2 command: " EXIV2_BUILD_EXIV2_COMMAND )
OptionOutput( "Building samples: " EXIV2_BUILD_SAMPLES ) OptionOutput( "Building samples: " EXIV2_BUILD_SAMPLES )
OptionOutput( "Building unit tests: " EXIV2_BUILD_UNIT_TESTS ) OptionOutput( "Building unit tests: " EXIV2_BUILD_UNIT_TESTS )
OptionOutput( "Building fuzz tests: " EXIV2_BUILD_FUZZ_TESTS )
OptionOutput( "Building doc: " EXIV2_BUILD_DOC ) OptionOutput( "Building doc: " EXIV2_BUILD_DOC )
OptionOutput( "Building with coverage flags: " BUILD_WITH_COVERAGE ) OptionOutput( "Building with coverage flags: " BUILD_WITH_COVERAGE )
OptionOutput( "Using ccache: " BUILD_WITH_CCACHE ) OptionOutput( "Using ccache: " BUILD_WITH_CCACHE )

@ -0,0 +1,14 @@
macro(fuzzer name)
add_executable(${name} ${name}.cpp)
set_target_properties(${name}
PROPERTIES
COMPILE_FLAGS "-fsanitize=fuzzer"
LINK_FLAGS "-fsanitize=fuzzer")
target_link_libraries(${name}
PRIVATE
exiv2lib
)
endmacro()
fuzzer(fuzz-read-print-write)

@ -0,0 +1,35 @@
#include <exiv2/exiv2.hpp>
#include <iostream>
#include <iomanip>
#include <cassert>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t * data, size_t size) {
// Invalid files generate a lot of warnings, so switch off logging.
Exiv2::LogMsg::setLevel(Exiv2::LogMsg::mute);
Exiv2::XmpParser::initialize();
::atexit(Exiv2::XmpParser::terminate);
try {
Exiv2::DataBuf data_copy(data, size);
Exiv2::Image::UniquePtr image =
Exiv2::ImageFactory::open(data_copy.pData_, size);
assert(image.get() != 0);
image->readMetadata();
image->exifData();
// Print to a std::ostringstream so that the fuzzer doesn't
// produce lots of garbage on stdout.
std::ostringstream buffer;
image->printStructure(buffer, Exiv2::kpsNone);
image->writeMetadata();
} catch(...) {
// Exiv2 throws an exception if the metadata is invalid.
}
return 0;
}

@ -474,13 +474,13 @@ namespace Exiv2 {
#endif #endif
} }
} }
#ifndef SUPPRESS_WARNINGS
else { else {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "IPTC dataset " << IptcKey(dataSet, record) EXV_WARNING << "IPTC dataset " << IptcKey(dataSet, record)
<< " has invalid size " << sizeData << "; skipped.\n"; << " has invalid size " << sizeData << "; skipped.\n";
#endif
return 7; return 7;
} }
#endif
pRead += sizeData; pRead += sizeData;
} }

@ -181,7 +181,7 @@ namespace Exiv2 {
#endif #endif
return -2; return -2;
} }
#ifndef EXIV2_DEBUG_MESSAGES #ifdef EXIV2_DEBUG_MESSAGES
if ( (dataSize & 1) if ( (dataSize & 1)
&& position + dataSize == static_cast<uint32_t>(sizePsData)) { && position + dataSize == static_cast<uint32_t>(sizePsData)) {
std::cerr << "Warning: " std::cerr << "Warning: "

@ -1335,7 +1335,9 @@ namespace Exiv2 {
tc->setStart(p); tc->setStart(p);
object->addChild(std::move(tc)); object->addChild(std::move(tc));
} else { } else {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Unable to handle tag " << tag << ".\n"; EXV_WARNING << "Unable to handle tag " << tag << ".\n";
#endif
} }
p += 12; p += 12;
} }

Loading…
Cancel
Save