Add a fuzzer for WebPDecoderConfig

Change-Id: I156304ae361c5f18ec2705adec51dae51ff91f2e
This commit is contained in:
Vincent Rabaud
2025-10-28 11:22:36 +01:00
parent 4efd97e825
commit 160ad07632
4 changed files with 260 additions and 72 deletions

View File

@@ -17,6 +17,7 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string_view>
#include "./fuzz_utils.h"
@@ -26,10 +27,9 @@
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) {
void AdvancedApiTest(std::string_view blob, uint8_t factor_u8, int colorspace,
bool incremental,
const fuzz_utils::WebPDecoderOptionsCpp& decoder_options) {
WebPDecoderConfig config;
if (!WebPInitDecoderConfig(&config)) return;
const uint8_t* const data = reinterpret_cast<const uint8_t*>(blob.data());
@@ -46,24 +46,14 @@ void AdvancedApiTest(std::string_view blob, uint8_t factor_u8, bool flip,
const uint8_t value = fuzz_utils::FuzzHash(data, size);
const float factor = factor_u8 / 255.f; // 0-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;
std::memcpy(&config.options, &decoder_options, sizeof(decoder_options));
if (config.options.use_cropping) {
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 (use_dithering) {
int strength = (int)(factor * 100);
config.options.dithering_strength = strength;
config.options.alpha_dithering_strength = 100 - strength;
}
if (use_scaling) {
config.options.use_scaling = 1;
if (config.options.use_scaling) {
config.options.scaled_width = (int)(config.input.width * factor * 2);
config.options.scaled_height = (int)(config.input.height * factor * 2);
}
@@ -162,13 +152,6 @@ 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),
@@ -177,4 +160,5 @@ FUZZ_TEST(AdvancedApi, AdvancedApiTest)
#else
fuzztest::InRange<int>(0, static_cast<int>(MODE_LAST) - 1),
#endif
/*incremental=*/fuzztest::Arbitrary<bool>());
/*incremental=*/fuzztest::Arbitrary<bool>(),
fuzz_utils::ArbitraryValidWebPDecoderOptions());

View File

@@ -17,6 +17,7 @@
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string_view>
#include "./nalloc.h"
@@ -25,7 +26,8 @@
namespace {
void DecodeWebP(std::string_view arbitrary_bytes) {
void DecodeWebP(std::string_view arbitrary_bytes,
const fuzz_utils::WebPDecoderOptionsCpp& decoder_options) {
WebPDecoderConfig decoder_config;
if (!WebPInitDecoderConfig(&decoder_config)) {
fprintf(stderr, "WebPInitDecoderConfig failed.\n");
@@ -34,6 +36,8 @@ void DecodeWebP(std::string_view arbitrary_bytes) {
nalloc_init(nullptr);
nalloc_start(reinterpret_cast<const uint8_t*>(arbitrary_bytes.data()),
arbitrary_bytes.size());
std::memcpy(&decoder_config.options, &decoder_options,
sizeof(decoder_options));
const VP8StatusCode status =
WebPDecode(reinterpret_cast<const uint8_t*>(arbitrary_bytes.data()),
arbitrary_bytes.size(), &decoder_config);
@@ -48,6 +52,7 @@ void DecodeWebP(std::string_view arbitrary_bytes) {
FUZZ_TEST(WebPSuite, DecodeWebP)
.WithDomains(fuzztest::String().WithMaxSize(fuzz_utils::kMaxWebPFileSize +
1));
1),
fuzz_utils::ArbitraryValidWebPDecoderOptions());
} // namespace

View File

@@ -18,6 +18,8 @@
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
#include "./fuzz_utils.h"
#include "src/dsp/cpu.h"
@@ -28,18 +30,12 @@ namespace {
const VP8CPUInfo default_VP8GetCPUInfo = fuzz_utils::VP8GetCPUInfo;
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 = fuzz_utils::GetSourcePicture(source_image_index, use_argb);
void Enc(const fuzz_utils::CropOrScaleParams& crop_or_scale_params,
WebPConfig& config, WebPPicture& pic,
WebPMemoryWriter& memory_writer) {
// Crop and scale.
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;
fprintf(stderr, "ExtractAndCropOrScale failed. Error code: %d\n",
error_code);
@@ -64,14 +60,10 @@ void EncDecTest(bool use_argb, int source_image_index, WebPConfig config,
}
// Encode.
WebPMemoryWriter memory_writer;
WebPMemoryWriterInit(&memory_writer);
pic.writer = WebPMemoryWrite;
pic.custom_ptr = &memory_writer;
if (!WebPEncode(&config, &pic)) {
const WebPEncodingError error_code = pic.error_code;
WebPMemoryWriterClear(&memory_writer);
WebPPictureFree(&pic);
if (error_code == VP8_ENC_ERROR_OUT_OF_MEMORY ||
error_code == VP8_ENC_ERROR_BAD_WRITE) {
return;
@@ -79,69 +71,151 @@ void EncDecTest(bool use_argb, int source_image_index, WebPConfig config,
fprintf(stderr, "WebPEncode failed. Error code: %d\n", error_code);
std::abort();
}
}
void EncDecValidTest(bool use_argb, int source_image_index, WebPConfig config,
int optimization_index,
const fuzz_utils::CropOrScaleParams& crop_or_scale_params,
int colorspace,
const fuzz_utils::WebPDecoderOptionsCpp& decoder_options) {
fuzz_utils::SetOptimization(default_VP8GetCPUInfo, optimization_index);
// Init the source picture.
WebPPicture pic = fuzz_utils::GetSourcePicture(source_image_index, use_argb);
std::unique_ptr<WebPPicture, fuzz_utils::UniquePtrDeleter> pic_owner(&pic);
WebPMemoryWriter memory_writer;
WebPMemoryWriterInit(&memory_writer);
std::unique_ptr<WebPMemoryWriter, fuzz_utils::UniquePtrDeleter>
memory_writer_owner(&memory_writer);
Enc(crop_or_scale_params, config, pic, memory_writer);
// Try decoding the result.
const uint8_t* const out_data = memory_writer.mem;
const size_t out_size = memory_writer.size;
WebPDecoderConfig dec_config;
std::unique_ptr<WebPDecoderConfig, fuzz_utils::UniquePtrDeleter>
dec_config_owner(&dec_config);
if (!WebPInitDecoderConfig(&dec_config)) {
fprintf(stderr, "WebPInitDecoderConfig failed.\n");
WebPMemoryWriterClear(&memory_writer);
WebPPictureFree(&pic);
std::abort();
}
dec_config.output.colorspace = MODE_BGRA;
const VP8StatusCode status = WebPDecode(out_data, out_size, &dec_config);
VP8StatusCode status = WebPDecode(out_data, out_size, &dec_config);
if ((status != VP8_STATUS_OK && status != VP8_STATUS_OUT_OF_MEMORY &&
status != VP8_STATUS_USER_ABORT) ||
(status == VP8_STATUS_OK && (dec_config.output.width != pic.width ||
dec_config.output.height != pic.height))) {
fprintf(stderr, "WebPDecode failed. status: %d.\n", status);
WebPFreeDecBuffer(&dec_config.output);
WebPMemoryWriterClear(&memory_writer);
WebPPictureFree(&pic);
std::abort();
}
if (status == VP8_STATUS_OK) {
// Compare the results if exact encoding.
if (status == VP8_STATUS_OK && pic.use_argb && config.lossless &&
config.near_lossless == 100) {
const uint8_t* const rgba = dec_config.output.u.RGBA.rgba;
const int w = dec_config.output.width;
const int h = dec_config.output.height;
// Compare the results if exact encoding.
if (pic.use_argb && config.lossless && config.near_lossless == 100) {
const uint32_t* src1 = (const uint32_t*)rgba;
const uint32_t* src2 = pic.argb;
for (int y = 0; y < h; ++y, src1 += w, src2 += pic.argb_stride) {
for (int x = 0; x < w; ++x) {
uint32_t v1 = src1[x], v2 = src2[x];
if (!config.exact) {
if ((v1 & 0xff000000u) == 0 || (v2 & 0xff000000u) == 0) {
// Only keep alpha for comparison of fully transparent area.
v1 &= 0xff000000u;
v2 &= 0xff000000u;
}
}
if (v1 != v2) {
fprintf(stderr, "Lossless compression failed pixel-exactness.\n");
WebPFreeDecBuffer(&dec_config.output);
WebPMemoryWriterClear(&memory_writer);
WebPPictureFree(&pic);
std::abort();
const uint32_t* src1 = (const uint32_t*)rgba;
const uint32_t* src2 = pic.argb;
for (int y = 0; y < h; ++y, src1 += w, src2 += pic.argb_stride) {
for (int x = 0; x < w; ++x) {
uint32_t v1 = src1[x], v2 = src2[x];
if (!config.exact) {
if ((v1 & 0xff000000u) == 0 || (v2 & 0xff000000u) == 0) {
// Only keep alpha for comparison of fully transparent area.
v1 &= 0xff000000u;
v2 &= 0xff000000u;
}
}
if (v1 != v2) {
fprintf(stderr, "Lossless compression failed pixel-exactness.\n");
std::abort();
}
}
}
}
// Use given decoding options.
if (static_cast<int64_t>(decoder_options.crop_left) +
decoder_options.crop_width >
static_cast<int64_t>(pic.width) ||
static_cast<int64_t>(decoder_options.crop_top) +
decoder_options.crop_height >
static_cast<int64_t>(pic.height)) {
return;
}
WebPFreeDecBuffer(&dec_config.output);
WebPMemoryWriterClear(&memory_writer);
WebPPictureFree(&pic);
if (!WebPInitDecoderConfig(&dec_config)) {
fprintf(stderr, "WebPInitDecoderConfig failed.\n");
abort();
}
dec_config.output.colorspace = static_cast<WEBP_CSP_MODE>(colorspace);
std::memcpy(&dec_config.options, &decoder_options, sizeof(decoder_options));
status = WebPDecode(out_data, out_size, &dec_config);
if (status != VP8_STATUS_OK && status != VP8_STATUS_OUT_OF_MEMORY &&
status != VP8_STATUS_USER_ABORT) {
fprintf(stderr, "WebPDecode failed. status: %d.\n", status);
abort();
}
}
void EncDecTest(bool use_argb, int source_image_index, WebPConfig config,
int optimization_index,
const fuzz_utils::CropOrScaleParams& crop_or_scale_params,
int colorspace,
const fuzz_utils::WebPDecoderOptionsCpp& decoder_options) {
fuzz_utils::SetOptimization(default_VP8GetCPUInfo, optimization_index);
// Init the source picture.
WebPPicture pic = fuzz_utils::GetSourcePicture(source_image_index, use_argb);
std::unique_ptr<WebPPicture, fuzz_utils::UniquePtrDeleter> pic_owner(&pic);
WebPMemoryWriter memory_writer;
WebPMemoryWriterInit(&memory_writer);
std::unique_ptr<WebPMemoryWriter, fuzz_utils::UniquePtrDeleter>
memory_writer_owner(&memory_writer);
Enc(crop_or_scale_params, config, pic, memory_writer);
// Try decoding the result.
const uint8_t* const out_data = memory_writer.mem;
const size_t out_size = memory_writer.size;
WebPDecoderConfig dec_config;
std::unique_ptr<WebPDecoderConfig, fuzz_utils::UniquePtrDeleter>
dec_config_owner(&dec_config);
if (!WebPInitDecoderConfig(&dec_config)) {
fprintf(stderr, "WebPInitDecoderConfig failed.\n");
abort();
}
dec_config.output.colorspace = static_cast<WEBP_CSP_MODE>(colorspace);
std::memcpy(&dec_config.options, &decoder_options, sizeof(decoder_options));
const VP8StatusCode status = WebPDecode(out_data, out_size, &dec_config);
if (status != VP8_STATUS_OK && status != VP8_STATUS_OUT_OF_MEMORY &&
status != VP8_STATUS_USER_ABORT && status != VP8_STATUS_INVALID_PARAM) {
fprintf(stderr, "WebPDecode failed. status: %d.\n", status);
abort();
}
}
} // namespace
FUZZ_TEST(EncDec, EncDecValidTest)
.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(),
/*colorspace=*/fuzztest::InRange<int>(0, MODE_LAST - 1),
fuzz_utils::ArbitraryValidWebPDecoderOptions());
FUZZ_TEST(EncDec, EncDecTest)
.WithDomains(/*use_argb=*/fuzztest::Arbitrary<bool>(),
/*source_image_index=*/
@@ -150,4 +224,6 @@ FUZZ_TEST(EncDec, EncDecTest)
/*optimization_index=*/
fuzztest::InRange<uint32_t>(0,
fuzz_utils::kMaxOptimizationIndex),
fuzz_utils::ArbitraryCropOrScaleParams());
fuzz_utils::ArbitraryCropOrScaleParams(),
/*colorspace=*/fuzztest::Arbitrary<int>(),
fuzz_utils::ArbitraryWebPDecoderOptions());

View File

@@ -17,9 +17,12 @@
#ifndef WEBP_TESTS_FUZZER_FUZZ_UTILS_H_
#define WEBP_TESTS_FUZZER_FUZZ_UTILS_H_
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <optional>
#include <string>
#include <string_view>
@@ -31,6 +34,7 @@
#include "./img_peak.h"
#include "fuzztest/fuzztest.h"
#include "src/dsp/cpu.h"
#include "src/webp/decode.h"
#include "src/webp/encode.h"
#include "src/webp/types.h"
@@ -79,6 +83,17 @@ constexpr size_t kNumSourceImages =
WebPPicture GetSourcePicture(int image_index, bool use_argb);
// Struct to use in a unique_ptr to free the memory.
struct UniquePtrDeleter {
void operator()(WebPMemoryWriter* writer) const {
WebPMemoryWriterClear(writer);
}
void operator()(WebPPicture* pic) const { WebPPictureFree(pic); }
void operator()(WebPDecoderConfig* config) const {
WebPFreeDecBuffer(&config->output);
}
};
static inline auto ArbitraryWebPConfig() {
return fuzztest::Map(
[](int lossless, int quality, int method, int image_hint, int segments,
@@ -89,7 +104,7 @@ static inline auto ArbitraryWebPConfig() {
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();
if (!WebPConfigInit(&config)) assert(false);
config.lossless = lossless;
config.quality = quality;
config.method = method;
@@ -115,7 +130,7 @@ static inline auto ArbitraryWebPConfig() {
config.exact = exact;
config.use_delta_palette = use_delta_palette;
config.use_sharp_yuv = use_sharp_yuv;
if (!WebPValidateConfig(&config)) abort();
if (!WebPValidateConfig(&config)) assert(false);
return config;
},
/*lossless=*/fuzztest::InRange<int>(0, 1),
@@ -144,6 +159,114 @@ static inline auto ArbitraryWebPConfig() {
/*use_sharp_yuv=*/fuzztest::InRange<int>(0, 1));
}
// Like WebPDecoderOptions but with no C array.
// This can be removed once b/294098900 is fixed.
struct WebPDecoderOptionsCpp {
int bypass_filtering;
int no_fancy_upsampling;
int use_cropping;
int crop_left, crop_top;
int crop_width, crop_height;
int use_scaling;
int scaled_width, scaled_height;
int use_threads;
int dithering_strength;
int flip;
int alpha_dithering_strength;
std::array<uint32_t, 5> pad;
};
static inline auto ArbitraryValidWebPDecoderOptions() {
return fuzztest::Map(
[](int bypass_filtering, int no_fancy_upsampling, int use_cropping,
int crop_left, int crop_top, int crop_width, int crop_height,
int use_scaling, int scaled_width, int scaled_height, int use_threads,
int dithering_strength, int flip,
int alpha_dithering_strength) -> WebPDecoderOptionsCpp {
WebPDecoderOptions options;
options.bypass_filtering = bypass_filtering;
options.no_fancy_upsampling = no_fancy_upsampling;
options.use_cropping = use_cropping;
options.crop_left = crop_left;
options.crop_top = crop_top;
options.crop_width = crop_width;
options.crop_height = crop_height;
options.use_scaling = use_scaling;
options.scaled_width = scaled_width;
options.scaled_height = scaled_height;
options.use_threads = use_threads;
options.dithering_strength = dithering_strength;
options.flip = flip;
options.alpha_dithering_strength = alpha_dithering_strength;
WebPDecoderConfig config;
if (!WebPInitDecoderConfig(&config)) assert(false);
config.options = options;
if (!WebPValidateDecoderConfig(&config)) assert(false);
WebPDecoderOptionsCpp options_cpp;
std::memcpy(&options_cpp, &options, sizeof(options));
return options_cpp;
},
/*bypass_filtering=*/fuzztest::InRange<int>(0, 1),
/*no_fancy_upsampling=*/fuzztest::InRange<int>(0, 1),
/*use_cropping=*/fuzztest::InRange<int>(0, 1),
/*crop_left=*/fuzztest::InRange<int>(0, 10),
/*crop_top=*/fuzztest::InRange<int>(0, 10),
/*crop_width=*/fuzztest::InRange<int>(1, 10),
/*crop_height=*/fuzztest::InRange<int>(1, 10),
/*use_scaling=*/fuzztest::InRange<int>(0, 1),
/*scaled_width=*/fuzztest::InRange<int>(1, 10),
/*scaled_height=*/fuzztest::InRange<int>(1, 10),
/*use_threads=*/fuzztest::InRange<int>(0, 1),
/*dithering_strength=*/fuzztest::InRange<int>(0, 100),
/*flip=*/fuzztest::InRange<int>(0, 1),
/*alpha_dithering_strength=*/fuzztest::InRange<int>(0, 100));
}
static inline auto ArbitraryWebPDecoderOptions() {
return fuzztest::Map(
[](int bypass_filtering, int no_fancy_upsampling, int use_cropping,
int crop_left, int crop_top, int crop_width, int crop_height,
int use_scaling, int scaled_width, int scaled_height, int use_threads,
int dithering_strength, int flip,
int alpha_dithering_strength) -> WebPDecoderOptionsCpp {
WebPDecoderOptions options;
options.bypass_filtering = bypass_filtering;
options.no_fancy_upsampling = no_fancy_upsampling;
options.use_cropping = use_cropping;
options.crop_left = crop_left;
options.crop_top = crop_top;
options.crop_width = crop_width;
options.crop_height = crop_height;
options.use_scaling = use_scaling;
options.scaled_width = scaled_width;
options.scaled_height = scaled_height;
options.use_threads = use_threads;
options.dithering_strength = dithering_strength;
options.flip = flip;
options.alpha_dithering_strength = alpha_dithering_strength;
WebPDecoderOptionsCpp options_cpp;
std::memcpy(&options_cpp, &options, sizeof(options));
return options_cpp;
},
/*bypass_filtering=*/fuzztest::Arbitrary<int>(),
/*no_fancy_upsampling=*/fuzztest::Arbitrary<int>(),
/*use_cropping=*/fuzztest::Arbitrary<int>(),
/*crop_left=*/fuzztest::Arbitrary<int>(),
/*crop_top=*/fuzztest::Arbitrary<int>(),
/*crop_width=*/fuzztest::Arbitrary<int>(),
/*crop_height=*/fuzztest::Arbitrary<int>(),
/*use_scaling=*/fuzztest::Arbitrary<int>(),
/*scaled_width=*/fuzztest::Arbitrary<int>(),
/*scaled_height=*/fuzztest::Arbitrary<int>(),
/*use_threads=*/fuzztest::Arbitrary<int>(),
/*dithering_strength=*/fuzztest::Arbitrary<int>(),
/*flip=*/fuzztest::Arbitrary<int>(),
/*alpha_dithering_strength=*/fuzztest::Arbitrary<int>());
}
struct CropOrScaleParams {
bool alter_input;
bool crop_or_scale;