From 448e5286883f433fdc1378e414fb05cba456db6a Mon Sep 17 00:00:00 2001 From: Jordi Kroon Date: Sun, 8 Feb 2026 23:46:29 +0100 Subject: [PATCH 1/5] prevent signed int overflow in gdImageCopy functions --- ext/gd/libgd/gd.c | 56 +++++++++++++++++++++++++++ ext/gd/tests/gh21163.phpt | 79 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 ext/gd/tests/gh21163.phpt diff --git a/ext/gd/libgd/gd.c b/ext/gd/libgd/gd.c index 44a90773ee12..2625c20810a5 100644 --- a/ext/gd/libgd/gd.c +++ b/ext/gd/libgd/gd.c @@ -2307,8 +2307,48 @@ void gdImageFilledRectangle (gdImagePtr im, int x1, int y1, int x2, int y2, int _gdImageFilledVRectangle(im, x1, y1, x2, y2, color); } +static int _gdValidateCopyRectBounds( + const gdImagePtr dst, + const gdImagePtr src, + int dstX, int dstY, + int srcX, int srcY, + int w, int h +) { + /* Check for null pointers */ + if (!dst || !src) { + return 0; + } + + /* Check for overflow in dstX + w */ + if (w > 0 && dstX > INT_MAX - w) { + return 0; + } + + /* Check for overflow in dstY + h */ + if (h > 0 && dstY > INT_MAX - h) { + return 0; + } + + /* Check for overflow in srcX + w */ + if (w > 0 && srcX > INT_MAX - w) { + return 0; + } + + /* Check for overflow in srcY + h */ + if (h > 0 && srcY > INT_MAX - h) { + return 0; + } + + return 1; +} + + void gdImageCopy (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h) { + if (!_gdValidateCopyRectBounds(dst, src, dstX, dstY, srcX, srcY, w, h)) { + return; + } + int c; int x, y; int tox, toy; @@ -2390,6 +2430,10 @@ void gdImageCopy (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, so it doesn't pay attention to the alpha channel. */ void gdImageCopyMerge (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h, int pct) { + if (!_gdValidateCopyRectBounds(dst, src, dstX, dstY, srcX, srcY, w, h)) { + return; + } + int c, dc; int x, y; int tox, toy; @@ -2430,6 +2474,10 @@ void gdImageCopyMerge (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int s so it doesn't pay attention to the alpha channel. */ void gdImageCopyMergeGray (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h, int pct) { + if (!_gdValidateCopyRectBounds(dst, src, dstX, dstY, srcX, srcY, w, h)) { + return; + } + int c, dc; int x, y; int tox, toy; @@ -2484,6 +2532,10 @@ void gdImageCopyMergeGray (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, i void gdImageCopyResized (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH) { + if (!_gdValidateCopyRectBounds(dst, src, dstX, dstY, srcX, srcY, dstW, dstH)) { + return; + } + int c; int x, y; int tox, toy; @@ -2594,6 +2646,10 @@ void gdImageCopyResized (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int void gdImageCopyResampled (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH) { + if (!_gdValidateCopyRectBounds(dst, src, dstX, dstY, srcX, srcY, dstW, dstH)) { + return; + } + int x, y; double sy1, sy2, sx1, sx2; diff --git a/ext/gd/tests/gh21163.phpt b/ext/gd/tests/gh21163.phpt new file mode 100644 index 000000000000..46f9c76266f8 --- /dev/null +++ b/ext/gd/tests/gh21163.phpt @@ -0,0 +1,79 @@ +--TEST-- +GH-17772 (prevents signed int overflow in gdImageCopy functions) +--EXTENSIONS-- +gd +--SKIPIF-- + +--FILE-- + +--EXPECT-- +OK imagecopy +OK imagecopymerge +OK imagecopymergegray +OK imagecopyresized +OK imagecopyresampled +done From 59a4db5db6a9c3dd98d56d2bb39c077c41783f48 Mon Sep 17 00:00:00 2001 From: Jordi Kroon Date: Mon, 9 Feb 2026 00:59:17 +0100 Subject: [PATCH 2/5] Add extra check for srcW/H --- ext/gd/libgd/gd.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ext/gd/libgd/gd.c b/ext/gd/libgd/gd.c index 2625c20810a5..3d023976e372 100644 --- a/ext/gd/libgd/gd.c +++ b/ext/gd/libgd/gd.c @@ -2342,7 +2342,6 @@ static int _gdValidateCopyRectBounds( return 1; } - void gdImageCopy (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h) { if (!_gdValidateCopyRectBounds(dst, src, dstX, dstY, srcX, srcY, w, h)) { @@ -2536,6 +2535,10 @@ void gdImageCopyResized (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int return; } + if (!_gdValidateCopyRectBounds(dst, src, dstX, dstY, srcX, srcY, srcW, srcH)) { + return; + } + int c; int x, y; int tox, toy; @@ -2650,6 +2653,10 @@ void gdImageCopyResampled (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, i return; } + if (!_gdValidateCopyRectBounds(dst, src, dstX, dstY, srcX, srcY, srcW, srcH)) { + return; + } + int x, y; double sy1, sy2, sx1, sx2; From 6db4292baa86963a029cb956b3bb9b7aa60e82cb Mon Sep 17 00:00:00 2001 From: Jordi Kroon Date: Tue, 26 May 2026 21:51:43 +0200 Subject: [PATCH 3/5] apply changes from upstream --- ext/gd/libgd/gd.c | 83 ++++++-------------------- ext/gd/libgd/gd_intern.h | 124 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 141 insertions(+), 66 deletions(-) diff --git a/ext/gd/libgd/gd.c b/ext/gd/libgd/gd.c index 3d023976e372..dc79cb1da64d 100644 --- a/ext/gd/libgd/gd.c +++ b/ext/gd/libgd/gd.c @@ -3,6 +3,7 @@ #include #include #include "gd.h" +#include "gd_intern.h" #include "gdhelpers.h" #include "gd_errors.h" @@ -2307,53 +2308,16 @@ void gdImageFilledRectangle (gdImagePtr im, int x1, int y1, int x2, int y2, int _gdImageFilledVRectangle(im, x1, y1, x2, y2, color); } -static int _gdValidateCopyRectBounds( - const gdImagePtr dst, - const gdImagePtr src, - int dstX, int dstY, - int srcX, int srcY, - int w, int h -) { - /* Check for null pointers */ - if (!dst || !src) { - return 0; - } - - /* Check for overflow in dstX + w */ - if (w > 0 && dstX > INT_MAX - w) { - return 0; - } - - /* Check for overflow in dstY + h */ - if (h > 0 && dstY > INT_MAX - h) { - return 0; - } - - /* Check for overflow in srcX + w */ - if (w > 0 && srcX > INT_MAX - w) { - return 0; - } - - /* Check for overflow in srcY + h */ - if (h > 0 && srcY > INT_MAX - h) { - return 0; - } - - return 1; -} - void gdImageCopy (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h) { - if (!_gdValidateCopyRectBounds(dst, src, dstX, dstY, srcX, srcY, w, h)) { - return; - } - int c; int x, y; int tox, toy; int i; int colorMap[gdMaxColors]; - + if (!gdImageClipCopy(dst, &dstX, &dstY, &srcX, &srcY, &w, &h)) { + return; + } if (dst->trueColor) { /* 2.0: much easier when the destination is truecolor. */ /* 2.0.10: needs a transparent-index check that is still valid if @@ -2429,14 +2393,13 @@ void gdImageCopy (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, so it doesn't pay attention to the alpha channel. */ void gdImageCopyMerge (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h, int pct) { - if (!_gdValidateCopyRectBounds(dst, src, dstX, dstY, srcX, srcY, w, h)) { - return; - } - int c, dc; int x, y; int tox, toy; int ncR, ncG, ncB; + if (!gdImageClipCopy(dst, &dstX, &dstY, &srcX, &srcY, &w, &h)) { + return; + } toy = dstY; for (y = srcY; y < (srcY + h); y++) { @@ -2473,15 +2436,16 @@ void gdImageCopyMerge (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int s so it doesn't pay attention to the alpha channel. */ void gdImageCopyMergeGray (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h, int pct) { - if (!_gdValidateCopyRectBounds(dst, src, dstX, dstY, srcX, srcY, w, h)) { - return; - } - int c, dc; int x, y; int tox, toy; int ncR, ncG, ncB; float g; + + if (!gdImageClipCopy(dst, &dstX, &dstY, &srcX, &srcY, &w, &h)) { + return; + } + toy = dstY; for (y = srcY; (y < (srcY + h)); y++) { @@ -2531,14 +2495,6 @@ void gdImageCopyMergeGray (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, i void gdImageCopyResized (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH) { - if (!_gdValidateCopyRectBounds(dst, src, dstX, dstY, srcX, srcY, dstW, dstH)) { - return; - } - - if (!_gdValidateCopyRectBounds(dst, src, dstX, dstY, srcX, srcY, srcW, srcH)) { - return; - } - int c; int x, y; int tox, toy; @@ -2554,7 +2510,9 @@ void gdImageCopyResized (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int if (overflow2(sizeof(int), srcH)) { return; } - + if (!gdImageClipCopyResized(dst, &dstX, &dstY, &dstW, &dstH, &srcX, &srcY, &srcW, &srcH)) { + return; + } stx = (int *) gdMalloc (sizeof (int) * srcW); sty = (int *) gdMalloc (sizeof (int) * srcH); @@ -2649,14 +2607,6 @@ void gdImageCopyResized (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int void gdImageCopyResampled (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH) { - if (!_gdValidateCopyRectBounds(dst, src, dstX, dstY, srcX, srcY, dstW, dstH)) { - return; - } - - if (!_gdValidateCopyRectBounds(dst, src, dstX, dstY, srcX, srcY, srcW, srcH)) { - return; - } - int x, y; double sy1, sy2, sx1, sx2; @@ -2664,6 +2614,9 @@ void gdImageCopyResampled (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, i gdImageCopyResized (dst, src, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH); return; } + if (!gdImageClipCopyResized(dst, &dstX, &dstY, &dstW, &dstH, &srcX, &srcY, &srcW, &srcH)) { + return; + } for (y = dstY; (y < dstY + dstH); y++) { sy1 = ((double) y - (double) dstY) * (double) srcH / (double) dstH; sy2 = ((double) (y + 1) - (double) dstY) * (double) srcH / (double) dstH; diff --git a/ext/gd/libgd/gd_intern.h b/ext/gd/libgd/gd_intern.h index ce72bf1cc1f9..e0f3a71e2bd7 100644 --- a/ext/gd/libgd/gd_intern.h +++ b/ext/gd/libgd/gd_intern.h @@ -1,5 +1,8 @@ #ifndef GD_INTERN_H #define GD_INTERN_H + +#include + #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) #endif @@ -10,5 +13,124 @@ #define MAX3(a,b,c) ((a)<(b)?(MAX(b,c)):(MAX(a,c))) #define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) -#endif +/** + * Clip a copy operation to the destination image bounds. + * Adjusts srcX/srcY to match any clipping applied to dstX/dstY. + * Returns 0 if the operation is entirely outside dst (nothing to do), + * 1 if there is work to do. + */ +static inline int gdImageClipCopy( + gdImagePtr dst, + int *dstX, int *dstY, + int *srcX, int *srcY, + int *w, int *h) +{ + int x1, y1, x2, y2; + + /* overflow-safe dst rect: [dstX, dstY] to [dstX+w, dstY+h] */ + x1 = *dstX; + y1 = *dstY; + + /* check w/h are positive */ + if (*w <= 0 || *h <= 0) { + return 0; + } + + /* overflow check for dstX+w and dstY+h */ + if (*dstX > 0 && *w > INT_MAX - *dstX) { + x2 = INT_MAX; + } else { + x2 = *dstX + *w; + } + if (*dstY > 0 && *h > INT_MAX - *dstY) { + y2 = INT_MAX; + } else { + y2 = *dstY + *h; + } + + /* entirely outside dst? */ + if (x1 >= gdImageSX(dst) || y1 >= gdImageSY(dst) || + x2 <= 0 || y2 <= 0) { + return 0; + } + + /* clip left */ + if (x1 < 0) { + *srcX -= x1; /* advance srcX by the same amount */ + *w += x1; /* reduce width */ + *dstX = 0; + } + + /* clip top */ + if (y1 < 0) { + *srcY -= y1; + *h += y1; + *dstY = 0; + } + + /* clip right */ + if (*dstX + *w > gdImageSX(dst)) { + *w = gdImageSX(dst) - *dstX; + } + + /* clip bottom */ + if (*dstY + *h > gdImageSY(dst)) { + *h = gdImageSY(dst) - *dstY; + } + + /* sanity: clipping may have reduced w/h to zero */ + if (*w <= 0 || *h <= 0) { + return 0; + } + + return 1; +} +static inline int gdImageClipCopyResized( + gdImagePtr dst, + int *dstX, int *dstY, + int *dstW, int *dstH, + int *srcX, int *srcY, + int *srcW, int *srcH) +{ + int orig_dstW = *dstW; + int orig_dstH = *dstH; + + if (*dstW <= 0 || *dstH <= 0 || *srcW <= 0 || *srcH <= 0) { + return 0; + } + if (*dstX >= gdImageSX(dst) || *dstY >= gdImageSY(dst) || + *dstX + *dstW <= 0 || *dstY + *dstH <= 0) { + return 0; + } + + /* clip left — adjust srcX proportionally */ + if (*dstX < 0) { + *srcX += (-*dstX) * *srcW / orig_dstW; + *srcW -= (-*dstX) * *srcW / orig_dstW; + *dstW += *dstX; + *dstX = 0; + } + /* clip top */ + if (*dstY < 0) { + *srcY += (-*dstY) * *srcH / orig_dstH; + *srcH -= (-*dstY) * *srcH / orig_dstH; + *dstH += *dstY; + *dstY = 0; + } + /* clip right */ + if (*dstX + *dstW > gdImageSX(dst)) { + int clip = *dstX + *dstW - gdImageSX(dst); + *srcW -= clip * *srcW / orig_dstW; + *dstW = gdImageSX(dst) - *dstX; + } + /* clip bottom */ + if (*dstY + *dstH > gdImageSY(dst)) { + int clip = *dstY + *dstH - gdImageSY(dst); + *srcH -= clip * *srcH / orig_dstH; + *dstH = gdImageSY(dst) - *dstY; + } + if (*dstW <= 0 || *dstH <= 0) return 0; + return 1; +} +#endif /* GD_INTERN_H */ From cbcf3a28aeda3a310c89cd064a95499136137588 Mon Sep 17 00:00:00 2001 From: Jordi Kroon Date: Tue, 26 May 2026 22:00:37 +0200 Subject: [PATCH 4/5] Added NEWS entry --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index ba7a6bc80584..4483670c5c4c 100644 --- a/NEWS +++ b/NEWS @@ -209,6 +209,7 @@ PHP NEWS invalid quality parameter. (David Carlier) . Check overflow/underflow for imagescale/imagefilter. (David Carlier) . Added gdImageClone to bundled libgd. (David Carlier) + . Fixed bug GH-21163 (Integer overflow in gdImageCopy* functions). (Jordi Kroon) - Gettext: . bind_textdomain_codeset, textdomain and d(*)gettext functions From 65d5607b44158cdf7bc099543e154d74916cfd49 Mon Sep 17 00:00:00 2001 From: Jordi Kroon Date: Tue, 26 May 2026 22:02:35 +0200 Subject: [PATCH 5/5] Added NEWS entry --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 4483670c5c4c..64b039ce286a 100644 --- a/NEWS +++ b/NEWS @@ -209,7 +209,7 @@ PHP NEWS invalid quality parameter. (David Carlier) . Check overflow/underflow for imagescale/imagefilter. (David Carlier) . Added gdImageClone to bundled libgd. (David Carlier) - . Fixed bug GH-21163 (Integer overflow in gdImageCopy* functions). (Jordi Kroon) + . Fixed bug GH-21163 (Integer overflow in gdImageCopy* functions). (Pierre Joye, Jordi Kroon) - Gettext: . bind_textdomain_codeset, textdomain and d(*)gettext functions