Skip to content

Memory-exhaustion DoS in BMP decoder (bundled bmp-ts dependency) via unbounded width/height #1415

Description

@router0mail

Summary

@jimp/js-bmp delegates BMP decoding entirely to the bmp-ts npm package with no validation of its own. bmp-ts's decoder allocates its output pixel buffer directly from the BMP header's width/height fields with no upper bound and no comparison against the actual input size:

// bmp-ts@1.0.9 dist/commonjs/decoder.js:153
this.data = Buffer.alloc(this.width * this.height * 4);

For RLE4/RLE8-compressed BMPs, the very next thing that happens is:

// decoder.js:201 (bit4) and :279 (bit8)
this.data.fill(0);

Buffer.fill() forces real physical memory to be committed across the whole buffer (unlike Buffer.alloc's lazily-zero-mapped pages), and this happens before any actual pixel bytes are read from the file, so a tiny file with a big declared width/height is enough to force a large real memory allocation.

Proof of Concept

A crafted 1078-byte BMP (valid BM magic + 40-byte BITMAPINFOHEADER, bitPP=8, compression=1 i.e. BI_RLE8, width=10000, height=10000, a 256-entry zero palette, and no actual RLE pixel data after the palette) causes roughly 405MB of RSS growth in a single Jimp.read() call, confirmed against the real published jimp@1.6.1 (which bundles bmp-ts@1.0.9):

input size 1078 bytes   rss before ~67.7MB
Jimp.read(buffer) -> eventually throws RangeError (only AFTER the .fill(0) commits memory)
time 278ms              rss after ~472.4MB   (~405MB forced commit from a 1KB file)

Scaling width/height further is unbounded — nothing in bmp-ts or in @jimp/js-bmp's wrapper (plugins/js-bmp/src/index.ts, which calls BMP.decode() directly with no width/height sanity check before delegating) caps the allocation. A handful of concurrent uploads of ~1KB crafted files could force multi-GB memory growth or an allocator OOM in any service that calls Jimp.read() on user-uploaded images (a very common pattern — avatar/image upload handling).

This is a memory-exhaustion availability issue (CWE-400/789), not memory corruption — Node's Buffer/TypedArray bounds checks prevent any actual out-of-bounds read/write.

Suggested Fix

Before allocating the output buffer, validate that width * height * bytesPerPixel is consistent with the actual input buffer size (or with a reasonable configurable maximum), and reject the file early if not. This applies both in bmp-ts's decoder.js (root cause) and as a defense-in-depth check in @jimp/js-bmp's wrapper before it delegates to bmp-ts, since jimp is the package most people actually depend on directly.

Disclosure note

Checked for existing reports first: no GHSA/CVE/OSV advisory exists for bmp-ts, jimp, or @jimp/js-bmp. Neither repo has SECURITY.md or GitHub private vulnerability reporting enabled, so filing this as a plain public issue rather than a private advisory — severity is DoS-only (not RCE/memory-corruption), so this seemed reasonable to disclose directly; happy to move to a private channel if one gets set up.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions