Skip to content

Commit 9f5a850

Browse files
1 parent 4e85c24 commit 9f5a850

File tree

1 file changed

+276
-0
lines changed

1 file changed

+276
-0
lines changed
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-wpp4-vqfq-v4hp",
4+
"modified": "2025-10-27T23:33:10Z",
5+
"published": "2025-10-27T23:33:10Z",
6+
"aliases": [
7+
"CVE-2025-62594"
8+
],
9+
"summary": "ImageMagick CLAHE : Unsigned underflow and division-by-zero lead to OOB pointer arithmetic and process crash (DoS)",
10+
"details": "## Summary\n\nA single root cause in the CLAHE implementation — tile width/height becoming zero — produces two distinct but related unsafe behaviors.\nVulnerabilities exists in the `CLAHEImage()` function of ImageMagick’s `MagickCore/enhance.c`.\n\n1. Unsigned integer underflow → out-of-bounds pointer arithmetic (OOB): when `tile_info.height == 0`, the expression `tile_info.height - 1` (unsigned) wraps to a very large value; using that value in pointer arithmetic yields a huge offset and OOB memory access (leading to memory corruption, SIGSEGV, or resource exhaustion).\n2. **Division/modulus by zero**: where code performs `... / tile_info.width` or `... % tile_info.height` without re-checking for zero, causing immediate division-by-zero crashes under sanitizers or `abort` at runtime.\n\nBoth behaviors are triggered by the same invalid tile condition (e.g., CLI exact `-clahe 0x0!` or automatic tile derivation `dim >> 3 == 0` for very small images). \n\n---\n\n## Details\n\n### **Unsigned underflow(can lea to OOB)**\n\n- Location: `MagickCore/enhance.c`, around line 609\n- Version tested: 7.1.2-8 (local ASan(undefined). /UBSan build)\n- Vulnerable code\n \n enhance.c: 609\n \n ```c\n p += (ptrdiff_t) clahe_info->width * (tile.height - 1);\n ```\n \n- Root Cause\n - If `tile.height == 0`, then `(tile.height - 1)` underflows to `UINT_MAX`.\n - Multiplication with `clahe_info->width` yields a huge value close to `SIZE_MAX`.\n - Adding this to `p` causes pointer arithmetic underflow.\n\n### **Division-by-zero**\n\n- File / Location: `MagickCore/enhance.c`, around line 669\n- Version tested: 7.1.2-8 (local ASan(undefined). /UBSan build)\n- vulnerable code\n \n enhance.c: 669-673\n \n ```c\n if ((image->columns % tile_info.width) != 0)\n tile_info.x=(ssize_t) (tile_info.width-(image->columns % tile_info.width));\n tile_info.y=0;\n if ((image->rows % tile_info.height) != 0)\n tile_info.y=(ssize_t) (tile_info.height-(image->rows % tile_info.height));\n ```\n \n- Root cause\n \n Missing input validation / bounds checks after computing default tile dimensions:\n \n If either `tile_info.width` or `tile_info.height` is 0, this triggers a division by zero. Zeros can reach this point through:\n \n 1. Exact tiles: CLI `clahe 0x0!` (the `!` forces zero to be used verbatim).\n 2. Auto tiles on tiny images: When a requested tile is `0` (no `!`), the code derives a default from the image size (e.g., `dim >> 3`). For images with `dim < 8`, this result is 0 unless clamped.\n\n---\n\n## Reproduction\n\n### **Unsigned underflow**\n\n**Environment**\n\nBuilt with AddressSanitizer and UndefinedBehaviorSanitizer enabled.\n\n```c\nexport UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1\nexport ASAN_OPTIONS=abort_on_error=1:allocator_may_return_null=1:detect_leaks=0\n```\n\n**Command**\n\n```bash\n./magick xc:black -clahe 0x0 null:\n```\n\n**Output**\n\n```\nMagickCore/enhance.c:609:6: runtime error: addition of unsigned offset overflowed\nSUMMARY: UndefinedBehaviorSanitizer: undefined-behavior MagickCore/enhance.c:609:6 in CLAHEImage\n```\n\n`./magick -size 10x10 xc:black -clahe 0x0 null:`\n\n<img width=\"1068\" height=\"64\" alt=\"image\" src=\"https://github.com/user-attachments/assets/cd9637ee-1d03-4066-834d-fda22410dd8b\" />\n\nmemory region corruption.\n\n`./magick -size 2000x2000 xc:black -clahe 0x0 null:`\n\n<img width=\"1069\" height=\"70\" alt=\"image\" src=\"https://github.com/user-attachments/assets/ecbab79c-a3c2-4e8c-96c9-8e2aa8f0d2b2\" />\n\n→ Significant memory consumption and evidence of memory region corruption.\n\n`./magick -size 4000x4000 xc:black -clahe 0x0 null:`\n\n<img width=\"776\" height=\"49\" alt=\"image\" src=\"https://github.com/user-attachments/assets/63a7cec5-616b-4aa5-87f3-a546a87e6625\" />\n\n→ Much larger memory usage; process appears to be aggressively consuming cache and address space.\n\n`./magick -size 8000x8000 xc:black -clahe 0x0 null:`\n\n<img width=\"748\" height=\"46\" alt=\"image\" src=\"https://github.com/user-attachments/assets/48b3aac8-98b3-4fbb-a5ca-4e7936bca44b\" />\n\n→ Memory usage escalates further and begins exhausting available cache. If left running, the process is likely to crash (DoS) after sustained allocation attempts.\n\n### **Division-by-zero**\n\n**Environment:** ASan/UBSan-enabled build.\n\n```c\nexport UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1\nexport ASAN_OPTIONS=abort_on_error=1:allocator_may_return_null=1:detect_leaks=0\n```\n\n**Command**\n\n```bash\n./magick -size 16x2 gradient: -type TrueColor -depth 8 -clahe 0x0! null:\n```\n\n**Output**\n\n<img width=\"1915\" height=\"818\" alt=\"image\" src=\"https://github.com/user-attachments/assets/cfe44432-b429-49e4-8673-2ed55ba9a961\" />\n\n**Notes:** Without sanitizers, the process may terminate with just `Aborted` (still DoS).\n\n---\n\n## Impact\n\n- Primary: Denial-of-Service — crash or sustained resource exhaustion (memory/cache thrash) when processing crafted parameters or small images via CLI or API. Attackers can trivially trigger via `clahe 0x0!` or by uploading very small images to services using ImageMagick.\n- Secondary (theoretical): OOB memory accesses and memory corruption could potentially be combined with other vulnerabilities to achieve more severe outcomes; however, no reliable code execution was demonstrated from these PoCs alone.\n\n---\n\n## Suggested concrete patch snippets\n\nApply in `CLAHEImage()` after `tile_info` is computed but **before** any division/modulus/pointer arithmetic:\n\n```c\nif (exact_tiles_requested && (tile_info.width == 0 || tile_info.height == 0)) {\n ThrowMagickException(exception, GetMagickModule(), OptionError,\n \"CLAHEInvalidTile\", \"%lux%lu\",\n (unsigned long) tile_info.width,\n (unsigned long) tile_info.height);\n return (Image *) NULL;\n}\n\nif (!exact_tiles_requested) {\n tile_info.width = (tile_info.width == 0) ? MagickMax((size_t)1, image->columns >> 3) : tile_info.width;\n tile_info.height = (tile_info.height == 0) ? MagickMax((size_t)1, image->rows >> 3) : tile_info.height;\n}\n\nif (tile_info.width == 0 || tile_info.height == 0) {\n ThrowMagickException(exception, GetMagickModule(), OptionError,\n \"CLAHEInvalidTile\", \"%lux%lu\",\n (unsigned long) tile_info.width,\n (unsigned long) tile_info.height);\n return (Image *) NULL;\n}\n\nssize_t tile_h_minus1 = (ssize_t)tile_info.height - 1;\nif (tile_h_minus1 < 0) {\n ThrowMagickException(exception, GetMagickModule(), OptionError,\n \"CLAHEInvalidTile\", \"%lux%lu\",\n (unsigned long) tile_info.width,\n (unsigned long) tile_info.height);\n return (Image *) NULL;\n}\np += (ptrdiff_t) clahe_info->width * tile_h_minus1;\n```\n\nNotes about `exact_tiles_requested`: if the CLI/Wand parser already exposes whether `!` was present, use it. If not, add a parse-time flag so CLAHEImage can know whether `0` is literal or auto.\n\n---\n\n## Credit\n\n### Team Whys \n\n**Bug Hunting Master Program, HSpace/Findthegap**\n\n**Youngmin Kim** \[email protected]\n\n**Woojin Park**\n\n[@jin-156](https://github.com/jin-156)\n[[email protected]](mailto:[email protected])\n\n**Youngin Won**\n\n[@amethyst0225](https://github.com/amethyst0225)\n[[email protected]](mailto:[email protected])\n\n**Siyeon Han**\n\n[@hanbunny](https://github.com/hanbunny)\n[[email protected]](mailto:[email protected])\n\n**Shinyoung Won**\n\n[@yosiimich]([email protected])\n[[email protected]](mailto:[email protected])",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:H"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "NuGet",
21+
"name": "Magick.NET-Q16-x64"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"last_affected": "14.9.0"
32+
}
33+
]
34+
}
35+
]
36+
},
37+
{
38+
"package": {
39+
"ecosystem": "NuGet",
40+
"name": "Magick.NET-Q8-x64"
41+
},
42+
"ranges": [
43+
{
44+
"type": "ECOSYSTEM",
45+
"events": [
46+
{
47+
"introduced": "0"
48+
},
49+
{
50+
"last_affected": "14.9.0"
51+
}
52+
]
53+
}
54+
]
55+
},
56+
{
57+
"package": {
58+
"ecosystem": "NuGet",
59+
"name": "Magick.NET-Q16-HDRI-x64"
60+
},
61+
"ranges": [
62+
{
63+
"type": "ECOSYSTEM",
64+
"events": [
65+
{
66+
"introduced": "0"
67+
},
68+
{
69+
"last_affected": "14.9.0"
70+
}
71+
]
72+
}
73+
]
74+
},
75+
{
76+
"package": {
77+
"ecosystem": "NuGet",
78+
"name": "Magick.NET-Q8-OpenMP-x64"
79+
},
80+
"ranges": [
81+
{
82+
"type": "ECOSYSTEM",
83+
"events": [
84+
{
85+
"introduced": "0"
86+
},
87+
{
88+
"last_affected": "14.9.0"
89+
}
90+
]
91+
}
92+
]
93+
},
94+
{
95+
"package": {
96+
"ecosystem": "NuGet",
97+
"name": "Magick.NET-Q16-HDRI-OpenMP-x64"
98+
},
99+
"ranges": [
100+
{
101+
"type": "ECOSYSTEM",
102+
"events": [
103+
{
104+
"introduced": "0"
105+
},
106+
{
107+
"last_affected": "14.9.0"
108+
}
109+
]
110+
}
111+
]
112+
},
113+
{
114+
"package": {
115+
"ecosystem": "NuGet",
116+
"name": "Magick.NET-Q16-OpenMP-x64"
117+
},
118+
"ranges": [
119+
{
120+
"type": "ECOSYSTEM",
121+
"events": [
122+
{
123+
"introduced": "0"
124+
},
125+
{
126+
"last_affected": "14.9.0"
127+
}
128+
]
129+
}
130+
]
131+
},
132+
{
133+
"package": {
134+
"ecosystem": "NuGet",
135+
"name": "Magick.NET-Q8-arm64"
136+
},
137+
"ranges": [
138+
{
139+
"type": "ECOSYSTEM",
140+
"events": [
141+
{
142+
"introduced": "0"
143+
},
144+
{
145+
"last_affected": "14.9.0"
146+
}
147+
]
148+
}
149+
]
150+
},
151+
{
152+
"package": {
153+
"ecosystem": "NuGet",
154+
"name": "Magick.NET-Q16-arm64"
155+
},
156+
"ranges": [
157+
{
158+
"type": "ECOSYSTEM",
159+
"events": [
160+
{
161+
"introduced": "0"
162+
},
163+
{
164+
"last_affected": "14.9.0"
165+
}
166+
]
167+
}
168+
]
169+
},
170+
{
171+
"package": {
172+
"ecosystem": "NuGet",
173+
"name": "Magick.NET-Q16-OpenMP-arm64"
174+
},
175+
"ranges": [
176+
{
177+
"type": "ECOSYSTEM",
178+
"events": [
179+
{
180+
"introduced": "0"
181+
},
182+
{
183+
"last_affected": "14.9.0"
184+
}
185+
]
186+
}
187+
]
188+
},
189+
{
190+
"package": {
191+
"ecosystem": "NuGet",
192+
"name": "Magick.NET-Q8-OpenMP-arm64"
193+
},
194+
"ranges": [
195+
{
196+
"type": "ECOSYSTEM",
197+
"events": [
198+
{
199+
"introduced": "0"
200+
},
201+
{
202+
"last_affected": "14.9.0"
203+
}
204+
]
205+
}
206+
]
207+
},
208+
{
209+
"package": {
210+
"ecosystem": "NuGet",
211+
"name": "Magick.NET-Q16-HDRI-OpenMP-arm64"
212+
},
213+
"ranges": [
214+
{
215+
"type": "ECOSYSTEM",
216+
"events": [
217+
{
218+
"introduced": "0"
219+
},
220+
{
221+
"last_affected": "14.9.0"
222+
}
223+
]
224+
}
225+
]
226+
},
227+
{
228+
"package": {
229+
"ecosystem": "NuGet",
230+
"name": "Magick.NET-Q16-HDRI-arm64"
231+
},
232+
"ranges": [
233+
{
234+
"type": "ECOSYSTEM",
235+
"events": [
236+
{
237+
"introduced": "0"
238+
},
239+
{
240+
"last_affected": "14.9.0"
241+
}
242+
]
243+
}
244+
]
245+
}
246+
],
247+
"references": [
248+
{
249+
"type": "WEB",
250+
"url": "https://github.com/ImageMagick/ImageMagick/security/advisories/GHSA-wpp4-vqfq-v4hp"
251+
},
252+
{
253+
"type": "ADVISORY",
254+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62594"
255+
},
256+
{
257+
"type": "WEB",
258+
"url": "https://github.com/ImageMagick/ImageMagick/commit/7b47fe369eda90483402fcd3d78fa4167d3bb129"
259+
},
260+
{
261+
"type": "PACKAGE",
262+
"url": "https://github.com/ImageMagick/ImageMagick"
263+
}
264+
],
265+
"database_specific": {
266+
"cwe_ids": [
267+
"CWE-119",
268+
"CWE-191",
269+
"CWE-369"
270+
],
271+
"severity": "MODERATE",
272+
"github_reviewed": true,
273+
"github_reviewed_at": "2025-10-27T23:33:10Z",
274+
"nvd_published_at": "2025-10-27T20:15:54Z"
275+
}
276+
}

0 commit comments

Comments
 (0)