mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
Warn when fpm socket was not registered on the expected path
This might happen if the UDS length limit is exceeded. Co-authored-by: Jakub Zelenka <bukka@php.net> Closes GH-11066
This commit is contained in:
committed by
Jakub Zelenka
parent
72e2e25066
commit
08b57772b0
4
NEWS
4
NEWS
@@ -22,6 +22,10 @@ PHP NEWS
|
|||||||
. Added DOMElement::className and DOMElement::id. (nielsdos)
|
. Added DOMElement::className and DOMElement::id. (nielsdos)
|
||||||
. Added DOMParentNode::replaceChildren(). (nielsdos)
|
. Added DOMParentNode::replaceChildren(). (nielsdos)
|
||||||
|
|
||||||
|
- FPM:
|
||||||
|
. Added warning to log when fpm socket was not registered on the expected
|
||||||
|
path. (Joshua Behrens, Jakub Zelenka)
|
||||||
|
|
||||||
- Intl:
|
- Intl:
|
||||||
. Fix memory leak in MessageFormatter::format() on failure. (Girgias)
|
. Fix memory leak in MessageFormatter::format() on failure. (Girgias)
|
||||||
|
|
||||||
|
|||||||
@@ -429,6 +429,9 @@ PHP 8.3 UPGRADE NOTES
|
|||||||
current system user. Previously, calling FFI::load() was not possible during
|
current system user. Previously, calling FFI::load() was not possible during
|
||||||
preloading if the opcache.preload_user directive was set.
|
preloading if the opcache.preload_user directive was set.
|
||||||
|
|
||||||
|
- FPM:
|
||||||
|
. FPM CLI test now fails if the socket path is longer than supported by OS.
|
||||||
|
|
||||||
- Opcache:
|
- Opcache:
|
||||||
. In the cli and phpdbg SAPIs, preloading does not require the
|
. In the cli and phpdbg SAPIs, preloading does not require the
|
||||||
opcache.preload_user directive to be set anymore when running as root. In
|
opcache.preload_user directive to be set anymore when running as root. In
|
||||||
|
|||||||
@@ -396,9 +396,25 @@ static int fpm_socket_af_inet_listening_socket(struct fpm_worker_pool_s *wp) /*
|
|||||||
static int fpm_socket_af_unix_listening_socket(struct fpm_worker_pool_s *wp) /* {{{ */
|
static int fpm_socket_af_unix_listening_socket(struct fpm_worker_pool_s *wp) /* {{{ */
|
||||||
{
|
{
|
||||||
struct sockaddr_un sa_un;
|
struct sockaddr_un sa_un;
|
||||||
|
size_t socket_length = sizeof(sa_un.sun_path);
|
||||||
|
size_t address_length = strlen(wp->config->listen_address);
|
||||||
|
|
||||||
memset(&sa_un, 0, sizeof(sa_un));
|
memset(&sa_un, 0, sizeof(sa_un));
|
||||||
strlcpy(sa_un.sun_path, wp->config->listen_address, sizeof(sa_un.sun_path));
|
strlcpy(sa_un.sun_path, wp->config->listen_address, socket_length);
|
||||||
|
|
||||||
|
if (address_length >= socket_length) {
|
||||||
|
zlog(
|
||||||
|
ZLOG_WARNING,
|
||||||
|
"[pool %s] cannot bind to UNIX socket '%s' as path is too long (found length: %zu, "
|
||||||
|
"maximal length: %zu), trying cut socket path instead '%s'",
|
||||||
|
wp->config->name,
|
||||||
|
wp->config->listen_address,
|
||||||
|
address_length,
|
||||||
|
socket_length,
|
||||||
|
sa_un.sun_path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
sa_un.sun_family = AF_UNIX;
|
sa_un.sun_family = AF_UNIX;
|
||||||
return fpm_sockets_get_listening_socket(wp, (struct sockaddr *) &sa_un, sizeof(struct sockaddr_un));
|
return fpm_sockets_get_listening_socket(wp, (struct sockaddr *) &sa_un, sizeof(struct sockaddr_un));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/un.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <grp.h>
|
#include <grp.h>
|
||||||
|
|
||||||
@@ -63,6 +64,33 @@ static struct passwd *fpm_unix_get_passwd(struct fpm_worker_pool_s *wp, const ch
|
|||||||
return pwd;
|
return pwd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool fpm_unix_check_listen_address(struct fpm_worker_pool_s *wp, const char *address, int flags)
|
||||||
|
{
|
||||||
|
if (wp->listen_address_domain != FPM_AF_UNIX) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_un test_socket;
|
||||||
|
size_t address_length = strlen(address);
|
||||||
|
size_t socket_length = sizeof(test_socket.sun_path);
|
||||||
|
|
||||||
|
if (address_length < socket_length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
zlog(
|
||||||
|
flags,
|
||||||
|
"[pool %s] cannot bind to UNIX socket '%s' as path is too long (found length: %zu, "
|
||||||
|
"maximal length: %zu)",
|
||||||
|
wp->config->name,
|
||||||
|
address,
|
||||||
|
address_length,
|
||||||
|
socket_length
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static inline bool fpm_unix_check_passwd(struct fpm_worker_pool_s *wp, const char *name, int flags)
|
static inline bool fpm_unix_check_passwd(struct fpm_worker_pool_s *wp, const char *name, int flags)
|
||||||
{
|
{
|
||||||
return !name || fpm_unix_is_id(name) || fpm_unix_get_passwd(wp, name, flags);
|
return !name || fpm_unix_is_id(name) || fpm_unix_get_passwd(wp, name, flags);
|
||||||
@@ -90,6 +118,7 @@ bool fpm_unix_test_config(struct fpm_worker_pool_s *wp)
|
|||||||
return (
|
return (
|
||||||
fpm_unix_check_passwd(wp, config->user, ZLOG_ERROR) &&
|
fpm_unix_check_passwd(wp, config->user, ZLOG_ERROR) &&
|
||||||
fpm_unix_check_group(wp, config->group, ZLOG_ERROR) &&
|
fpm_unix_check_group(wp, config->group, ZLOG_ERROR) &&
|
||||||
|
fpm_unix_check_listen_address(wp, config->listen_address, ZLOG_SYSERROR) &&
|
||||||
fpm_unix_check_passwd(wp, config->listen_owner, ZLOG_SYSERROR) &&
|
fpm_unix_check_passwd(wp, config->listen_owner, ZLOG_SYSERROR) &&
|
||||||
fpm_unix_check_group(wp, config->listen_group, ZLOG_SYSERROR)
|
fpm_unix_check_group(wp, config->listen_group, ZLOG_SYSERROR)
|
||||||
);
|
);
|
||||||
@@ -273,7 +302,7 @@ int fpm_unix_set_socket_permissions(struct fpm_worker_pool_s *wp, const char *pa
|
|||||||
/* Copy the new ACL entry from config */
|
/* Copy the new ACL entry from config */
|
||||||
for (i=ACL_FIRST_ENTRY ; acl_get_entry(aclconf, i, &entryconf) ; i=ACL_NEXT_ENTRY) {
|
for (i=ACL_FIRST_ENTRY ; acl_get_entry(aclconf, i, &entryconf) ; i=ACL_NEXT_ENTRY) {
|
||||||
if (0 > acl_create_entry (&aclfile, &entryfile) ||
|
if (0 > acl_create_entry (&aclfile, &entryfile) ||
|
||||||
0 > acl_copy_entry(entryfile, entryconf)) {
|
0 > acl_copy_entry(entryfile, entryconf)) {
|
||||||
zlog(ZLOG_SYSERROR, "[pool %s] failed to add entry to the ACL of the socket '%s'", wp->config->name, path);
|
zlog(ZLOG_SYSERROR, "[pool %s] failed to add entry to the ACL of the socket '%s'", wp->config->name, path);
|
||||||
acl_free(aclfile);
|
acl_free(aclfile);
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class LogReader
|
|||||||
*
|
*
|
||||||
* @var string|null
|
* @var string|null
|
||||||
*/
|
*/
|
||||||
private ?string $currentSourceName;
|
private ?string $currentSourceName = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log descriptors.
|
* Log descriptors.
|
||||||
|
|||||||
74
sapi/fpm/tests/socket-uds-too-long-filename-start.phpt
Normal file
74
sapi/fpm/tests/socket-uds-too-long-filename-start.phpt
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
--TEST--
|
||||||
|
FPM: UNIX socket filename is too for start
|
||||||
|
--SKIPIF--
|
||||||
|
<?php
|
||||||
|
include "skipif.inc"; ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "tester.inc";
|
||||||
|
$socketFilePrefix = __DIR__ . '/socket-file';
|
||||||
|
$socketFile = sprintf(
|
||||||
|
"%s-fpm-unix-socket-too-long-filename-but-starts-anyway%s.sock",
|
||||||
|
$socketFilePrefix,
|
||||||
|
str_repeat('-0000', 11)
|
||||||
|
);
|
||||||
|
|
||||||
|
$cfg = <<<EOT
|
||||||
|
[global]
|
||||||
|
error_log = {{FILE:LOG}}
|
||||||
|
|
||||||
|
[fpm_pool]
|
||||||
|
listen = $socketFile
|
||||||
|
pm = static
|
||||||
|
pm.max_children = 1
|
||||||
|
catch_workers_output = yes
|
||||||
|
EOT;
|
||||||
|
|
||||||
|
$tester = new FPM\Tester($cfg);
|
||||||
|
$tester->start();
|
||||||
|
$tester->expectLogStartNotices();
|
||||||
|
$tester->expectLogPattern(
|
||||||
|
sprintf(
|
||||||
|
'/\[pool fpm_pool\] cannot bind to UNIX socket \'%s\' as path is too long '
|
||||||
|
. '\(found length: %d, maximal length: \d+\), trying cut socket path instead \'.+\'/',
|
||||||
|
preg_quote($socketFile, '/'),
|
||||||
|
strlen($socketFile)
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
$files = glob($socketFilePrefix . '*');
|
||||||
|
|
||||||
|
if ($files === []) {
|
||||||
|
echo 'Socket files were not found.' . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($socketFile === $files[0]) {
|
||||||
|
// this means the socket file path length is not an issue (anymore). Might be not long enough
|
||||||
|
echo 'Socket file is the same as configured.' . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tester->terminate();
|
||||||
|
$tester->expectLogTerminatingNotices();
|
||||||
|
$tester->close();
|
||||||
|
?>
|
||||||
|
Done
|
||||||
|
--EXPECT--
|
||||||
|
Done
|
||||||
|
--CLEAN--
|
||||||
|
<?php
|
||||||
|
require_once "tester.inc";
|
||||||
|
FPM\Tester::clean();
|
||||||
|
|
||||||
|
// cleanup socket file if php-fpm was not killed
|
||||||
|
$socketFile = sprintf(
|
||||||
|
"/socket-file-fpm-unix-socket-too-long-filename-but-starts-anyway%s.sock",
|
||||||
|
__DIR__,
|
||||||
|
str_repeat('-0000', 11)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (is_file($socketFile)) {
|
||||||
|
unlink($socketFile);
|
||||||
|
}
|
||||||
|
?>
|
||||||
47
sapi/fpm/tests/socket-uds-too-long-filename-test.phpt
Normal file
47
sapi/fpm/tests/socket-uds-too-long-filename-test.phpt
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
--TEST--
|
||||||
|
FPM: UNIX socket filename is too for test
|
||||||
|
--SKIPIF--
|
||||||
|
<?php
|
||||||
|
include "skipif.inc"; ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "tester.inc";
|
||||||
|
|
||||||
|
$socketFilePrefix = __DIR__ . '/socket-file';
|
||||||
|
$socketFile = sprintf(
|
||||||
|
"/socket-file-fpm-unix-socket-too-long-filename-but-starts-anyway%s.sock",
|
||||||
|
__DIR__,
|
||||||
|
str_repeat('-0000', 11)
|
||||||
|
);
|
||||||
|
|
||||||
|
$cfg = <<<EOT
|
||||||
|
[global]
|
||||||
|
error_log = {{FILE:LOG}}
|
||||||
|
|
||||||
|
[fpm_pool]
|
||||||
|
listen = $socketFile
|
||||||
|
pm = static
|
||||||
|
pm.max_children = 1
|
||||||
|
catch_workers_output = yes
|
||||||
|
EOT;
|
||||||
|
|
||||||
|
$tester = new FPM\Tester($cfg);
|
||||||
|
$tester->testConfig(true, [
|
||||||
|
sprintf(
|
||||||
|
'/cannot bind to UNIX socket \'%s\' as path is too long '
|
||||||
|
. '\(found length: %d, maximal length: \d+\)/',
|
||||||
|
preg_quote($socketFile, '/'),
|
||||||
|
strlen($socketFile)
|
||||||
|
),
|
||||||
|
'/FPM initialization failed/',
|
||||||
|
]);
|
||||||
|
?>
|
||||||
|
Done
|
||||||
|
--EXPECT--
|
||||||
|
Done
|
||||||
|
--CLEAN--
|
||||||
|
<?php
|
||||||
|
require_once "tester.inc";
|
||||||
|
FPM\Tester::clean();
|
||||||
|
?>
|
||||||
@@ -382,26 +382,50 @@ class Tester
|
|||||||
* @return null|array
|
* @return null|array
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function testConfig($silent = false)
|
public function testConfig($silent = false, array|string|null $expectedPattern = null): ?array
|
||||||
{
|
{
|
||||||
$configFile = $this->createConfig();
|
$configFile = $this->createConfig();
|
||||||
$cmd = self::findExecutable() . ' -n -tt -y ' . $configFile . ' 2>&1';
|
$cmd = self::findExecutable() . ' -n -tt -y ' . $configFile . ' 2>&1';
|
||||||
$this->trace('Testing config using command', $cmd, true);
|
$this->trace('Testing config using command', $cmd, true);
|
||||||
exec($cmd, $output, $code);
|
exec($cmd, $output, $code);
|
||||||
|
$found = 0;
|
||||||
|
if ($expectedPattern !== null) {
|
||||||
|
$expectedPatterns = is_array($expectedPattern) ? $expectedPattern : [$expectedPattern];
|
||||||
|
}
|
||||||
if ($code) {
|
if ($code) {
|
||||||
$messages = [];
|
$messages = [];
|
||||||
foreach ($output as $outputLine) {
|
foreach ($output as $outputLine) {
|
||||||
$message = preg_replace("/\[.+?\]/", "", $outputLine, 1);
|
$message = preg_replace("/\[.+?\]/", "", $outputLine, 1);
|
||||||
|
if ($expectedPattern !== null) {
|
||||||
|
for ($i = 0; $i < count($expectedPatterns); $i++) {
|
||||||
|
$pattern = $expectedPatterns[$i];
|
||||||
|
if ($pattern !== null && preg_match($pattern, $message)) {
|
||||||
|
$found++;
|
||||||
|
$expectedPatterns[$i] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
$messages[] = $message;
|
$messages[] = $message;
|
||||||
if ( ! $silent) {
|
if ( ! $silent) {
|
||||||
$this->error($message, null, false);
|
$this->error($message, null, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
return $messages;
|
$messages = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
if ($expectedPattern !== null && $found < count($expectedPatterns)) {
|
||||||
|
$missingPatterns = array_filter($expectedPatterns);
|
||||||
|
$errorMessage = sprintf(
|
||||||
|
"The expected config %s %s %s not been found",
|
||||||
|
count($missingPatterns) > 1 ? 'patterns' : 'pattern',
|
||||||
|
implode(', ', $missingPatterns),
|
||||||
|
count($missingPatterns) > 1 ? 'have' : 'has',
|
||||||
|
);
|
||||||
|
$this->error($errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1155,9 +1179,19 @@ class Tester
|
|||||||
return $address;
|
return $address;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sys_get_temp_dir() . '/' .
|
$addressPart = hash('crc32', dirname($address)) . '-' . basename($address);
|
||||||
hash('crc32', dirname($address)) . '-' .
|
|
||||||
basename($address);
|
// is longer on Mac, than on Linux
|
||||||
|
$tmpDirAddress = sys_get_temp_dir() . '/' . $addressPart;
|
||||||
|
;
|
||||||
|
|
||||||
|
if (strlen($tmpDirAddress) <= 104) {
|
||||||
|
return $tmpDirAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
$srcRootAddress = dirname(__DIR__, 3) . '/' . $addressPart;
|
||||||
|
|
||||||
|
return $srcRootAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->getHost($type) . ':' . $port;
|
return $this->getHost($type) . ':' . $port;
|
||||||
|
|||||||
Reference in New Issue
Block a user