Skip to content

Commit cc39bc2

Browse files
committed
Fix GH-16590: UAF in session_encode()
The `PS_ENCODE_LOOP` does not protect the session hash table that it iterates over. Change it by temporarily creating a copy. Closes GH-16640.
1 parent faef0df commit cc39bc2

File tree

5 files changed

+55
-2
lines changed

5 files changed

+55
-2
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ PHP NEWS
4949
- Reflection:
5050
. Fixed bug GH-16601 (Memory leak in Reflection constructors). (nielsdos)
5151

52+
- Session:
53+
. Fixed bug GH-16590 (UAF in session_encode()). (nielsdos)
54+
5255
- SPL:
5356
. Fixed bug GH-16588 (UAF in Observer->serialize). (nielsdos)
5457
. Fix GH-16477 (Segmentation fault when calling __debugInfo() after failed

UPGRADING.INTERNALS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,8 @@ PHP 8.4 INTERNALS UPGRADE NOTES
391391
needing to create a zend_string.
392392
- The ext/session/php_session.h doesn't transitively include the
393393
ext/hash/php_hash.h header anymore.
394+
- It is no longer allowed to return out of the PS_ENCODE_LOOP macro.
395+
Instead, you should break out of the loop instead.
394396

395397
i. ext/xml
396398
- Made the expat compatibility wrapper XML_GetCurrentByteIndex return a long

ext/session/php_session.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,13 @@ PHPAPI zend_result php_session_reset_id(void);
291291
zend_ulong num_key; \
292292
zval *struc;
293293

294+
/* Do not use a return statement in `code` because that may leak memory.
295+
* Break out of the loop instead. */
294296
#define PS_ENCODE_LOOP(code) do { \
295-
HashTable *_ht = Z_ARRVAL_P(Z_REFVAL(PS(http_session_vars))); \
297+
zval _zv; \
298+
/* protect against user interference */ \
299+
ZVAL_COPY(&_zv, Z_REFVAL(PS(http_session_vars))); \
300+
HashTable *_ht = Z_ARRVAL(_zv); \
296301
ZEND_HASH_FOREACH_KEY(_ht, num_key, key) { \
297302
if (key == NULL) { \
298303
php_error_docref(NULL, E_WARNING, \
@@ -303,6 +308,7 @@ PHPAPI zend_result php_session_reset_id(void);
303308
code; \
304309
} \
305310
} ZEND_HASH_FOREACH_END(); \
311+
zval_ptr_dtor(&_zv); \
306312
} while(0)
307313

308314
PHPAPI ZEND_EXTERN_MODULE_GLOBALS(ps)

ext/session/session.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1056,6 +1056,7 @@ PS_SERIALIZER_ENCODE_FUNC(php) /* {{{ */
10561056
{
10571057
smart_str buf = {0};
10581058
php_serialize_data_t var_hash;
1059+
bool fail = false;
10591060
PS_ENCODE_VARS;
10601061

10611062
PHP_VAR_SERIALIZE_INIT(var_hash);
@@ -1065,12 +1066,17 @@ PS_SERIALIZER_ENCODE_FUNC(php) /* {{{ */
10651066
if (memchr(ZSTR_VAL(key), PS_DELIMITER, ZSTR_LEN(key))) {
10661067
PHP_VAR_SERIALIZE_DESTROY(var_hash);
10671068
smart_str_free(&buf);
1068-
return NULL;
1069+
fail = true;
1070+
break;
10691071
}
10701072
smart_str_appendc(&buf, PS_DELIMITER);
10711073
php_var_serialize(&buf, struc, &var_hash);
10721074
);
10731075

1076+
if (fail) {
1077+
return NULL;
1078+
}
1079+
10741080
smart_str_0(&buf);
10751081

10761082
PHP_VAR_SERIALIZE_DESTROY(var_hash);

ext/session/tests/gh16590.phpt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
GH-16590 (UAF in session_encode())
3+
--EXTENSIONS--
4+
session
5+
--SKIPIF--
6+
<?php include('skipif.inc'); ?>
7+
--INI--
8+
session.use_cookies=0
9+
session.cache_limiter=
10+
session.serialize_handler=php
11+
session.save_handler=files
12+
--FILE--
13+
<?php
14+
15+
class C {
16+
function __serialize() {
17+
$_SESSION = [];
18+
return [];
19+
}
20+
}
21+
22+
session_start();
23+
24+
$_SESSION['Lz'] = new C;
25+
for ($i = 0; $i < 2; $i++) {
26+
$_SESSION[$i] = $i;
27+
}
28+
29+
var_dump(session_encode());
30+
31+
?>
32+
--EXPECTF--
33+
Warning: session_encode(): Skipping numeric key 0 in %s on line %d
34+
35+
Warning: session_encode(): Skipping numeric key 1 in %s on line %d
36+
string(15) "Lz|O:1:"C":0:{}"

0 commit comments

Comments
 (0)