#5238·WasmEdge

bug: 32-bit overflow in stable-diffusion image_to_image resize allows a heap buffer overflow

Author: prerak09Created Jul 31, 2026Updated Aug 6, 2026
Labelsbug

Summary

SDImageToImage::body takes Width and Height as uint32_t host-function parameters. The only validation is parameterCheck, which requires each to be a multiple of 64 but sets no upper bound. The resize path then computes the output allocation as ResizedHeight * ResizedWidth * 3 in int arithmetic, so a guest can wrap the product to 0, get a non-NULL malloc(0) pointer past the null check, and reach stbir_resize with a zero-sized destination buffer.

The vendored stb_image_resize.h does not validate output dimensions, so it writes out of bounds. Reproduced with AddressSanitizer against the exact header the plugin builds with.

This is the same defect class as #4914 (ac37ae82a) and #5177 (678e63744), which fixed W * H * 3 wrapping past a bounds check in wasmedge_image.

Current State

plugins/wasmedge_stablediffusion/sd_func.cpp:519-533:

cpp
      int ResizedHeight = Height;
      int ResizedWidth = Width;
      uint8_t *ResizedImageBuffer =
          (uint8_t *)malloc(ResizedHeight * ResizedWidth * 3);
      if (ResizedImageBuffer == nullptr) {
        spdlog::error(
            "[WasmEdge-StableDiffusion] Failed to allocate memory for resize input image."sv);
        free(InputImageBuffer);
        return static_cast<uint32_t>(ErrNo::InvalidArgument);
      }
      stbir_resize(InputImageBuffer, ImageWidth, ImageHeight, 0,
                   ResizedImageBuffer, ResizedWidth, ResizedHeight, 0, ...);

Width and Height are uint32_t parameters of SDImageToImage::body (sd_func.cpp:437), registered as a guest-callable host function in sd_module.cpp:13 (addHostFunc("image_to_image", ...)).

The only validation between entry and line 522 is parameterCheck (sd_func.cpp:61):

cpp
  if (Width % 64 != 0) { ... return false; }
  if (Height % 64 != 0) { ... return false; }

There is no upper bound, so Width = 0x40000000 passes. Computing the size in int:

Width Height passes % 64 malloc arg true size
0x40000000 64 yes 0 206158430208
65536 65536 yes 0 12884901888
0x20000000 128 yes 0 206158430208
1024 1024 yes 3145728 3145728

malloc(0) returns a valid non-NULL pointer, so the == nullptr guard does not catch it, and stbir_resize is handed a zero-sized destination.

The vendored stb_image_resize.h does not defend against this. Its entry point (stbir__resize_arbitrary) validates channels, filters, alpha and scratch memory, but performs no check on output_w / output_h — its only early return there is if (!extra_memory) return 0;.

Expected State

The required size should be computed so it cannot wrap, matching the fix already merged for wasmedge_image in ac37ae82a:

cpp
      const uint64_t BytesPerPixel = 3;
      const uint64_t NumPixels = static_cast<uint64_t>(Width) * Height;
      if (unlikely(NumPixels > SomeSensibleLimit / BytesPerPixel)) {
        spdlog::error(
            "[WasmEdge-StableDiffusion] Requested output image size is too large."sv);
        free(InputImageBuffer);
        return static_cast<uint32_t>(ErrNo::InvalidArgument);
      }

An explicit upper bound in parameterCheck would also be reasonable, since Width/Height are used elsewhere in the same function. I've left the actual cap unspecified since that's your call.

I'm happy to send a PR for whichever shape you prefer.

Reproduction steps

The overflow was reproduced standalone against the exact header the plugin builds with, using the values from sd_func.cpp.

  1. Configure the plugin so the dependency is fetched:
bash
cmake -S . -Bbuild-sd -GNinja -DWASMEDGE_PLUGIN_STABLEDIFFUSION=ON

The vendored header lands at build-sd/_deps/stable-diffusion-src/thirdparty/stb_image_resize.h. It comes from stable-diffusion.cpp at the commit pinned in plugins/wasmedge_stablediffusion/CMakeLists.txt (GIT_TAG dcf91f9e0f2cbf9da472ee2a556751ed4bab2d2a).

  1. Reproduce the same call sd_func.cpp:522-533 makes, with Width = 0x40000000, Height = 64:
c
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize.h"   // the vendored copy

int iw = 64, ih = 64;
unsigned char *in = calloc((size_t)iw * ih * 3, 1);

int RW = (int)0x40000000u, RH = 64;      // guest Width/Height
int mallocarg = RH * RW * 3;             // wraps to 0
unsigned char *out = malloc(mallocarg);  // malloc(0) -> non-NULL

stbir_resize(in, iw, ih, 0, out, RW, RH, 0,
             STBIR_TYPE_UINT8, 3, STBIR_ALPHA_CHANNEL_NONE, 0,
             STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP,
             STBIR_FILTER_BOX, STBIR_FILTER_BOX,
             STBIR_COLORSPACE_SRGB, NULL);
bash
clang -O0 -g -w -fsanitize=address -o t t.c && ./t

Reaching line 522 from a guest additionally requires the "path:" input branch (sd_func.cpp:490) and Width/Height differing from the decoded image's dimensions (sd_func.cpp:517-518).

Any logs you want to share for showing the specific issue

malloc arg = 0 out ptr = 0x6020000000b0 (null-check PASSES)

==13645==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61c0000007a0 WRITE of size 4 at 0x61c0000007a0 thread T0 #0 stbir__calculate_coefficients_upsample stb_image_resize.h:1043 #1 stbir__calculate_filters stb_image_resize.h:1209 #2 stbir__resize_allocated stb_image_resize.h:2398 #3 stbir__resize_arbitrary stb_image_resize.h:2444 #4 stbir_resize stb_image_resize.h:2542 #5 main t.c:16

Without AddressSanitizer the same program terminates with SIGSEGV (exit 139).

Control - replacing the size computation with a 64-bit check: REJECTED: need 206158430208 bytes exit 0, no ASan report.

Components

Core, Plugins

WasmEdge Version or Commit you used

77a76b72f

Operating system information

macOS 15 (Darwin 24.6.0)

Hardware Architecture

arm64

Appendix

Scope and verification limits, stated up front:

  • The overflow, the non-NULL malloc(0), and the out-of-bounds write are all measured, using the exact stb_image_resize.h that stable-diffusion.cpp vendors. I did not drive this through a running WasmEdge guest — the plugin wiring (addHostFunc("image_to_image", ...) -> parameterCheck -> line 522) is read from source rather than executed.
  • Reaching line 522 requires the "path:" input branch, i.e. the guest supplies a filesystem path rather than raw image bytes. The stbi_load_from_memory branch performs no resize.
  • Verified on macOS/arm64 only.

Related prior fixes for the same defect class in wasmedge_image: ac37ae82a (#4914) and 678e63744 (#5177).