Skip to content

Fix memory leak in JPEG EXIF rotation (rotate90/rotate270) - #2574

Merged
chearon merged 1 commit into
Automattic:masterfrom
iurisilvio:fix/jpeg-rotate-leak
Jun 21, 2026
Merged

Fix memory leak in JPEG EXIF rotation (rotate90/rotate270)#2574
chearon merged 1 commit into
Automattic:masterfrom
iurisilvio:fix/jpeg-rotate-leak

Conversation

@iurisilvio

Copy link
Copy Markdown
Contributor

Fixes #2572.

The rotate90 and rotate270 lambdas in Image::rotatePixels allocate a temporary unrotated buffer via new uint8_t[n_bytes] but never free it. Every JPEG decoded with EXIF orientation 5, 6, 7, or 8 leaks the full rotated pixel buffer (width * height * 4 bytes) on each loadImage call.

For a 1200×1800 test image (recurser/exif-orientation-examples Landscape_6.jpg, EXIF Orientation=6), 100 sequential loads leak 837 MiB (~8.4 MiB/iter, matching width * height * 4 = 8,640,000 bytes exactly). After this fix, RSS plateaus after the first ~10 iterations and stays flat.

The full repro script and observed numbers are in #2572.

Diff is one line added to each lambda:

   auto rotate90 = [](uint8_t* pixels, int width, int height, int channels) {
     ...
+    delete[] unrotated;
   };

   auto rotate270 = [](uint8_t* pixels, int width, int height, int channels) {
     ...
+    delete[] unrotated;
   };

The rotate90 and rotate270 lambdas in Image::rotatePixels allocate a
temporary 'unrotated' buffer via 'new uint8_t[n_bytes]' but never free
it. Every JPEG decoded with EXIF orientation 5, 6, 7, or 8 leaks the
full rotated pixel buffer (width * height * 4 bytes) on each loadImage
call.

For a 1200x1800 test image (recurser/exif-orientation-examples
Landscape_6.jpg, EXIF Orientation=6), 100 sequential loads leak 837 MiB
(~8.4 MiB/iter, matching width * height * 4 = 8,640,000 bytes exactly).
After this fix, RSS plateaus after the first ~10 iterations.

Fixes Automattic#2572
@chearon
chearon merged commit fbac0f6 into Automattic:master Jun 21, 2026
iurisilvio added a commit to iurisilvio/node-canvas that referenced this pull request Jun 25, 2026
Trim the fork to only the memory leaks reproduced with before/after RSS:
- JPEG EXIF rotation (rotate90/rotate270) leak (Automattic#2574, merged upstream)
- RsvgHandle / partial cairo surface leak on SVG decode error paths (Automattic#2585)

Revert everything else back to v3.2.3 after testing: image.src-on-error
(Automattic#2580, no measurable effect), libjpeg longjmp data/src free (Automattic#2581, not
reproducible on libjpeg-turbo), cairo_pattern_t refcount (Automattic#2582), CanvasPattern
source-retention UAF guard (Automattic#2583), rare decoder error-path frees (Automattic#2587), and
the node-addon-api 8 / NAPI_EXPERIMENTAL experiment. Built on node-addon-api 7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Memory leak in JPEG EXIF rotation: rotate90/rotate270 leak the rotated pixel buffer

2 participants