Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fuzz target #1773

Merged
merged 9 commits into from
Jul 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/on_PR_linux_fuzz.yml
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ option( EXIV2_ENABLE_BMFF "Build with BMFF support"
option( EXIV2_BUILD_SAMPLES "Build sample applications" ON )
option( EXIV2_BUILD_EXIV2_COMMAND "Build exiv2 command-line executable" ON )
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 )

# Only intended to be used by Exiv2 developers/contributors
Expand Down Expand Up @@ -91,6 +92,14 @@ if( EXIV2_BUILD_UNIT_TESTS )
add_subdirectory ( unitTests )
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 )
add_subdirectory( samples )
get_directory_property(SAMPLES DIRECTORY samples DEFINITION APPLICATIONS)
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
4. [Python tests](#4-4)
5. [Test Summary](#4-5)
6. [Fuzzing](#4-6)
5. [Platform Notes](#5)
1. [Linux](#5-1)
2. [macOS](#5-2)
Expand Down Expand Up @@ -1039,6 +1040,30 @@ $ cd <exiv2dir>/build
$ 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)
<div id="4-5">

Expand Down
4 changes: 3 additions & 1 deletion cmake/compilerFlags.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ if ( MINGW OR UNIX OR MSYS ) # MINGW, Linux, APPLE, CYGWIN
set(SANITIZER_FLAGS "-fno-omit-frame-pointer -fsanitize=address")
endif()
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")
elseif ( CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.4 )
set(SANITIZER_FLAGS "-fno-omit-frame-pointer -fsanitize=address,undefined")
Expand Down
1 change: 1 addition & 0 deletions cmake/printSummary.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ endif()
OptionOutput( "Building exiv2 command: " EXIV2_BUILD_EXIV2_COMMAND )
OptionOutput( "Building samples: " EXIV2_BUILD_SAMPLES )
OptionOutput( "Building unit tests: " EXIV2_BUILD_UNIT_TESTS )
OptionOutput( "Building fuzz tests: " EXIV2_BUILD_FUZZ_TESTS )
OptionOutput( "Building doc: " EXIV2_BUILD_DOC )
OptionOutput( "Building with coverage flags: " BUILD_WITH_COVERAGE )
OptionOutput( "Using ccache: " BUILD_WITH_CCACHE )
14 changes: 14 additions & 0 deletions fuzz/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
35 changes: 35 additions & 0 deletions fuzz/fuzz-read-print-write.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
4 changes: 2 additions & 2 deletions src/iptc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,13 +474,13 @@ namespace Exiv2 {
#endif
}
}
#ifndef SUPPRESS_WARNINGS
else {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "IPTC dataset " << IptcKey(dataSet, record)
<< " has invalid size " << sizeData << "; skipped.\n";
#endif
return 7;
}
#endif
pRead += sizeData;
}

Expand Down
2 changes: 1 addition & 1 deletion src/jpgimage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ namespace Exiv2 {
#endif
return -2;
}
#ifndef EXIV2_DEBUG_MESSAGES
#ifdef EXIV2_DEBUG_MESSAGES
if ( (dataSize & 1)
&& position + dataSize == static_cast<uint32_t>(sizePsData)) {
std::cerr << "Warning: "
Expand Down
2 changes: 2 additions & 0 deletions src/tiffvisitor_int.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1335,7 +1335,9 @@ namespace Exiv2 {
tc->setStart(p);
object->addChild(std::move(tc));
} else {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Unable to handle tag " << tag << ".\n";
#endif
}
p += 12;
}
Expand Down