There is 1 comment.
 
 
XWiki Platform / cid:jira-generated-image-avatar-b9bad77c-69f7-4c4a-bc6c-78525f22eaf7 XWIKI-24117 Closed

OOM when resizing a lot of images concurrently

 
View issue   ·   Add comment
 

1 comment

 
cid:jira-generated-image-avatar-9e6eb8d6-726f-4a50-9384-3d7d6c96f137 Clément Christiaens on 15/Sep/26 14:31
 
??not human reviewed yet??

This was closed as a duplicate in March 2026 and no bound was ever added. Re-checked against 18.7.0 and master ({{952e591eb3d}}):

* {{git log --grep=XWIKI-24117}} over {{xwiki-platform}} returns *no commit*.
* {{grep -rniE 'maxPixel|maxWidth|maxHeight|MAX_IMAGE|getMaximum|semaphore|Executors|permits'}} over all of {{xwiki-platform-image/}} (tests excluded) returns *zero* hits. {{ImageIO.setUseCache}} / {{deregisterServiceProvider}} / {{ImageReadParam}} / {{setSourceSubsampling}} return zero hits across {{xwiki-platform-core}} + {{xwiki-commons}}.
* The ??resize pool?? discussed in the only comment on this ticket does not exist in the tree.

The reason to reopen the question is that the same stack is reachable *deliberately*, at a cost the reporter's scenario does not suggest.

{{ImagePlugin.shrinkImage()}} calls {{imageProcessor.readImage(stream)}} before it computes the target dimensions ([ImagePlugin.java#L314-L326|https://github.com/xwiki/xwiki-platform/blob/master/xwiki-platform-core/xwiki-platform-image/xwiki-platform-image-processing/xwiki-platform-image-processing-plugin/src/main/java/com/xpn/xwiki/plugin/image/ImagePlugin.java#L314-L326]), so {{?width=}} never bounds the allocation, and {{ThumbnailatorImageProcessor.readImage()}} then forces a full-size {{TYPE_INT_ARGB}} copy ({{w*h*4}}) via {{.imageType(ThumbnailParameter.DEFAULT_IMAGE_TYPE)}} regardless of the source's bit depth ([ThumbnailatorImageProcessor.java#L53-L66|https://github.com/xwiki/xwiki-platform/blob/master/xwiki-platform-core/xwiki-platform-image/xwiki-platform-image-processing/xwiki-platform-image-processing-api/src/main/java/com/xpn/xwiki/internal/plugin/image/ThumbnailatorImageProcessor.java#L53-L66]). A 1-bit greyscale PNG is therefore amplified 32x on top of its compression ratio.

Measured on 18.7.0 (Tomcat, {{-Xmx4096m}}), single sequential *unauthenticated* requests:

|| uploaded PNG || file bytes || INT_ARGB demand || guest request || response ||
| 2000x2000 | 566 | 15.3 MiB | 0.045 s | 95 B |
| 4000x4000 | 2,022 | 61.0 MiB | 0.131 s | 98 B |
| 8000x8000 | *7,852* | *244.1 MiB* | *0.573 s* | *99 B* |

This ticket's scenario needed *320 x 11.9 MB = ~3.8 GB* of uploads to reach OOM. The crafted equivalent needs *7,852 bytes*, uploaded once with EDIT right, and every detonation afterwards is a ~120-byte unauthenticated GET. Because {{width}} is part of the thumbnail cache key ({{ImagePlugin.java:286}}) and the shipped capacity is 30, varying {{?width=}} makes every request a fresh full decode.

Two consequences for this ticket's framing:

# The concurrency reading is right but the volume is not a defence. 17 parallel requests for that one 7,852-byte attachment demand 4 GiB — the reference Docker image's whole heap. Tomcat's default {{maxThreads}} is 200.
# {{downloadAttachment}} catches {{Exception}} ({{ImagePlugin.java:223}}), not {{Error}}, so the resulting {{OutOfMemoryError}} is not swallowed.

Not executed, established from the format and the arithmetic: a 32768x32768 1-bit greyscale PNG is *130,555 bytes* and demands *exactly 4.00 GiB*, i.e. a single-request OOM against the reference deployment.

The ARGB multiplier is recent — it was introduced by {{c9263100a26}} (XWIKI-23514, 2025-09-16, first released *17.9.0*); before that {{readImage}} preserved the source's own image type via {{getBestImageTypeFor()}}. The decode-before-bound ordering itself predates the 2011 repository reorganisation.

A separate security ticket is being filed for the deliberate case ({{PR:L}} upload / unauthenticated detonation, CVSS 4.0 7.1). This comment is only to record that XWIKI-24117 was closed as a *duplicate* rather than fixed, that no allocation or pixel bound exists anywhere in the module today, and that the duplicate target (a resize pool) was never implemented.

h3. Credit
Assisted-by: Claude
Supervised-by: Clément Christiaens ([https://github.com/ciaens])