Skip to content

Commit 290ff05

Browse files
committed
fix(security): validate pixel-buffer bounds and widen size math
WebPEncoder: - New ValidatePixelBuffer helper enforces width/height <= WEBP_MAX_DIMENSION, stride >= width*bytesPerPixel, and pixels.Length >= stride*height (long-widened to avoid int32 overflow). Without this, libwebp reads past the end of the pinned managed buffer if the caller mis-declares dimensions, exposing adjacent GC heap memory. - Encode(byte[],..., float) result-length cast guards against size_t > int.MaxValue truncation. WebPDecoder: - Decode(byte[],...) reads dimensions via WebPGetInfo, rejects values outside (0, WEBP_MAX_DIMENSION], and uses long arithmetic for stride*height to detect overflow before allocation. - Decode(byte[], byte[], stride, format) now validates output.Length >= stride*height before handing the pointer to libwebp.
1 parent 23ae687 commit 290ff05

2 files changed

Lines changed: 63 additions & 5 deletions

File tree

src/Imazen.WebP/WebPDecoder.cs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,19 @@ public static byte[] Decode(byte[] data, out int width, out int height, WebPPixe
5353
() => NativeMethods.WebPGetInfo(dataPtr, dataSize, ref w, ref h)) == 0)
5454
throw new Exception("Invalid WebP header detected");
5555

56+
if (w <= 0 || h <= 0 || w > NativeMethods.WEBP_MAX_DIMENSION || h > NativeMethods.WEBP_MAX_DIMENSION)
57+
throw new InvalidDataException($"WebP dimensions out of range: {w}x{h}");
58+
5659
width = w;
5760
height = h;
5861

5962
int bytesPerPixel = (format == WebPPixelFormat.Bgr || format == WebPPixelFormat.Rgb) ? 3 : 4;
60-
int stride = w * bytesPerPixel;
61-
byte[] output = new byte[stride * h];
63+
long strideL = (long)w * bytesPerPixel;
64+
long sizeL = strideL * h;
65+
if (sizeL > int.MaxValue)
66+
throw new InvalidDataException($"Decoded buffer would exceed int.MaxValue: {sizeL} bytes");
67+
int stride = (int)strideL;
68+
byte[] output = new byte[(int)sizeL];
6269

6370
var outHandle = GCHandle.Alloc(output, GCHandleType.Pinned);
6471
try
@@ -90,16 +97,35 @@ public static void Decode(byte[] data, byte[] output, int stride, WebPPixelForma
9097
{
9198
if (data == null) throw new ArgumentNullException(nameof(data));
9299
if (output == null) throw new ArgumentNullException(nameof(output));
93-
100+
if (stride <= 0) throw new ArgumentOutOfRangeException(nameof(stride));
101+
102+
// Verify the output buffer is large enough for the declared image
103+
// before handing libwebp the raw pointer. libwebp itself checks
104+
// output_buffer_size, but doing the check here makes the failure
105+
// mode a managed exception (with parameter name) rather than a
106+
// silent WebPDecode... return-NULL.
107+
int w = 0, h = 0;
94108
var dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
95109
var outHandle = GCHandle.Alloc(output, GCHandleType.Pinned);
96110
try
97111
{
98112
IntPtr dataPtr = dataHandle.AddrOfPinnedObject();
99113
IntPtr outPtr = outHandle.AddrOfPinnedObject();
100114
UIntPtr dataSize = (UIntPtr)data.Length;
101-
UIntPtr outSize = (UIntPtr)output.Length;
102115

116+
if (NativeLibraryLoader.FixDllNotFoundException("webp",
117+
() => NativeMethods.WebPGetInfo(dataPtr, dataSize, ref w, ref h)) == 0)
118+
throw new InvalidDataException("Invalid WebP header detected");
119+
if (w <= 0 || h <= 0 || w > NativeMethods.WEBP_MAX_DIMENSION || h > NativeMethods.WEBP_MAX_DIMENSION)
120+
throw new InvalidDataException($"WebP dimensions out of range: {w}x{h}");
121+
122+
long needed = (long)stride * h;
123+
if (output.Length < needed)
124+
throw new ArgumentException(
125+
$"output.Length {output.Length} is smaller than stride*height ({needed}).",
126+
nameof(output));
127+
128+
UIntPtr outSize = (UIntPtr)output.Length;
103129
IntPtr result = DecodeInto(dataPtr, dataSize, outPtr, outSize, stride, format);
104130
if (outPtr != result)
105131
throw new Exception("Failed to decode WebP image");

src/Imazen.WebP/WebPEncoder.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public static byte[] Encode(byte[] pixels, int width, int height, int stride,
5252
if (pixels == null) throw new ArgumentNullException(nameof(pixels));
5353
if (width <= 0) throw new ArgumentOutOfRangeException(nameof(width));
5454
if (height <= 0) throw new ArgumentOutOfRangeException(nameof(height));
55+
ValidatePixelBuffer(pixels, width, height, stride, format);
5556

5657
var handle = GCHandle.Alloc(pixels, GCHandleType.Pinned);
5758
try
@@ -75,7 +76,11 @@ public static byte[] Encode(byte[] pixels, int width, int height, int stride,
7576

7677
try
7778
{
78-
byte[] output = new byte[(int)(ulong)length];
79+
ulong len = (ulong)length;
80+
if (len > int.MaxValue)
81+
throw new InvalidOperationException(
82+
$"libwebp produced {len} bytes — exceeds int.MaxValue and cannot be returned as a byte[].");
83+
byte[] output = new byte[(int)len];
7984
Marshal.Copy(result, output, 0, output.Length);
8085
return output;
8186
}
@@ -192,6 +197,7 @@ public static void Encode(byte[] pixels, int width, int height, int stride,
192197
if (width <= 0) throw new ArgumentOutOfRangeException(nameof(width));
193198
if (height <= 0) throw new ArgumentOutOfRangeException(nameof(height));
194199
if (outputStream == null) throw new ArgumentNullException(nameof(outputStream));
200+
ValidatePixelBuffer(pixels, width, height, stride, format);
195201

196202
if (!config.Validate())
197203
throw new ArgumentException("Invalid WebP encoder configuration", nameof(config));
@@ -261,5 +267,31 @@ public static void Encode(byte[] pixels, int width, int height, int stride,
261267
outputHandle.Free();
262268
}
263269
}
270+
271+
// Guards libwebp against an out-of-bounds read into the managed heap.
272+
// libwebp reads stride * height bytes from the pinned pointer; the
273+
// managed buffer must be at least that large. We also enforce the
274+
// libwebp dimension cap so width*height won't silently wrap.
275+
private static void ValidatePixelBuffer(byte[] pixels, int width, int height, int stride, WebPPixelFormat format)
276+
{
277+
if (width > NativeMethods.WEBP_MAX_DIMENSION || height > NativeMethods.WEBP_MAX_DIMENSION)
278+
throw new ArgumentOutOfRangeException(nameof(width),
279+
$"width/height must be <= {NativeMethods.WEBP_MAX_DIMENSION}; got {width}x{height}.");
280+
281+
int bytesPerPixel = (format == WebPPixelFormat.Rgb || format == WebPPixelFormat.Bgr) ? 3 : 4;
282+
long minStride = (long)width * bytesPerPixel;
283+
// libwebp accepts negative strides only for bottom-up bitmaps in
284+
// some entry points; we don't expose those entry points and require
285+
// a positive stride large enough for one row.
286+
if (stride < minStride)
287+
throw new ArgumentOutOfRangeException(nameof(stride),
288+
$"stride {stride} is smaller than width*bytesPerPixel ({minStride}).");
289+
290+
long needed = (long)stride * height;
291+
if (pixels.Length < needed)
292+
throw new ArgumentException(
293+
$"pixels.Length {pixels.Length} is smaller than stride*height ({needed}).",
294+
nameof(pixels));
295+
}
264296
}
265297
}

0 commit comments

Comments
 (0)