diff --git a/NEWS b/NEWS index e56e562a4c92..7272c1535b80 100644 --- a/NEWS +++ b/NEWS @@ -46,6 +46,8 @@ PHP NEWS - PDO: . Fixed a leak when a persistent connection failed a liveness check with no other live PDO handle. (iliaal) + . Fixed a leak of the driver_params argument in bindParam() and + bindColumn(). (iliaal) - Standard: . Fixed a memory leak in array_merge_recursive() when the recursive merge of diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c index 4e41ea40f08b..14c9b2c06a19 100644 --- a/ext/pdo/pdo_stmt.c +++ b/ext/pdo/pdo_stmt.c @@ -330,6 +330,7 @@ static bool really_register_bound_param(struct pdo_bound_param_data *param, pdo_ zend_string_release_ex(param->name, 0); param->name = NULL; } + zval_ptr_dtor(¶m->driver_params); return 0; } @@ -344,6 +345,7 @@ static bool really_register_bound_param(struct pdo_bound_param_data *param, pdo_ zend_string_release_ex(param->name, 0); param->name = NULL; } + zval_ptr_dtor(¶m->driver_params); return 0; } } @@ -1461,9 +1463,11 @@ static void register_bound_param(INTERNAL_FUNCTION_PARAMETERS, int is_param) /* if (!Z_ISUNDEF(param.parameter)) { zval_ptr_dtor(&(param.parameter)); } + zval_ptr_dtor(¶m.driver_params); RETURN_FALSE; } + zval_ptr_dtor(¶m.driver_params); RETURN_TRUE; } /* }}} */ diff --git a/ext/pdo/tests/bug_driver_params_leak.phpt b/ext/pdo/tests/bug_driver_params_leak.phpt new file mode 100644 index 000000000000..bd775452165a --- /dev/null +++ b/ext/pdo/tests/bug_driver_params_leak.phpt @@ -0,0 +1,89 @@ +--TEST-- +PDO: bindParam() must not leak driver_params +--EXTENSIONS-- +pdo +pdo_sqlite +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$stmt = $db->prepare('SELECT ?'); + +$n = 20000; +$dp = str_repeat('a', 1024); +$obj = new C(); +try { + $stmt->bindParam(1, $obj, PDO::PARAM_STR, 0, $dp); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} +for ($i = 0; $i < $n; $i++) { + $dp = str_repeat('a', 1024); + $obj = new C(); + try { + $stmt->bindParam(1, $obj, PDO::PARAM_STR, 0, $dp); + } catch (Error $e) { + } +} +$before = memory_get_usage(); +for ($i = 0; $i < $n; $i++) { + $dp = str_repeat('b', 1024); + $obj = new C(); + try { + $stmt->bindParam(1, $obj, PDO::PARAM_STR, 0, $dp); + } catch (Error $e) { + } +} +$diff = memory_get_usage() - $before; +if ($diff > 1000) { + echo "LEAK\n"; +} else { + echo "OK\n"; +} + +$stmt2 = $db->prepare('SELECT :bar'); +$v = 'x'; +for ($i = 0; $i < $n; $i++) { + $dp = str_repeat('c', 1024); + try { + $stmt2->bindParam(':missing', $v, PDO::PARAM_STR, 0, $dp); + } catch (PDOException $e) { + } +} +$before = memory_get_usage(); +for ($i = 0; $i < $n; $i++) { + $dp = str_repeat('d', 1024); + try { + $stmt2->bindParam(':missing', $v, PDO::PARAM_STR, 0, $dp); + } catch (PDOException $e) { + } +} +$diff = memory_get_usage() - $before; +if ($diff > 1000) { + echo "LEAK\n"; +} else { + echo "OK\n"; +} + +for ($i = 0; $i < $n; $i++) { + $dp = str_repeat('e', 1024); + $stmt->bindParam(1, $v, PDO::PARAM_STR, 0, $dp); +} +$before = memory_get_usage(); +for ($i = 0; $i < $n; $i++) { + $dp = str_repeat('f', 1024); + $stmt->bindParam(1, $v, PDO::PARAM_STR, 0, $dp); +} +$diff = memory_get_usage() - $before; +if ($diff > 1000) { + echo "LEAK\n"; +} else { + echo "OK\n"; +} +?> +--EXPECT-- +Error: Object of class C could not be converted to string +OK +OK +OK