diff --git a/unit_test/planar_test.cc b/unit_test/planar_test.cc index 2e26b4cf..bc3b45b9 100644 --- a/unit_test/planar_test.cc +++ b/unit_test/planar_test.cc @@ -12,6 +12,11 @@ #include #include +#if defined(__linux__) +#include +#include +#endif + #include "../unit_test/unit_test.h" #include "libyuv/compare.h" #include "libyuv/convert.h" @@ -37,6 +42,77 @@ namespace libyuv { +#if defined(__linux__) && defined(__x86_64__) +// POC: CopyPlane row-coalescing path performs `width *= height` on signed int +// without overflow checking. With width=stride=131072 and height=16384 (which +// matches the GPU-process call site at copy_shared_image_helper.cc:690 for a +// 16384x16384 RGBA_F16 readback), the product is 2^31 which wraps to INT_MIN. +// On ERMS-capable x86_64, the negative width is sign-extended to ~2^63 in rcx +// for `rep movsb`, producing an unbounded copy past correctly-sized 2 GiB +// source and destination buffers. +// +// This test allocates two 2 GiB buffers (matching the API contract for the +// given width/height/stride), each followed by a PROT_NONE guard page. A +// correct CopyPlane copies exactly 2 GiB and never touches the guard. The +// buggy CopyPlane runs `rep movsb` with rcx = 0xFFFFFFFF80000000 and faults +// at the first guard page it reaches. +TEST_F(LibYUVPlanarTest, CopyPlane_CoalesceOverflow_ERMS) { + // Only meaningful on ERMS hardware (Ivy-Bridge+ Intel, Zen3+ AMD); other + // x86 row kernels self-terminate on negative widths. + if (!TestCpuFlag(kCpuHasERMS)) { + printf("ERMS not detected; CopyRow_ERMS not selected. Skipping.\n"); + GTEST_SKIP(); + } + + const long page = sysconf(_SC_PAGESIZE); + // 16384 px * 8 bytes/px (RGBA_F16) = 131072 bytes/row, 16384 rows. + // These are the exact values that reach libyuv::CopyPlane from + // gpu::CopySharedImageHelper::ReadPixels on the Graphite backend when a + // compromised renderer issues ReadbackARGBImagePixelsINTERNAL with + // dst=16384x16384, color_type=kRGBA_F16, row_bytes=131072. + const int kRowBytes = 131072; + const int kHeight = 16384; + const size_t kPlaneBytes = (size_t)kRowBytes * (size_t)kHeight; // 2^31 + const size_t kMapBytes = kPlaneBytes + (size_t)page; + + uint8_t* src = (uint8_t*)mmap(nullptr, kMapBytes, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(src, MAP_FAILED) << "mmap src 2 GiB failed"; + ASSERT_EQ(0, mprotect(src + kPlaneBytes, page, PROT_NONE)); + + uint8_t* dst = (uint8_t*)mmap(nullptr, kMapBytes, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(dst, MAP_FAILED) << "mmap dst 2 GiB failed"; + ASSERT_EQ(0, mprotect(dst + kPlaneBytes, page, PROT_NONE)); + + // Touch the last in-bounds byte of each to fault the pages in and prove the + // 2 GiB region itself is accessible (so any crash below is past-end). + src[kPlaneBytes - 1] = 0xAB; + dst[kPlaneBytes - 1] = 0xCD; + + printf( + "Calling CopyPlane(src, %d, dst, %d, width=%d, height=%d)\n" + " src guard page at %p\n" + " dst guard page at %p\n" + " expected: copy exactly %zu bytes and return\n" + " bug: width*=height overflows to INT_MIN, CopyRow_ERMS\n" + " sign-extends to rcx=0xFFFFFFFF80000000, rep movsb runs OOB\n", + kRowBytes, kRowBytes, kRowBytes, kHeight, (void*)(src + kPlaneBytes), + (void*)(dst + kPlaneBytes), kPlaneBytes); + fflush(stdout); + + // src_stride == dst_stride == width → coalesce branch fires. + // width *= height = 131072 * 16384 = 2147483648 → INT_MIN on 32-bit int. + CopyPlane(src, kRowBytes, dst, kRowBytes, kRowBytes, kHeight); + + // Not reached when the bug is present. + printf("CopyPlane returned without crashing (bug fixed).\n"); + EXPECT_EQ(0xAB, dst[kPlaneBytes - 1]); + munmap(src, kMapBytes); + munmap(dst, kMapBytes); +} +#endif // __linux__ && __x86_64__ + TEST_F(LibYUVPlanarTest, TestAttenuate) { const int kSize = 1280 * 4; align_buffer_page_end(orig_pixels, kSize);