blob: 4b99663a1a69ee87cab5dda859b36cef0b7a46a6 [file]
/*
* Copyright (c) 2025 The WebM project authors. All Rights Reserved.
*
* 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.
*/
/*
* Fuzzer for libvpx encoders
* ==========================
* Requirements
* --------------
* Requires Clang 6.0 or above as -fsanitize=fuzzer is used as a linker
* option.
* Steps to build
* --------------
* Clone libvpx repository
$git clone https://chromium.googlesource.com/webm/libvpx
* Create a directory in parallel to libvpx and change directory
$mkdir vpx_enc_fuzzer
$cd vpx_enc_fuzzer/
* Enable sanitizers (Supported: address integer memory thread undefined)
$source ../libvpx/tools/set_analyzer_env.sh address
* Configure libvpx.
* Note --size-limit and VPX_MAX_ALLOCABLE_MEMORY are defined to avoid
* Out of memory errors when running generated fuzzer binary
$../libvpx/configure --disable-unit-tests --size-limit=12288x12288 \
--extra-cflags="-fsanitize=fuzzer-no-link \
-DVPX_MAX_ALLOCABLE_MEMORY=1073741824" \
--disable-webm-io --enable-debug --enable-vp8-encoder \
--enable-vp9-encoder --disable-examples
* Build libvpx
$make -j32
* Build vp9 fuzzer
$ $CXX $CXXFLAGS -std=gnu++17 -DENCODER=vp9 \
-fsanitize=fuzzer -I../libvpx -I. -Wl,--start-group \
../libvpx/examples/vpx_enc_fuzzer.cc -o ./vpx_enc_fuzzer_vp9 \
./libvpx.a -Wl,--end-group
* ENCODER should be defined as vp9 or vp8 to enable vp9/vp8
*
* create a corpus directory and copy some ivf files there.
* Based on which codec (vp8/vp9) is being tested, it is recommended to
* have corresponding ivf files in corpus directory
* Empty corpus directory also is acceptable, though not recommended
$mkdir CORPUS && cp some-files CORPUS
* Run fuzzing:
$./vpx_enc_fuzzer_vp9 CORPUS
* References:
* http://llvm.org/docs/LibFuzzer.html
* https://github.com/google/oss-fuzz
*/
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "vpx/vp8cx.h"
#include "vpx/vpx_encoder.h"
#include "vpx_ports/mem_ops.h"
#include "third_party/nalloc/nalloc.h"
// fuzz header to have config options, before raw image data
#define FUZZ_HDR_SZ 32
#define VPXC_INTERFACE(name) VPXC_INTERFACE_(name)
#define VPXC_INTERFACE_(name) vpx_codec_##name##_cx()
extern "C" void usage_exit(void) { exit(EXIT_FAILURE); }
static int vpx_img_plane_width(const vpx_image_t *img, int plane) {
if (plane > 0 && img->x_chroma_shift > 0)
return (img->d_w + 1) >> img->x_chroma_shift;
else
return img->d_w;
}
static int vpx_img_plane_height(const vpx_image_t *img, int plane) {
if (plane > 0 && img->y_chroma_shift > 0)
return (img->d_h + 1) >> img->y_chroma_shift;
else
return img->d_h;
}
static int fuzz_vpx_img_read(vpx_image_t *img, const uint8_t *data,
size_t size) {
int plane;
// TODO: wtc - Need to clamp the sample values so that they are in range
// For example, if the bit depth is 10, the sample values must be <= 1023.
assert(img->bit_depth == 8);
const size_t bytespp = (img->fmt & VPX_IMG_FMT_HIGHBITDEPTH) ? 2 : 1;
if (size == 0) return 0;
size_t used = 0;
for (plane = 0; plane < 3; ++plane) {
unsigned char *buf = img->planes[plane];
const int stride = img->stride[plane];
int w = vpx_img_plane_width(img, plane);
const int h = vpx_img_plane_height(img, plane);
int y;
// Assuming that for nv12 we read all chroma data at once
if (img->fmt == VPX_IMG_FMT_NV12 && plane > 1) break;
// Fixing NV12 chroma width if it is odd
if (img->fmt == VPX_IMG_FMT_NV12 && plane == 1) w = (w + 1) & ~1;
for (y = 0; y < h; ++y) {
size_t nb = bytespp * w;
if (nb > size - used) {
nb = size - used;
}
memcpy(buf, data, nb);
memset(buf + nb, 0, bytespp * w - nb);
buf += stride;
data += nb;
used += nb;
}
}
return used;
}
static int encode_frame(vpx_codec_ctx_t *codec, vpx_image_t *img,
int frame_index, int flags, FILE *out,
vpx_enc_deadline_t quality) {
int got_pkts = 0;
vpx_codec_iter_t iter = NULL;
const vpx_codec_cx_pkt_t *pkt = NULL;
const vpx_codec_err_t res =
vpx_codec_encode(codec, img, frame_index, 1, flags, quality);
if (res != VPX_CODEC_OK) return 0;
while ((pkt = vpx_codec_get_cx_data(codec, &iter)) != NULL) {
got_pkts = 1;
if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) {
if (fwrite(pkt->data.frame.buf, 1, pkt->data.frame.sz, out) !=
pkt->data.frame.sz)
return 0;
}
}
return got_pkts;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size <= FUZZ_HDR_SZ) {
return 0;
}
nalloc_init(nullptr);
int keyframe_interval = 0;
int frame_count = 0;
vpx_codec_ctx_t codec;
vpx_image_t raw;
vpx_codec_enc_cfg_t cfg;
vpx_enc_deadline_t quality = VPX_DL_GOOD_QUALITY;
if ((data[0] & 0x80) != 0) {
keyframe_interval = 8;
}
if ((data[0] & 0x40) != 0) {
quality = VPX_DL_REALTIME;
} else if ((data[0] & 0x20) != 0) {
quality = VPX_DL_BEST_QUALITY;
}
if (vpx_codec_enc_config_default(VPXC_INTERFACE(ENCODER), &cfg, 0)) abort();
FILE *out = fopen("/dev/null", "wb");
switch (data[0] & 0x1F) {
case 0: cfg.g_w = 64; cfg.g_h = 1;
case 1: cfg.g_w = 1; cfg.g_h = 48;
case 2: cfg.g_w = 1; cfg.g_h = 1;
case 3: cfg.g_w = 4; cfg.g_h = 4;
case 4: cfg.g_w = 16; cfg.g_h = 16;
default: cfg.g_w = 64; cfg.g_h = 48;
}
cfg.g_timebase.num = 1;
cfg.g_timebase.den = 30; // fps
cfg.rc_target_bitrate = 200;
cfg.g_error_resilient = 1;
if (vpx_codec_enc_init(&codec, VPXC_INTERFACE(ENCODER), &cfg, 0)) {
return 0;
}
if (!vpx_img_alloc(&raw, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1)) {
goto fail;
}
nalloc_start(data, size);
// We may want to add more config options (for more complex encoders as seen
// in the examples) in the future while still maintaining the same format (so
// that generated corpus is still valid). So we reserve FUZZ_HDR_SZ=32 bytes
// for this even if we just use one byte so far.
data += FUZZ_HDR_SZ;
size -= FUZZ_HDR_SZ;
// Encode frames.
while (1) {
int flags = 0;
size_t size_read = fuzz_vpx_img_read(&raw, data, size);
if (size_read == 0) break;
data += size_read;
size -= size_read;
if (keyframe_interval > 0 && frame_count % keyframe_interval == 0)
flags |= VPX_EFLAG_FORCE_KF;
encode_frame(&codec, &raw, frame_count++, flags, out, quality);
}
// Flush encoder.
while (encode_frame(&codec, NULL, -1, 0, out, quality)) {
}
fail:
nalloc_end();
vpx_img_free(&raw);
vpx_codec_destroy(&codec);
fclose(out);
return 0;
}