diff --git a/media/video/mappable_shared_image_video_frame_pool_unittest.cc b/media/video/mappable_shared_image_video_frame_pool_unittest.cc index 23d8f4e0fef97..bd6a2ae396b44 100644 --- a/media/video/mappable_shared_image_video_frame_pool_unittest.cc +++ b/media/video/mappable_shared_image_video_frame_pool_unittest.cc @@ -21,6 +21,12 @@ #include "media/base/video_frame.h" #include "media/video/mock_gpu_video_accelerator_factories.h" #include "testing/gmock/include/gmock/gmock.h" +#include "third_party/libyuv/include/libyuv/convert.h" +#include "third_party/libyuv/include/libyuv/cpu_id.h" + +#include +#include +#include using ::testing::_; using ::testing::AtLeast; @@ -1117,4 +1123,120 @@ TEST_F(MappableSharedImageVideoFramePoolTest, frame->shared_image()->color_space()); } +// ----------------------------------------------------------------------------- +// Regression tests: libyuv::I010ToNV12 dispatches Convert16To8Row_AVX512BW +// keyed on the full luma `width`, but then invokes it on the chroma planes +// with `halfwidth` into an internal heap temp padded only to a 32-byte +// multiple. When width % 128 == 64 (e.g. width=192 -> halfwidth=96), the bare +// 64-byte-stride AVX-512 kernel is selected and over-writes the temp buffer by +// 32 bytes. This is the exact call CopyRowsToNV12Buffer() makes for a +// software-decoded PIXEL_FORMAT_YUV420P10 frame. +// +// The kernel is hand-written inline asm so AddressSanitizer's compile-time +// instrumentation does not flag the OOB stores. The two tests below instead +// place buffers against PROT_NONE guard pages so the over-read / over-write +// is observed as a hard SIGSEGV with a stack trace through I010ToNV12. +// ----------------------------------------------------------------------------- + +namespace { + +// Map two adjacent pages and revoke access to the second one. Returns the +// base of the first (RW) page; |*guard| is the start of the PROT_NONE page. +uint8_t* MapPageWithGuard(uint8_t** guard) { + const long ps = sysconf(_SC_PAGESIZE); + void* base = mmap(nullptr, ps * 2, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + CHECK_NE(base, MAP_FAILED); + uint8_t* g = UNSAFE_TODO(static_cast(base) + ps); + CHECK_EQ(0, mprotect(g, ps, PROT_NONE)); + *guard = g; + return static_cast(base); +} + +} // namespace + +// Test 1: prove that I010ToNV12 selects the *bare* 64-byte AVX512BW kernel +// for the chroma rows when width=192 and calls it with halfwidth=96. The +// kernel reads two 64-byte zmm loads per iteration (128 bytes) and runs two +// iterations for width=96, i.e. it reads 256 bytes from a 192-byte src_v +// plane. We place src_v 192 bytes before a guard page; the 64-byte over-read +// faults. (If the safe Convert16To8Row_Any_AVX512BW wrapper had been +// selected, it would copy the 32-element tail through a stack bounce buffer +// and would *not* fault.) +TEST(I010ToNV12HeapOverflowTest, BareAVX512KernelDispatchedForChroma) { + if (!libyuv::TestCpuFlag(libyuv::kCpuHasAVX512BW)) { + GTEST_SKIP() << "CPU does not support AVX-512BW."; + } + + constexpr int kWidth = 192; // 64-aligned -> selects bare kernel + constexpr int kHeight = 2; // halfheight = 1 chroma row + constexpr int kHalfWidth = 96; // NOT 64-aligned + + uint8_t* guard = nullptr; + uint8_t* page = MapPageWithGuard(&guard); + // src_v occupies the last 96 uint16_t (192 bytes) before the guard page. + uint16_t* src_v = + reinterpret_cast(UNSAFE_TODO(guard - kHalfWidth * 2)); + for (int i = 0; i < kHalfWidth; ++i) UNSAFE_TODO(src_v[i]) = 0x03FF; + + // Everything else is generously oversized so only the src_v over-read can + // fault (proving it is the chroma dispatch that is wrong). + std::vector src_y(kWidth * kHeight + 256, 0x03FF); + std::vector src_u(kHalfWidth + 256, 0x03FF); + std::vector dst_uv(kWidth + 256, 0); + + // dst_y = nullptr skips Y-plane conversion (it has its own dispatch). + fprintf(stderr, + "[POC] Calling I010ToNV12(width=%d). Bare AVX512BW kernel will read " + "src_v[0..256) but only [0..192) is mapped -> expect SIGSEGV at %p\n", + kWidth, static_cast(guard)); + libyuv::I010ToNV12(src_y.data(), kWidth, src_u.data(), kHalfWidth, src_v, + kHalfWidth, /*dst_y=*/nullptr, 0, dst_uv.data(), kWidth, + kWidth, kHeight); + munmap(page, sysconf(_SC_PAGESIZE) * 2); + FAIL() << "Expected SIGSEGV in Convert16To8Row_AVX512BW (over-read) — bare " + "kernel was NOT dispatched (bug fixed or CPU mis-detected)."; +} + +// Test 2: prove the heap-buffer-overflow WRITE. I010ToNV12 lays out its +// internal temp as: +// align_buffer_64(row_u, ((halfwidth+31)&~31) * 2); // = 192 bytes +// row_v = row_u + ((halfwidth+31)&~31); // = row_u + 96 +// and then calls Convert16To8Row(src_v, row_v, scale, halfwidth=96). We +// reproduce that exact layout against a guard page and call I010ToNV12's +// dispatched kernel directly: the second 32-byte ymm store of the second +// iteration writes row_u[192..224), i.e. 32 bytes past the temp buffer. +extern "C" void Convert16To8Row_AVX512BW(const uint16_t* src_y, + uint8_t* dst_y, + int scale, + int width); + +TEST(I010ToNV12HeapOverflowTest, RowVWriteOverflows192ByteTemp) { + if (!libyuv::TestCpuFlag(libyuv::kCpuHasAVX512BW)) { + GTEST_SKIP() << "CPU does not support AVX-512BW."; + } + + constexpr int kHalfWidth = 96; + constexpr int kPad32 = (kHalfWidth + 31) & ~31; // = 96 + constexpr int kTempSize = kPad32 * 2; // = 192 (matches convert.cc) + + uint8_t* guard = nullptr; + uint8_t* page = MapPageWithGuard(&guard); + // Mirror I010ToNV12's row_u/row_v layout, ending exactly at the guard page. + uint8_t* row_u = UNSAFE_TODO(guard - kTempSize); + uint8_t* row_v = UNSAFE_TODO(row_u + kPad32); + ASSERT_EQ(UNSAFE_TODO(row_u + kTempSize), guard); + + std::vector src_v(kHalfWidth + 256, 0x03FF); + + fprintf(stderr, + "[POC] Convert16To8Row_AVX512BW(halfwidth=%d) into row_v at row_u+%d " + "of a %d-byte temp. Kernel writes 128 bytes -> row_u[192..224) = " + "32-byte OOB write -> expect SIGSEGV at %p\n", + kHalfWidth, kPad32, kTempSize, static_cast(guard)); + Convert16To8Row_AVX512BW(src_v.data(), row_v, /*scale=*/16385, kHalfWidth); + munmap(page, sysconf(_SC_PAGESIZE) * 2); + FAIL() << "Expected SIGSEGV (32-byte over-write) — kernel did not overrun."; +} + } // namespace media