/src/libvpx/examples/vpx_enc_fuzzer.cc
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2025 The WebM project authors. All Rights Reserved. |
3 | | * |
4 | | * Use of this source code is governed by a BSD-style license |
5 | | * that can be found in the LICENSE file in the root of the source |
6 | | * tree. An additional intellectual property rights grant can be found |
7 | | * in the file PATENTS. All contributing project authors may |
8 | | * be found in the AUTHORS file in the root of the source tree. |
9 | | */ |
10 | | |
11 | | /* |
12 | | * Fuzzer for libvpx encoders |
13 | | * ========================== |
14 | | * Requirements |
15 | | * -------------- |
16 | | * Requires Clang 6.0 or above as -fsanitize=fuzzer is used as a linker |
17 | | * option. |
18 | | |
19 | | * Steps to build |
20 | | * -------------- |
21 | | * Clone libvpx repository |
22 | | $git clone https://chromium.googlesource.com/webm/libvpx |
23 | | |
24 | | * Create a directory in parallel to libvpx and change directory |
25 | | $mkdir vpx_enc_fuzzer |
26 | | $cd vpx_enc_fuzzer/ |
27 | | |
28 | | * Enable sanitizers (Supported: address integer memory thread undefined) |
29 | | $source ../libvpx/tools/set_analyzer_env.sh address |
30 | | |
31 | | * Configure libvpx. |
32 | | * Note --size-limit and VPX_MAX_ALLOCABLE_MEMORY are defined to avoid |
33 | | * Out of memory errors when running generated fuzzer binary |
34 | | $../libvpx/configure --disable-unit-tests --size-limit=12288x12288 \ |
35 | | --extra-cflags="-fsanitize=fuzzer-no-link \ |
36 | | -DVPX_MAX_ALLOCABLE_MEMORY=1073741824" \ |
37 | | --disable-webm-io --enable-debug --enable-vp8-encoder \ |
38 | | --enable-vp9-encoder --disable-examples |
39 | | |
40 | | * Build libvpx |
41 | | $make -j32 |
42 | | |
43 | | * Build vp9 fuzzer |
44 | | $ $CXX $CXXFLAGS -std=gnu++17 -Wall -Wextra -Wimplicit-fallthrough \ |
45 | | -DENCODER=vp9 -fsanitize=fuzzer -I../libvpx -I. -Wl,--start-group \ |
46 | | ../libvpx/examples/vpx_enc_fuzzer.cc -o ./vpx_enc_fuzzer_vp9 \ |
47 | | ./libvpx.a -Wl,--end-group |
48 | | |
49 | | * ENCODER should be defined as vp9 or vp8 to enable vp9/vp8 |
50 | | * |
51 | | * create a corpus directory and copy some ivf files there. |
52 | | * Based on which codec (vp8/vp9) is being tested, it is recommended to |
53 | | * have corresponding ivf files in corpus directory |
54 | | * Empty corpus directory also is acceptable, though not recommended |
55 | | $mkdir CORPUS && cp some-files CORPUS |
56 | | |
57 | | * Run fuzzing: |
58 | | $./vpx_enc_fuzzer_vp9 CORPUS |
59 | | |
60 | | * References: |
61 | | * http://llvm.org/docs/LibFuzzer.html |
62 | | * https://github.com/google/oss-fuzz |
63 | | */ |
64 | | |
65 | | #include <assert.h> |
66 | | #include <stddef.h> |
67 | | #include <stdint.h> |
68 | | #include <stdio.h> |
69 | | #include <stdlib.h> |
70 | | #include <string.h> |
71 | | |
72 | | #include "vpx/vp8cx.h" |
73 | | #include "vpx/vpx_encoder.h" |
74 | | #include "vpx_ports/mem_ops.h" |
75 | | #include "third_party/nalloc/nalloc.h" |
76 | | |
77 | | // fuzz header to have config options, before raw image data |
78 | 73.3k | #define FUZZ_HDR_SZ 32 |
79 | | |
80 | 24.4k | #define VPXC_INTERFACE(name) VPXC_INTERFACE_(name) |
81 | 24.4k | #define VPXC_INTERFACE_(name) vpx_codec_##name##_cx() |
82 | | |
83 | 0 | extern "C" void usage_exit(void) { exit(EXIT_FAILURE); } |
84 | | |
85 | 2.25M | static int vpx_img_plane_width(const vpx_image_t *img, int plane) { |
86 | 2.25M | if (plane > 0 && img->x_chroma_shift > 0) |
87 | 1.50M | return (img->d_w + 1) >> img->x_chroma_shift; |
88 | 752k | else |
89 | 752k | return img->d_w; |
90 | 2.25M | } |
91 | | |
92 | 2.25M | static int vpx_img_plane_height(const vpx_image_t *img, int plane) { |
93 | 2.25M | if (plane > 0 && img->y_chroma_shift > 0) |
94 | 1.50M | return (img->d_h + 1) >> img->y_chroma_shift; |
95 | 752k | else |
96 | 752k | return img->d_h; |
97 | 2.25M | } |
98 | | |
99 | | static int fuzz_vpx_img_read(vpx_image_t *img, const uint8_t *data, |
100 | 776k | size_t size) { |
101 | 776k | int plane; |
102 | | // TODO: wtc - Need to clamp the sample values so that they are in range |
103 | | // For example, if the bit depth is 10, the sample values must be <= 1023. |
104 | 776k | assert(img->bit_depth == 8); |
105 | 776k | const size_t bytespp = (img->fmt & VPX_IMG_FMT_HIGHBITDEPTH) ? 2 : 1; |
106 | | |
107 | 776k | if (size == 0) return 0; |
108 | 752k | size_t used = 0; |
109 | 3.01M | for (plane = 0; plane < 3; ++plane) { |
110 | 2.25M | unsigned char *buf = img->planes[plane]; |
111 | 2.25M | const int stride = img->stride[plane]; |
112 | 2.25M | int w = vpx_img_plane_width(img, plane); |
113 | 2.25M | const int h = vpx_img_plane_height(img, plane); |
114 | 2.25M | int y; |
115 | | |
116 | | // Assuming that for nv12 we read all chroma data at once |
117 | 2.25M | if (img->fmt == VPX_IMG_FMT_NV12 && plane > 1) break; |
118 | | // NV12 UV plane is interleaved, so it has twice the width of a subsampled |
119 | | // plane. |
120 | 2.25M | if (img->fmt == VPX_IMG_FMT_NV12 && plane == 1) w = w * 2; |
121 | | |
122 | 31.7M | for (y = 0; y < h; ++y) { |
123 | 29.5M | size_t nb = bytespp * w; |
124 | 29.5M | if (nb > size - used) { |
125 | 852k | nb = size - used; |
126 | 852k | } |
127 | 29.5M | memcpy(buf, data, nb); |
128 | 29.5M | memset(buf + nb, 0, bytespp * w - nb); |
129 | 29.5M | buf += stride; |
130 | 29.5M | data += nb; |
131 | 29.5M | used += nb; |
132 | 29.5M | } |
133 | 2.25M | } |
134 | | |
135 | 752k | return used; |
136 | 776k | } |
137 | | |
138 | | static int encode_frame(vpx_codec_ctx_t *codec, vpx_image_t *img, |
139 | | int frame_index, int flags, FILE *out, |
140 | 792k | vpx_enc_deadline_t quality) { |
141 | 792k | int got_pkts = 0; |
142 | 792k | vpx_codec_iter_t iter = nullptr; |
143 | 792k | const vpx_codec_cx_pkt_t *pkt = nullptr; |
144 | 792k | const vpx_codec_err_t res = |
145 | 792k | vpx_codec_encode(codec, img, frame_index, 1, flags, quality); |
146 | 792k | if (res != VPX_CODEC_OK) return 0; |
147 | | |
148 | 1.53M | while ((pkt = vpx_codec_get_cx_data(codec, &iter)) != nullptr) { |
149 | 747k | got_pkts = 1; |
150 | | |
151 | 747k | if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) { |
152 | 747k | if (fwrite(pkt->data.frame.buf, 1, pkt->data.frame.sz, out) != |
153 | 747k | pkt->data.frame.sz) |
154 | 0 | return 0; |
155 | 747k | } |
156 | 747k | } |
157 | | |
158 | 790k | return got_pkts; |
159 | 790k | } |
160 | | |
161 | 24.4k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { |
162 | 24.4k | if (size <= FUZZ_HDR_SZ) { |
163 | 33 | return 0; |
164 | 33 | } |
165 | 24.4k | nalloc_init(nullptr); |
166 | | |
167 | 24.4k | int keyframe_interval = 0; |
168 | 24.4k | int frame_count = 0; |
169 | 24.4k | vpx_codec_ctx_t codec; |
170 | 24.4k | vpx_image_t raw; |
171 | 24.4k | vpx_codec_enc_cfg_t cfg; |
172 | 24.4k | vpx_enc_deadline_t quality = VPX_DL_GOOD_QUALITY; |
173 | | |
174 | 24.4k | if ((data[0] & 0x80) != 0) { |
175 | 6.16k | keyframe_interval = 8; |
176 | 6.16k | } |
177 | 24.4k | if ((data[0] & 0x40) != 0) { |
178 | 8.70k | quality = VPX_DL_REALTIME; |
179 | 15.7k | } else if ((data[0] & 0x20) != 0) { |
180 | 7.50k | quality = VPX_DL_BEST_QUALITY; |
181 | 7.50k | } |
182 | 24.4k | const int max_frames = (quality == VPX_DL_BEST_QUALITY) ? 150 : 300; |
183 | | |
184 | 24.4k | if (vpx_codec_enc_config_default(VPXC_INTERFACE(ENCODER), &cfg, 0)) abort(); |
185 | 24.4k | FILE *out = fopen("/dev/null", "wb"); |
186 | | |
187 | 24.4k | switch (data[0] & 0x1F) { |
188 | 3.60k | case 0: |
189 | 3.60k | cfg.g_w = 64; |
190 | 3.60k | cfg.g_h = 1; |
191 | 3.60k | break; |
192 | 3.96k | case 1: |
193 | 3.96k | cfg.g_w = 1; |
194 | 3.96k | cfg.g_h = 48; |
195 | 3.96k | break; |
196 | 3.12k | case 2: |
197 | 3.12k | cfg.g_w = 1; |
198 | 3.12k | cfg.g_h = 1; |
199 | 3.12k | break; |
200 | 4.94k | case 3: |
201 | 4.94k | cfg.g_w = 4; |
202 | 4.94k | cfg.g_h = 4; |
203 | 4.94k | break; |
204 | 869 | case 4: |
205 | 869 | cfg.g_w = 16; |
206 | 869 | cfg.g_h = 16; |
207 | 869 | break; |
208 | 7.93k | default: |
209 | 7.93k | cfg.g_w = 64; |
210 | 7.93k | cfg.g_h = 48; |
211 | 7.93k | break; |
212 | 24.4k | } |
213 | 24.4k | cfg.g_timebase.num = 1; |
214 | 24.4k | cfg.g_timebase.den = 30; // fps |
215 | 24.4k | cfg.rc_target_bitrate = 200; |
216 | 24.4k | cfg.g_error_resilient = 1; |
217 | | |
218 | 24.4k | if (vpx_codec_enc_init(&codec, VPXC_INTERFACE(ENCODER), &cfg, 0)) { |
219 | 0 | return 0; |
220 | 0 | } |
221 | | |
222 | 24.4k | if (!vpx_img_alloc(&raw, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1)) { |
223 | 0 | goto fail; |
224 | 0 | } |
225 | | |
226 | 24.4k | nalloc_start(data, size); |
227 | | // We may want to add more config options (for more complex encoders as seen |
228 | | // in the examples) in the future while still maintaining the same format (so |
229 | | // that generated corpus is still valid). So we reserve FUZZ_HDR_SZ=32 bytes |
230 | | // for this even if we just use one byte so far. |
231 | 24.4k | data += FUZZ_HDR_SZ; |
232 | 24.4k | size -= FUZZ_HDR_SZ; |
233 | | |
234 | | // Encode frames. |
235 | 776k | for (int i = 0; i < max_frames; ++i) { |
236 | 776k | int flags = 0; |
237 | 776k | size_t size_read = fuzz_vpx_img_read(&raw, data, size); |
238 | 776k | if (size_read == 0) break; |
239 | 752k | data += size_read; |
240 | 752k | size -= size_read; |
241 | 752k | if (keyframe_interval > 0 && frame_count % keyframe_interval == 0) |
242 | 29.0k | flags |= VPX_EFLAG_FORCE_KF; |
243 | 752k | encode_frame(&codec, &raw, frame_count++, flags, out, quality); |
244 | 752k | } |
245 | | |
246 | | // Flush encoder. |
247 | 39.6k | while (encode_frame(&codec, nullptr, -1, 0, out, quality)) { |
248 | 15.2k | } |
249 | | |
250 | 24.4k | fail: |
251 | 24.4k | nalloc_end(); |
252 | 24.4k | vpx_img_free(&raw); |
253 | 24.4k | vpx_codec_destroy(&codec); |
254 | 24.4k | fclose(out); |
255 | 24.4k | return 0; |
256 | 24.4k | } |