Skip to content

Commit 943720b

Browse files
committed
ext/openssl: Throw on repeat Openssl\Session::__unserialize
Calling __unserialize() on an Openssl\Session that already holds an SSL_SESSION overwrote obj->session without freeing the prior one, leaking an SSL_SESSION (OpenSSL's allocator, bypasses memory_limit). There is no legitimate use for re-running __unserialize() on a live instance, so reject it: throw when obj->session is already populated, before any overwrite. Closes GH-22040
1 parent dee1317 commit 943720b

2 files changed

Lines changed: 90 additions & 1 deletion

File tree

ext/openssl/openssl.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,12 @@ PHP_METHOD(Openssl_Session, __unserialize)
504504
Z_PARAM_ARRAY_HT(data)
505505
ZEND_PARSE_PARAMETERS_END();
506506

507+
php_openssl_session_object *obj = Z_OPENSSL_SESSION_P(ZEND_THIS);
508+
if (obj->session != NULL) {
509+
zend_throw_error(NULL, "Cannot call Openssl\\Session::__unserialize() on an already-initialized session");
510+
RETURN_THROWS();
511+
}
512+
507513
zval *pem_zv = zend_hash_str_find(data, ZEND_STRL("pem"));
508514
if (!pem_zv || Z_TYPE_P(pem_zv) != IS_STRING) {
509515
zend_throw_exception(php_openssl_exception_ce, "Invalid serialization data", 0);
@@ -524,7 +530,6 @@ PHP_METHOD(Openssl_Session, __unserialize)
524530
RETURN_THROWS();
525531
}
526532

527-
php_openssl_session_object *obj = Z_OPENSSL_SESSION_P(ZEND_THIS);
528533
obj->session = session;
529534

530535
/* Populate id property */
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
--TEST--
2+
Openssl\Session::__unserialize throws on a repeat call
3+
--EXTENSIONS--
4+
openssl
5+
--SKIPIF--
6+
<?php
7+
if (!function_exists("proc_open")) die("skip no proc_open");
8+
?>
9+
--FILE--
10+
<?php
11+
$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'session_unserialize_repeat.pem.tmp';
12+
13+
$serverCode = <<<'CODE'
14+
$serverCtx = stream_context_create(['ssl' => [
15+
'local_cert' => '%s',
16+
]]);
17+
18+
$server = stream_socket_server('tls://127.0.0.1:0', $errno, $errstr,
19+
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $serverCtx);
20+
phpt_notify_server_start($server);
21+
22+
$client = @stream_socket_accept($server, 10);
23+
if ($client) {
24+
fwrite($client, "ok\n");
25+
fclose($client);
26+
}
27+
CODE;
28+
$serverCode = sprintf($serverCode, $certFile);
29+
30+
$clientCode = <<<'CODE'
31+
$captured = null;
32+
$ctx = stream_context_create(['ssl' => [
33+
'verify_peer' => false,
34+
'verify_peer_name' => false,
35+
'session_new_cb' => function ($s, $session) use (&$captured) {
36+
$captured = $session;
37+
return true;
38+
},
39+
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,
40+
]]);
41+
42+
$c = stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 10,
43+
STREAM_CLIENT_CONNECT, $ctx);
44+
if (!$c) {
45+
echo "connect failed: $errstr\n";
46+
return;
47+
}
48+
fread($c, 8);
49+
fclose($c);
50+
51+
if (!$captured instanceof Openssl\Session) {
52+
echo "no session captured\n";
53+
return;
54+
}
55+
56+
$payload = $captured->__serialize();
57+
$sess = unserialize(serialize($captured));
58+
echo "first: " . (is_object($sess) ? get_class($sess) : "fail") . "\n";
59+
60+
try {
61+
$sess->__unserialize($payload);
62+
echo "second: no throw\n";
63+
} catch (Error $e) {
64+
echo "second: " . $e->getMessage() . "\n";
65+
}
66+
67+
echo "alive\n";
68+
CODE;
69+
70+
include 'CertificateGenerator.inc';
71+
$certificateGenerator = new CertificateGenerator();
72+
$certificateGenerator->saveNewCertAsFileWithKey('session-unserialize-repeat', $certFile);
73+
74+
include 'ServerClientTestCase.inc';
75+
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
76+
?>
77+
--CLEAN--
78+
<?php
79+
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'session_unserialize_repeat.pem.tmp');
80+
?>
81+
--EXPECTF--
82+
first: Openssl\Session
83+
second: Cannot call Openssl\Session::__unserialize() on an already-initialized session
84+
alive

0 commit comments

Comments
 (0)