Skip to content

Commit 814a932

Browse files
committed
Add ZEND_ACC_NOT_SERIALIZABLE flag
This prevents serialization and unserialization of a class and its children in a way that does not depend on the zend_class_serialize_deny and zend_class_unserialize_deny handlers that will be going away in PHP 9 together with the Serializable interface. In stubs, `@not-serializable` can be used to set this flag. This patch only uses the new flag for a handful of Zend classes, converting the remainder is left for later. Closes GH-7249. Fixes bug #81111.
1 parent 273720d commit 814a932

23 files changed

+125
-31
lines changed

NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ PHP NEWS
1515
- Reflection:
1616
. Fixed bug #80097 (ReflectionAttribute is not a Reflector). (beberlei)
1717

18+
- Standard:
19+
. Fixed bug #81111 (Serialization is unexpectedly allowed on anonymous classes
20+
with __serialize()). (Nikita)
21+
1822
08 Jul 2021, PHP 8.1.0alpha3
1923

2024
- Core:

UPGRADING.INTERNALS

+4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ PHP 8.1 INTERNALS UPGRADE NOTES
4747
implementations. Use ZEND_LONG_FMT and ZEND_ULONG_FMT instead.
4848
e. ZEND_ATOL() now returns the integer instead of assigning it as part of the
4949
macro. Replace ZEND_ATOL(i, s) with i = ZEND_ATOL(s).
50+
f. Non-serializable classes should be indicated using the
51+
ZEND_ACC_NOT_SERIALIZABLE (@not-serializable in stubs) rather than the
52+
zend_class_(un)serialize_deny handlers. Support for the serialization
53+
handlers will be dropped in the future.
5054

5155
========================
5256
2. Build system changes

Zend/tests/generators/errors/serialize_unserialize_error.phpt

+4-4
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ Stack trace:
3232
#0 %s(%d): serialize(Object(Generator))
3333
#1 {main}
3434

35+
Exception: Unserialization of 'Generator' is not allowed in %s:%d
36+
Stack trace:
37+
#0 %s(%d): unserialize('O:9:"Generator"...')
38+
#1 {main}
3539

36-
Warning: Erroneous data format for unserializing 'Generator' in %sserialize_unserialize_error.php on line %d
37-
38-
Notice: unserialize(): Error at offset 19 of 20 bytes in %sserialize_unserialize_error.php on line %d
39-
bool(false)
4040
Exception: Unserialization of 'Generator' is not allowed in %s:%d
4141
Stack trace:
4242
#0 %s(%d): unserialize('C:9:"Generator"...')

Zend/zend_closures.c

-2
Original file line numberDiff line numberDiff line change
@@ -640,8 +640,6 @@ void zend_register_closure_ce(void) /* {{{ */
640640
{
641641
zend_ce_closure = register_class_Closure();
642642
zend_ce_closure->create_object = zend_closure_new;
643-
zend_ce_closure->serialize = zend_class_serialize_deny;
644-
zend_ce_closure->unserialize = zend_class_unserialize_deny;
645643

646644
memcpy(&closure_handlers, &std_object_handlers, sizeof(zend_object_handlers));
647645
closure_handlers.free_obj = zend_closure_free_storage;

Zend/zend_closures.stub.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
/** @generate-class-entries */
44

5-
/** @strict-properties */
5+
/**
6+
* @strict-properties
7+
* @not-serializable
8+
*/
69
final class Closure
710
{
811
private function __construct() {}

Zend/zend_closures_arginfo.h

+2-2
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: 7c4df531cdb30ac4206f43f0d40098666466b9a6 */
2+
* Stub hash: e3b480674671a698814db282c5ea34d438fe519d */
33

44
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Closure___construct, 0, 0, 0)
55
ZEND_END_ARG_INFO()
@@ -47,7 +47,7 @@ static zend_class_entry *register_class_Closure(void)
4747

4848
INIT_CLASS_ENTRY(ce, "Closure", class_Closure_methods);
4949
class_entry = zend_register_internal_class_ex(&ce, NULL);
50-
class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES;
50+
class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE;
5151

5252
return class_entry;
5353
}

Zend/zend_compile.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -7671,8 +7671,7 @@ void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{
76717671

76727672
if (UNEXPECTED((decl->flags & ZEND_ACC_ANON_CLASS))) {
76737673
/* Serialization is not supported for anonymous classes */
7674-
ce->serialize = zend_class_serialize_deny;
7675-
ce->unserialize = zend_class_unserialize_deny;
7674+
ce->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE;
76767675
}
76777676

76787677
if (extends_ast) {

Zend/zend_compile.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ typedef struct _zend_oparray_context {
238238
/* or IS_CONSTANT_VISITED_MARK | | | */
239239
#define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */
240240
/* | | | */
241-
/* Class Flags (unused: 29...) | | | */
241+
/* Class Flags (unused: 30...) | | | */
242242
/* =========== | | | */
243243
/* | | | */
244244
/* Special class types | | | */
@@ -301,6 +301,9 @@ typedef struct _zend_oparray_context {
301301
/* loaded from file cache to process memory | | | */
302302
#define ZEND_ACC_FILE_CACHED (1 << 27) /* X | | | */
303303
/* | | | */
304+
/* Class cannot be serialized or unserialized | | | */
305+
#define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */
306+
/* | | | */
304307
/* Function Flags (unused: 27-30) | | | */
305308
/* ============== | | | */
306309
/* | | | */

Zend/zend_fibers.c

-2
Original file line numberDiff line numberDiff line change
@@ -868,8 +868,6 @@ void zend_register_fiber_ce(void)
868868
{
869869
zend_ce_fiber = register_class_Fiber();
870870
zend_ce_fiber->create_object = zend_fiber_object_create;
871-
zend_ce_fiber->serialize = zend_class_serialize_deny;
872-
zend_ce_fiber->unserialize = zend_class_unserialize_deny;
873871

874872
zend_fiber_handlers = std_object_handlers;
875873
zend_fiber_handlers.dtor_obj = zend_fiber_object_destroy;

Zend/zend_fibers.stub.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
/** @generate-class-entries */
44

5-
/** @strict-properties */
5+
/**
6+
* @strict-properties
7+
* @not-serializable
8+
*/
69
final class Fiber
710
{
811
public function __construct(callable $callback) {}

Zend/zend_fibers_arginfo.h

+2-2
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: 7a3a7030f97d2c1e787499ef25341607841a607c */
2+
* Stub hash: e82bbc8e81fe98873a9a5697a4b38e63a24379da */
33

44
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Fiber___construct, 0, 0, 1)
55
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0)
@@ -79,7 +79,7 @@ static zend_class_entry *register_class_Fiber(void)
7979

8080
INIT_CLASS_ENTRY(ce, "Fiber", class_Fiber_methods);
8181
class_entry = zend_register_internal_class_ex(&ce, NULL);
82-
class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES;
82+
class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE;
8383

8484
return class_entry;
8585
}

Zend/zend_generators.c

-2
Original file line numberDiff line numberDiff line change
@@ -1117,8 +1117,6 @@ void zend_register_generator_ce(void) /* {{{ */
11171117
{
11181118
zend_ce_generator = register_class_Generator(zend_ce_iterator);
11191119
zend_ce_generator->create_object = zend_generator_create;
1120-
zend_ce_generator->serialize = zend_class_serialize_deny;
1121-
zend_ce_generator->unserialize = zend_class_unserialize_deny;
11221120
/* get_iterator has to be assigned *after* implementing the interface */
11231121
zend_ce_generator->get_iterator = zend_generator_get_iterator;
11241122

Zend/zend_generators.stub.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
/** @generate-class-entries */
44

5-
/** @strict-properties */
5+
/**
6+
* @strict-properties
7+
* @not-serializable
8+
*/
69
final class Generator implements Iterator
710
{
811
public function rewind(): void {}

Zend/zend_generators_arginfo.h

+2-2
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: 06d4e8126db48fe8633ecd40b93904a0f9c59263 */
2+
* Stub hash: 0af5e8985dd4645bf23490b8cec312f8fd1fee2e */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Generator_rewind, 0, 0, IS_VOID, 0)
55
ZEND_END_ARG_INFO()
@@ -58,7 +58,7 @@ static zend_class_entry *register_class_Generator(zend_class_entry *class_entry_
5858

5959
INIT_CLASS_ENTRY(ce, "Generator", class_Generator_methods);
6060
class_entry = zend_register_internal_class_ex(&ce, NULL);
61-
class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES;
61+
class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE;
6262
zend_class_implements(class_entry, 1, class_entry_Iterator);
6363

6464
return class_entry;

Zend/zend_inheritance.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1629,7 +1629,7 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par
16291629
ce->ce_flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS;
16301630
}
16311631
}
1632-
ce->ce_flags |= parent_ce->ce_flags & (ZEND_HAS_STATIC_IN_METHODS | ZEND_ACC_HAS_TYPE_HINTS | ZEND_ACC_USE_GUARDS);
1632+
ce->ce_flags |= parent_ce->ce_flags & (ZEND_HAS_STATIC_IN_METHODS | ZEND_ACC_HAS_TYPE_HINTS | ZEND_ACC_USE_GUARDS | ZEND_ACC_NOT_SERIALIZABLE);
16331633
}
16341634
/* }}} */
16351635

build/gen_stub.php

+12
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,8 @@ class ClassInfo {
11951195
public $isDeprecated;
11961196
/** @var bool */
11971197
public $isStrictProperties;
1198+
/** @var bool */
1199+
public $isNotSerializable;
11981200
/** @var Name[] */
11991201
public $extends;
12001202
/** @var Name[] */
@@ -1217,6 +1219,7 @@ public function __construct(
12171219
?string $alias,
12181220
bool $isDeprecated,
12191221
bool $isStrictProperties,
1222+
bool $isNotSerializable,
12201223
array $extends,
12211224
array $implements,
12221225
array $propertyInfos,
@@ -1228,6 +1231,7 @@ public function __construct(
12281231
$this->alias = $alias;
12291232
$this->isDeprecated = $isDeprecated;
12301233
$this->isStrictProperties = $isStrictProperties;
1234+
$this->isNotSerializable = $isNotSerializable;
12311235
$this->extends = $extends;
12321236
$this->implements = $implements;
12331237
$this->propertyInfos = $propertyInfos;
@@ -1318,6 +1322,10 @@ private function getFlagsAsString(): string
13181322
$flags[] = "ZEND_ACC_NO_DYNAMIC_PROPERTIES";
13191323
}
13201324

1325+
if ($this->isNotSerializable) {
1326+
$flags[] = "ZEND_ACC_NOT_SERIALIZABLE";
1327+
}
1328+
13211329
return implode("|", $flags);
13221330
}
13231331
}
@@ -1625,6 +1633,7 @@ function parseClass(Name $name, Stmt\ClassLike $class, array $properties, array
16251633
$alias = null;
16261634
$isDeprecated = false;
16271635
$isStrictProperties = false;
1636+
$isNotSerializable = false;
16281637

16291638
if ($comment) {
16301639
$tags = parseDocComment($comment);
@@ -1635,6 +1644,8 @@ function parseClass(Name $name, Stmt\ClassLike $class, array $properties, array
16351644
$isDeprecated = true;
16361645
} else if ($tag->name === 'strict-properties') {
16371646
$isStrictProperties = true;
1647+
} else if ($tag->name === 'not-serializable') {
1648+
$isNotSerializable = true;
16381649
}
16391650
}
16401651
}
@@ -1658,6 +1669,7 @@ function parseClass(Name $name, Stmt\ClassLike $class, array $properties, array
16581669
$alias,
16591670
$isDeprecated,
16601671
$isStrictProperties,
1672+
$isNotSerializable,
16611673
$extends,
16621674
$implements,
16631675
$properties,

ext/spl/spl_directory.c

-2
Original file line numberDiff line numberDiff line change
@@ -2744,8 +2744,6 @@ PHP_MINIT_FUNCTION(spl_directory)
27442744
spl_filesystem_object_handlers.cast_object = spl_filesystem_object_cast;
27452745
spl_filesystem_object_handlers.dtor_obj = spl_filesystem_object_destroy_object;
27462746
spl_filesystem_object_handlers.free_obj = spl_filesystem_object_free_storage;
2747-
spl_ce_SplFileInfo->serialize = zend_class_serialize_deny;
2748-
spl_ce_SplFileInfo->unserialize = zend_class_unserialize_deny;
27492747

27502748
spl_ce_DirectoryIterator = register_class_DirectoryIterator(spl_ce_SplFileInfo, spl_ce_SeekableIterator);
27512749
spl_ce_DirectoryIterator->create_object = spl_filesystem_object_new;

ext/spl/spl_directory.stub.php

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
/** @generate-class-entries */
44

5+
/** @not-serializable */
56
class SplFileInfo implements Stringable
67
{
78
public function __construct(string $filename) {}

ext/spl/spl_directory_arginfo.h

+2-1
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: 7c64c21963df5a11e902298eb5957b868c4b48cf */
2+
* Stub hash: eab71d8a7172dba2dac3c6fa97b2064c7a99191f */
33

44
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SplFileInfo___construct, 0, 0, 1)
55
ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0)
@@ -496,6 +496,7 @@ static zend_class_entry *register_class_SplFileInfo(zend_class_entry *class_entr
496496

497497
INIT_CLASS_ENTRY(ce, "SplFileInfo", class_SplFileInfo_methods);
498498
class_entry = zend_register_internal_class_ex(&ce, NULL);
499+
class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE;
499500
zend_class_implements(class_entry, 1, class_entry_Stringable);
500501

501502
return class_entry;

ext/standard/tests/serialize/bug67072.phpt

+6-5
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
Bug #67072 Echoing unserialized "SplFileObject" crash
33
--FILE--
44
<?php
5-
echo unserialize('O:13:"SplFileObject":1:{s:9:"*filename";s:15:"/home/flag/flag";}');
5+
echo unserialize('O:13:"SplFileObject":1:{s:9:"*filename";s:15:"/home/flag/flag";}');
66
?>
77
===DONE==
88
--EXPECTF--
9-
Warning: Erroneous data format for unserializing 'SplFileObject' in %sbug67072.php on line %d
10-
11-
Notice: unserialize(): Error at offset 24 of 64 bytes in %sbug67072.php on line %d
12-
===DONE==
9+
Fatal error: Uncaught Exception: Unserialization of 'SplFileObject' is not allowed in %s:%d
10+
Stack trace:
11+
#0 %s(%d): unserialize('O:13:"SplFileOb...')
12+
#1 {main}
13+
thrown in %s on line %d
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
--TEST--
2+
Bug #81111: Serialization is unexpectedly allowed on anonymous classes with __serialize()
3+
--FILE--
4+
<?php
5+
6+
class MySplFileInfo extends SplFileInfo {
7+
public function __serialize() { return []; }
8+
public function __unserialize($value) { return new self('file'); }
9+
}
10+
11+
try {
12+
serialize(new MySplFileInfo(__FILE__));
13+
} catch (Exception $e) {
14+
echo $e->getMessage(), "\n";
15+
}
16+
17+
$anon = new class () {
18+
public function __serialize() { return []; }
19+
public function __unserialize($value) { }
20+
};
21+
22+
try {
23+
serialize($anon);
24+
} catch (Exception $e) {
25+
echo $e->getMessage(), "\n";
26+
}
27+
28+
try {
29+
unserialize("O:13:\"MySplFileInfo\":0:{}");
30+
} catch (Exception $e) {
31+
echo $e->getMessage(), "\n";
32+
}
33+
try {
34+
unserialize("C:13:\"MySplFileInfo\":0:{}");
35+
} catch (Exception $e) {
36+
echo $e->getMessage(), "\n";
37+
}
38+
39+
$name = $anon::class;
40+
try {
41+
unserialize("O:" . strlen($name) . ":\"" . $name . "\":0:{}");
42+
} catch (Exception $e) {
43+
echo $e->getMessage(), "\n";
44+
}
45+
46+
?>
47+
--EXPECTF--
48+
Serialization of 'MySplFileInfo' is not allowed
49+
Serialization of 'class@anonymous' is not allowed
50+
Unserialization of 'MySplFileInfo' is not allowed
51+
Unserialization of 'MySplFileInfo' is not allowed
52+
53+
Notice: unserialize(): Error at offset 0 of %d bytes in %s on line %d

ext/standard/var.c

+7
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "basic_functions.h"
2828
#include "php_incomplete_class.h"
2929
#include "zend_enum.h"
30+
#include "zend_exceptions.h"
3031
/* }}} */
3132

3233
struct php_serialize_data {
@@ -1058,6 +1059,12 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_
10581059
bool incomplete_class;
10591060
uint32_t count;
10601061

1062+
if (ce->ce_flags & ZEND_ACC_NOT_SERIALIZABLE) {
1063+
zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed",
1064+
ZSTR_VAL(ce->name));
1065+
return;
1066+
}
1067+
10611068
if (ce->ce_flags & ZEND_ACC_ENUM) {
10621069
PHP_CLASS_ATTRIBUTES;
10631070

ext/standard/var_unserializer.re

+8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "ext/standard/php_var.h"
1919
#include "php_incomplete_class.h"
2020
#include "zend_portability.h"
21+
#include "zend_exceptions.h"
2122

2223
/* {{{ reference-handling for unserializer: var_* */
2324
#define VAR_ENTRIES_MAX 1018 /* 1024 - offsetof(php_unserialize_data, entries) / sizeof(void*) */
@@ -1267,6 +1268,13 @@ object ":" uiv ":" ["] {
12671268

12681269
*p = YYCURSOR;
12691270

1271+
if (ce->ce_flags & ZEND_ACC_NOT_SERIALIZABLE) {
1272+
zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed",
1273+
ZSTR_VAL(ce->name));
1274+
zend_string_release_ex(class_name, 0);
1275+
return 0;
1276+
}
1277+
12701278
if (custom_object) {
12711279
int ret;
12721280

0 commit comments

Comments
 (0)