Commit bd4a469ad for imagemagick.org
commit bd4a469adb6ddb2bdcb856a3d307420268675b09
Author: Ryan Williams <ryan@runsascoded.com>
Date: Sat Feb 28 09:44:45 2026 -0500
Fix double-free in SVG `gradientTransform` / `transform` parsing (#8583)
In `SVGStartElement`, the `gradientTransform` and `transform` attribute
handlers reassign `value` to `tokens[j+1]` inside the inner token-parsing
loop. After the loop, all tokens are freed via `DestroyString()`. The
outer attribute loop then calls `DestroyString(value)`, which double-frees
the already-destroyed token string, causing SIGABRT.
Use a separate `token_value` local variable inside each inner loop instead
of reassigning `value`, so the outer loop's `DestroyString(value)` frees
the original `SVGEscapeString()`-allocated string exactly once.
Add regression test for SVG `gradientTransform` (#8582).
Fixes ImageMagick/ImageMagick#8582
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
diff --git a/coders/svg.c b/coders/svg.c
index 83d7b1dcd..b7d62e62b 100644
--- a/coders/svg.c
+++ b/coders/svg.c
@@ -1870,12 +1870,15 @@ static void SVGStartElement(void *context,const xmlChar *name,
break;
for (j=0; j < ((ssize_t) number_tokens-1); j+=2)
{
+ char
+ *token_value;
+
keyword=(char *) tokens[j];
if (keyword == (char *) NULL)
continue;
- value=(char *) tokens[j+1];
+ token_value=(char *) tokens[j+1];
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
- " %s: %s",keyword,value);
+ " %s: %s",keyword,token_value);
current=transform;
GetAffineMatrix(&affine);
switch (*keyword)
@@ -1885,9 +1888,9 @@ static void SVGStartElement(void *context,const xmlChar *name,
{
if (LocaleCompare(keyword,"matrix") == 0)
{
- p=value;
+ p=token_value;
(void) GetNextToken(p,&p,MagickPathExtent,token);
- affine.sx=StringToDouble(value,(char **) NULL);
+ affine.sx=StringToDouble(token_value,(char **) NULL);
(void) GetNextToken(p,&p,MagickPathExtent,token);
if (*token == ',')
(void) GetNextToken(p,&p,MagickPathExtent,token);
@@ -1920,7 +1923,7 @@ static void SVGStartElement(void *context,const xmlChar *name,
double
angle;
- angle=GetUserSpaceCoordinateValue(svg_info,0,value);
+ angle=GetUserSpaceCoordinateValue(svg_info,0,token_value);
affine.sx=cos(DegreesToRadians(fmod(angle,360.0)));
affine.rx=sin(DegreesToRadians(fmod(angle,360.0)));
affine.ry=(-sin(DegreesToRadians(fmod(angle,360.0))));
@@ -1934,11 +1937,11 @@ static void SVGStartElement(void *context,const xmlChar *name,
{
if (LocaleCompare(keyword,"scale") == 0)
{
- for (p=value; *p != '\0'; p++)
+ for (p=token_value; *p != '\0'; p++)
if ((isspace((int) ((unsigned char) *p)) != 0) ||
(*p == ','))
break;
- affine.sx=GetUserSpaceCoordinateValue(svg_info,1,value);
+ affine.sx=GetUserSpaceCoordinateValue(svg_info,1,token_value);
affine.sy=affine.sx;
if (*p != '\0')
affine.sy=
@@ -1950,7 +1953,7 @@ static void SVGStartElement(void *context,const xmlChar *name,
{
affine.sx=svg_info->affine.sx;
affine.ry=tan(DegreesToRadians(fmod(
- GetUserSpaceCoordinateValue(svg_info,1,value),
+ GetUserSpaceCoordinateValue(svg_info,1,token_value),
360.0)));
affine.sy=svg_info->affine.sy;
break;
@@ -1959,7 +1962,7 @@ static void SVGStartElement(void *context,const xmlChar *name,
{
affine.sx=svg_info->affine.sx;
affine.rx=tan(DegreesToRadians(fmod(
- GetUserSpaceCoordinateValue(svg_info,-1,value),
+ GetUserSpaceCoordinateValue(svg_info,-1,token_value),
360.0)));
affine.sy=svg_info->affine.sy;
break;
@@ -1971,11 +1974,11 @@ static void SVGStartElement(void *context,const xmlChar *name,
{
if (LocaleCompare(keyword,"translate") == 0)
{
- for (p=value; *p != '\0'; p++)
+ for (p=token_value; *p != '\0'; p++)
if ((isspace((int) ((unsigned char) *p)) != 0) ||
(*p == ','))
break;
- affine.tx=GetUserSpaceCoordinateValue(svg_info,1,value);
+ affine.tx=GetUserSpaceCoordinateValue(svg_info,1,token_value);
affine.ty=affine.tx;
if (*p != '\0')
affine.ty=
@@ -2270,10 +2273,13 @@ static void SVGStartElement(void *context,const xmlChar *name,
break;
for (j=0; j < ((ssize_t) number_tokens-1); j+=2)
{
+ char
+ *token_value;
+
keyword=(char *) tokens[j];
- value=(char *) tokens[j+1];
+ token_value=(char *) tokens[j+1];
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
- " %s: %s",keyword,value);
+ " %s: %s",keyword,token_value);
current=transform;
GetAffineMatrix(&affine);
switch (*keyword)
@@ -2283,9 +2289,9 @@ static void SVGStartElement(void *context,const xmlChar *name,
{
if (LocaleCompare(keyword,"matrix") == 0)
{
- p=value;
+ p=token_value;
(void) GetNextToken(p,&p,MagickPathExtent,token);
- affine.sx=StringToDouble(value,(char **) NULL);
+ affine.sx=StringToDouble(token_value,(char **) NULL);
(void) GetNextToken(p,&p,MagickPathExtent,token);
if (*token == ',')
(void) GetNextToken(p,&p,MagickPathExtent,token);
@@ -2320,9 +2326,9 @@ static void SVGStartElement(void *context,const xmlChar *name,
x,
y;
- p=value;
+ p=token_value;
(void) GetNextToken(p,&p,MagickPathExtent,token);
- angle=StringToDouble(value,(char **) NULL);
+ angle=StringToDouble(token_value,(char **) NULL);
affine.sx=cos(DegreesToRadians(fmod(angle,360.0)));
affine.rx=sin(DegreesToRadians(fmod(angle,360.0)));
affine.ry=(-sin(DegreesToRadians(fmod(angle,360.0))));
@@ -2351,11 +2357,11 @@ static void SVGStartElement(void *context,const xmlChar *name,
{
if (LocaleCompare(keyword,"scale") == 0)
{
- for (p=value; *p != '\0'; p++)
+ for (p=token_value; *p != '\0'; p++)
if ((isspace((int) ((unsigned char) *p)) != 0) ||
(*p == ','))
break;
- affine.sx=GetUserSpaceCoordinateValue(svg_info,1,value);
+ affine.sx=GetUserSpaceCoordinateValue(svg_info,1,token_value);
affine.sy=affine.sx;
if (*p != '\0')
affine.sy=GetUserSpaceCoordinateValue(svg_info,-1,
@@ -2367,7 +2373,7 @@ static void SVGStartElement(void *context,const xmlChar *name,
{
affine.sx=svg_info->affine.sx;
affine.ry=tan(DegreesToRadians(fmod(
- GetUserSpaceCoordinateValue(svg_info,1,value),
+ GetUserSpaceCoordinateValue(svg_info,1,token_value),
360.0)));
affine.sy=svg_info->affine.sy;
break;
@@ -2376,7 +2382,7 @@ static void SVGStartElement(void *context,const xmlChar *name,
{
affine.sx=svg_info->affine.sx;
affine.rx=tan(DegreesToRadians(fmod(
- GetUserSpaceCoordinateValue(svg_info,-1,value),
+ GetUserSpaceCoordinateValue(svg_info,-1,token_value),
360.0)));
affine.sy=svg_info->affine.sy;
break;
@@ -2388,11 +2394,11 @@ static void SVGStartElement(void *context,const xmlChar *name,
{
if (LocaleCompare(keyword,"translate") == 0)
{
- for (p=value; *p != '\0'; p++)
+ for (p=token_value; *p != '\0'; p++)
if ((isspace((int) ((unsigned char) *p)) != 0) ||
(*p == ','))
break;
- affine.tx=GetUserSpaceCoordinateValue(svg_info,1,value);
+ affine.tx=GetUserSpaceCoordinateValue(svg_info,1,token_value);
affine.ty=0;
if (*p != '\0')
affine.ty=GetUserSpaceCoordinateValue(svg_info,-1,
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 3d54586d8..b20e279d5 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -40,6 +40,7 @@ TESTS_XFAIL_TESTS =
TESTS_TESTS = \
tests/cli-colorspace.tap \
tests/cli-pipe.tap \
+ tests/cli-svg.tap \
tests/validate-colorspace.tap \
tests/validate-compare.tap \
tests/validate-composite.tap \
@@ -58,6 +59,7 @@ TESTS_TESTS = \
TESTS_EXTRA_DIST = \
tests/common.shi \
tests/rose.pnm \
+ tests/input_svg_gradient_transform.svg \
tests/input_256c.miff \
tests/input_bilevel.miff \
tests/input_gray.miff \
diff --git a/tests/cli-svg.tap b/tests/cli-svg.tap
new file mode 100755
index 000000000..033280217
--- /dev/null
+++ b/tests/cli-svg.tap
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright 1999 ImageMagick Studio LLC, a non-profit organization
+# dedicated to making software imaging solutions freely available.
+#
+# You may not use this file except in compliance with the License. You may
+# obtain a copy of the License at
+#
+# https://imagemagick.org/license/
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Regression tests for SVG coder.
+#
+. ./common.shi
+. ${srcdir}/tests/common.shi
+echo "1..1"
+
+# gradientTransform should not cause a double-free / SIGABRT (#8582)
+${MAGICK} ${SRCDIR}/input_svg_gradient_transform.svg null: && echo "ok" || echo "not ok"
+:
diff --git a/tests/input_svg_gradient_transform.svg b/tests/input_svg_gradient_transform.svg
new file mode 100644
index 000000000..4324a990a
--- /dev/null
+++ b/tests/input_svg_gradient_transform.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
+ <defs>
+ <linearGradient id="g" gradientTransform="matrix(1 0 0 1 0 0)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:red"/>
+ <stop offset="1" style="stop-color:blue"/>
+ </linearGradient>
+ </defs>
+ <rect fill="url(#g)" width="100" height="100"/>
+</svg>