Skip to content

Commit 5d9ab53

Browse files
committed
Check string bounds in strspn/strcspn
strspn/strcspn are string search functions, and as such should throw ValueError on out-of-bounds offsets, just like strpos etc do.
1 parent 12e772f commit 5d9ab53

15 files changed

+5085
-10043
lines changed

ext/standard/basic_functions.stub.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -542,9 +542,9 @@ function bin2hex(string $data): string {}
542542

543543
function hex2bin(string $data): string|false {}
544544

545-
function strspn(string $str, string $mask, int $start = 0, ?int $len = null): int|false {}
545+
function strspn(string $str, string $mask, int $start = 0, ?int $len = null): int {}
546546

547-
function strcspn(string $str, string $mask, int $start = 0, ?int $len = null): int|false {}
547+
function strcspn(string $str, string $mask, int $start = 0, ?int $len = null): int {}
548548

549549
#if HAVE_NL_LANGINFO
550550
function nl_langinfo(int $item): string|false {}

ext/standard/basic_functions_arginfo.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: e71146872a1913d2cef37132c8b27ce1dc9b1a39 */
2+
* Stub hash: 3f866608d73047b04b6a1abb6cbfa75abfeeed58 */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0)
55
ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0)
@@ -807,7 +807,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_hex2bin, 0, 1, MAY_BE_STRING|MAY
807807
ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0)
808808
ZEND_END_ARG_INFO()
809809

810-
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_strspn, 0, 2, MAY_BE_LONG|MAY_BE_FALSE)
810+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_strspn, 0, 2, IS_LONG, 0)
811811
ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0)
812812
ZEND_ARG_TYPE_INFO(0, mask, IS_STRING, 0)
813813
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, start, IS_LONG, 0, "0")

ext/standard/string.c

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -258,33 +258,28 @@ static void php_spn_common_handler(INTERNAL_FUNCTION_PARAMETERS, int behavior) /
258258
Z_PARAM_LONG_OR_NULL(len, len_is_null)
259259
ZEND_PARSE_PARAMETERS_END();
260260

261-
if (len_is_null) {
262-
len = ZSTR_LEN(s11);
263-
}
264-
265-
/* look at substr() function for more information */
266-
267261
if (start < 0) {
268262
start += (zend_long)ZSTR_LEN(s11);
269-
if (start < 0) {
270-
start = 0;
271-
}
272-
} else if ((size_t)start > ZSTR_LEN(s11)) {
273-
RETURN_FALSE;
263+
}
264+
if (start < 0 || (size_t)start > ZSTR_LEN(s11)) {
265+
zend_argument_value_error(3, "must be contained in argument #1 ($str)");
266+
RETURN_THROWS();
274267
}
275268

276-
if (len < 0) {
277-
len += (ZSTR_LEN(s11) - start);
269+
size_t remain_len = ZSTR_LEN(s11) - start;
270+
if (!len_is_null) {
278271
if (len < 0) {
279-
len = 0;
272+
len += remain_len;
280273
}
274+
if (len < 0 || (size_t)len > remain_len) {
275+
zend_argument_value_error(4, "must be contained in argument #1 ($str)");
276+
RETURN_THROWS();
277+
}
278+
} else {
279+
len = remain_len;
281280
}
282281

283-
if (len > (zend_long)ZSTR_LEN(s11) - start) {
284-
len = ZSTR_LEN(s11) - start;
285-
}
286-
287-
if(len == 0) {
282+
if (len == 0) {
288283
RETURN_LONG(0);
289284
}
290285

@@ -293,13 +288,13 @@ static void php_spn_common_handler(INTERNAL_FUNCTION_PARAMETERS, int behavior) /
293288
ZSTR_VAL(s22) /*str2_start*/,
294289
ZSTR_VAL(s11) + start + len /*str1_end*/,
295290
ZSTR_VAL(s22) + ZSTR_LEN(s22) /*str2_end*/));
296-
} else if (behavior == STR_STRCSPN) {
291+
} else {
292+
ZEND_ASSERT(behavior == STR_STRCSPN);
297293
RETURN_LONG(php_strcspn(ZSTR_VAL(s11) + start /*str1_start*/,
298294
ZSTR_VAL(s22) /*str2_start*/,
299295
ZSTR_VAL(s11) + start + len /*str1_end*/,
300296
ZSTR_VAL(s22) + ZSTR_LEN(s22) /*str2_end*/));
301297
}
302-
303298
}
304299
/* }}} */
305300

ext/standard/tests/strings/bug40754.phpt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,17 @@ $v = 2147483647;
88
var_dump(substr("abcde", 1, $v));
99
var_dump(substr_replace("abcde", "x", $v, $v));
1010

11-
var_dump(strspn("abcde", "abc", $v, $v));
12-
var_dump(strcspn("abcde", "abc", $v, $v));
11+
try {
12+
var_dump(strspn("abcde", "abc", $v, $v));
13+
} catch (ValueError $exception) {
14+
echo $exception->getMessage() . "\n";
15+
}
16+
17+
try {
18+
var_dump(strcspn("abcde", "abc", $v, $v));
19+
} catch (ValueError $exception) {
20+
echo $exception->getMessage() . "\n";
21+
}
1322

1423
try {
1524
var_dump(substr_count("abcde", "abc", $v, $v));
@@ -79,8 +88,8 @@ var_dump(substr("abcde", $v, $v));
7988
--EXPECT--
8089
string(4) "bcde"
8190
string(6) "abcdex"
82-
bool(false)
83-
bool(false)
91+
strspn(): Argument #3 ($start) must be contained in argument #1 ($str)
92+
strcspn(): Argument #3 ($start) must be contained in argument #1 ($str)
8493
substr_count(): Argument #3 ($offset) must be contained in argument #1 ($haystack)
8594
substr_compare(): Argument #3 ($offset) must be contained in argument #1 ($main_str)
8695
stripos(): Argument #3 ($offset) must be contained in argument #1 ($haystack)

ext/standard/tests/strings/strcspn.phpt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@ var_dump($b);
99
var_dump(strcspn($a,$b));
1010
var_dump(strcspn($a,$b,9));
1111
var_dump(strcspn($a,$b,9,6));
12-
var_dump(strcspn('a', 'B', 1, 2147483647));
12+
try {
13+
var_dump(strcspn('a', 'B', 1, 2147483647));
14+
} catch (ValueError $e) {
15+
echo $e->getMessage(), "\n";
16+
}
1317
?>
1418
--EXPECT--
1519
string(25) "22222222aaaa bbb1111 cccc"
1620
string(4) "1234"
1721
int(0)
1822
int(7)
1923
int(6)
20-
int(0)
24+
strcspn(): Argument #4 ($len) must be contained in argument #1 ($str)

ext/standard/tests/strings/strcspn_basic.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ echo "*** Testing strcspn() : basic functionality ***\n";
1313
$str = "this is the test string";
1414
$mask = "es";
1515
$start = 15;
16-
$len = 30;
16+
$len = 3;
1717

1818
// Calling strcspn() with all possible arguments
1919
var_dump( strcspn($str, $mask, $start, $len) );

0 commit comments

Comments
 (0)