CVE-2026-65391
Overview
Background
- I420 / I010 video buffers
- Planar YUV 4:2:0 formats. I420 stores 8-bit samples; I010 stores 10-bit samples in 16-bit words. Chroma (U/V) planes are sub-sampled to
ceil(width/2) × ceil(height/2). - NV12 / P010 output
- Semi-planar formats (
kCVPixelFormatType_420YpCbCr8/10BiPlanarFullRange) where U and V are interleaved in one plane. Each interleaved UV row holds two samples per chroma column, so its width in samples is2 * ceil(width/2), which for oddwidthiswidth + 1. - `SharedVideoFrameInfo`
- WebCore descriptor (
Source/WebCore/platform/cocoa/SharedVideoFrameInfo.mm) carrying plane dimensions/strides used to size a shared-memory buffer (storageSize()) and to drive the YUV→semi-planar copy. Built byfromVideoFrameBuffer(). - `copyVideoFrameBuffer` (libwebrtc)
WebKitUtilities.mmroutine that runslibyuv::I420ToNV12/I010ToP010, writing the interleaved UV plane into the destination buffer atdataUV = data + width*heightwith a given UV stride.
Root Cause Analysis
Both the buffer-sizing path (SharedVideoFrameInfo::fromVideoFrameBuffer) and the conversion path (copyVideoFrameBuffer) used strideUV = width and chroma dimensions width/2, height/2 (floor division). That is only correct for even dimensions. For an odd width, the interleaved NV12/P010 UV plane needs a stride of width + 1 samples per row (because 2 * ceil(width/2) = width + 1), and the chroma plane has ceil(width/2) columns and ceil(height/2) rows — not the truncated width/2, height/2. With the old math, SharedVideoFrameInfo computed a storageSize() based on the too-small UV stride and floored chroma dimensions, while libyuv::I420ToNV12/I010ToP010 wrote UV rows that are actually width + 1 samples wide and ceil(height/2) rows tall. The writer therefore ran past the end of the undersized destination buffer: a heap out-of-bounds write, the memory corruption the advisory reports.
The patch rounds everything up consistently. copyVideoFrameBuffer now sets strideUV = (width & 1) ? width + 1 : width for both I420 and I010. SharedVideoFrameInfo.mm introduces computeStrideY<byteType> (sizeof(byteType) * width), computeStrideUV<byteType> (sizeof(byteType) * (width & 1 ? width + 1 : width)), computeWidthUV ((width + 1) / 2) and computeHeightUV ((height + 1) / 2), and fromVideoFrameBuffer builds the I420/I010 descriptors from these helpers so that storageSize() and the strides agree with what the converter writes, including the extra padding column for odd widths. The new SharedVideoFrame round-trip tests with odd widths/heights (681×1280, 680×15, 681×15 for both I420 and I010) plus a 64-byte guard region that must stay 0xCD directly assert no write past the computed buffer.
strideUV = width, width/2), but interleaved NV12/P010 needs width + 1 stride and ceil dimensions for odd sizes — so an odd-dimension frame made the converter write past the undersized shared buffer.Attack Path
- Supply an odd-dimension frame
Attacker drives a WebRTC/media path that produces an I420 or I010
VideoFrameBufferwith odd width (and/or odd height). (inferred: exact web API surface delivering an attacker-chosen odd-size frame is not shown by the diff, but odd dimensions are the trigger.) - Size the shared buffer
SharedVideoFrameInfo::fromVideoFrameBuffercomputesstorageSize()usingstrideUV = widthand floored chroma dims — too small for the real interleaved UV plane. - Convert into the buffer
copyVideoFrameBufferrunslibyuv::I420ToNV12/I010ToP010, writing UV rows that arewidth + 1samples wide into the undersized destination. - Out-of-bounds write The converter writes past the end of the allocated shared-memory buffer (heap OOB write), corrupting adjacent memory.
- Memory corruption Controlled frame contents land in out-of-bounds bytes; escalation to a stronger primitive is inferred, not shown by the diff.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifSource/WebCore/platform/cocoa/SharedVideoFrameInfo.mm |
modified | |
forTools/TestWebKitAPI/Tests/WebCore/cocoa/SharedVideoFrame.mm |
modified |
Files Changed
Source/ThirdParty/libwebrtc/Configurations/libwebrtc.expSource/ThirdParty/libwebrtc/Configurations/libwebrtc.xcconfigSource/ThirdParty/libwebrtc/Source/webrtc/webkit_sdk/WebKit/WebKitUtilities.mmSource/WebCore/platform/cocoa/SharedVideoFrameInfo.mmTools/TestWebKitAPI/Configurations/Base.xcconfigTools/TestWebKitAPI/Configurations/TestWebKitAPIBase.xcconfigTools/TestWebKitAPI/Tests/WebCore/cocoa/SharedVideoFrame.mm
Audit Directions
- Floor vs ceil on sub-sampled planesAudit every YUV/chroma dimension or stride computed with
/ 2or= width; chroma planes and interleaved formats needceiland odd-width stride padding. - storageSize vs writer agreementReview all
SharedVideoFrameInfo/shared-buffer consumers to confirm the size used for allocation exactly matches the strides/dimensions the copy routine writes. - Other pixel formatsCheck P010/NV12/NV21 and 10/12-bit paths elsewhere (ImageBuffer, CoreVideo pixel-buffer creation) for the same odd-dimension stride assumption.
- Attacker-controlled frame dimensionsTrace which web APIs (WebRTC, WebCodecs, canvas captureStream) can deliver odd-dimension frames into this conversion.
Patch
diff --git a/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp b/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp
index 8b1c8e0f22e8..1abac82e44bb 100644
--- a/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp
+++ b/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.exp
@@ -1,6 +1,8 @@
+__ZN6webrtc10I010Buffer6CreateEii
__ZN6webrtc10I420Buffer12MutableDataUEv
__ZN6webrtc10I420Buffer12MutableDataVEv
__ZN6webrtc10I420Buffer12MutableDataYEv
+__ZN6webrtc10I420Buffer6CreateEii
__ZN6webrtc10I420Buffer6RotateERKNS_19I420BufferInterfaceENS_13VideoRotationE
__ZN6webrtc10I420Buffer8SetBlackEPS0_
__ZN6webrtc10VideoFrameD1Ev
@@ -426,3 +428,6 @@ __ZNK6webrtc18VideoFrameMetadata15GetDependenciesEv
__ZNK6webrtc8RtpCodec9mime_typeEv
__ZN6webrtc14SdpVideoFormatC1ENSt3__117basic_string_viewIcNS1_11char_traitsIcEEEERKNS_17CodecParameterMapE
__ZN4absl19ThrowStdLengthErrorEPKc
+__ZN6webrtc10I010Buffer12MutableDataUEv
+__ZN6webrtc10I010Buffer12MutableDataVEv
+__ZN6webrtc10I010Buffer12MutableDataYEv
diff --git a/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.xcconfig b/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.xcconfig
index 364eae747465..417e25c009a7 100644
--- a/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.xcconfig
+++ b/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.xcconfig
@@ -90,6 +90,6 @@ OTHER_LDFLAGS = $(inherited) $(SOURCE_VERSION_LDFLAGS) $(WEBRTC_LDFLAGS_ENABLE_L
// Allow fuzzers to link to libwebrtc.dylib.
WEBRTC_ALLOWABLE_CLIENTS = $(WEBRTC_ALLOWABLE_CLIENTS_$(WK_NOT_$(ENABLE_LIBFUZZER)));
-WEBRTC_ALLOWABLE_CLIENTS_YES = -allowable_client WebCore -allowable_client WebCoreTestSupport -allowable_client WebKit;
+WEBRTC_ALLOWABLE_CLIENTS_YES = -allowable_client WebCore -allowable_client WebCoreTestSupport -allowable_client WebKit -allowable_client TestWebKitAPI;
WARNING_CFLAGS = $(inherited) -Wno-nullability-completeness;
diff --git a/Source/ThirdParty/libwebrtc/Source/webrtc/webkit_sdk/WebKit/WebKitUtilities.mm b/Source/ThirdParty/libwebrtc/Source/webrtc/webkit_sdk/WebKit/WebKitUtilities.mm
index f5631db00b03..d5342a5ad11b 100644
--- a/Source/ThirdParty/libwebrtc/Source/webrtc/webkit_sdk/WebKit/WebKitUtilities.mm
+++ b/Source/ThirdParty/libwebrtc/Source/webrtc/webkit_sdk/WebKit/WebKitUtilities.mm
@@ -436,7 +436,7 @@ bool copyVideoFrameBuffer(VideoFrameBuffer& buffer, uint8_t* data)
auto* i420Frame = buffer.GetI420();
auto* dataY = data;
auto strideY = i420Frame->width();
- auto strideUV = i420Frame->width();
+ auto strideUV = i420Frame->width() & 1 ? i420Frame->width() + 1 : i420Frame->width();
auto* dataUV = data + (i420Frame->width() * i420Frame->height());
return !libyuv::I420ToNV12(i420Frame->DataY(), i420Frame->StrideY(),
i420Frame->DataU(), i420Frame->StrideU(),
@@ -448,7 +448,7 @@ bool copyVideoFrameBuffer(VideoFrameBuffer& buffer, uint8_t* data)
auto* i010Frame = buffer.GetI010();
auto* dataY = reinterpret_cast<uint16_t*>(data);
auto strideY = i010Frame->width();
- auto strideUV = i010Frame->width();
+ auto strideUV = i010Frame->width() & 1 ? i010Frame->width() + 1 : i010Frame->width();
auto* dataUV = dataY + (i010Frame->width() * i010Frame->height());
return !libyuv::I010ToP010(i010Frame->DataY(), i010Frame->StrideY(),
i010Frame->DataU(), i010Frame->StrideU(),
diff --git a/Source/WebCore/platform/cocoa/SharedVideoFrameInfo.mm b/Source/WebCore/platform/cocoa/SharedVideoFrameInfo.mm
index a7ff7a820a30..15d0433ec029 100644
--- a/Source/WebCore/platform/cocoa/SharedVideoFrameInfo.mm
+++ b/Source/WebCore/platform/cocoa/SharedVideoFrameInfo.mm
@@ -281,23 +281,53 @@ RetainPtr<CVPixelBufferPoolRef> SharedVideoFrameInfo::createCompatibleBufferPool
}
#if USE(LIBWEBRTC)
+template<typename byteType>
+uint32_t computeStrideY(const webrtc::VideoFrameBuffer& frame)
+{
+ auto width = static_cast<uint32_t>(frame.width());
+ return sizeof(byteType) * width;
+}
+
+template<typename byteType>
+uint32_t computeStrideUV(const webrtc::VideoFrameBuffer& frame)
+{
+ auto width = static_cast<uint32_t>(frame.width());
+ return sizeof(byteType) * (width & 1 ? width + 1 : width);
+}
+
+static uint32_t computeWidthUV(const webrtc::VideoFrameBuffer& frame)
+{
+ auto width = static_cast<uint32_t>(frame.width());
+ return (width + 1) / 2;
+}
+
+static uint32_t computeHeightUV(const webrtc::VideoFrameBuffer& frame)
+{
+ auto height = static_cast<uint32_t>(frame.height());
+ return (height + 1) / 2;
+}
+
SharedVideoFrameInfo SharedVideoFrameInfo::fromVideoFrameBuffer(const webrtc::VideoFrameBuffer& frame)
{
if (frame.type() == webrtc::VideoFrameBuffer::Type::kNative)
return SharedVideoFrameInfo { };
auto type = frame.type();
- if (type == webrtc::VideoFrameBuffer::Type::kI420)
- return SharedVideoFrameInfo { kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
- static_cast<uint32_t>(frame.width()), static_cast<uint32_t>(frame.height()), static_cast<uint32_t>(frame.width()),
- static_cast<uint32_t>(frame.width()) / 2, static_cast<uint32_t>(frame.height()) / 2, static_cast<uint32_t>(frame.width()) };
-
- if (type == webrtc::VideoFrameBuffer::Type::kI010)
- return SharedVideoFrameInfo { kCVPixelFormatType_420YpCbCr10BiPlanarFullRange,
- static_cast<uint32_t>(frame.width()), static_cast<uint32_t>(frame.height()), static_cast<uint32_t>(frame.width() * 2),
- static_cast<uint32_t>(frame.width()) / 2, static_cast<uint32_t>(frame.height()) / 2, static_cast<uint32_t>(frame.width()) * 2 };
-
- return SharedVideoFrameInfo { };
+ if (type == webrtc::VideoFrameBuffer::Type::kI420) {
+ return {
+ kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
+ static_cast<uint32_t>(frame.width()), static_cast<uint32_t>(frame.height()), computeStrideY<uint8_t>(frame),
+ computeWidthUV(frame), computeHeightUV(frame), computeStrideUV<uint8_t>(frame)
+ };
+ }
+ if (type == webrtc::VideoFrameBuffer::Type::kI010) {
+ return {
+ kCVPixelFormatType_420YpCbCr10BiPlanarFullRange,
+ static_cast<uint32_t>(frame.width()), static_cast<uint32_t>(frame.height()), computeStrideY<uint16_t>(frame),
+ computeWidthUV(frame), computeHeightUV(frame), computeStrideUV<uint16_t>(frame)
+ };
+ }
+ return { };
}
bool SharedVideoFrameInfo::writeVideoFrameBuffer(webrtc::VideoFrameBuffer& frameBuffer, std::span<uint8_t> data)
diff --git a/Tools/TestWebKitAPI/Configurations/Base.xcconfig b/Tools/TestWebKitAPI/Configurations/Base.xcconfig
index fa2529f333e7..40779526a7bc 100644
--- a/Tools/TestWebKitAPI/Configurations/Base.xcconfig
+++ b/Tools/TestWebKitAPI/Configurations/Base.xcconfig
@@ -48,10 +48,10 @@ PROJECT_HEADER_SEARCH_PATHS_cocoatouch = $(inherited) $(SRCROOT)/../../Source/We
SWIFT_INCLUDE_PATHS = $(inherited) $(SWIFT_INCLUDE_PATHS_$(USE_INTERNAL_SDK));
SWIFT_INCLUDE_PATHS_ = $(SRCROOT)/../../Source/WebKit/Platform/spi/Cocoa;
-HEADER_SEARCH_PATHS = $(ALTERNATE_HEADER_SEARCH_PATHS) ${BUILT_PRODUCTS_DIR}/usr/local/include $(WEBCORE_PRIVATE_HEADERS_DIR)/ForwardingHeaders $(BUILT_PRODUCTS_DIR)/WebKitTestSupport $(WEBKIT_TESTSUPPORT_INSTALLED_DIR) ${SRCROOT} $(PROJECT_HEADER_SEARCH_PATHS);
+HEADER_SEARCH_PATHS = $(ALTERNATE_HEADER_SEARCH_PATHS) ${BUILT_PRODUCTS_DIR}/usr/local/include $(LIBWEBRTC_HEADER_SEARCH_PATHS) $(WEBCORE_PRIVATE_HEADERS_DIR)/ForwardingHeaders $(BUILT_PRODUCTS_DIR)/WebKitTestSupport $(WEBKIT_TESTSUPPORT_INSTALLED_DIR) ${SRCROOT} $(PROJECT_HEADER_SEARCH_PATHS);
WEBKIT_TESTSUPPORT_INSTALLED_DIR[config=Production] = $(SDK_DIR)$(WK_ALTERNATE_WEBKIT_SDK_PATH)$(WK_LIBRARY_HEADERS_FOLDER_PATH)/WebKitTestSupport;
-LIBRARY_SEARCH_PATHS = $(SDK_DIR)$(WK_ALTERNATE_WEBKIT_SDK_PATH)$(WK_LIBRARY_INSTALL_PATH) $(inherited);
+LIBRARY_SEARCH_PATHS = $(SDK_DIR)$(WK_ALTERNATE_WEBKIT_SDK_PATH)$(WK_LIBRARY_INSTALL_PATH) "$(SDK_DIR)$(WEBCORE_LIBRARY_DIR)" $(inherited);
SYSTEM_HEADER_SEARCH_PATHS = $(inherited) $(WK_PRIVATE_SDK_DIR)$(WK_ALTERNATE_WEBKIT_SDK_PATH)$(WK_LIBRARY_HEADERS_FOLDER_PATH);
SYSTEM_FRAMEWORK_SEARCH_PATHS = $(inherited) $(SYSTEM_FRAMEWORK_SEARCH_PATHS_$(WK_USE_ALTERNATE_FRAMEWORKS_DIR)) $(SYSTEM_FRAMEWORK_SEARCH_PATHS_WEBCORE_$(WK_COCOA_TOUCH));
diff --git a/Tools/TestWebKitAPI/Configurations/TestWebKitAPIBase.xcconfig b/Tools/TestWebKitAPI/Configurations/TestWebKitAPIBase.xcconfig
index dcf80fd688a0..48cff520e661 100644
--- a/Tools/TestWebKitAPI/Configurations/TestWebKitAPIBase.xcconfig
+++ b/Tools/TestWebKitAPI/Configurations/TestWebKitAPIBase.xcconfig
@@ -114,7 +114,15 @@ WK_GAMECONTROLLER_LDFLAGS_iphonesimulator = -framework GameController
WK_GAMECONTROLLER_LDFLAGS_xros = -framework GameController
WK_GAMECONTROLLER_LDFLAGS_xrsimulator = -framework GameController
-OTHER_LDFLAGS = $(inherited) $(GTEST_LDFLAGS) -lxml2 -force_load $(BUILT_PRODUCTS_DIR)/libTestWebKitAPI.a -framework JavaScriptCore -framework WebCore -framework WebKit -lWebCoreTestSupport -framework Metal -framework IOSurface $(WK_APPSERVERSUPPORT_LDFLAGS) $(WK_AUTHKIT_LDFLAGS) -framework Network -framework UniformTypeIdentifiers -framework CoreFoundation -framework CoreServices -framework CFNetwork -framework CoreGraphics -framework CoreLocation -framework CoreText -framework IOKit -lboringssl -licucore -framework LocalAuthentication -framework QuartzCore -framework Security -framework AVKit $(WK_BROWSERENGINEKIT_LDFLAGS) $(WK_HID_LDFLAGS) $(WK_IMAGEIO_LDFLAGS) $(WK_OPENGL_LDFLAGS) $(WK_PDFKIT_LDFLAGS) $(WK_SYSTEM_LDFLAGS) $(WK_UIKITMACHELPER_LDFLAGS) $(WK_VISIONKITCORE_LDFLAGS) $(WK_WEBCORE_LDFLAGS) $(WK_REVEAL_LDFLAGS) $(WK_WRITING_TOOLS_LDFLAGS) $(WK_WRITING_TOOLS_UI_LDFLAGS) $(WK_GAMECONTROLLER_LDFLAGS) $(OTHER_LDFLAGS_DELAY_INIT) $(OTHER_LDFLAGS_PLATFORM_$(WK_COCOA_TOUCH)) $(OTHER_LDFLAGS_ENTITLEMENTS);
+// See ENABLE_WEB_RTC in FeatureDefines.xcconfig in WebCore and WebKit.
+WK_LIBWEBRTC_LDFLAGS = $(WK_LIBWEBRTC_LDFLAGS_$(WK_PLATFORM_NAME))
+WK_LIBWEBRTC_LDFLAGS_iphoneos = -weak-lwebrtc
+WK_LIBWEBRTC_LDFLAGS_iphonesimulator = -weak-lwebrtc
+WK_LIBWEBRTC_LDFLAGS_macosx = -weak-lwebrtc
+WK_LIBWEBRTC_LDFLAGS_xros = -weak-lwebrtc
+WK_LIBWEBRTC_LDFLAGS_xrsimulator = -weak-lwebrtc
+
+OTHER_LDFLAGS = $(inherited) $(GTEST_LDFLAGS) -lxml2 -force_load $(BUILT_PRODUCTS_DIR)/libTestWebKitAPI.a -framework JavaScriptCore -framework WebCore -framework WebKit -lWebCoreTestSupport -framework Metal -framework IOSurface $(WK_APPSERVERSUPPORT_LDFLAGS) $(WK_AUTHKIT_LDFLAGS) -framework Network -framework UniformTypeIdentifiers -framework CoreFoundation -framework CoreServices -framework CFNetwork -framework CoreGraphics -framework CoreLocation -framework CoreText -framework IOKit -lboringssl -licucore -framework LocalAuthentication -framework QuartzCore -framework Security -framework AVKit $(WK_BROWSERENGINEKIT_LDFLAGS) $(WK_HID_LDFLAGS) $(WK_IMAGEIO_LDFLAGS) $(WK_OPENGL_LDFLAGS) $(WK_PDFKIT_LDFLAGS) $(WK_SYSTEM_LDFLAGS) $(WK_UIKITMACHELPER_LDFLAGS) $(WK_VISIONKITCORE_LDFLAGS) $(WK_WEBCORE_LDFLAGS) $(WK_REVEAL_LDFLAGS) $(WK_WRITING_TOOLS_LDFLAGS) $(WK_WRITING_TOOLS_UI_LDFLAGS) $(WK_GAMECONTROLLER_LDFLAGS) $(WK_LIBWEBRTC_LDFLAGS) $(OTHER_LDFLAGS_DELAY_INIT) $(OTHER_LDFLAGS_PLATFORM_$(WK_COCOA_TOUCH)) $(OTHER_LDFLAGS_ENTITLEMENTS) -Xlinker --no-demangle;
OTHER_LDFLAGS_DELAY_INIT[sdk=iphone*] = -Wl,-delay_framework,CoreTelephony;
OTHER_LDFLAGS_DELAY_INIT[sdk=appletv*] = ;
diff --git a/Tools/TestWebKitAPI/Tests/WebCore/cocoa/SharedVideoFrame.mm b/Tools/TestWebKitAPI/Tests/WebCore/cocoa/SharedVideoFrame.mm
index fd95a943ada2..336f80891ce7 100644
--- a/Tools/TestWebKitAPI/Tests/WebCore/cocoa/SharedVideoFrame.mm
+++ b/Tools/TestWebKitAPI/Tests/WebCore/cocoa/SharedVideoFrame.mm
@@ -29,9 +29,16 @@
#import <WebCore/SharedVideoFrameInfo.h>
#include <wtf/Vector.h>
+#if USE(LIBWEBRTC)
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <webrtc/api/video/i010_buffer.h>
+#include <webrtc/api/video/i420_buffer.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+#endif
+
namespace TestWebKitAPI {
-TEST(WebCore, SharedVideoFramePlaneAlphaSize)
+TEST(SharedVideoFrame, PlaneAlphaSize)
{
Vector<uint8_t> data(128);
WebCore::SharedVideoFrameInfo info {
@@ -54,4 +61,125 @@ TEST(WebCore, SharedVideoFramePlaneAlphaSize)
EXPECT_FALSE(info2);
}
+#if USE(LIBWEBRTC)
+
+template<typename SampleType, typename Buffer>
+static void testSharedVideoFrameInfoRoundTrip(Buffer& buffer)
+{
+ static_assert(sizeof(SampleType) == 1 || sizeof(SampleType) == 2);
+
+ const int width = buffer.width();
+ const int height = buffer.height();
+ const int chromaWidth = (width + 1) / 2;
+ const int chromaHeight = (height + 1) / 2;
+
+ // I420 stores 8-bit samples; I010 stores 10-bit samples in uint16_t.
+ constexpr unsigned valueMask = sizeof(SampleType) == 1 ? 0xFFu : 0x3FFu;
+ constexpr unsigned msbShift = sizeof(SampleType) == 1 ? 0u : 6u;
+
+ auto fillPlane = [&](std::span<SampleType> plane, int stride, int planeWidth, int planeHeight, unsigned seed) {
+ for (int y = 0; y < planeHeight; ++y) {
+ auto row = plane.subspan(y * stride, planeWidth);
+ for (int x = 0; x < planeWidth; ++x)
+ row[x] = static_cast<SampleType>((seed + 31u * y + 7u * x) & valueMask);
+ }
+ };
+ fillPlane(unsafeMakeSpan(buffer.MutableDataY(), buffer.StrideY() * height), buffer.StrideY(), width, height, 1);
+ fillPlane(unsafeMakeSpan(buffer.MutableDataU(), buffer.StrideU() * chromaHeight), buffer.StrideU(), chromaWidth, chromaHeight, 101);
+ fillPlane(unsafeMakeSpan(buffer.MutableDataV(), buffer.StrideV() * chromaHeight), buffer.StrideV(), chromaWidth, chromaHeight, 211);
+
+ auto info = WebCore::SharedVideoFrameInfo::fromVideoFrameBuffer(buffer);
+ EXPECT_GT(info.storageSize(), 0u);
+
+ constexpr size_t guardSize = 64;
+ constexpr uint8_t guardByte = 0xCD;
+
+ Vector<uint8_t> storage(info.storageSize() + guardSize);
+ auto allBytes = storage.mutableSpan();
+ auto frameBytes = allBytes.subspan(0, info.storageSize());
+ auto guardBytes = allBytes.subspan(info.storageSize());
+ for (auto& b : guardBytes)
+ b = guardByte;
+
+ EXPECT_TRUE(info.writeVideoFrameBuffer(buffer, frameBytes));
+
+ for (auto byte : guardBytes)
+ EXPECT_EQ(byte, guardByte);
+
+ auto payload = spanReinterpretCast<const SampleType>(frameBytes.subspan(sizeof(WebCore::SharedVideoFrameInfo)));
+ const int outStrideY = width;
+ const int outStrideUV = (width & 1) ? width + 1 : width;
+ auto outY = payload.first(outStrideY * height);
+ auto outUV = payload.subspan(outStrideY * height, outStrideUV * chromaHeight);
+
+ auto srcY = unsafeMakeSpan(buffer.DataY(), buffer.StrideY() * height);
+ auto srcU = unsafeMakeSpan(buffer.DataU(), buffer.StrideU() * chromaHeight);
+ auto srcV = unsafeMakeSpan(buffer.DataV(), buffer.StrideV() * chromaHeight);
+
+ for (int y = 0; y < height; ++y) {
+ auto srcRow = srcY.subspan(y * buffer.StrideY(), width);
+ auto outRow = outY.subspan(y * outStrideY, width);
+ for (int x = 0; x < width; ++x) {
+ auto expected = static_cast<SampleType>(static_cast<uint32_t>(srcRow[x]) << msbShift);
+ EXPECT_EQ(outRow[x], expected) << "Y mismatch at (" << x << ", " << y << ")";
+ }
+ }
+
+ for (int y = 0; y < chromaHeight; ++y) {
+ auto srcURow = srcU.subspan(y * buffer.StrideU(), chromaWidth);
+ auto srcVRow = srcV.subspan(y * buffer.StrideV(), chromaWidth);
+ auto outRow = outUV.subspan(y * outStrideUV, 2 * chromaWidth);
+ for (int x = 0; x < chromaWidth; ++x) {
+ auto expectedU = static_cast<SampleType>(static_cast<uint32_t>(srcURow[x]) << msbShift);
+ auto expectedV = static_cast<SampleType>(static_cast<uint32_t>(srcVRow[x]) << msbShift);
+ EXPECT_EQ(outRow[2 * x], expectedU) << "U mismatch at (" << x << ", " << y << ")";
+ EXPECT_EQ(outRow[2 * x + 1], expectedV) << "V mismatch at (" << x << ", " << y << ")";
+ }
+ }
+}
+
+TEST(SharedVideoFrame, OddWidthI420)
+{
+ auto buffer = webrtc::I420Buffer::Create(681, 1280);
+ ASSERT_TRUE(buffer);
+ testSharedVideoFrameInfoRoundTrip<uint8_t>(*buffer);
+}
+
+TEST(SharedVideoFrame, OddHeightI420)
+{
+ auto buffer = webrtc::I420Buffer::Create(680, 15);
+ ASSERT_TRUE(buffer);
+ testSharedVideoFrameInfoRoundTrip<uint8_t>(*buffer);
+}
+
+TEST(SharedVideoFrame, OddWidthAndHeightI420)
+{
+ auto buffer = webrtc::I420Buffer::Create(681, 15);
+ ASSERT_TRUE(buffer);
+ testSharedVideoFrameInfoRoundTrip<uint8_t>(*buffer);
+}
+
+TEST(SharedVideoFrame, OddWidthI010)
+{
+ auto buffer = webrtc::I010Buffer::Create(681, 1280);
+ ASSERT_TRUE(buffer);
+ testSharedVideoFrameInfoRoundTrip<uint16_t>(*buffer);
+}
+
+TEST(SharedVideoFrame, OddHeightI010)
+{
+ auto buffer = webrtc::I010Buffer::Create(680, 15);
+ ASSERT_TRUE(buffer);
+ testSharedVideoFrameInfoRoundTrip<uint16_t>(*buffer);
+}
+
+TEST(SharedVideoFrame, OddWidthAndHeightI010)
+{
+ auto buffer = webrtc::I010Buffer::Create(681, 15);
+ ASSERT_TRUE(buffer);
+ testSharedVideoFrameInfoRoundTrip<uint16_t>(*buffer);
+}
+
+#endif // USE(LIBWEBRTC)
+
}; // namespace TestWebKitAPI
Regression Test / PoC
diff --git a/Tools/TestWebKitAPI/Configurations/Base.xcconfig b/Tools/TestWebKitAPI/Configurations/Base.xcconfig
index fa2529f333e7..40779526a7bc 100644
--- a/Tools/TestWebKitAPI/Configurations/Base.xcconfig
+++ b/Tools/TestWebKitAPI/Configurations/Base.xcconfig
@@ -48,10 +48,10 @@ PROJECT_HEADER_SEARCH_PATHS_cocoatouch = $(inherited) $(SRCROOT)/../../Source/We
SWIFT_INCLUDE_PATHS = $(inherited) $(SWIFT_INCLUDE_PATHS_$(USE_INTERNAL_SDK));
SWIFT_INCLUDE_PATHS_ = $(SRCROOT)/../../Source/WebKit/Platform/spi/Cocoa;
-HEADER_SEARCH_PATHS = $(ALTERNATE_HEADER_SEARCH_PATHS) ${BUILT_PRODUCTS_DIR}/usr/local/include $(WEBCORE_PRIVATE_HEADERS_DIR)/ForwardingHeaders $(BUILT_PRODUCTS_DIR)/WebKitTestSupport $(WEBKIT_TESTSUPPORT_INSTALLED_DIR) ${SRCROOT} $(PROJECT_HEADER_SEARCH_PATHS);
+HEADER_SEARCH_PATHS = $(ALTERNATE_HEADER_SEARCH_PATHS) ${BUILT_PRODUCTS_DIR}/usr/local/include $(LIBWEBRTC_HEADER_SEARCH_PATHS) $(WEBCORE_PRIVATE_HEADERS_DIR)/ForwardingHeaders $(BUILT_PRODUCTS_DIR)/WebKitTestSupport $(WEBKIT_TESTSUPPORT_INSTALLED_DIR) ${SRCROOT} $(PROJECT_HEADER_SEARCH_PATHS);
WEBKIT_TESTSUPPORT_INSTALLED_DIR[config=Production] = $(SDK_DIR)$(WK_ALTERNATE_WEBKIT_SDK_PATH)$(WK_LIBRARY_HEADERS_FOLDER_PATH)/WebKitTestSupport;
-LIBRARY_SEARCH_PATHS = $(SDK_DIR)$(WK_ALTERNATE_WEBKIT_SDK_PATH)$(WK_LIBRARY_INSTALL_PATH) $(inherited);
+LIBRARY_SEARCH_PATHS = $(SDK_DIR)$(WK_ALTERNATE_WEBKIT_SDK_PATH)$(WK_LIBRARY_INSTALL_PATH) "$(SDK_DIR)$(WEBCORE_LIBRARY_DIR)" $(inherited);
SYSTEM_HEADER_SEARCH_PATHS = $(inherited) $(WK_PRIVATE_SDK_DIR)$(WK_ALTERNATE_WEBKIT_SDK_PATH)$(WK_LIBRARY_HEADERS_FOLDER_PATH);
SYSTEM_FRAMEWORK_SEARCH_PATHS = $(inherited) $(SYSTEM_FRAMEWORK_SEARCH_PATHS_$(WK_USE_ALTERNATE_FRAMEWORKS_DIR)) $(SYSTEM_FRAMEWORK_SEARCH_PATHS_WEBCORE_$(WK_COCOA_TOUCH));
diff --git a/Tools/TestWebKitAPI/Configurations/TestWebKitAPIBase.xcconfig b/Tools/TestWebKitAPI/Configurations/TestWebKitAPIBase.xcconfig
index dcf80fd688a0..48cff520e661 100644
--- a/Tools/TestWebKitAPI/Configurations/TestWebKitAPIBase.xcconfig
+++ b/Tools/TestWebKitAPI/Configurations/TestWebKitAPIBase.xcconfig
@@ -114,7 +114,15 @@ WK_GAMECONTROLLER_LDFLAGS_iphonesimulator = -framework GameController
WK_GAMECONTROLLER_LDFLAGS_xros = -framework GameController
WK_GAMECONTROLLER_LDFLAGS_xrsimulator = -framework GameController
-OTHER_LDFLAGS = $(inherited) $(GTEST_LDFLAGS) -lxml2 -force_load $(BUILT_PRODUCTS_DIR)/libTestWebKitAPI.a -framework JavaScriptCore -framework WebCore -framework WebKit -lWebCoreTestSupport -framework Metal -framework IOSurface $(WK_APPSERVERSUPPORT_LDFLAGS) $(WK_AUTHKIT_LDFLAGS) -framework Network -framework UniformTypeIdentifiers -framework CoreFoundation -framework CoreServices -framework CFNetwork -framework CoreGraphics -framework CoreLocation -framework CoreText -framework IOKit -lboringssl -licucore -framework LocalAuthentication -framework QuartzCore -framework Security -framework AVKit $(WK_BROWSERENGINEKIT_LDFLAGS) $(WK_HID_LDFLAGS) $(WK_IMAGEIO_LDFLAGS) $(WK_OPENGL_LDFLAGS) $(WK_PDFKIT_LDFLAGS) $(WK_SYSTEM_LDFLAGS) $(WK_UIKITMACHELPER_LDFLAGS) $(WK_VISIONKITCORE_LDFLAGS) $(WK_WEBCORE_LDFLAGS) $(WK_REVEAL_LDFLAGS) $(WK_WRITING_TOOLS_LDFLAGS) $(WK_WRITING_TOOLS_UI_LDFLAGS) $(WK_GAMECONTROLLER_LDFLAGS) $(OTHER_LDFLAGS_DELAY_INIT) $(OTHER_LDFLAGS_PLATFORM_$(WK_COCOA_TOUCH)) $(OTHER_LDFLAGS_ENTITLEMENTS);
+// See ENABLE_WEB_RTC in FeatureDefines.xcconfig in WebCore and WebKit.
+WK_LIBWEBRTC_LDFLAGS = $(WK_LIBWEBRTC_LDFLAGS_$(WK_PLATFORM_NAME))
+WK_LIBWEBRTC_LDFLAGS_iphoneos = -weak-lwebrtc
+WK_LIBWEBRTC_LDFLAGS_iphonesimulator = -weak-lwebrtc
+WK_LIBWEBRTC_LDFLAGS_macosx = -weak-lwebrtc
+WK_LIBWEBRTC_LDFLAGS_xros = -weak-lwebrtc
+WK_LIBWEBRTC_LDFLAGS_xrsimulator = -weak-lwebrtc
+
+OTHER_LDFLAGS = $(inherited) $(GTEST_LDFLAGS) -lxml2 -force_load $(BUILT_PRODUCTS_DIR)/libTestWebKitAPI.a -framework JavaScriptCore -framework WebCore -framework WebKit -lWebCoreTestSupport -framework Metal -framework IOSurface $(WK_APPSERVERSUPPORT_LDFLAGS) $(WK_AUTHKIT_LDFLAGS) -framework Network -framework UniformTypeIdentifiers -framework CoreFoundation -framework CoreServices -framework CFNetwork -framework CoreGraphics -framework CoreLocation -framework CoreText -framework IOKit -lboringssl -licucore -framework LocalAuthentication -framework QuartzCore -framework Security -framework AVKit $(WK_BROWSERENGINEKIT_LDFLAGS) $(WK_HID_LDFLAGS) $(WK_IMAGEIO_LDFLAGS) $(WK_OPENGL_LDFLAGS) $(WK_PDFKIT_LDFLAGS) $(WK_SYSTEM_LDFLAGS) $(WK_UIKITMACHELPER_LDFLAGS) $(WK_VISIONKITCORE_LDFLAGS) $(WK_WEBCORE_LDFLAGS) $(WK_REVEAL_LDFLAGS) $(WK_WRITING_TOOLS_LDFLAGS) $(WK_WRITING_TOOLS_UI_LDFLAGS) $(WK_GAMECONTROLLER_LDFLAGS) $(WK_LIBWEBRTC_LDFLAGS) $(OTHER_LDFLAGS_DELAY_INIT) $(OTHER_LDFLAGS_PLATFORM_$(WK_COCOA_TOUCH)) $(OTHER_LDFLAGS_ENTITLEMENTS) -Xlinker --no-demangle;
OTHER_LDFLAGS_DELAY_INIT[sdk=iphone*] = -Wl,-delay_framework,CoreTelephony;
OTHER_LDFLAGS_DELAY_INIT[sdk=appletv*] = ;
diff --git a/Tools/TestWebKitAPI/Tests/WebCore/cocoa/SharedVideoFrame.mm b/Tools/TestWebKitAPI/Tests/WebCore/cocoa/SharedVideoFrame.mm
index fd95a943ada2..336f80891ce7 100644
--- a/Tools/TestWebKitAPI/Tests/WebCore/cocoa/SharedVideoFrame.mm
+++ b/Tools/TestWebKitAPI/Tests/WebCore/cocoa/SharedVideoFrame.mm
@@ -29,9 +29,16 @@
#import <WebCore/SharedVideoFrameInfo.h>
#include <wtf/Vector.h>
+#if USE(LIBWEBRTC)
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <webrtc/api/video/i010_buffer.h>
+#include <webrtc/api/video/i420_buffer.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+#endif
+
namespace TestWebKitAPI {
-TEST(WebCore, SharedVideoFramePlaneAlphaSize)
+TEST(SharedVideoFrame, PlaneAlphaSize)
{
Vector<uint8_t> data(128);
WebCore::SharedVideoFrameInfo info {
@@ -54,4 +61,125 @@ TEST(WebCore, SharedVideoFramePlaneAlphaSize)
EXPECT_FALSE(info2);
}
+#if USE(LIBWEBRTC)
+
+template<typename SampleType, typename Buffer>
+static void testSharedVideoFrameInfoRoundTrip(Buffer& buffer)
+{
+ static_assert(sizeof(SampleType) == 1 || sizeof(SampleType) == 2);
+
+ const int width = buffer.width();
+ const int height = buffer.height();
+ const int chromaWidth = (width + 1) / 2;
+ const int chromaHeight = (height + 1) / 2;
+
+ // I420 stores 8-bit samples; I010 stores 10-bit samples in uint16_t.
+ constexpr unsigned valueMask = sizeof(SampleType) == 1 ? 0xFFu : 0x3FFu;
+ constexpr unsigned msbShift = sizeof(SampleType) == 1 ? 0u : 6u;
+
+ auto fillPlane = [&](std::span<SampleType> plane, int stride, int planeWidth, int planeHeight, unsigned seed) {
+ for (int y = 0; y < planeHeight; ++y) {
+ auto row = plane.subspan(y * stride, planeWidth);
+ for (int x = 0; x < planeWidth; ++x)
+ row[x] = static_cast<SampleType>((seed + 31u * y + 7u * x) & valueMask);
+ }
+ };
+ fillPlane(unsafeMakeSpan(buffer.MutableDataY(), buffer.StrideY() * height), buffer.StrideY(), width, height, 1);
+ fillPlane(unsafeMakeSpan(buffer.MutableDataU(), buffer.StrideU() * chromaHeight), buffer.StrideU(), chromaWidth, chromaHeight, 101);
+ fillPlane(unsafeMakeSpan(buffer.MutableDataV(), buffer.StrideV() * chromaHeight), buffer.StrideV(), chromaWidth, chromaHeight, 211);
+
+ auto info = WebCore::SharedVideoFrameInfo::fromVideoFrameBuffer(buffer);
+ EXPECT_GT(info.storageSize(), 0u);
+
+ constexpr size_t guardSize = 64;
+ constexpr uint8_t guardByte = 0xCD;
+
+ Vector<uint8_t> storage(info.storageSize() + guardSize);
+ auto allBytes = storage.mutableSpan();
+ auto frameBytes = allBytes.subspan(0, info.storageSize());
+ auto guardBytes = allBytes.subspan(info.storageSize());
+ for (auto& b : guardBytes)
+ b = guardByte;
+
+ EXPECT_TRUE(info.writeVideoFrameBuffer(buffer, frameBytes));
+
+ for (auto byte : guardBytes)
+ EXPECT_EQ(byte, guardByte);
+
+ auto payload = spanReinterpretCast<const SampleType>(frameBytes.subspan(sizeof(WebCore::SharedVideoFrameInfo)));
+ const int outStrideY = width;
+ const int outStrideUV = (width & 1) ? width + 1 : width;
+ auto outY = payload.first(outStrideY * height);
+ auto outUV = payload.subspan(outStrideY * height, outStrideUV * chromaHeight);
+
+ auto srcY = unsafeMakeSpan(buffer.DataY(), buffer.StrideY() * height);
+ auto srcU = unsafeMakeSpan(buffer.DataU(), buffer.StrideU() * chromaHeight);
+ auto srcV = unsafeMakeSpan(buffer.DataV(), buffer.StrideV() * chromaHeight);
+
+ for (int y = 0; y < height; ++y) {
+ auto srcRow = srcY.subspan(y * buffer.StrideY(), width);
+ auto outRow = outY.subspan(y * outStrideY, width);
+ for (int x = 0; x < width; ++x) {
+ auto expected = static_cast<SampleType>(static_cast<uint32_t>(srcRow[x]) << msbShift);
+ EXPECT_EQ(outRow[x], expected) << "Y mismatch at (" << x << ", " << y << ")";
+ }
+ }
+
+ for (int y = 0; y < chromaHeight; ++y) {
+ auto srcURow = srcU.subspan(y * buffer.StrideU(), chromaWidth);
+ auto srcVRow = srcV.subspan(y * buffer.StrideV(), chromaWidth);
+ auto outRow = outUV.subspan(y * outStrideUV, 2 * chromaWidth);
+ for (int x = 0; x < chromaWidth; ++x) {
+ auto expectedU = static_cast<SampleType>(static_cast<uint32_t>(srcURow[x]) << msbShift);
+ auto expectedV = static_cast<SampleType>(static_cast<uint32_t>(srcVRow[x]) << msbShift);
+ EXPECT_EQ(outRow[2 * x], expectedU) << "U mismatch at (" << x << ", " << y << ")";
+ EXPECT_EQ(outRow[2 * x + 1], expectedV) << "V mismatch at (" << x << ", " << y << ")";
+ }
+ }
+}
+
+TEST(SharedVideoFrame, OddWidthI420)
+{
+ auto buffer = webrtc::I420Buffer::Create(681, 1280);
+ ASSERT_TRUE(buffer);
+ testSharedVideoFrameInfoRoundTrip<uint8_t>(*buffer);
+}
+
+TEST(SharedVideoFrame, OddHeightI420)
+{
+ auto buffer = webrtc::I420Buffer::Create(680, 15);
+ ASSERT_TRUE(buffer);
+ testSharedVideoFrameInfoRoundTrip<uint8_t>(*buffer);
+}
+
+TEST(SharedVideoFrame, OddWidthAndHeightI420)
+{
+ auto buffer = webrtc::I420Buffer::Create(681, 15);
+ ASSERT_TRUE(buffer);
+ testSharedVideoFrameInfoRoundTrip<uint8_t>(*buffer);
+}
+
+TEST(SharedVideoFrame, OddWidthI010)
+{
+ auto buffer = webrtc::I010Buffer::Create(681, 1280);
+ ASSERT_TRUE(buffer);
+ testSharedVideoFrameInfoRoundTrip<uint16_t>(*buffer);
+}
+
+TEST(SharedVideoFrame, OddHeightI010)
+{
+ auto buffer = webrtc::I010Buffer::Create(680, 15);
+ ASSERT_TRUE(buffer);
+ testSharedVideoFrameInfoRoundTrip<uint16_t>(*buffer);
+}
+
+TEST(SharedVideoFrame, OddWidthAndHeightI010)
+{
+ auto buffer = webrtc::I010Buffer::Create(681, 15);
+ ASSERT_TRUE(buffer);
+ testSharedVideoFrameInfoRoundTrip<uint16_t>(*buffer);
+}
+
+#endif // USE(LIBWEBRTC)
+
}; // namespace TestWebKitAPI