Commit fb217524830 for php.net

commit fb217524830e9fe5a16419e4954239ec393c3e14
Merge: 4fc487f6d9c e5c3b4b0833
Author: Ilija Tovilo <ilija.tovilo@me.com>
Date:   Tue Jul 28 11:58:05 2026 +0200

    Merge branch 'PHP-8.5'

    * PHP-8.5:
      Fix out-of-bounds write in ext-bcmath bccomp() via bc_str2num()
      Add NEWS entries
      Fix GHSA-vc5h-9ppw-p5f3: phar circular symlink crash
      Fix SQL injection in ext-pgsql via E'...' backslash breakout
      libgd patch for CVE-2026-9672

diff --cc ext/gd/libgd/gd_gif_in.c
index 5a19a8a3abd,14c27f3293c..9de085f93d4
--- a/ext/gd/libgd/gd_gif_in.c
+++ b/ext/gd/libgd/gd_gif_in.c
@@@ -1156,390 -316,338 +1156,392 @@@ static int DoExtension(gdIOCtx *fd, in
  			Gif89.inputFlag   = (buf[0] >> 1) & 0x1;
  			Gif89.delayTime   = LM_to_uint(buf[1],buf[2]);
  #endif
 -			if ((buf[0] & 0x1) != 0)
 -				*Transparent = buf[3];
 +        if ((buf[0] & 0x1) != 0) {
 +            *Transparent = buf[3];
 +        }

 -			while (GetDataBlock(fd, (unsigned char*) buf, ZeroDataBlockP) > 0);
 -			return FALSE;
 -		default:
 -			break;
 -	}
 -       while (GetDataBlock(fd, (unsigned char*) buf, ZeroDataBlockP) > 0)
 -		;
 +        while (GetDataBlock(fd, (unsigned char *)buf, ZeroDataBlockP) > 0)
 +            ;
 +        return FALSE;

 -	return FALSE;
 +    default:
 +        break;
 +    }
 +
 +    while (GetDataBlock(fd, (unsigned char *)buf, ZeroDataBlockP) > 0)
 +        ;
 +
 +    return FALSE;
  }
 -/* }}} */

 -static int
 -GetDataBlock_(gdIOCtx *fd, unsigned char *buf, int *ZeroDataBlockP)
 +static int GetDataBlock_(gdIOCtx *fd, unsigned char *buf, int *ZeroDataBlockP)
  {
 -	unsigned char   count;
 +    unsigned char count;

 -	if (! ReadOK(fd,&count,1)) {
 -		return -1;
 -	}
 +    if (!ReadOK(fd, &count, 1)) {
 +        return -1;
 +    }

 -	*ZeroDataBlockP = count == 0;
 +    *ZeroDataBlockP = count == 0;

 -	if ((count != 0) && (! ReadOK(fd, buf, count))) {
 -		return -1;
 -	}
 +    if ((count != 0) && (!ReadOK(fd, buf, count))) {
 +        return -1;
 +    }

 -	return count;
 +    return count;
  }
 -/* }}} */

 -static int
 -GetDataBlock(gdIOCtx *fd, unsigned char *buf, int *ZeroDataBlockP)
 -{
 -	int rv;
 -	int i;
 -
 -	rv = GetDataBlock_(fd,buf, ZeroDataBlockP);
 -	if (VERBOSE) {
 -		char *tmp = NULL;
 -		if (rv > 0) {
 -			tmp = safe_emalloc(3 * rv, sizeof(char), 1);
 -			for (i=0;i<rv;i++) {
 -				snprintf(tmp + 3 * i, 4, " %02x", buf[i]);
 -			}
 -		} else {
 -			tmp = estrdup("");
 -		}
 -		gd_error_ex(GD_NOTICE, "[GetDataBlock returning %d: %s]", rv, tmp);
 -		efree(tmp);
 -	}
 -	return(rv);
 +static int GetDataBlock(gdIOCtx *fd, unsigned char *buf, int *ZeroDataBlockP)
 +{
 +    int rv, i;
 +
 +    rv = GetDataBlock_(fd, buf, ZeroDataBlockP);
 +
 +    if (VERBOSE) {
 +        printf("[GetDataBlock returning %d", rv);
 +        if (rv > 0) {
 +            printf(":");
 +            for (i = 0; i < rv; i++) {
 +                printf(" %02x", buf[i]);
 +            }
 +        }
 +        printf("]\n");
 +    }
 +
 +    return rv;
  }
 -/* }}} */

 -static int
 -GetCode_(gdIOCtx *fd, CODE_STATIC_DATA *scd, int code_size, int flag, int *ZeroDataBlockP)
 +static int GetCode_(gdIOCtx *fd, CODE_STATIC_DATA *scd, int code_size, int flag,
 +                    int *ZeroDataBlockP)
  {
 -	int           i, j, ret;
 -	int           count;
 -
 -	if (flag) {
 -		scd->curbit = 0;
 -		scd->lastbit = 0;
 -		scd->last_byte = 2;
 -		scd->done = FALSE;
 -		return 0;
 -	}
 -
 -	if ( (scd->curbit + code_size) >= scd->lastbit) {
 -		if (scd->done) {
 -			if (scd->curbit >= scd->lastbit) {
 -				/* Oh well */
 -			}
 -			return -1;
 -		}
 -		scd->buf[0] = scd->buf[scd->last_byte-2];
 -		scd->buf[1] = scd->buf[scd->last_byte-1];
 +    int i, j, ret;
 +    int count;
 +
 +    if (flag) {
 +        scd->curbit = 0;
 +        scd->lastbit = 0;
 +        scd->last_byte = 2;
 +        scd->done = FALSE;
 +        return 0;
 +    }
 +
 +    if ((scd->curbit + code_size) >= scd->lastbit) {
 +        if (scd->done) {
 +            if (scd->curbit >= scd->lastbit) {
 +                /* Oh well */
 +            }
 +            return -1;
 +        }
 +
 +        scd->buf[0] = scd->buf[scd->last_byte - 2];
 +        scd->buf[1] = scd->buf[scd->last_byte - 1];
 +        scd->last_byte = 2;
 +        scd->curbit = (scd->curbit - scd->lastbit) + 16;
 +        scd->lastbit = 16;
 +
 +        do {
 +            if (scd->last_byte > (CSD_BUF_SIZE - 255)) {
 +                return -1;
 +            }
 +
 +            if ((count = GetDataBlock(fd, &scd->buf[scd->last_byte], ZeroDataBlockP)) <= 0) {
 +                scd->done = TRUE;
 +                break;
 +            }
 +
 +            scd->last_byte += count;
 +            scd->lastbit += count * 8;
 +        } while ((scd->curbit + code_size) > scd->lastbit);
 +    }
 +
 +    if ((scd->curbit + code_size) > scd->lastbit) {
 +        return -1;
 +    }
 +
 +    if ((scd->curbit + code_size - 1) >= (CSD_BUF_SIZE * 8)) {
 +        ret = -1;
 +    } else {
 +        ret = 0;
 +        for (i = scd->curbit, j = 0; j < code_size; ++i, ++j) {
 +            ret |= ((scd->buf[i / 8] & (1 << (i % 8))) != 0) << j;
 +        }
 +    }
 +
 +    scd->curbit += code_size;
 +
 +    return ret;
 +}

 -               if ((count = GetDataBlock(fd, &scd->buf[2], ZeroDataBlockP)) <= 0)
 -			scd->done = TRUE;
 +static int GetCode(gdIOCtx *fd, CODE_STATIC_DATA *scd, int code_size, int flag, int *ZeroDataBlockP)
 +{
 +    int rv;

 -		scd->last_byte = 2 + count;
 -		scd->curbit = (scd->curbit - scd->lastbit) + 16;
 -		scd->lastbit = (2+count)*8 ;
 -	}
 +    rv = GetCode_(fd, scd, code_size, flag, ZeroDataBlockP);

 -	if ((scd->curbit + code_size - 1) >= (CSD_BUF_SIZE * 8)) {
 -		ret = -1;
 -	} else {
 -		ret = 0;
 -		for (i = scd->curbit, j = 0; j < code_size; ++i, ++j) {
 -			ret |= ((scd->buf[i / 8] & (1 << (i % 8))) != 0) << j;
 -		}
 -	}
 +    if (VERBOSE) {
 +        printf("[GetCode(,%d,%d) returning %d]\n", code_size, flag, rv);
 +    }

 -	scd->curbit += code_size;
 -	return ret;
 +    return rv;
  }

 -static int
 -GetCode(gdIOCtx *fd, CODE_STATIC_DATA *scd, int code_size, int flag, int *ZeroDataBlockP)
 +static int LWZReadByte_(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, int input_code_size,
 +                        int *ZeroDataBlockP)
  {
 -	int rv;
 -
 - rv = GetCode_(fd, scd, code_size,flag, ZeroDataBlockP);
 - if (VERBOSE) printf("[GetCode(,%d,%d) returning %d]\n",code_size,flag,rv);
 -	return(rv);
 +    int code, incode, i;
 +
 +    if (flag) {
 +        sd->set_code_size = input_code_size;
 +        sd->code_size = sd->set_code_size + 1;
 +        sd->clear_code = 1 << sd->set_code_size;
 +        sd->end_code = sd->clear_code + 1;
 +        sd->max_code_size = 2 * sd->clear_code;
 +        sd->max_code = sd->clear_code + 2;
 +
 +        GetCode(fd, &sd->scd, 0, TRUE, ZeroDataBlockP);
 +
 +        sd->fresh = TRUE;
 +
 +        for (i = 0; i < sd->clear_code; ++i) {
 +            sd->table[0][i] = 0;
 +            sd->table[1][i] = i;
 +        }
 +
 +        for (; i < (1 << MAX_LWZ_BITS); ++i) {
-             sd->table[0][i] = sd->table[1][0] = 0;
++            sd->table[0][i] = sd->table[1][i] = 0;
 +        }
 +
 +        sd->sp = sd->stack;
 +
 +        return 0;
 +
 +    } else if (sd->fresh) {
 +        sd->fresh = FALSE;
 +
 +        do {
 +            sd->firstcode = sd->oldcode =
 +                GetCode(fd, &sd->scd, sd->code_size, FALSE, ZeroDataBlockP);
 +        } while (sd->firstcode == sd->clear_code);
 +
 +        return sd->firstcode;
 +    }
 +
 +    if (sd->sp > sd->stack) {
 +        return *--sd->sp;
 +    }
 +
 +    while ((code = GetCode(fd, &sd->scd, sd->code_size, FALSE, ZeroDataBlockP)) >= 0) {
 +        if (code == sd->clear_code) {
 +            for (i = 0; i < sd->clear_code; ++i) {
 +                sd->table[0][i] = 0;
 +                sd->table[1][i] = i;
 +            }
 +
 +            for (; i < (1 << MAX_LWZ_BITS); ++i) {
 +                sd->table[0][i] = sd->table[1][i] = 0;
 +            }
 +
 +            sd->code_size = sd->set_code_size + 1;
 +            sd->max_code_size = 2 * sd->clear_code;
 +            sd->max_code = sd->clear_code + 2;
 +            sd->sp = sd->stack;
 +            sd->firstcode = sd->oldcode =
 +                GetCode(fd, &sd->scd, sd->code_size, FALSE, ZeroDataBlockP);
 +
 +            return sd->firstcode;
 +        } else if (code == sd->end_code) {
 +            int count;
 +            unsigned char buf[260];
 +
 +            if (*ZeroDataBlockP) {
 +                return -2;
 +            }
 +
 +            while ((count = GetDataBlock(fd, buf, ZeroDataBlockP)) > 0)
 +                ;
 +
 +            if (count != 0) {
 +                return -2;
 +            }
++
++            return -2;
 +        }
 +
 +        incode = code;
 +
 +        if (sd->sp == (sd->stack + STACK_SIZE)) {
 +            /* Bad compressed data stream */
 +            return -1;
 +        }
 +
 +        if (code >= sd->max_code) {
 +            *sd->sp++ = sd->firstcode;
 +            code = sd->oldcode;
 +        }
 +
 +        while (code >= sd->clear_code) {
 +            if (sd->sp == (sd->stack + STACK_SIZE)) {
 +                /* Bad compressed data stream */
 +                return -1;
 +            }
 +            if (code >= (1 << MAX_LWZ_BITS)) {
 +                /* Corrupted code */
 +                return -1;
 +            }
 +
 +            *sd->sp++ = sd->table[1][code];
 +
 +            if (code == sd->table[0][code]) {
 +                /* Oh well */
 +            }
 +
 +            code = sd->table[0][code];
 +        }
 +        if (code >= (1 << MAX_LWZ_BITS)) {
 +            /* Corrupted code */
 +            return -1;
 +        }
 +
 +        *sd->sp++ = sd->firstcode = sd->table[1][code];
 +
 +        if ((code = sd->max_code) < (1 << MAX_LWZ_BITS)) {
 +            sd->table[0][code] = sd->oldcode;
 +            sd->table[1][code] = sd->firstcode;
 +            ++sd->max_code;
 +
 +            if ((sd->max_code >= sd->max_code_size) && (sd->max_code_size < (1 << MAX_LWZ_BITS))) {
 +                sd->max_code_size *= 2;
 +                ++sd->code_size;
 +            }
 +        }
 +
 +        sd->oldcode = incode;
 +
 +        if (sd->sp > sd->stack) {
 +            return *--sd->sp;
 +        }
 +    }
 +
 +    return code;
  }
 -/* }}} */

 -static int
 -LWZReadByte_(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, int input_code_size, int *ZeroDataBlockP)
 -{
 -	int code, incode, i;
 -
 -	if (flag) {
 -		sd->set_code_size = input_code_size;
 -		sd->code_size = sd->set_code_size+1;
 -		sd->clear_code = 1 << sd->set_code_size ;
 -		sd->end_code = sd->clear_code + 1;
 -		sd->max_code_size = 2*sd->clear_code;
 -		sd->max_code = sd->clear_code+2;
 -
 -		GetCode(fd, &sd->scd, 0, TRUE, ZeroDataBlockP);
 -
 -		sd->fresh = TRUE;
 -
 -		for (i = 0; i < sd->clear_code; ++i) {
 -			sd->table[0][i] = 0;
 -			sd->table[1][i] = i;
 -		}
 -		for (; i < (1<<MAX_LWZ_BITS); ++i)
 -			sd->table[0][i] = sd->table[1][i] = 0;
 -
 -		sd->sp = sd->stack;
 -
 -		return 0;
 -	} else if (sd->fresh) {
 -		sd->fresh = FALSE;
 -		do {
 -			sd->firstcode = sd->oldcode =
 -			GetCode(fd, &sd->scd, sd->code_size, FALSE, ZeroDataBlockP);
 -		} while (sd->firstcode == sd->clear_code);
 -		return sd->firstcode;
 -	}
 -
 -	if (sd->sp > sd->stack)
 -		return *--sd->sp;
 -
 -	while ((code = GetCode(fd, &sd->scd, sd->code_size, FALSE, ZeroDataBlockP)) >= 0) {
 -		if (code == sd->clear_code) {
 -			for (i = 0; i < sd->clear_code; ++i) {
 -				sd->table[0][i] = 0;
 -				sd->table[1][i] = i;
 -			}
 -			for (; i < (1<<MAX_LWZ_BITS); ++i)
 -				sd->table[0][i] = sd->table[1][i] = 0;
 -			sd->code_size = sd->set_code_size+1;
 -			sd->max_code_size = 2*sd->clear_code;
 -			sd->max_code = sd->clear_code+2;
 -			sd->sp = sd->stack;
 -			sd->firstcode = sd->oldcode =
 -								GetCode(fd, &sd->scd, sd->code_size, FALSE, ZeroDataBlockP);
 -			return sd->firstcode;
 -		} else if (code == sd->end_code) {
 -			int             count;
 -			unsigned char   buf[260];
 -
 -			if (*ZeroDataBlockP)
 -				return -2;
 -
 -			while ((count = GetDataBlock(fd, buf, ZeroDataBlockP)) > 0)
 -				;
 -
 -			if (count != 0)
 -				return -2;
 -
 -			return -2;
 -		}
 -
 -		incode = code;
 -
 -		if (sd->sp == (sd->stack + STACK_SIZE)) {
 -			/* Bad compressed data stream */
 -			return -1;
 -		}
 -
 -		if (code >= sd->max_code) {
 -			*sd->sp++ = sd->firstcode;
 -			code = sd->oldcode;
 -		}
 -
 -		while (code >= sd->clear_code) {
 -			if (sd->sp == (sd->stack + STACK_SIZE)) {
 -				/* Bad compressed data stream */
 -				return -1;
 -			}
 -			*sd->sp++ = sd->table[1][code];
 -			if (code == sd->table[0][code]) {
 -				/* Oh well */
 -			}
 -			code = sd->table[0][code];
 -		}
 -
 -		*sd->sp++ = sd->firstcode = sd->table[1][code];
 -
 -		if ((code = sd->max_code) <(1<<MAX_LWZ_BITS)) {
 -			sd->table[0][code] = sd->oldcode;
 -			sd->table[1][code] = sd->firstcode;
 -			++sd->max_code;
 -			if ((sd->max_code >= sd->max_code_size) &&
 -					(sd->max_code_size < (1<<MAX_LWZ_BITS))) {
 -				sd->max_code_size *= 2;
 -				++sd->code_size;
 -			}
 -		}
 -
 -		sd->oldcode = incode;
 -
 -		if (sd->sp > sd->stack)
 -			return *--sd->sp;
 -	}
 -	return code;
 -}
 -/* }}} */
 -
 -static int
 -LWZReadByte(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, int input_code_size, int *ZeroDataBlockP)
 +static int LWZReadByte(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, int input_code_size,
 +                       int *ZeroDataBlockP)
  {
 -	int rv;
 +    int rv;
 +
 +    rv = LWZReadByte_(fd, sd, flag, input_code_size, ZeroDataBlockP);

 - rv = LWZReadByte_(fd, sd, flag, input_code_size, ZeroDataBlockP);
 - if (VERBOSE) printf("[LWZReadByte(,%d,%d) returning %d]\n",flag,input_code_size,rv);
 -	return(rv);
 +    if (VERBOSE) {
 +        printf("[LWZReadByte(,%d,%d) returning %d]\n", flag, input_code_size, rv);
 +    }
 +
 +    return rv;
  }
 -/* }}} */

 -static void
 -ReadImage(gdImagePtr im, gdIOCtx *fd, int len, int height, unsigned char (*cmap)[256], int interlace, int *ZeroDataBlockP) /*1.4//, int ignore) */
 -{
 -	unsigned char   c;
 -	int             v;
 -	int             xpos = 0, ypos = 0, pass = 0;
 -	int i;
 -	LZW_STATIC_DATA sd = {0};
 -
 -
 -	/*
 -	 **  Initialize the Compression routines
 -	 */
 -	if (! ReadOK(fd,&c,1)) {
 -		return;
 -	}
 -
 -	if (c > MAX_LWZ_BITS) {
 -		return;
 -	}
 -
 -	/* Stash the color map into the image */
 -	for (i=0; (i<gdMaxColors); i++) {
 -		im->red[i] = cmap[CM_RED][i];
 -		im->green[i] = cmap[CM_GREEN][i];
 -		im->blue[i] = cmap[CM_BLUE][i];
 -		im->open[i] = 1;
 -	}
 -	/* Many (perhaps most) of these colors will remain marked open. */
 -	im->colorsTotal = gdMaxColors;
 -	if (LWZReadByte(fd, &sd, TRUE, c, ZeroDataBlockP) < 0) {
 -		return;
 -	}
 -
 -	/*
 -	 **  If this is an "uninteresting picture" ignore it.
 -	 **  REMOVED For 1.4
 -	 */
 -	/*if (ignore) { */
 -	/*        while (LWZReadByte(fd, &sd, FALSE, c) >= 0) */
 -	/*                ; */
 -	/*        return; */
 -	/*} */
 -
 -	while ((v = LWZReadByte(fd, &sd, FALSE, c, ZeroDataBlockP)) >= 0) {
 -		if (v >= gdMaxColors) {
 -			v = 0;
 -		}
 -		/* This how we recognize which colors are actually used. */
 -		if (im->open[v]) {
 -			im->open[v] = 0;
 -		}
 -		gdImageSetPixel(im, xpos, ypos, v);
 -		++xpos;
 -		if (xpos == len) {
 -			xpos = 0;
 -			if (interlace) {
 -				switch (pass) {
 -					case 0:
 -					case 1:
 -						ypos += 8; break;
 -					case 2:
 -						ypos += 4; break;
 -					case 3:
 -						ypos += 2; break;
 -				}
 -
 -				if (ypos >= height) {
 -					++pass;
 -					switch (pass) {
 -						case 1:
 -							ypos = 4; break;
 -						case 2:
 -							ypos = 2; break;
 -						case 3:
 -							ypos = 1; break;
 -						default:
 -							goto fini;
 -					}
 -				}
 -			} else {
 -				++ypos;
 -			}
 -		}
 -		if (ypos >= height)
 -			break;
 -	}
 +static int ReadImage(gdImagePtr im, gdIOCtx *fd, int len, int height, unsigned char (*cmap)[256],
 +                     int colorCount, int interlace, int *ZeroDataBlockP) /*1.4//, int ignore) */
 +{
 +    unsigned char c;
 +    int xpos = 0, ypos = 0, pass = 0;
 +    int v, i;
-     LZW_STATIC_DATA sd;
++    LZW_STATIC_DATA sd = {0};
 +
 +    /* Initialize the Compression routines */
 +    if (!ReadOK(fd, &c, 1)) {
 +        return 0;
 +    }
 +
 +    if (c > MAX_LWZ_BITS) {
 +        return 0;
 +    }
 +
 +    /* Stash the color map into the image */
 +    for (i = 0; (i < gdMaxColors); i++) {
 +        im->red[i] = cmap[CM_RED][i];
 +        im->green[i] = cmap[CM_GREEN][i];
 +        im->blue[i] = cmap[CM_BLUE][i];
 +        im->open[i] = 1;
 +    }
 +
 +    /* Many (perhaps most) of these colors will remain marked open. */
 +    im->colorsTotal = gdMaxColors;
 +    if (LWZReadByte(fd, &sd, TRUE, c, ZeroDataBlockP) < 0) {
 +        return 0;
 +    }
 +
 +    /*
 +     **  If this is an "uninteresting picture" ignore it.
 +     **  REMOVED For 1.4
 +     */
 +    /*if (ignore) { */
 +    /*        while (LWZReadByte(fd, &sd, FALSE, c) >= 0) */
 +    /*                ; */
 +    /*        return; */
 +    /*} */
 +
 +    while ((v = LWZReadByte(fd, &sd, FALSE, c, ZeroDataBlockP)) >= 0) {
 +        if (colorCount > 0 && v >= colorCount) {
 +            return 0;
 +        }
 +        if (v >= gdMaxColors) {
 +            v = 0;
 +        }
 +
 +        /* This how we recognize which colors are actually used. */
 +        if (im->open[v]) {
 +            im->open[v] = 0;
 +        }
 +
 +        gdImageSetPixel(im, xpos, ypos, v);
 +
 +        ++xpos;
 +        if (xpos == len) {
 +            xpos = 0;
 +            if (interlace) {
 +                switch (pass) {
 +                case 0:
 +                case 1:
 +                    ypos += 8;
 +                    break;
 +                case 2:
 +                    ypos += 4;
 +                    break;
 +                case 3:
 +                    ypos += 2;
 +                    break;
 +                }
 +
 +                if (ypos >= height) {
 +                    ++pass;
 +                    switch (pass) {
 +                    case 1:
 +                        ypos = 4;
 +                        break;
 +                    case 2:
 +                        ypos = 2;
 +                        break;
 +                    case 3:
 +                        ypos = 1;
 +                        break;
 +                    default:
 +                        goto fini;
 +                    }
 +                }
 +            } else {
 +                ++ypos;
 +            }
 +        }
 +
 +        if (ypos >= height) {
 +            break;
 +        }
 +    }

  fini:
 -	if (LWZReadByte(fd, &sd, FALSE, c, ZeroDataBlockP) >=0) {
 -		/* Ignore extra */
 -	}
 +    if (LWZReadByte(fd, &sd, FALSE, c, ZeroDataBlockP) >= 0) {
 +        /* Ignore extra */
 +    }
 +    return 1;
  }
  /* }}} */
diff --cc ext/phar/util.c
index c78ee50ece1,7e245fe7585..37373c26bfa
--- a/ext/phar/util.c
+++ b/ext/phar/util.c
@@@ -63,29 -62,55 +63,55 @@@ static zend_string *phar_get_link_locat
  }
  /* }}} */

- phar_entry_info *phar_get_link_source(phar_entry_info *entry) /* {{{ */
+ static phar_entry_info *phar_follow_one_link(phar_entry_info *entry)
  {
  	phar_entry_info *link_entry;
- 	uint32_t depth = 0, max_depth;
 -	char *link;
++	zend_string *link;
+
+ 	link = phar_get_link_location(entry);
 -	if (NULL != (link_entry = zend_hash_str_find_ptr(&(entry->phar->manifest), entry->link, strlen(entry->link))) ||
 -		NULL != (link_entry = zend_hash_str_find_ptr(&(entry->phar->manifest), link, strlen(link)))) {
 -		if (link != entry->link) {
++	if (NULL != (link_entry = zend_hash_find_ptr(&(entry->phar->manifest), entry->symlink)) ||
++		NULL != (link_entry = zend_hash_find_ptr(&(entry->phar->manifest), link))) {
++		if (link != entry->symlink) {
+ 			efree(link);
+ 		}
+ 		return link_entry;
+ 	}
+
 -	if (link != entry->link) {
++	if (link != entry->symlink) {
+ 		efree(link);
+ 	}
+ 	return NULL;
+ }
+
+ phar_entry_info *phar_get_link_source(phar_entry_info *entry)
+ {
+ 	phar_entry_info *slow, *fast;

 -	if (!entry->link) {
 +	if (!entry->symlink) {
  		return entry;
  	}

- 	max_depth = zend_hash_num_elements(&(entry->phar->manifest));
-
- 	while (entry->symlink) {
- 		if (UNEXPECTED(++depth > max_depth)) {
- 			return NULL;
+ 	/*
+ 	 * Use Floyd's cycle detection algorithm to follow the symlink chain without unbounded
+ 	 * recursion. Each entry has at most one outgoing link, so if a cycle exists the fast pointer
+ 	 * will eventually meet the slow one. Otherwise the fast pointer reaches the end first.
+ 	 */
+ 	slow = fast = entry;
+ 	while (1) {
+ 		fast = phar_follow_one_link(fast);
 -		if (!fast || !fast->link) {
++		if (!fast || !fast->symlink) {
+ 			return fast;
+ 		}
+ 		fast = phar_follow_one_link(fast);
 -		if (!fast || !fast->link) {
++		if (!fast || !fast->symlink) {
+ 			return fast;
  		}
- 		zend_string *link = phar_get_link_location(entry);
-
- 		if (NULL != (link_entry = zend_hash_find_ptr(&(entry->phar->manifest), entry->symlink)) ||
- 			NULL != (link_entry = zend_hash_find_ptr(&(entry->phar->manifest), link))) {
- 			zend_string_release(link);
- 			entry = link_entry;
- 		} else {
- 			zend_string_release(link);
+
+ 		/* no need to check slow as it's always behind */
+ 		slow = phar_follow_one_link(slow);
+
+ 		if (slow == fast) {
+ 			/* circular symlink chain */
  			return NULL;
  		}
  	}