jimp-dev / jimp-dev/jimp

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

Open
#1,415 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
14.7k
Forks
777
PR merge metrics
No merged PRs in 30d

Description

## 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:

```js
// 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:

```js
// 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.

Contributor guide

Open the contributing guide

Research direction

Start with plugins/js-bmp/src/index.ts and the bundled bmp-ts decoder.js allocation and fill paths described in the report. Run the provided crafted BMP reproduction against Jimp.read() to establish the memory-growth behavior, then add coverage for rejecting oversized dimensions before allocation. Done means the reproduction is rejected without the large allocation and normal BMP decoding remains functional.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
62/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.