Switch public fuzz tests to fuzztest.

Change-Id: I75afb65058690585bbf2671c27d6a99a87bcaab7
This commit is contained in:
Vincent Rabaud 2024-05-31 15:02:28 +02:00
parent 64186bb36c
commit 4f200de591
16 changed files with 742 additions and 404 deletions

View File

@ -45,6 +45,7 @@ option(WEBP_BUILD_LIBWEBPMUX "Build the libwebpmux library." ON)
option(WEBP_BUILD_WEBPMUX "Build the webpmux command line tool." ON)
option(WEBP_BUILD_EXTRAS "Build extras." ON)
option(WEBP_BUILD_WEBP_JS "Emscripten build of webp.js." OFF)
option(WEBP_BUILD_FUZZTEST "Build the fuzztest tests." OFF)
option(WEBP_USE_THREAD "Enable threading support" ON)
option(WEBP_NEAR_LOSSLESS "Enable near-lossless encoding" ON)
option(WEBP_ENABLE_SWAP_16BIT_CSP "Enable byte swap for 16 bit colorspaces."
@ -375,9 +376,11 @@ if(XCODE)
endif()
target_link_libraries(webpdecoder ${WEBP_DEP_LIBRARIES})
target_include_directories(
webpdecoder PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}
INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
webpdecoder
PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}
INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR};${CMAKE_CURRENT_BINARY_DIR}>"
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
set_target_properties(
webpdecoder
PROPERTIES PUBLIC_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/src/webp/decode.h;\
@ -771,6 +774,10 @@ if(WEBP_BUILD_ANIM_UTILS)
target_include_directories(anim_dump PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/src)
endif()
if(WEBP_BUILD_FUZZTEST)
add_subdirectory(tests/fuzzer)
endif()
# Install the different headers and libraries.
install(
TARGETS ${INSTALLED_LIBRARIES}

View File

@ -11,8 +11,9 @@ https://chromium.googlesource.com/webm/libwebp-test-data
Follow the [build instructions](../doc/building.md) for libwebp, optionally
adding build flags for various sanitizers (e.g., -fsanitize=address).
`fuzzer/makefile.unix` can then be used to compile the fuzzer targets:
`-DWEBP_BUILD_FUZZTEST=ON` can then be used to compile the fuzzer targets:
```shell
$ make -C fuzzer -f makefile.unix
$ cmake -B ./build -S . -DWEBP_BUILD_FUZZTEST=ON
$ make -C build
```

View File

@ -0,0 +1,61 @@
# Copyright (c) 2024 Google LLC
#
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file in the root of the source
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
# Adds a fuzztest from file TEST_NAME.cc located in the gtest folder. Extra
# arguments are considered as extra source files.
if(CMAKE_VERSION VERSION_LESS "3.19.0")
return()
endif()
macro(add_webp_fuzztest TEST_NAME)
add_executable(${TEST_NAME} ${TEST_NAME}.cc)
# FuzzTest bundles GoogleTest so no need to link to gtest libraries.
target_link_libraries(${TEST_NAME} PRIVATE fuzz_utils webp ${ARGN})
link_fuzztest(${TEST_NAME})
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
set_property(
TEST ${TEST_NAME}
PROPERTY ENVIRONMENT "TEST_DATA_DIRS=${CMAKE_CURRENT_SOURCE_DIR}/data/")
endmacro()
enable_language(CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
set(fuzztest_SOURCE_DIR ${CMAKE_BINARY_DIR}/_deps/fuzztest-src)
FetchContent_Declare(
fuzztest
GIT_REPOSITORY https://github.com/google/fuzztest.git
GIT_TAG a40caf40aaf621dd0e04f9d8b47d1153fd2682d2
GIT_PROGRESS TRUE
PATCH_COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/patch.sh)
FetchContent_MakeAvailable(fuzztest)
fuzztest_setup_fuzzing_flags()
add_library(fuzz_utils fuzz_utils.h fuzz_utils.cc img_alpha.h img_grid.h
img_peak.h)
target_link_libraries(fuzz_utils PUBLIC webpdecoder)
link_fuzztest(fuzz_utils)
add_webp_fuzztest(advanced_api_fuzzer)
add_webp_fuzztest(enc_dec_fuzzer)
add_webp_fuzztest(huffman_fuzzer)
add_webp_fuzztest(simple_api_fuzzer)
if(WEBP_BUILD_LIBWEBPMUX)
add_webp_fuzztest(animation_api_fuzzer webpdemux)
add_webp_fuzztest(animdecoder_fuzzer imageioutil webpdemux)
add_webp_fuzztest(animencoder_fuzzer libwebpmux)
add_webp_fuzztest(mux_demux_api_fuzzer libwebpmux webpdemux)
endif()

View File

@ -14,54 +14,59 @@
//
////////////////////////////////////////////////////////////////////////////////
#include <stdint.h>
#include <string.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <string_view>
#include "./fuzz_utils.h"
#include "src/utils/rescaler_utils.h"
#include "src/webp/decode.h"
int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
namespace {
void AdvancedApiTest(std::string_view blob, uint8_t factor_u8, bool flip,
bool bypass_filtering, bool no_fancy_upsampling,
bool use_threads, bool use_cropping, bool use_scaling,
bool use_dithering, int colorspace, bool incremental) {
WebPDecoderConfig config;
if (!WebPInitDecoderConfig(&config)) return 0;
if (WebPGetFeatures(data, size, &config.input) != VP8_STATUS_OK) return 0;
if ((size_t)config.input.width * config.input.height > kFuzzPxLimit) return 0;
if (!WebPInitDecoderConfig(&config)) return;
const uint8_t* const data = reinterpret_cast<const uint8_t*>(blob.data());
const size_t size = blob.size();
if (WebPGetFeatures(data, size, &config.input) != VP8_STATUS_OK) return;
if ((size_t)config.input.width * config.input.height >
fuzz_utils::kFuzzPxLimit) {
return;
}
// Using two independent criteria ensures that all combinations of options
// can reach each path at the decoding stage, with meaningful differences.
const uint8_t value = FuzzHash(data, size);
const float factor = value / 255.f; // 0-1
const uint8_t value = fuzz_utils::FuzzHash(data, size);
const float factor = factor_u8 / 255.f; // 0-1
config.options.flip = value & 1;
config.options.bypass_filtering = value & 2;
config.options.no_fancy_upsampling = value & 4;
config.options.use_threads = value & 8;
if (size & 1) {
config.options.flip = flip;
config.options.bypass_filtering = bypass_filtering;
config.options.no_fancy_upsampling = no_fancy_upsampling;
config.options.use_threads = use_threads;
if (use_cropping) {
config.options.use_cropping = 1;
config.options.crop_width = (int)(config.input.width * (1 - factor));
config.options.crop_height = (int)(config.input.height * (1 - factor));
config.options.crop_left = config.input.width - config.options.crop_width;
config.options.crop_top = config.input.height - config.options.crop_height;
}
if (size & 2) {
if (use_dithering) {
int strength = (int)(factor * 100);
config.options.dithering_strength = strength;
config.options.alpha_dithering_strength = 100 - strength;
}
if (size & 4) {
if (use_scaling) {
config.options.use_scaling = 1;
config.options.scaled_width = (int)(config.input.width * factor * 2);
config.options.scaled_height = (int)(config.input.height * factor * 2);
}
#if defined(WEBP_REDUCE_CSP)
config.output.colorspace = (value & 1)
? ((value & 2) ? MODE_RGBA : MODE_BGRA)
: ((value & 2) ? MODE_rgbA : MODE_bgrA);
#else
config.output.colorspace = (WEBP_CSP_MODE)(value % MODE_LAST);
#endif // WEBP_REDUCE_CSP
config.output.colorspace = static_cast<WEBP_CSP_MODE>(colorspace);
for (int i = 0; i < 2; ++i) {
if (i == 1) {
@ -80,7 +85,7 @@ int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
if (WebPRescalerGetScaledDimensions(config.input.width,
config.input.height, &scaled_width,
&scaled_height)) {
size_t fuzz_px_limit = kFuzzPxLimit;
size_t fuzz_px_limit = fuzz_utils::kFuzzPxLimit;
if (scaled_width != config.input.width ||
scaled_height != config.input.height) {
// Using the WebPRescalerImport internally can significantly slow
@ -92,18 +97,18 @@ int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
// very wide input image to a very tall canvas can be as slow as
// decoding a huge number of pixels. Avoid timeouts due to these.
const uint64_t max_num_operations =
(uint64_t)Max(scaled_width, config.input.width) *
Max(scaled_height, config.input.height);
(uint64_t)std::max(scaled_width, config.input.width) *
std::max(scaled_height, config.input.height);
if (max_num_operations > fuzz_px_limit) {
break;
}
}
}
}
if (size % 3) {
if (incremental) {
// Decodes incrementally in chunks of increasing size.
WebPIDecoder* idec = WebPIDecode(NULL, 0, &config);
if (!idec) return 0;
if (!idec) return;
VP8StatusCode status;
if (size & 8) {
size_t available_size = value + 1;
@ -135,5 +140,28 @@ int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
WebPFreeDecBuffer(&config.output);
}
return 0;
}
} // namespace
FUZZ_TEST(AdvancedApi, AdvancedApiTest)
.WithDomains(
fuzztest::String()
.WithMaxSize(fuzz_utils::kMaxWebPFileSize + 1),
/*factor_u8=*/fuzztest::Arbitrary<uint8_t>(),
/*flip=*/fuzztest::Arbitrary<bool>(),
/*bypass_filtering=*/fuzztest::Arbitrary<bool>(),
/*no_fancy_upsampling=*/fuzztest::Arbitrary<bool>(),
/*use_threads=*/fuzztest::Arbitrary<bool>(),
/*use_cropping=*/fuzztest::Arbitrary<bool>(),
/*use_scaling=*/fuzztest::Arbitrary<bool>(),
/*use_dithering=*/fuzztest::Arbitrary<bool>(),
#if defined(WEBP_REDUCE_CSP)
fuzztest::ElementOf<int>({static_cast<int>(MODE_RGBA),
static_cast<int>(MODE_BGRA),
static_cast<int>(MODE_rgbA),
static_cast<int>(MODE_bgrA)}),
#else
fuzztest::InRange<int>(0, static_cast<int>(MODE_LAST) - 1),
#endif
/*incremental=*/fuzztest::Arbitrary<bool>());

View File

@ -14,37 +14,46 @@
//
////////////////////////////////////////////////////////////////////////////////
#include <cstddef>
#include <cstdint>
#include <string_view>
#include "./fuzz_utils.h"
#include "src/webp/decode.h"
#include "src/webp/demux.h"
#include "src/webp/mux_types.h"
int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
namespace {
void AnimationApiTest(std::string_view blob, bool use_threads,
WEBP_CSP_MODE color_mode) {
const size_t size = blob.size();
WebPData webp_data;
WebPDataInit(&webp_data);
webp_data.size = size;
webp_data.bytes = data;
webp_data.bytes = reinterpret_cast<const uint8_t*>(blob.data());
// WebPAnimDecoderNew uses WebPDemux internally to calloc canvas size.
WebPDemuxer* const demux = WebPDemux(&webp_data);
if (!demux) return 0;
if (!demux) return;
const uint32_t cw = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH);
const uint32_t ch = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT);
if ((size_t)cw * ch > kFuzzPxLimit) {
if ((size_t)cw * ch > fuzz_utils::kFuzzPxLimit) {
WebPDemuxDelete(demux);
return 0;
return;
}
// In addition to canvas size, check each frame separately.
WebPIterator iter;
for (int i = 0; i < kFuzzFrameLimit; i++) {
for (int i = 0; i < fuzz_utils::kFuzzFrameLimit; i++) {
if (!WebPDemuxGetFrame(demux, i + 1, &iter)) break;
int w, h;
if (WebPGetInfo(iter.fragment.bytes, iter.fragment.size, &w, &h)) {
if ((size_t)w * h > kFuzzPxLimit) { // image size of the frame payload
if ((size_t)w * h >
fuzz_utils::kFuzzPxLimit) { // image size of the frame payload
WebPDemuxReleaseIterator(&iter);
WebPDemuxDelete(demux);
return 0;
return;
}
}
}
@ -53,26 +62,30 @@ int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
WebPDemuxDelete(demux);
WebPAnimDecoderOptions dec_options;
if (!WebPAnimDecoderOptionsInit(&dec_options)) return 0;
if (!WebPAnimDecoderOptionsInit(&dec_options)) return;
dec_options.use_threads = size & 1;
// Animations only support 4 (of 12) modes.
dec_options.color_mode = (WEBP_CSP_MODE)(size % MODE_LAST);
if (dec_options.color_mode != MODE_BGRA &&
dec_options.color_mode != MODE_rgbA &&
dec_options.color_mode != MODE_bgrA) {
dec_options.color_mode = MODE_RGBA;
}
dec_options.use_threads = use_threads;
dec_options.color_mode = color_mode;
WebPAnimDecoder* dec = WebPAnimDecoderNew(&webp_data, &dec_options);
if (!dec) return 0;
if (!dec) return;
for (int i = 0; i < kFuzzFrameLimit; i++) {
for (int i = 0; i < fuzz_utils::kFuzzFrameLimit; i++) {
uint8_t* buf;
int timestamp;
if (!WebPAnimDecoderGetNext(dec, &buf, &timestamp)) break;
}
WebPAnimDecoderDelete(dec);
return 0;
}
} // namespace
FUZZ_TEST(AnimationApi, AnimationApiTest)
.WithDomains(
fuzztest::String()
.WithMaxSize(fuzz_utils::kMaxWebPFileSize + 1),
/*use_threads=*/fuzztest::Arbitrary<bool>(),
// Animations only support 4 (out of 12) modes.
fuzztest::ElementOf<WEBP_CSP_MODE>({MODE_RGBA, MODE_BGRA, MODE_rgbA,
MODE_bgrA}));

View File

@ -16,13 +16,20 @@
#include <cstddef>
#include <cstdint>
#include <string_view>
#include "./fuzz_utils.h"
#include "imageio/imageio_util.h"
#include "src/webp/decode.h"
#include "src/webp/demux.h"
#include "src/webp/mux_types.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
namespace {
void AnimDecoderTest(std::string_view blob) {
const uint8_t* const data = reinterpret_cast<const uint8_t*>(blob.data());
const size_t size = blob.size();
// WebPAnimDecoderGetInfo() is too late to check the canvas size as
// WebPAnimDecoderNew() will handle the allocations.
const size_t kMaxNumBytes = 2684354560; // RSS (resident set size) limit.
@ -34,14 +41,14 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
features.height) ||
static_cast<size_t>(features.width) * features.height >
kMaxNumPixelsSafe) {
return 0;
return;
}
}
// decode everything as an animation
WebPData webp_data = {data, size};
WebPAnimDecoder* const dec = WebPAnimDecoderNew(&webp_data, nullptr);
if (dec == nullptr) return 0;
if (dec == nullptr) return;
WebPAnimInfo info;
if (!WebPAnimDecoderGetInfo(dec, &info)) goto End;
@ -57,5 +64,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
}
End:
WebPAnimDecoderDelete(dec);
return 0;
}
} // namespace
FUZZ_TEST(AnimDecoder, AnimDecoderTest)
.WithDomains(
fuzztest::String()
.WithMaxSize(fuzz_utils::kMaxWebPFileSize + 1));

View File

@ -14,21 +14,48 @@
//
////////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <string_view>
#include <utility>
#include <vector>
#include "./fuzz_utils.h"
#include "src/dsp/cpu.h"
#include "src/webp/encode.h"
#include "src/webp/mux.h"
#include "src/webp/mux_types.h"
namespace {
const VP8CPUInfo default_VP8GetCPUInfo = VP8GetCPUInfo;
const VP8CPUInfo default_VP8GetCPUInfo = fuzz_utils::VP8GetCPUInfo;
struct FrameConfig {
int use_argb;
int timestamp;
WebPConfig webp_config;
fuzz_utils::CropOrScaleParams crop_or_scale_params;
int source_image_index;
};
auto ArbitraryKMinKMax() {
return fuzztest::FlatMap(
[](int kmax) {
const int min_kmin = (kmax > 1) ? (kmax / 2) : 0;
const int max_kmin = (kmax > 1) ? (kmax - 1) : 0;
return fuzztest::PairOf(fuzztest::InRange(min_kmin, max_kmin),
fuzztest::Just(kmax));
},
fuzztest::InRange(0, 15));
}
int AddFrame(WebPAnimEncoder** const enc,
const WebPAnimEncoderOptions& anim_config, int* const width,
int* const height, int timestamp_ms, const uint8_t data[],
size_t size, uint32_t* const bit_pos) {
int* const height, int timestamp_ms,
const FrameConfig& frame_config, const uint8_t data[], size_t size,
uint32_t* const bit_pos) {
if (enc == nullptr || width == nullptr || height == nullptr) {
fprintf(stderr, "NULL parameters.\n");
if (enc != nullptr) WebPAnimEncoderDelete(*enc);
@ -36,27 +63,12 @@ int AddFrame(WebPAnimEncoder** const enc,
}
// Init the source picture.
WebPPicture pic;
if (!WebPPictureInit(&pic)) {
fprintf(stderr, "WebPPictureInit failed.\n");
WebPAnimEncoderDelete(*enc);
abort();
}
pic.use_argb = Extract(1, data, size, bit_pos);
// Read the source picture.
if (!ExtractSourcePicture(&pic, data, size, bit_pos)) {
const WebPEncodingError error_code = pic.error_code;
WebPAnimEncoderDelete(*enc);
WebPPictureFree(&pic);
if (error_code == VP8_ENC_ERROR_OUT_OF_MEMORY) return 0;
fprintf(stderr, "Can't read input image. Error code: %d\n", error_code);
abort();
}
WebPPicture pic = fuzz_utils::GetSourcePicture(
frame_config.source_image_index, frame_config.use_argb);
// Crop and scale.
if (*enc == nullptr) { // First frame will set canvas width and height.
if (!ExtractAndCropOrScale(&pic, data, size, bit_pos)) {
if (!fuzz_utils::CropOrScale(&pic, frame_config.crop_or_scale_params)) {
const WebPEncodingError error_code = pic.error_code;
WebPPictureFree(&pic);
if (error_code == VP8_ENC_ERROR_OUT_OF_MEMORY) return 0;
@ -89,13 +101,7 @@ int AddFrame(WebPAnimEncoder** const enc,
}
// Create frame encoding config.
WebPConfig config;
if (!ExtractWebPConfig(&config, data, size, bit_pos)) {
fprintf(stderr, "ExtractWebPConfig failed.\n");
WebPAnimEncoderDelete(*enc);
WebPPictureFree(&pic);
abort();
}
WebPConfig config = frame_config.webp_config;
// Skip slow settings on big images, it's likely to timeout.
if (pic.width * pic.height > 32 * 32) {
config.method = (config.method > 4) ? 4 : config.method;
@ -125,14 +131,17 @@ int AddFrame(WebPAnimEncoder** const enc,
return 1;
}
} // namespace
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
void AnimEncoderTest(std::string_view blob, bool minimize_size,
std::pair<int, int> kmin_kmax, bool allow_mixed,
const std::vector<FrameConfig>& frame_configs,
int optimization_index) {
WebPAnimEncoder* enc = nullptr;
int width = 0, height = 0, timestamp_ms = 0;
uint32_t bit_pos = 0;
const uint8_t* const data = reinterpret_cast<const uint8_t*>(blob.data());
const size_t size = blob.size();
ExtractAndDisableOptimizations(default_VP8GetCPUInfo, data, size, &bit_pos);
fuzz_utils::SetOptimization(default_VP8GetCPUInfo, optimization_index);
// Extract a configuration from the packed bits.
WebPAnimEncoderOptions anim_config;
@ -140,26 +149,20 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
fprintf(stderr, "WebPAnimEncoderOptionsInit failed.\n");
abort();
}
anim_config.minimize_size = Extract(1, data, size, &bit_pos);
anim_config.kmax = Extract(15, data, size, &bit_pos);
const int min_kmin = (anim_config.kmax > 1) ? (anim_config.kmax / 2) : 0;
const int max_kmin = (anim_config.kmax > 1) ? (anim_config.kmax - 1) : 0;
anim_config.kmin =
min_kmin + Extract((uint32_t)(max_kmin - min_kmin), data, size, &bit_pos);
anim_config.allow_mixed = Extract(1, data, size, &bit_pos);
anim_config.minimize_size = minimize_size;
anim_config.kmin = kmin_kmax.first;
anim_config.kmax = kmin_kmax.second;
anim_config.allow_mixed = allow_mixed;
anim_config.verbose = 0;
const int nb_frames = 1 + Extract(15, data, size, &bit_pos);
// For each frame.
for (int i = 0; i < nb_frames; ++i) {
if (!AddFrame(&enc, anim_config, &width, &height, timestamp_ms, data, size,
&bit_pos)) {
return 0;
for (const FrameConfig& frame_config : frame_configs) {
if (!AddFrame(&enc, anim_config, &width, &height, timestamp_ms,
frame_config, data, size, &bit_pos)) {
return;
}
timestamp_ms += (1 << (2 + Extract(15, data, size, &bit_pos))) +
Extract(1, data, size, &bit_pos); // [1..131073], arbitrary
timestamp_ms += frame_config.timestamp;
}
// Assemble.
@ -184,5 +187,22 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
WebPAnimEncoderDelete(enc);
WebPDataClear(&webp_data);
return 0;
}
} // namespace
FUZZ_TEST(AnimEncoder, AnimEncoderTest)
.WithDomains(
fuzztest::String(),
/*minimize_size=*/fuzztest::Arbitrary<bool>(), ArbitraryKMinKMax(),
/*allow_mixed=*/fuzztest::Arbitrary<bool>(),
fuzztest::VectorOf(
fuzztest::StructOf<FrameConfig>(
fuzztest::InRange<int>(0, 1), fuzztest::InRange<int>(0, 131073),
fuzz_utils::ArbitraryWebPConfig(),
fuzz_utils::ArbitraryCropOrScaleParams(),
fuzztest::InRange<int>(0, fuzz_utils::kNumSourceImages - 1)))
.WithMinSize(1)
.WithMaxSize(15),
/*optimization_index=*/
fuzztest::InRange<uint32_t>(0, fuzz_utils::kMaxOptimizationIndex));

View File

@ -14,57 +14,37 @@
//
////////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include "./fuzz_utils.h"
#include "src/dsp/cpu.h"
#include "src/webp/decode.h"
#include "src/webp/encode.h"
namespace {
const VP8CPUInfo default_VP8GetCPUInfo = VP8GetCPUInfo;
const VP8CPUInfo default_VP8GetCPUInfo = fuzz_utils::VP8GetCPUInfo;
} // namespace
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
uint32_t bit_pos = 0;
ExtractAndDisableOptimizations(default_VP8GetCPUInfo, data, size, &bit_pos);
void EncDecTest(bool use_argb, int source_image_index, WebPConfig config,
int optimization_index,
const fuzz_utils::CropOrScaleParams& crop_or_scale_params) {
fuzz_utils::SetOptimization(default_VP8GetCPUInfo, optimization_index);
// Init the source picture.
WebPPicture pic;
if (!WebPPictureInit(&pic)) {
fprintf(stderr, "WebPPictureInit failed.\n");
abort();
}
pic.use_argb = Extract(1, data, size, &bit_pos);
// Read the source picture.
if (!ExtractSourcePicture(&pic, data, size, &bit_pos)) {
const WebPEncodingError error_code = pic.error_code;
WebPPictureFree(&pic);
if (error_code == VP8_ENC_ERROR_OUT_OF_MEMORY) return 0;
fprintf(stderr, "Can't read input image. Error code: %d\n", error_code);
abort();
}
WebPPicture pic = fuzz_utils::GetSourcePicture(source_image_index, use_argb);
// Crop and scale.
if (!ExtractAndCropOrScale(&pic, data, size, &bit_pos)) {
if (!fuzz_utils::CropOrScale(&pic, crop_or_scale_params)) {
const WebPEncodingError error_code = pic.error_code;
WebPPictureFree(&pic);
if (error_code == VP8_ENC_ERROR_OUT_OF_MEMORY) return 0;
if (error_code == VP8_ENC_ERROR_OUT_OF_MEMORY) return;
fprintf(stderr, "ExtractAndCropOrScale failed. Error code: %d\n",
error_code);
abort();
}
// Extract a configuration from the packed bits.
WebPConfig config;
if (!ExtractWebPConfig(&config, data, size, &bit_pos)) {
fprintf(stderr, "ExtractWebPConfig failed.\n");
abort();
}
// Skip slow settings on big images, it's likely to timeout.
if (pic.width * pic.height > 32 * 32) {
if (config.lossless) {
@ -93,7 +73,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
WebPPictureFree(&pic);
if (error_code == VP8_ENC_ERROR_OUT_OF_MEMORY ||
error_code == VP8_ENC_ERROR_BAD_WRITE) {
return 0;
return;
}
fprintf(stderr, "WebPEncode failed. Error code: %d\n", error_code);
abort();
@ -157,5 +137,16 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
WebPFreeDecBuffer(&dec_config.output);
WebPMemoryWriterClear(&memory_writer);
WebPPictureFree(&pic);
return 0;
}
} // namespace
FUZZ_TEST(EncDec, EncDecTest)
.WithDomains(/*use_argb=*/fuzztest::Arbitrary<bool>(),
/*source_image_index=*/
fuzztest::InRange<int>(0, fuzz_utils::kNumSourceImages - 1),
fuzz_utils::ArbitraryWebPConfig(),
/*optimization_index=*/
fuzztest::InRange<uint32_t>(0,
fuzz_utils::kMaxOptimizationIndex),
fuzz_utils::ArbitraryCropOrScaleParams());

201
tests/fuzzer/fuzz_utils.cc Normal file
View File

@ -0,0 +1,201 @@
// Copyright 2024 Google LLC
//
// Licensed 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 "./fuzz_utils.h"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#include <string_view>
#include <tuple>
#include <vector>
#include "./img_alpha.h"
#include "./img_grid.h"
#include "./img_peak.h"
#include "src/dsp/cpu.h"
#include "src/webp/decode.h"
#include "src/webp/encode.h"
#include "src/webp/types.h"
namespace fuzz_utils {
WebPPicture GetSourcePicture(int image_index, bool use_argb) {
WebPPicture pic;
if (!WebPPictureInit(&pic)) abort();
pic.use_argb = use_argb;
// Pick a source picture.
const int kImagesWidth[] = {kImgAlphaWidth, kImgGridWidth, kImgPeakWidth};
const int kImagesHeight[] = {kImgAlphaHeight, kImgGridHeight, kImgPeakHeight};
const uint8_t* const image_data = kImagesData[image_index];
pic.width = kImagesWidth[image_index];
pic.height = kImagesHeight[image_index];
pic.argb_stride = pic.width * 4 * sizeof(uint8_t);
// Read the bytes.
if (!WebPPictureImportRGBA(&pic, image_data, pic.argb_stride)) abort();
return pic;
}
//------------------------------------------------------------------------------
int CropOrScale(WebPPicture* const pic, const CropOrScaleParams& params) {
if (pic == NULL) return 0;
#if !defined(WEBP_REDUCE_SIZE)
if (params.alter_input) {
if (params.crop_or_scale) {
const int cropped_width = std::max(1, pic->width / params.width_ratio);
const int cropped_height = std::max(1, pic->height / params.height_ratio);
const int cropped_left = (pic->width - cropped_width) / params.left_ratio;
const int cropped_top = (pic->height - cropped_height) / params.top_ratio;
return WebPPictureCrop(pic, cropped_left, cropped_top, cropped_width,
cropped_height);
} else {
const int scaled_width = 1 + (pic->width * params.width_ratio) / 8;
const int scaled_height = 1 + (pic->height * params.height_ratio) / 8;
return WebPPictureRescale(pic, scaled_width, scaled_height);
}
}
#else // defined(WEBP_REDUCE_SIZE)
(void)data;
(void)size;
(void)bit_pos;
#endif // !defined(WEBP_REDUCE_SIZE)
return 1;
}
extern "C" VP8CPUInfo VP8GetCPUInfo;
static VP8CPUInfo GetCPUInfo;
static WEBP_INLINE int GetCPUInfoNoSSE41(CPUFeature feature) {
if (feature == kSSE4_1 || feature == kAVX) return 0;
return GetCPUInfo(feature);
}
static WEBP_INLINE int GetCPUInfoNoAVX(CPUFeature feature) {
if (feature == kAVX) return 0;
return GetCPUInfo(feature);
}
static WEBP_INLINE int GetCPUInfoForceSlowSSSE3(CPUFeature feature) {
if (feature == kSlowSSSE3 && GetCPUInfo(kSSE3)) {
return 1; // we have SSE3 -> force SlowSSSE3
}
return GetCPUInfo(feature);
}
static WEBP_INLINE int GetCPUInfoOnlyC(CPUFeature feature) {
(void)feature;
return 0;
}
void SetOptimization(VP8CPUInfo default_VP8GetCPUInfo, uint32_t index) {
assert(index <= kMaxOptimizationIndex);
GetCPUInfo = default_VP8GetCPUInfo;
const VP8CPUInfo kVP8CPUInfos[kMaxOptimizationIndex + 1] = {
GetCPUInfoOnlyC, GetCPUInfoForceSlowSSSE3, GetCPUInfoNoSSE41,
GetCPUInfoNoAVX, GetCPUInfo};
VP8GetCPUInfo = kVP8CPUInfos[index];
}
//------------------------------------------------------------------------------
std::vector<std::string> ReadFilesFromDirectory(std::string_view dir) {
std::vector<std::tuple<std::string>> tuples =
fuzztest::ReadFilesFromDirectory(dir);
std::vector<std::string> strings(tuples.size());
for (size_t i = 0; i < tuples.size(); ++i) {
using std::swap;
swap(std::get<0>(tuples[i]), strings[i]);
}
return strings;
}
//------------------------------------------------------------------------------
// The code in this section is copied from
// https://github.com/webmproject/sjpeg/blob/
// 1c025b3dbc2246de3e1d7c287970f1a01291800f/src/jpeg_tools.cc#L47
// (same license as this file).
namespace {
// Constants below are marker codes defined in JPEG spec
// ISO/IEC 10918-1 : 1993(E) Table B.1
// See also: http://www.w3.org/Graphics/JPEG/itu-t81.pdf
#define M_SOF0 0xffc0
#define M_SOF1 0xffc1
const uint8_t* GetSOFData(const uint8_t* src, int size) {
if (src == NULL) return NULL;
const uint8_t* const end = src + size - 8; // 8 bytes of safety, for marker
src += 2; // skip M_SOI
for (; src < end && *src != 0xff; ++src) { /* search first 0xff marker */
}
while (src < end) {
const uint32_t marker = static_cast<uint32_t>((src[0] << 8) | src[1]);
if (marker == M_SOF0 || marker == M_SOF1) return src;
const size_t s = 2 + ((src[2] << 8) | src[3]);
src += s;
}
return NULL; // No SOF marker found
}
bool SjpegDimensions(const uint8_t* src0, size_t size, int* width, int* height,
int* is_yuv420) {
if (width == NULL || height == NULL) return false;
const uint8_t* src = GetSOFData(src0, size);
const size_t left_over = size - (src - src0);
if (src == NULL || left_over < 8 + 3 * 1) return false;
if (height != NULL) *height = (src[5] << 8) | src[6];
if (width != NULL) *width = (src[7] << 8) | src[8];
if (is_yuv420 != NULL) {
const size_t nb_comps = src[9];
*is_yuv420 = (nb_comps == 3);
if (left_over < 11 + 3 * nb_comps) return false;
for (int c = 0; *is_yuv420 && c < 3; ++c) {
const int expected_dim = (c == 0 ? 0x22 : 0x11);
*is_yuv420 &= (src[11 + c * 3] == expected_dim);
}
}
return true;
}
} // namespace
//------------------------------------------------------------------------------
bool IsImageTooBig(const uint8_t* data, size_t size) {
int width, height, components;
if (SjpegDimensions(data, size, &width, &height, &components) ||
WebPGetInfo(data, size, &width, &height)) {
// Look at the number of 8x8px blocks rather than the overall pixel count
// when comparing to memory and duration thresholds.
const size_t ceiled_width = ((size_t)width + 7) / 8 * 8;
const size_t ceiled_height = ((size_t)height + 7) / 8 * 8;
// Threshold to avoid out-of-memory and timeout issues.
// The threshold is arbitrary but below the fuzzer limit of 2 GB.
// The value cannot be 2 GB because of the added memory by MSAN.
if (ceiled_width * ceiled_height > kFuzzPxLimit) return true;
}
return false;
}
} // namespace fuzz_utils

View File

@ -1,4 +1,4 @@
// Copyright 2018 Google Inc.
// Copyright 2018-2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -17,14 +17,23 @@
#ifndef WEBP_TESTS_FUZZER_FUZZ_UTILS_H_
#define WEBP_TESTS_FUZZER_FUZZ_UTILS_H_
#include <stdint.h>
#include <stdlib.h>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "./img_alpha.h"
#include "./img_grid.h"
#include "./img_peak.h"
#include "src/dsp/dsp.h"
#include "src/dsp/cpu.h"
#include "src/webp/encode.h"
#include "fuzztest/fuzztest.h"
namespace fuzz_utils {
//------------------------------------------------------------------------------
// Arbitrary limits to prevent OOM, timeout, or slow execution.
@ -54,170 +63,139 @@ static WEBP_INLINE uint8_t FuzzHash(const uint8_t* const data, size_t size) {
return value;
}
//------------------------------------------------------------------------------
// Extract an integer in [0, max_value].
static WEBP_INLINE uint32_t Extract(uint32_t max_value,
const uint8_t data[], size_t size,
uint32_t* const bit_pos) {
uint32_t v = 0;
uint32_t range = 1;
while (*bit_pos < 8 * size && range <= max_value) {
const uint8_t mask = 1u << (*bit_pos & 7);
v = (v << 1) | !!(data[*bit_pos >> 3] & mask);
range <<= 1;
++*bit_pos;
}
return v % (max_value + 1);
}
//------------------------------------------------------------------------------
// Some functions to override VP8GetCPUInfo and disable some optimizations.
#ifdef __cplusplus
extern "C" VP8CPUInfo VP8GetCPUInfo;
#else
extern VP8CPUInfo VP8GetCPUInfo;
#endif
static VP8CPUInfo GetCPUInfo;
static WEBP_INLINE int GetCPUInfoNoSSE41(CPUFeature feature) {
if (feature == kSSE4_1 || feature == kAVX) return 0;
return GetCPUInfo(feature);
}
static WEBP_INLINE int GetCPUInfoNoAVX(CPUFeature feature) {
if (feature == kAVX) return 0;
return GetCPUInfo(feature);
}
static WEBP_INLINE int GetCPUInfoForceSlowSSSE3(CPUFeature feature) {
if (feature == kSlowSSSE3 && GetCPUInfo(kSSE3)) {
return 1; // we have SSE3 -> force SlowSSSE3
}
return GetCPUInfo(feature);
}
static WEBP_INLINE int GetCPUInfoOnlyC(CPUFeature feature) {
(void)feature;
return 0;
}
static WEBP_INLINE void ExtractAndDisableOptimizations(
VP8CPUInfo default_VP8GetCPUInfo, const uint8_t data[], size_t size,
uint32_t* const bit_pos) {
GetCPUInfo = default_VP8GetCPUInfo;
const VP8CPUInfo kVP8CPUInfos[5] = {GetCPUInfoOnlyC, GetCPUInfoForceSlowSSSE3,
GetCPUInfoNoSSE41, GetCPUInfoNoAVX,
GetCPUInfo};
int VP8GetCPUInfo_index = Extract(4, data, size, bit_pos);
VP8GetCPUInfo = kVP8CPUInfos[VP8GetCPUInfo_index];
}
//------------------------------------------------------------------------------
static WEBP_INLINE int ExtractWebPConfig(WebPConfig* const config,
const uint8_t data[], size_t size,
uint32_t* const bit_pos) {
if (config == NULL || !WebPConfigInit(config)) return 0;
config->lossless = Extract(1, data, size, bit_pos);
config->quality = Extract(100, data, size, bit_pos);
config->method = Extract(6, data, size, bit_pos);
config->image_hint =
(WebPImageHint)Extract(WEBP_HINT_LAST - 1, data, size, bit_pos);
config->segments = 1 + Extract(3, data, size, bit_pos);
config->sns_strength = Extract(100, data, size, bit_pos);
config->filter_strength = Extract(100, data, size, bit_pos);
config->filter_sharpness = Extract(7, data, size, bit_pos);
config->filter_type = Extract(1, data, size, bit_pos);
config->autofilter = Extract(1, data, size, bit_pos);
config->alpha_compression = Extract(1, data, size, bit_pos);
config->alpha_filtering = Extract(2, data, size, bit_pos);
config->alpha_quality = Extract(100, data, size, bit_pos);
config->pass = 1 + Extract(9, data, size, bit_pos);
config->show_compressed = 1;
config->preprocessing = Extract(2, data, size, bit_pos);
config->partitions = Extract(3, data, size, bit_pos);
config->partition_limit = 10 * Extract(10, data, size, bit_pos);
config->emulate_jpeg_size = Extract(1, data, size, bit_pos);
config->thread_level = Extract(1, data, size, bit_pos);
config->low_memory = Extract(1, data, size, bit_pos);
config->near_lossless = 20 * Extract(5, data, size, bit_pos);
config->exact = Extract(1, data, size, bit_pos);
config->use_delta_palette = Extract(1, data, size, bit_pos);
config->use_sharp_yuv = Extract(1, data, size, bit_pos);
return WebPValidateConfig(config);
constexpr const uint8_t* kImagesData[] = {kImgAlphaData, kImgGridData,
kImgPeakData};
constexpr size_t kNumSourceImages =
sizeof(kImagesData) / sizeof(kImagesData[0]);
WebPPicture GetSourcePicture(int image_index, bool use_argb);
static inline auto ArbitraryWebPConfig() {
return fuzztest::Map(
[](int lossless, int quality, int method, int image_hint, int segments,
int sns_strength, int filter_strength, int filter_sharpness,
int filter_type, int autofilter, int alpha_compression,
int alpha_filtering, int alpha_quality, int pass, int preprocessing,
int partitions, int partition_limit, int emulate_jpeg_size,
int thread_level, int low_memory, int near_lossless, int exact,
int use_delta_palette, int use_sharp_yuv) -> WebPConfig {
WebPConfig config;
if (!WebPConfigInit(&config)) abort();
config.lossless = lossless;
config.quality = quality;
config.method = method;
config.image_hint = (WebPImageHint)image_hint;
config.segments = segments;
config.sns_strength = sns_strength;
config.filter_strength = filter_strength;
config.filter_sharpness = filter_sharpness;
config.filter_type = filter_type;
config.autofilter = autofilter;
config.alpha_compression = alpha_compression;
config.alpha_filtering = alpha_filtering;
config.alpha_quality = alpha_quality;
config.pass = pass;
config.show_compressed = 1;
config.preprocessing = preprocessing;
config.partitions = partitions;
config.partition_limit = 10 * partition_limit;
config.emulate_jpeg_size = emulate_jpeg_size;
config.thread_level = thread_level;
config.low_memory = low_memory;
config.near_lossless = 20 * near_lossless;
config.exact = exact;
config.use_delta_palette = use_delta_palette;
config.use_sharp_yuv = use_sharp_yuv;
if (!WebPValidateConfig(&config)) abort();
return config;
},
/*lossless=*/fuzztest::InRange<int>(0, 1),
/*quality=*/fuzztest::InRange<int>(0, 100),
/*method=*/fuzztest::InRange<int>(0, 6),
/*image_hint=*/fuzztest::InRange<int>(0, WEBP_HINT_LAST - 1),
/*segments=*/fuzztest::InRange<int>(1, 4),
/*sns_strength=*/fuzztest::InRange<int>(0, 100),
/*filter_strength=*/fuzztest::InRange<int>(0, 100),
/*filter_sharpness=*/fuzztest::InRange<int>(0, 7),
/*filter_type=*/fuzztest::InRange<int>(0, 1),
/*autofilter=*/fuzztest::InRange<int>(0, 1),
/*alpha_compression=*/fuzztest::InRange<int>(0, 1),
/*alpha_filtering=*/fuzztest::InRange<int>(0, 2),
/*alpha_quality=*/fuzztest::InRange<int>(0, 100),
/*pass=*/fuzztest::InRange<int>(1, 10),
/*preprocessing=*/fuzztest::InRange<int>(0, 2),
/*partitions=*/fuzztest::InRange<int>(0, 3),
/*partition_limit=*/fuzztest::InRange<int>(0, 10),
/*emulate_jpeg_size=*/fuzztest::InRange<int>(0, 1),
/*thread_level=*/fuzztest::InRange<int>(0, 1),
/*low_memory=*/fuzztest::InRange<int>(0, 1),
/*near_lossless=*/fuzztest::InRange<int>(0, 5),
/*exact=*/fuzztest::InRange<int>(0, 1),
/*use_delta_palette=*/fuzztest::InRange<int>(0, 1),
/*use_sharp_yuv=*/fuzztest::InRange<int>(0, 1));
}
struct CropOrScaleParams {
bool alter_input;
bool crop_or_scale;
int width_ratio;
int height_ratio;
int left_ratio;
int top_ratio;
};
static inline auto ArbitraryCropOrScaleParams() {
return fuzztest::Map(
[](const std::optional<std::pair<int, int>>& width_height_ratio,
const std::optional<std::pair<int, int>>& left_top_ratio)
-> CropOrScaleParams {
CropOrScaleParams params;
params.alter_input = width_height_ratio.has_value();
if (params.alter_input) {
params.width_ratio = width_height_ratio->first;
params.height_ratio = width_height_ratio->second;
params.crop_or_scale = left_top_ratio.has_value();
if (params.crop_or_scale) {
params.left_ratio = left_top_ratio->first;
params.top_ratio = left_top_ratio->second;
}
}
return params;
},
fuzztest::OptionalOf(
fuzztest::PairOf(fuzztest::InRange(1, 8), fuzztest::InRange(1, 8))),
fuzztest::OptionalOf(
fuzztest::PairOf(fuzztest::InRange(1, 8), fuzztest::InRange(1, 8))));
}
// Crops or scales a picture according to the given params.
int CropOrScale(WebPPicture* pic, const CropOrScaleParams& params);
// Imposes a level of optimization among one of the kMaxOptimizationIndex+1
// possible values: OnlyC, ForceSlowSSSE3, NoSSE41, NoAVX, default.
static constexpr uint32_t kMaxOptimizationIndex = 4;
void SetOptimization(VP8CPUInfo default_VP8GetCPUInfo, uint32_t index);
//------------------------------------------------------------------------------
static WEBP_INLINE int ExtractSourcePicture(WebPPicture* const pic,
const uint8_t data[], size_t size,
uint32_t* const bit_pos) {
if (pic == NULL) return 0;
// See https://developers.google.com/speed/webp/docs/riff_container.
static constexpr size_t kMaxWebPFileSize = (1ull << 32) - 2; // 4 GiB - 2
// Pick a source picture.
const uint8_t* kImagesData[] = {
kImgAlphaData,
kImgGridData,
kImgPeakData
};
const int kImagesWidth[] = {
kImgAlphaWidth,
kImgGridWidth,
kImgPeakWidth
};
const int kImagesHeight[] = {
kImgAlphaHeight,
kImgGridHeight,
kImgPeakHeight
};
const size_t kNbImages = sizeof(kImagesData) / sizeof(kImagesData[0]);
const size_t image_index = Extract(kNbImages - 1, data, size, bit_pos);
const uint8_t* const image_data = kImagesData[image_index];
pic->width = kImagesWidth[image_index];
pic->height = kImagesHeight[image_index];
pic->argb_stride = pic->width * 4 * sizeof(uint8_t);
std::vector<std::string> GetDictionaryFromFiles(
const std::vector<std::string_view>& file_paths);
// Read the bytes.
return WebPPictureImportRGBA(pic, image_data, pic->argb_stride);
}
// Checks whether the binary blob containing a JPEG or WebP is too big for the
// fuzzer.
bool IsImageTooBig(const uint8_t* data, size_t size);
//------------------------------------------------------------------------------
static WEBP_INLINE int Max(int a, int b) { return ((a < b) ? b : a); }
static WEBP_INLINE int ExtractAndCropOrScale(WebPPicture* const pic,
const uint8_t data[], size_t size,
uint32_t* const bit_pos) {
if (pic == NULL) return 0;
#if !defined(WEBP_REDUCE_SIZE)
const int alter_input = Extract(1, data, size, bit_pos);
const int crop_or_scale = Extract(1, data, size, bit_pos);
const int width_ratio = 1 + Extract(7, data, size, bit_pos);
const int height_ratio = 1 + Extract(7, data, size, bit_pos);
if (alter_input) {
if (crop_or_scale) {
const uint32_t left_ratio = 1 + Extract(7, data, size, bit_pos);
const uint32_t top_ratio = 1 + Extract(7, data, size, bit_pos);
const int cropped_width = Max(1, pic->width / width_ratio);
const int cropped_height = Max(1, pic->height / height_ratio);
const int cropped_left = (pic->width - cropped_width) / left_ratio;
const int cropped_top = (pic->height - cropped_height) / top_ratio;
return WebPPictureCrop(pic, cropped_left, cropped_top, cropped_width,
cropped_height);
} else {
const int scaled_width = 1 + (pic->width * width_ratio) / 8;
const int scaled_height = 1 + (pic->height * height_ratio) / 8;
return WebPPictureRescale(pic, scaled_width, scaled_height);
}
}
#else // defined(WEBP_REDUCE_SIZE)
(void)data;
(void)size;
(void)bit_pos;
#endif // !defined(WEBP_REDUCE_SIZE)
return 1;
}
} // namespace fuzz_utils
#endif // WEBP_TESTS_FUZZER_FUZZ_UTILS_H_

View File

@ -14,22 +14,29 @@
//
////////////////////////////////////////////////////////////////////////////////
#include <stdint.h>
#include <string.h>
#include <cstddef>
#include <cstdint>
#include <string_view>
#include "./fuzz_utils.h"
#include "src/dec/vp8li_dec.h"
#include "src/utils/bit_reader_utils.h"
#include "src/utils/huffman_utils.h"
#include "src/utils/utils.h"
#include "src/webp/format_constants.h"
int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
namespace {
void HuffmanTest(std::string_view blob) {
const uint8_t* const data = reinterpret_cast<const uint8_t*>(blob.data());
const size_t size = blob.size();
// Number of bits to initialize data.
static const int kColorCacheBitsBits = 4;
// 'num_htree_groups' is contained in the RG channel, hence 16 bits.
static const int kNumHtreeGroupsBits = 16;
if (size * sizeof(*data) < kColorCacheBitsBits + kNumHtreeGroupsBits) {
return 0;
return;
}
// A non-NULL mapping brings minor changes that are tested by the normal
@ -39,27 +46,32 @@ int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
memset(&huffman_tables, 0, sizeof(huffman_tables));
HTreeGroup* htree_groups = NULL;
int num_htree_groups, num_htree_groups_max, color_cache_bits;
VP8LBitReader* br;
VP8LDecoder* dec = VP8LNew();
if (dec == NULL) goto Error;
VP8LBitReader* const br = &dec->br_;
br = &dec->br_;
VP8LInitBitReader(br, data, size);
const int color_cache_bits = VP8LReadBits(br, kColorCacheBitsBits);
color_cache_bits = VP8LReadBits(br, kColorCacheBitsBits);
if (color_cache_bits < 1 || color_cache_bits > MAX_CACHE_BITS) goto Error;
const int num_htree_groups = VP8LReadBits(br, kNumHtreeGroupsBits);
num_htree_groups = VP8LReadBits(br, kNumHtreeGroupsBits);
// 'num_htree_groups' cannot be 0 as it is built from a non-empty image.
if (num_htree_groups == 0) goto Error;
if (num_htree_groups == 0) return;
// This variable is only useful when mapping is not NULL.
const int num_htree_groups_max = num_htree_groups;
num_htree_groups_max = num_htree_groups;
(void)ReadHuffmanCodesHelper(color_cache_bits, num_htree_groups,
num_htree_groups_max, mapping, dec,
&huffman_tables, &htree_groups);
Error:
Error:
WebPSafeFree(mapping);
VP8LHtreeGroupsFree(htree_groups);
VP8LHuffmanTablesDeallocate(&huffman_tables);
VP8LDelete(dec);
return 0;
}
} // namespace
FUZZ_TEST(Huffman, HuffmanTest).WithDomains(fuzztest::String());

View File

@ -1,31 +0,0 @@
# This Makefile will compile all fuzzing targets. It doesn't check tool
# requirements and paths may need to be updated depending on your environment.
# Note a clang 6+ toolchain is assumed for use of -fsanitize=fuzzer.
CC = clang
CXX = clang++
CFLAGS = -fsanitize=fuzzer -I../../src -I../.. -Wall -Wextra
CXXFLAGS = $(CFLAGS)
LDFLAGS = -fsanitize=fuzzer
LDLIBS = ../../src/mux/libwebpmux.a ../../src/demux/libwebpdemux.a
LDLIBS += ../../src/libwebp.a ../../imageio/libimageio_util.a
LDLIBS += ../../sharpyuv/libsharpyuv.a
FUZZERS = advanced_api_fuzzer animation_api_fuzzer animdecoder_fuzzer
FUZZERS += animencoder_fuzzer enc_dec_fuzzer huffman_fuzzer
FUZZERS += mux_demux_api_fuzzer simple_api_fuzzer
%.o: fuzz_utils.h img_alpha.h img_grid.h img_peak.h
all: $(FUZZERS)
define FUZZER_template
$(1): $$(addsuffix .o, $(1)) $(LDLIBS)
OBJS += $$(addsuffix .o, $(1))
endef
$(foreach fuzzer, $(FUZZERS), $(eval $(call FUZZER_template, $(fuzzer))))
clean:
$(RM) $(FUZZERS) $(OBJS)
.PHONY: all clean

View File

@ -14,23 +14,30 @@
//
////////////////////////////////////////////////////////////////////////////////
#include <cstddef>
#include <cstdint>
#include <string_view>
#include "./fuzz_utils.h"
#include "src/webp/demux.h"
#include "src/webp/mux.h"
int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
namespace {
void MuxDemuxApiTest(std::string_view data_in, bool mux) {
const size_t size = data_in.size();
WebPData webp_data;
WebPDataInit(&webp_data);
webp_data.size = size;
webp_data.bytes = data;
webp_data.bytes = reinterpret_cast<const uint8_t*>(data_in.data());
// Extracted chunks and frames are not processed or decoded,
// which is already covered extensively by the other fuzz targets.
if (size & 1) {
if (mux) {
// Mux API
WebPMux* mux = WebPMuxCreate(&webp_data, size & 2);
if (!mux) return 0;
if (!mux) return;
WebPData chunk;
(void)WebPMuxGetChunk(mux, "EXIF", &chunk);
@ -45,7 +52,7 @@ int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
WebPMuxError status;
WebPMuxFrameInfo info;
for (int i = 0; i < kFuzzFrameLimit; i++) {
for (int i = 0; i < fuzz_utils::kFuzzFrameLimit; i++) {
status = WebPMuxGetFrame(mux, i + 1, &info);
if (status == WEBP_MUX_NOT_FOUND) {
break;
@ -63,11 +70,11 @@ int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
demux = WebPDemuxPartial(&webp_data, &state);
if (state < WEBP_DEMUX_PARSED_HEADER) {
WebPDemuxDelete(demux);
return 0;
return;
}
} else {
demux = WebPDemux(&webp_data);
if (!demux) return 0;
if (!demux) return;
}
WebPChunkIterator chunk_iter;
@ -83,7 +90,7 @@ int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
WebPIterator iter;
if (WebPDemuxGetFrame(demux, 1, &iter)) {
for (int i = 1; i < kFuzzFrameLimit; i++) {
for (int i = 1; i < fuzz_utils::kFuzzFrameLimit; i++) {
if (!WebPDemuxNextFrame(&iter)) break;
}
}
@ -91,6 +98,12 @@ int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
WebPDemuxReleaseIterator(&iter);
WebPDemuxDelete(demux);
}
return 0;
}
} // namespace
FUZZ_TEST(MuxDemuxApi, MuxDemuxApiTest)
.WithDomains(
fuzztest::String()
.WithMaxSize(fuzz_utils::kMaxWebPFileSize + 1),
/*mux=*/fuzztest::Arbitrary<bool>());

View File

@ -15,61 +15,69 @@
#
################################################################################
# This script is meant to be run by the oss-fuzz infrastructure from the script
# https://github.com/google/oss-fuzz/blob/master/projects/libwebp/build.sh
# It builds the different fuzz targets.
# To test changes to this file:
# - make changes and commit to your REPO
# - run:
# git clone --depth=1 git@github.com:google/oss-fuzz.git
# cd oss-fuzz
# - modify projects/libwebp/Dockerfile to point to your REPO
# - run:
# python3 infra/helper.py build_image libwebp
# # enter 'y' and wait for everything to be downloaded
# - run:
# python3 infra/helper.py build_fuzzers --sanitizer address libwebp
# # wait for the tests to be built
# And then run the fuzzer locally, for example:
# python3 infra/helper.py run_fuzzer libwebp \
# --sanitizer address \
# animencoder_fuzzer__AnimEncoder.AnimEncoderTest
set -eu
# limit allocation size to reduce spurious OOMs
WEBP_CFLAGS="$CFLAGS -DWEBP_MAX_IMAGE_SIZE=838860800" # 800MiB
./autogen.sh
CFLAGS="$WEBP_CFLAGS" ./configure \
--enable-asserts \
--enable-libwebpdemux \
--enable-libwebpmux \
--disable-shared \
--disable-jpeg \
--disable-tiff \
--disable-gif \
--disable-wic
make clean
make -j$(nproc)
export CFLAGS="$WEBP_CFLAGS"
cmake -S . -B build -DWEBP_BUILD_FUZZTEST=ON
cd build && make -j$(nproc) && cd ..
find $SRC/libwebp-test-data -type f -size -32k -iname "*.webp" \
-exec zip -qju fuzz_seed_corpus.zip "{}" \;
webp_libs=(
src/demux/.libs/libwebpdemux.a
src/mux/.libs/libwebpmux.a
src/.libs/libwebp.a
imageio/.libs/libimageio_util.a
sharpyuv/.libs/libsharpyuv.a
)
webp_c_fuzzers=(
advanced_api_fuzzer
animation_api_fuzzer
huffman_fuzzer
mux_demux_api_fuzzer
simple_api_fuzzer
)
webp_cxx_fuzzers=(
animdecoder_fuzzer
animencoder_fuzzer
enc_dec_fuzzer
)
for fuzzer in "${webp_c_fuzzers[@]}"; do
$CC $CFLAGS -Isrc -I. tests/fuzzer/${fuzzer}.c -c -o tests/fuzzer/${fuzzer}.o
$CXX $CXXFLAGS $LIB_FUZZING_ENGINE \
tests/fuzzer/${fuzzer}.o -o $OUT/${fuzzer} \
"${webp_libs[@]}"
done
for fuzzer in "${webp_cxx_fuzzers[@]}"; do
$CXX $CXXFLAGS -Isrc -I. $LIB_FUZZING_ENGINE \
tests/fuzzer/${fuzzer}.cc -o $OUT/${fuzzer} \
"${webp_libs[@]}"
done
for fuzzer in "${webp_c_fuzzers[@]}" "${webp_cxx_fuzzers[@]}"; do
cp fuzz_seed_corpus.zip $OUT/${fuzzer}_seed_corpus.zip
cp tests/fuzzer/fuzz.dict $OUT/${fuzzer}.dict
done
# Restrict fuzztest tests to the only compatible fuzz engine: libfuzzer.
if [[ "$FUZZING_ENGINE" == "libfuzzer" ]]; then
# build fuzztests
# The following is taken from https://github.com/google/oss-fuzz/blob/31ac7244748ea7390015455fb034b1f4eda039d9/infra/base-images/base-builder/compile_fuzztests.sh#L59
# Iterate the fuzz binaries and list each fuzz entrypoint in the binary. For
# each entrypoint create a wrapper script that calls into the binaries the
# given entrypoint as argument.
# The scripts will be named:
# {binary_name}@{fuzztest_entrypoint}
FUZZ_TEST_BINARIES_OUT_PATHS=$(find ./build/tests/fuzzer/ -executable -type f)
echo "Fuzz binaries: $FUZZ_TEST_BINARIES_OUT_PATHS"
for fuzz_main_file in $FUZZ_TEST_BINARIES_OUT_PATHS; do
FUZZ_TESTS=$($fuzz_main_file --list_fuzz_tests | cut -d ' ' -f 4)
cp -f ${fuzz_main_file} $OUT/
fuzz_basename=$(basename $fuzz_main_file)
chmod -x $OUT/$fuzz_basename
for fuzz_entrypoint in $FUZZ_TESTS; do
TARGET_FUZZER="${fuzz_basename}@$fuzz_entrypoint"
# Write executer script
echo "#!/bin/sh
# LLVMFuzzerTestOneInput for fuzzer detection.
this_dir=\$(dirname \"\$0\")
export TEST_DATA_DIRS=\$this_dir/corpus
chmod +x \$this_dir/$fuzz_basename
\$this_dir/$fuzz_basename --fuzz=$fuzz_entrypoint -- \$@
chmod -x \$this_dir/$fuzz_basename" > $OUT/$TARGET_FUZZER
chmod +x $OUT/$TARGET_FUZZER
done
# Copy data.
cp fuzz_seed_corpus.zip $OUT/${fuzz_basename}_seed_corpus.zip
cp tests/fuzzer/fuzz.dict $OUT/${fuzz_basename}.dict
done
fi

10
tests/fuzzer/patch.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/sh
# Fixes for https://github.com/google/fuzztest/issues/1124
sed -i -e "s/-fsanitize=address//g" -e "s/-DADDRESS_SANITIZER//g" \
./cmake/FuzzTestFlagSetup.cmake
# Fixes for https://github.com/google/fuzztest/issues/1125
before="if (IsEnginePlaceholderInput(data)) return;"
after="if (data.size() == 0) return;"
sed -i "s/${before}/${after}/" ./fuzztest/internal/compatibility_mode.cc
sed -i "s/set(GTEST_HAS_ABSL ON)/set(GTEST_HAS_ABSL OFF)/" \
./cmake/BuildDependencies.cmake

View File

@ -14,15 +14,23 @@
//
////////////////////////////////////////////////////////////////////////////////
#include <cstddef>
#include <cstdint>
#include <string_view>
#include "./fuzz_utils.h"
#include "src/webp/decode.h"
int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
int w, h;
if (!WebPGetInfo(data, size, &w, &h)) return 0;
if ((size_t)w * h > kFuzzPxLimit) return 0;
namespace {
const uint8_t value = FuzzHash(data, size);
void SimpleApiTest(std::string_view data_in) {
const uint8_t* const data = reinterpret_cast<const uint8_t*>(data_in.data());
const size_t size = data_in.size();
int w, h;
if (!WebPGetInfo(data, size, &w, &h)) return;
if ((size_t)w * h > fuzz_utils::kFuzzPxLimit) return;
const uint8_t value = fuzz_utils::FuzzHash(data, size);
uint8_t* buf = NULL;
// For *Into functions, which decode into an external buffer, an
@ -84,6 +92,11 @@ int LLVMFuzzerTestOneInput(const uint8_t* const data, size_t size) {
}
if (buf) WebPFree(buf);
return 0;
}
} // namespace
FUZZ_TEST(SimpleApi, SimpleApiTest)
.WithDomains(
fuzztest::String()
.WithMaxSize(fuzz_utils::kMaxWebPFileSize + 1));