Skip to content

Commit 1d5ee08

Browse files
committed
Standard: Validate numeric values passed to pack()
Use zval_try_get_long() and zval_try_get_double() for integer and floating-point format codes, and report conversion failures instead of silently coercing them.
1 parent 278a069 commit 1d5ee08

9 files changed

Lines changed: 710 additions & 35 deletions

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ PHP NEWS
7070
- Standard:
7171
. Fixed a segfault when a stream filter callback unsets StreamBucket::$data
7272
before re-attaching the bucket. (iliaal)
73+
. Fixed pack() accepting values that cannot be converted to int or float for
74+
integer and floating-point format codes. (Weilin Du)
7375
. Fixed an out-of-bounds read when following a redirect response with an
7476
empty Location header. (iliaal)
7577
. Fixed read buffer compaction in php_stream_filter_flush(). (crystarm)

UPGRADING

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,15 @@ PHP 8.6 UPGRADE NOTES
234234
and DirectoryIterator::current() returns string|SplFileInfo|static.
235235

236236
- Standard:
237+
. pack() now throws a TypeError when a value for an integer or floating-point
238+
format code cannot be converted to int or float, instead of silently
239+
coercing it. Floating-point format codes use PHP numeric-string syntax, so
240+
"INF" and "NAN" strings are no longer accepted; the corresponding float
241+
values remain accepted. Integer format codes now emit E_DEPRECATED when
242+
converting a float or float-string to int loses precision. Integer and
243+
floating-point format codes emit E_WARNING for leading-numeric strings.
244+
These diagnostics are propagated if an error handler converts them to
245+
exceptions.
237246
. array_intersect() with at least two arrays now converts values to strings
238247
while scanning its inputs instead of during sort comparisons. This can
239248
change the number and order of conversion warnings and __toString() calls,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
pack() accepts GMP values for numeric format codes
3+
--EXTENSIONS--
4+
gmp
5+
--FILE--
6+
<?php
7+
8+
$integerFormats = ['c', 'C', 's', 'S', 'n', 'v', 'i', 'I', 'l', 'L', 'N', 'V'];
9+
if (PHP_INT_SIZE >= 8) {
10+
array_push($integerFormats, 'q', 'Q', 'J', 'P');
11+
}
12+
$floatFormats = ['f', 'g', 'G', 'd', 'e', 'E'];
13+
$value = gmp_init(42);
14+
15+
$passed = true;
16+
foreach ($integerFormats as $format) {
17+
$actual = unpack($format, pack($format, $value))[1];
18+
if ($actual !== 42) {
19+
echo "Unexpected result for $format: ";
20+
var_dump($actual);
21+
$passed = false;
22+
}
23+
}
24+
echo "integer formats: ";
25+
var_dump($passed);
26+
27+
$passed = true;
28+
foreach ($floatFormats as $format) {
29+
$actual = unpack($format, pack($format, $value))[1];
30+
if ($actual !== 42.0) {
31+
echo "Unexpected result for $format: ";
32+
var_dump($actual);
33+
$passed = false;
34+
}
35+
}
36+
echo "floating-point formats: ";
37+
var_dump($passed);
38+
39+
?>
40+
--EXPECT--
41+
integer formats: bool(true)
42+
floating-point formats: bool(true)

ext/standard/pack.c

Lines changed: 99 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,21 @@ typedef ZEND_SET_ALIGNED(1, unsigned int unaligned_uint);
5555
typedef ZEND_SET_ALIGNED(1, int unaligned_int);
5656

5757
/* {{{ php_pack */
58-
static void php_pack(const zval *val, size_t size, php_pack_endianness endianness, char *output)
58+
static bool php_pack(
59+
const zval *val, uint32_t arg_num, char format_code, size_t size,
60+
php_pack_endianness endianness, char *output
61+
)
5962
{
60-
zend_ulong zl = zval_get_long(val);
63+
bool failed;
64+
zend_ulong zl = zval_try_get_long(val, &failed);
65+
66+
if (UNEXPECTED(failed)) {
67+
zend_argument_type_error(
68+
arg_num, "must be of type int for format code '%c', %s given",
69+
format_code, zend_zval_value_name(val)
70+
);
71+
return false;
72+
}
6173

6274
if ((endianness == PHP_LITTLE_ENDIAN) != MACHINE_LITTLE_ENDIAN) {
6375
zl = PHP_LONG_BSWAP(zl);
@@ -71,9 +83,28 @@ static void php_pack(const zval *val, size_t size, php_pack_endianness endiannes
7183
}
7284

7385
memcpy(output, (const char *) &zl, size);
86+
return true;
7487
}
7588
/* }}} */
7689

90+
static bool php_pack_try_get_double(
91+
const zval *value, uint32_t arg_num, char format_code, double *result
92+
)
93+
{
94+
bool failed;
95+
96+
*result = zval_try_get_double(value, &failed);
97+
if (UNEXPECTED(failed)) {
98+
zend_argument_type_error(
99+
arg_num, "must be of type float for format code '%c', %s given",
100+
format_code, zend_zval_value_name(value)
101+
);
102+
return false;
103+
}
104+
105+
return true;
106+
}
107+
77108
ZEND_ATTRIBUTE_CONST static inline uint16_t php_pack_reverse_int16(uint16_t arg)
78109
{
79110
return ((arg & 0xFF) << 8) | ((arg >> 8) & 0xFF);
@@ -211,6 +242,7 @@ PHP_FUNCTION(pack)
211242
size_t formatcount = 0;
212243
int outputpos = 0, outputsize = 0;
213244
zend_string *output;
245+
bool conversion_failed = false;
214246

215247
ZEND_PARSE_PARAMETERS_START(1, -1)
216248
Z_PARAM_STRING(format, formatlen)
@@ -614,7 +646,14 @@ PHP_FUNCTION(pack)
614646
case 'c':
615647
case 'C':
616648
while (arg-- > 0) {
617-
php_pack(&argv[currentarg++], 1, PHP_MACHINE_ENDIAN, &ZSTR_VAL(output)[outputpos]);
649+
uint32_t arg_num = currentarg + 2;
650+
if (!php_pack(
651+
&argv[currentarg], arg_num, code, 1, PHP_MACHINE_ENDIAN, &ZSTR_VAL(output)[outputpos]
652+
)) {
653+
conversion_failed = true;
654+
goto cleanup;
655+
}
656+
currentarg++;
618657
outputpos++;
619658
}
620659
break;
@@ -636,7 +675,14 @@ PHP_FUNCTION(pack)
636675
}
637676

638677
while (arg-- > 0) {
639-
php_pack(&argv[currentarg++], 2, endianness, &ZSTR_VAL(output)[outputpos]);
678+
uint32_t arg_num = currentarg + 2;
679+
if (!php_pack(
680+
&argv[currentarg], arg_num, code, 2, endianness, &ZSTR_VAL(output)[outputpos]
681+
)) {
682+
conversion_failed = true;
683+
goto cleanup;
684+
}
685+
currentarg++;
640686
outputpos += 2;
641687
}
642688
break;
@@ -645,7 +691,15 @@ PHP_FUNCTION(pack)
645691
case 'i':
646692
case 'I':
647693
while (arg-- > 0) {
648-
php_pack(&argv[currentarg++], sizeof(int), PHP_MACHINE_ENDIAN, &ZSTR_VAL(output)[outputpos]);
694+
uint32_t arg_num = currentarg + 2;
695+
if (!php_pack(
696+
&argv[currentarg], arg_num, code, sizeof(int), PHP_MACHINE_ENDIAN,
697+
&ZSTR_VAL(output)[outputpos]
698+
)) {
699+
conversion_failed = true;
700+
goto cleanup;
701+
}
702+
currentarg++;
649703
outputpos += sizeof(int);
650704
}
651705
break;
@@ -667,7 +721,14 @@ PHP_FUNCTION(pack)
667721
}
668722

669723
while (arg-- > 0) {
670-
php_pack(&argv[currentarg++], 4, endianness, &ZSTR_VAL(output)[outputpos]);
724+
uint32_t arg_num = currentarg + 2;
725+
if (!php_pack(
726+
&argv[currentarg], arg_num, code, 4, endianness, &ZSTR_VAL(output)[outputpos]
727+
)) {
728+
conversion_failed = true;
729+
goto cleanup;
730+
}
731+
currentarg++;
671732
outputpos += 4;
672733
}
673734
break;
@@ -691,7 +752,14 @@ PHP_FUNCTION(pack)
691752
}
692753

693754
while (arg-- > 0) {
694-
php_pack(&argv[currentarg++], 8, endianness, &ZSTR_VAL(output)[outputpos]);
755+
uint32_t arg_num = currentarg + 2;
756+
if (!php_pack(
757+
&argv[currentarg], arg_num, code, 8, endianness, &ZSTR_VAL(output)[outputpos]
758+
)) {
759+
conversion_failed = true;
760+
goto cleanup;
761+
}
762+
currentarg++;
695763
outputpos += 8;
696764
}
697765
break;
@@ -702,7 +770,15 @@ PHP_FUNCTION(pack)
702770
case 'g':
703771
case 'G': {
704772
while (arg-- > 0) {
705-
float v = (float) zval_get_double(&argv[currentarg++]);
773+
double d;
774+
float v;
775+
uint32_t arg_num = currentarg + 2;
776+
if (!php_pack_try_get_double(&argv[currentarg], arg_num, code, &d)) {
777+
conversion_failed = true;
778+
goto cleanup;
779+
}
780+
currentarg++;
781+
v = (float) d;
706782
if (code == 'g' || formatendian[i] == PHP_LITTLE_ENDIAN) {
707783
php_pack_copy_float(1, &ZSTR_VAL(output)[outputpos], v);
708784
} else if (code == 'G' || formatendian[i] == PHP_BIG_ENDIAN) {
@@ -719,7 +795,13 @@ PHP_FUNCTION(pack)
719795
case 'e':
720796
case 'E': {
721797
while (arg-- > 0) {
722-
double v = zval_get_double(&argv[currentarg++]);
798+
double v;
799+
uint32_t arg_num = currentarg + 2;
800+
if (!php_pack_try_get_double(&argv[currentarg], arg_num, code, &v)) {
801+
conversion_failed = true;
802+
goto cleanup;
803+
}
804+
currentarg++;
723805
if (code == 'e' || formatendian[i] == PHP_LITTLE_ENDIAN) {
724806
php_pack_copy_double(1, &ZSTR_VAL(output)[outputpos], v);
725807
} else if (code == 'E' || formatendian[i] == PHP_BIG_ENDIAN) {
@@ -754,11 +836,17 @@ PHP_FUNCTION(pack)
754836
}
755837
}
756838

839+
ZSTR_VAL(output)[outputpos] = '\0';
840+
ZSTR_LEN(output) = outputpos;
841+
842+
cleanup:
757843
efree(formatcodes);
758844
efree(formatargs);
759845
efree(formatendian);
760-
ZSTR_VAL(output)[outputpos] = '\0';
761-
ZSTR_LEN(output) = outputpos;
846+
if (UNEXPECTED(conversion_failed)) {
847+
zend_string_release(output);
848+
RETURN_THROWS();
849+
}
762850
RETURN_NEW_STR(output);
763851
}
764852
/* }}} */

ext/standard/tests/strings/pack_float.phpt

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ pack()/unpack(): float/double tests
44
<?php
55
var_dump(
66
'pack e',
7-
bin2hex(pack("e", "")),
8-
bin2hex(pack("e", "a")),
9-
bin2hex(pack("e", " ")),
107
bin2hex(pack("e", NULL)),
118
bin2hex(pack("e", 0)),
129
bin2hex(pack("e", 1)),
@@ -21,9 +18,6 @@ var_dump(
2118
bin2hex(pack("e", -12345678901234567890.1234567898765432123456789)),
2219

2320
'pack E',
24-
bin2hex(pack("E", "")),
25-
bin2hex(pack("E", "a")),
26-
bin2hex(pack("E", " ")),
2721
bin2hex(pack("E", NULL)),
2822
bin2hex(pack("E", 0)),
2923
bin2hex(pack("E", 1)),
@@ -38,9 +32,6 @@ var_dump(
3832
bin2hex(pack("E", -12345678901234567890.1234567898765432123456789)),
3933

4034
'pack g',
41-
bin2hex(pack("g", "")),
42-
bin2hex(pack("g", "a")),
43-
bin2hex(pack("g", " ")),
4435
bin2hex(pack("g", NULL)),
4536
bin2hex(pack("g", 0)),
4637
bin2hex(pack("g", 1)),
@@ -55,9 +46,6 @@ var_dump(
5546
bin2hex(pack("g", -12345678901234567890.1234567898765432123456789)),
5647

5748
'pack G',
58-
bin2hex(pack("G", "")),
59-
bin2hex(pack("G", "a")),
60-
bin2hex(pack("G", " ")),
6149
bin2hex(pack("G", NULL)),
6250
bin2hex(pack("G", 0)),
6351
bin2hex(pack("G", 1)),
@@ -117,9 +105,6 @@ var_dump(
117105
string(6) "pack e"
118106
string(16) "0000000000000000"
119107
string(16) "0000000000000000"
120-
string(16) "0000000000000000"
121-
string(16) "0000000000000000"
122-
string(16) "0000000000000000"
123108
string(16) "000000000000f03f"
124109
string(16) "000000000000f03f"
125110
string(16) "0080e03779c34143"
@@ -133,9 +118,6 @@ string(16) "e1639d31956ae5c3"
133118
string(6) "pack E"
134119
string(16) "0000000000000000"
135120
string(16) "0000000000000000"
136-
string(16) "0000000000000000"
137-
string(16) "0000000000000000"
138-
string(16) "0000000000000000"
139121
string(16) "3ff0000000000000"
140122
string(16) "3ff0000000000000"
141123
string(16) "4341c37937e08000"
@@ -149,9 +131,6 @@ string(16) "c3e56a95319d63e1"
149131
string(6) "pack g"
150132
string(8) "00000000"
151133
string(8) "00000000"
152-
string(8) "00000000"
153-
string(8) "00000000"
154-
string(8) "00000000"
155134
string(8) "0000803f"
156135
string(8) "0000803f"
157136
string(8) "ca1b0e5a"
@@ -165,9 +144,6 @@ string(8) "aa542bdf"
165144
string(6) "pack G"
166145
string(8) "00000000"
167146
string(8) "00000000"
168-
string(8) "00000000"
169-
string(8) "00000000"
170-
string(8) "00000000"
171147
string(8) "3f800000"
172148
string(8) "3f800000"
173149
string(8) "5a0e1bca"

0 commit comments

Comments
 (0)