From f50fe7e59c71659491d9fb77f4ddb8f3f9a0fa60 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sun, 14 Jun 2026 12:06:23 -0400 Subject: [PATCH] Fix file descriptor leak when proc_open() descriptor setup fails When a descriptor spec entry fails to set up (unknown type, missing mode) after an earlier entry already opened a pipe or socket, proc_open() jumped to the failure path without closing the descriptors it had already opened, leaking those fds; repeated calls exhaust the process descriptor table. Close the opened descriptors on the failure path and drop the now-redundant per-call close before each spawn-failure goto. --- ext/standard/proc_open.c | 7 +++---- .../proc_open_fd_leak_on_setup_failure.phpt | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 ext/standard/tests/general_functions/proc_open_fd_leak_on_setup_failure.phpt diff --git a/ext/standard/proc_open.c b/ext/standard/proc_open.c index 278f7486e1ad..d2d51de5a856 100644 --- a/ext/standard/proc_open.c +++ b/ext/standard/proc_open.c @@ -1371,7 +1371,6 @@ PHP_FUNCTION(proc_open) if (newprocok == FALSE) { DWORD dw = GetLastError(); - close_all_descriptors(descriptors, ndesc); char *msg = php_win32_error_to_msg(dw); php_error_docref(NULL, E_WARNING, "CreateProcess failed: %s", msg); php_win32_error_msg_free(msg); @@ -1388,7 +1387,6 @@ PHP_FUNCTION(proc_open) if (close_parentends_of_pipes(&factions, descriptors, ndesc) == FAILURE) { posix_spawn_file_actions_destroy(&factions); - close_all_descriptors(descriptors, ndesc); goto exit_fail; } @@ -1408,7 +1406,6 @@ PHP_FUNCTION(proc_open) } posix_spawn_file_actions_destroy(&factions); if (r != 0) { - close_all_descriptors(descriptors, ndesc); php_error_docref(NULL, E_WARNING, "posix_spawn() failed: %s", strerror(r)); goto exit_fail; } @@ -1450,7 +1447,6 @@ PHP_FUNCTION(proc_open) _exit(127); } else if (child < 0) { /* Failed to fork() */ - close_all_descriptors(descriptors, ndesc); php_error_docref(NULL, E_WARNING, "Fork failed: %s", strerror(errno)); goto exit_fail; } @@ -1540,6 +1536,9 @@ PHP_FUNCTION(proc_open) } else { exit_fail: _php_free_envp(env); + if (descriptors) { + close_all_descriptors(descriptors, ndesc); + } RETVAL_FALSE; } diff --git a/ext/standard/tests/general_functions/proc_open_fd_leak_on_setup_failure.phpt b/ext/standard/tests/general_functions/proc_open_fd_leak_on_setup_failure.phpt new file mode 100644 index 000000000000..e072f75a82d2 --- /dev/null +++ b/ext/standard/tests/general_functions/proc_open_fd_leak_on_setup_failure.phpt @@ -0,0 +1,20 @@ +--TEST-- +proc_open() does not leak file descriptors when descriptor setup fails mid-spec +--SKIPIF-- + +--FILE-- + ["pipe", "r"], 1 => ["bogus_type"]], $pipes); +} +$after = count(scandir("/proc/self/fd")); +var_dump($after <= $before + 2); +?> +--EXPECT-- +bool(true)