Skip to content

Commit 700d456

Browse files
committed
secured unserialize
- update for BC-compatible unserialize - add tests
1 parent 9de44a2 commit 700d456

File tree

6 files changed

+237
-49
lines changed

6 files changed

+237
-49
lines changed

ext/standard/php_var.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ PHPAPI void php_var_serialize(smart_str *buf, zval *struc, php_serialize_data_t
5757
PHPAPI int php_var_unserialize(zval *rval, const unsigned char **p, const unsigned char *max, php_unserialize_data_t *var_hash TSRMLS_DC);
5858
PHPAPI int php_var_unserialize_ref(zval *rval, const unsigned char **p, const unsigned char *max, php_unserialize_data_t *var_hash TSRMLS_DC);
5959
PHPAPI int php_var_unserialize_intern(zval *rval, const unsigned char **p, const unsigned char *max, php_unserialize_data_t *var_hash TSRMLS_DC);
60+
PHPAPI int php_var_unserialize_ex(zval *rval, const unsigned char **p, const unsigned char *max, php_unserialize_data_t *var_hash, HashTable *classes TSRMLS_DC);
6061

6162
#define PHP_VAR_SERIALIZE_INIT(d) \
6263
do { \

ext/standard/tests/serialize/serialization_error_001.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var_dump( unserialize() );
2121

2222
//Test serialize with one more than the expected number of arguments
2323
var_dump( serialize(1,2) );
24-
var_dump( unserialize(1,2) );
24+
var_dump( unserialize(1,2,3) );
2525

2626
echo "Done";
2727
?>
@@ -31,12 +31,12 @@ echo "Done";
3131
Warning: serialize() expects exactly 1 parameter, 0 given in %s on line 16
3232
NULL
3333

34-
Warning: unserialize() expects exactly 1 parameter, 0 given in %s on line 17
34+
Warning: unserialize() expects at least 1 parameter, 0 given in %s on line 17
3535
bool(false)
3636

3737
Warning: serialize() expects exactly 1 parameter, 2 given in %s on line 20
3838
NULL
3939

40-
Warning: unserialize() expects exactly 1 parameter, 2 given in %s on line 21
40+
Warning: unserialize() expects at most 2 parameters, 3 given in %s on line 21
4141
bool(false)
4242
Done
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
--TEST--
2+
Test unserialize() with second parameter
3+
--FILE--
4+
<?php
5+
class foo {
6+
public $x = "bar";
7+
}
8+
$z = array(new foo(), 2, "3");
9+
$s = serialize($z);
10+
11+
var_dump(unserialize($s));
12+
var_dump(unserialize($s, ["allowed_classes" => false]));
13+
var_dump(unserialize($s, ["allowed_classes" => true]));
14+
var_dump(unserialize($s, ["allowed_classes" => ["bar"]]));
15+
var_dump(unserialize($s, ["allowed_classes" => ["FOO"]]));
16+
var_dump(unserialize($s, ["allowed_classes" => ["bar", "foO"]]));
17+
18+
--EXPECTF--
19+
array(3) {
20+
[0]=>
21+
object(foo)#%d (1) {
22+
["x"]=>
23+
string(3) "bar"
24+
}
25+
[1]=>
26+
int(2)
27+
[2]=>
28+
string(1) "3"
29+
}
30+
array(3) {
31+
[0]=>
32+
object(__PHP_Incomplete_Class)#%d (2) {
33+
["__PHP_Incomplete_Class_Name"]=>
34+
string(3) "foo"
35+
["x"]=>
36+
string(3) "bar"
37+
}
38+
[1]=>
39+
int(2)
40+
[2]=>
41+
string(1) "3"
42+
}
43+
array(3) {
44+
[0]=>
45+
object(foo)#%d (1) {
46+
["x"]=>
47+
string(3) "bar"
48+
}
49+
[1]=>
50+
int(2)
51+
[2]=>
52+
string(1) "3"
53+
}
54+
array(3) {
55+
[0]=>
56+
object(__PHP_Incomplete_Class)#%d (2) {
57+
["__PHP_Incomplete_Class_Name"]=>
58+
string(3) "foo"
59+
["x"]=>
60+
string(3) "bar"
61+
}
62+
[1]=>
63+
int(2)
64+
[2]=>
65+
string(1) "3"
66+
}
67+
array(3) {
68+
[0]=>
69+
object(foo)#%d (1) {
70+
["x"]=>
71+
string(3) "bar"
72+
}
73+
[1]=>
74+
int(2)
75+
[2]=>
76+
string(1) "3"
77+
}
78+
array(3) {
79+
[0]=>
80+
object(foo)#%d (1) {
81+
["x"]=>
82+
string(3) "bar"
83+
}
84+
[1]=>
85+
int(2)
86+
[2]=>
87+
string(1) "3"
88+
}

ext/standard/var.c

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -990,16 +990,18 @@ PHP_FUNCTION(serialize)
990990
}
991991
/* }}} */
992992

993-
/* {{{ proto mixed unserialize(string variable_representation)
993+
/* {{{ proto mixed unserialize(string variable_representation[, bool|array allowed_classes])
994994
Takes a string representation of variable and recreates it */
995995
PHP_FUNCTION(unserialize)
996996
{
997997
char *buf = NULL;
998998
size_t buf_len;
999999
const unsigned char *p;
10001000
php_unserialize_data_t var_hash;
1001+
zval *options = NULL, *classes = NULL;
1002+
HashTable *class_hash = NULL;
10011003

1002-
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &buf, &buf_len) == FAILURE) {
1004+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &buf, &buf_len, &options) == FAILURE) {
10031005
RETURN_FALSE;
10041006
}
10051007

@@ -1009,15 +1011,43 @@ PHP_FUNCTION(unserialize)
10091011

10101012
p = (const unsigned char*) buf;
10111013
PHP_VAR_UNSERIALIZE_INIT(var_hash);
1012-
if (!php_var_unserialize(return_value, &p, p + buf_len, &var_hash TSRMLS_CC)) {
1014+
if(options != NULL) {
1015+
classes = zend_hash_str_find(Z_ARRVAL_P(options), "allowed_classes", sizeof("allowed_classes")-1);
1016+
if(classes && (Z_TYPE_P(classes) == IS_ARRAY || !zend_is_true(classes TSRMLS_CC))) {
1017+
ALLOC_HASHTABLE(class_hash);
1018+
zend_hash_init(class_hash, (Z_TYPE_P(classes) == IS_ARRAY)?zend_hash_num_elements(Z_ARRVAL_P(classes)):0, NULL, NULL, 0);
1019+
}
1020+
if(class_hash && Z_TYPE_P(classes) == IS_ARRAY) {
1021+
zval *entry;
1022+
zend_string *lcname;
1023+
1024+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(classes), entry) {
1025+
convert_to_string_ex(entry);
1026+
lcname = zend_string_alloc(Z_STRLEN_P(entry), 0);
1027+
zend_str_tolower_copy(lcname->val, Z_STRVAL_P(entry), Z_STRLEN_P(entry));
1028+
zend_hash_add_empty_element(class_hash, lcname);
1029+
zend_string_release(lcname);
1030+
} ZEND_HASH_FOREACH_END();
1031+
}
1032+
}
1033+
1034+
if (!php_var_unserialize_ex(return_value, &p, p + buf_len, &var_hash, class_hash TSRMLS_CC)) {
10131035
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
1036+
if(class_hash) {
1037+
zend_hash_destroy(class_hash);
1038+
FREE_HASHTABLE(class_hash);
1039+
}
10141040
zval_dtor(return_value);
10151041
if (!EG(exception)) {
10161042
php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Error at offset " ZEND_LONG_FMT " of %d bytes", (zend_long)((char*)p - buf), buf_len);
10171043
}
10181044
RETURN_FALSE;
10191045
}
10201046
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
1047+
if(class_hash) {
1048+
zend_hash_destroy(class_hash);
1049+
FREE_HASHTABLE(class_hash);
1050+
}
10211051
}
10221052
/* }}} */
10231053

0 commit comments

Comments
 (0)