Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[JsonPath] Handle slice selector overflow #60798

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 7.3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 44 additions & 5 deletions src/Symfony/Component/JsonPath/JsonCrawler.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ private function evaluateBracket(string $expr, mixed $value): array

// single negative index
if (preg_match('/^-\d+$/', $expr)) {
if (JsonPathUtils::hasLeadingZero($expr) || JsonPathUtils::isIntegerOverflow($expr) || '-0' === $expr) {
throw new JsonCrawlerException($expr, 'invalid index selector');
}

if (!array_is_list($value)) {
return [];
}
Expand All @@ -154,6 +158,12 @@ private function evaluateBracket(string $expr, mixed $value): array

// start and end index
if (preg_match('/^-?\d+(?:\s*,\s*-?\d+)*$/', $expr)) {
foreach (explode(',', $expr) as $exprPart) {
if (JsonPathUtils::hasLeadingZero($exprPart = trim($exprPart)) || JsonPathUtils::isIntegerOverflow($exprPart) || '-0' === $exprPart) {
throw new JsonCrawlerException($expr, 'invalid index selector');
}
}

if (!array_is_list($value)) {
return [];
}
Expand All @@ -172,17 +182,41 @@ private function evaluateBracket(string $expr, mixed $value): array
return $result;
}

if (preg_match('/^(-?\d*+)\s*+:\s*+(-?\d*+)(?:\s*+:\s*+(-?\d++))?$/', $expr, $matches)) {
if (preg_match('/^(-?\d*+)\s*+:\s*+(-?\d*+)(?:\s*+:\s*+(-?\d*+))?$/', $expr, $matches)) {
if (!array_is_list($value)) {
return [];
}

$startStr = trim($matches[1]);
$endStr = trim($matches[2]);
$stepStr = trim($matches[3] ?? '1');

if (
JsonPathUtils::hasLeadingZero($startStr)
|| JsonPathUtils::hasLeadingZero($endStr)
|| JsonPathUtils::hasLeadingZero($stepStr)
) {
throw new JsonCrawlerException($expr, 'slice selector numbers cannot have leading zeros');
}

if ('-0' === $startStr || '-0' === $endStr || '-0' === $stepStr) {
throw new JsonCrawlerException($expr, 'slice selector cannot contain negative zero');
}

if (
JsonPathUtils::isIntegerOverflow($startStr)
|| JsonPathUtils::isIntegerOverflow($endStr)
|| JsonPathUtils::isIntegerOverflow($stepStr)
) {
throw new JsonCrawlerException($expr, 'slice selector integer overflow');
}

$length = \count($value);
$start = '' !== $matches[1] ? (int) $matches[1] : null;
$end = '' !== $matches[2] ? (int) $matches[2] : null;
$step = isset($matches[3]) && '' !== $matches[3] ? (int) $matches[3] : 1;
$start = '' !== $startStr ? (int) $startStr : null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this work if $startStr is the representation of an integer that exceeds PHP_INT_MAX? Or is lower than PHP_INT_MINX?

Copy link
Member Author

@alexandre-daubois alexandre-daubois Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be caught by the call to JsonPathUtils::isIntegerOverflow() above: safe JSON integer interval is narrower compared to PHP integer interval. It may be different on 32 bits systems though. I don't know how common it is these days to add a check for it? It would imply more complexity as we would compare numbers as strings.

$end = '' !== $endStr ? (int) $endStr : null;
$step = '' !== $stepStr ? (int) $stepStr : 1;

if (0 === $step || $start > $length) {
if (0 === $step) {
return [];
}

Expand All @@ -192,6 +226,11 @@ private function evaluateBracket(string $expr, mixed $value): array
if ($start < 0) {
$start = $length + $start;
}

if ($step > 0 && $start >= $length) {
return [];
}

$start = max(0, min($start, $length - 1));
}

Expand Down
30 changes: 30 additions & 0 deletions src/Symfony/Component/JsonPath/JsonPathUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,34 @@ public static function parseCommaSeparatedValues(string $expr): array

return $parts;
}

public static function hasLeadingZero(string $s): bool
{
if ('' === $s || str_starts_with($s, '-') && '' === $s = substr($s, 1)) {
return false;
}

return '0' === $s[0] && 1 < \strlen($s);
}

/**
* Safe integer range is [-(2^53) + 1, (2^53) - 1].
*
* @see https://datatracker.ietf.org/doc/rfc9535/, section 2.1
*/
public static function isIntegerOverflow(string $s): bool
{
if ('' === $s) {
return false;
}

if (\strlen($s) > (str_starts_with($s, '-') ? 17 : 16)) {
// obviously too long to represent anything in the interval
return true;
}

$value = (int) $s;

return $value < (-(1 << 53) + 1) || ((1 << 53) - 1) < $value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,6 @@ final class JsonPathComplianceTestSuiteTest extends TestCase
'filter, group terms, right',
'name selector, double quotes, escaped reverse solidus',
'name selector, single quotes, escaped reverse solidus',
'slice selector, slice selector with everything omitted, long form',
'slice selector, start, min exact',
'slice selector, start, max exact',
'slice selector, end, min exact',
'slice selector, end, max exact',
'basic, descendant segment, multiple selectors',
'basic, bald descendant segment',
'filter, relative non-singular query, index, equal',
Expand Down Expand Up @@ -142,12 +137,6 @@ final class JsonPathComplianceTestSuiteTest extends TestCase
'filter, absolute non-singular query, slice, less-or-equal',
'filter, equals, special nothing',
'filter, group terms, left',
'index selector, min exact index - 1',
'index selector, max exact index + 1',
'index selector, overflowing index',
'index selector, leading 0',
'index selector, -0',
'index selector, leading -0',
'name selector, double quotes, escaped line feed',
'name selector, double quotes, invalid escaped single quote',
'name selector, double quotes, question mark escape',
Expand All @@ -172,18 +161,6 @@ final class JsonPathComplianceTestSuiteTest extends TestCase
'name selector, single quotes, escaped backspace',
'name selector, single quotes, escaped line feed',
'name selector, single quotes, invalid escaped double quote',
'slice selector, excessively large from value with negative step',
'slice selector, step, min exact - 1',
'slice selector, step, max exact + 1',
'slice selector, overflowing to value',
'slice selector, underflowing from value',
'slice selector, overflowing from value with negative step',
'slice selector, underflowing to value with negative step',
'slice selector, overflowing step',
'slice selector, underflowing step',
'slice selector, step, leading 0',
'slice selector, step, -0',
'slice selector, step, leading -0',
];

/**
Expand Down
Loading