summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xrun-tests.php2
-rw-r--r--sapi/fpm/config.m417
-rw-r--r--sapi/fpm/fpm/fpm.c4
-rw-r--r--sapi/fpm/fpm/fpm.h3
-rw-r--r--sapi/fpm/fpm/fpm_conf.c6
-rw-r--r--sapi/fpm/fpm/fpm_conf.h3
-rw-r--r--sapi/fpm/fpm/fpm_main.c12
-rw-r--r--sapi/fpm/fpm/fpm_stdio.c2
-rw-r--r--sapi/fpm/fpm/fpm_unix.c30
-rw-r--r--sapi/fpm/tests/001.phpt21
-rw-r--r--sapi/fpm/tests/002.phpt53
-rw-r--r--sapi/fpm/tests/apparmor.phpt54
-rw-r--r--sapi/fpm/tests/include.inc79
-rw-r--r--sapi/fpm/tests/skipapparmor.inc30
-rw-r--r--sapi/fpm/tests/skipif.inc13
15 files changed, 323 insertions, 6 deletions
diff --git a/run-tests.php b/run-tests.php
index 45ea29de51..cd6038e9b1 100755
--- a/run-tests.php
+++ b/run-tests.php
@@ -850,7 +850,7 @@ $exts_skipped = 0;
$ignored_by_ext = 0;
sort($exts_to_test);
$test_dirs = array();
-$optionals = array('tests', 'ext', 'Zend', 'ZendEngine2', 'sapi/cli', 'sapi/cgi');
+$optionals = array('tests', 'ext', 'Zend', 'ZendEngine2', 'sapi/cli', 'sapi/cgi', 'sapi/fpm');
foreach($optionals as $dir) {
if (@filetype($dir) == 'dir') {
diff --git a/sapi/fpm/config.m4 b/sapi/fpm/config.m4
index bd6d64930b..ba2b8e2d82 100644
--- a/sapi/fpm/config.m4
+++ b/sapi/fpm/config.m4
@@ -536,6 +536,22 @@ AC_DEFUN([AC_FPM_SELECT],
])
dnl }}}
+AC_DEFUN([AC_FPM_APPARMOR],
+[
+ AC_MSG_CHECKING([for apparmor])
+
+ SAVED_LIBS="$LIBS"
+ LIBS="$LIBS -lapparmor"
+
+ AC_TRY_LINK([ #include <sys/apparmor.h> ], [change_hat("test", 0);], [
+ AC_DEFINE([HAVE_APPARMOR], 1, [do we have apparmor support?])
+ AC_MSG_RESULT([yes])
+ ], [
+ LIBS="$SAVED_LIBS"
+ AC_MSG_RESULT([no])
+ ])
+])
+
AC_MSG_CHECKING(for FPM build)
if test "$PHP_FPM" != "no"; then
@@ -555,6 +571,7 @@ if test "$PHP_FPM" != "no"; then
AC_FPM_EPOLL
AC_FPM_POLL
AC_FPM_SELECT
+ AC_FPM_APPARMOR
PHP_ARG_WITH(fpm-user,,
[ --with-fpm-user[=USER] Set the user for php-fpm to run as. (default: nobody)], nobody, no)
diff --git a/sapi/fpm/fpm/fpm.c b/sapi/fpm/fpm/fpm.c
index b866f37f2d..abfc786801 100644
--- a/sapi/fpm/fpm/fpm.c
+++ b/sapi/fpm/fpm/fpm.c
@@ -39,10 +39,11 @@ struct fpm_globals_s fpm_globals = {
.test_successful = 0,
.heartbeat = 0,
.run_as_root = 0,
+ .force_stderr = 0,
.send_config_pipe = {0, 0},
};
-int fpm_init(int argc, char **argv, char *config, char *prefix, char *pid, int test_conf, int run_as_root, int force_daemon) /* {{{ */
+int fpm_init(int argc, char **argv, char *config, char *prefix, char *pid, int test_conf, int run_as_root, int force_daemon, int force_stderr) /* {{{ */
{
fpm_globals.argc = argc;
fpm_globals.argv = argv;
@@ -52,6 +53,7 @@ int fpm_init(int argc, char **argv, char *config, char *prefix, char *pid, int t
fpm_globals.prefix = prefix;
fpm_globals.pid = pid;
fpm_globals.run_as_root = run_as_root;
+ fpm_globals.force_stderr = force_stderr;
if (0 > fpm_php_init_main() ||
0 > fpm_stdio_init_main() ||
diff --git a/sapi/fpm/fpm/fpm.h b/sapi/fpm/fpm/fpm.h
index 65d0e0d691..68642a955d 100644
--- a/sapi/fpm/fpm/fpm.h
+++ b/sapi/fpm/fpm/fpm.h
@@ -37,7 +37,7 @@
int fpm_run(int *max_requests);
-int fpm_init(int argc, char **argv, char *config, char *prefix, char *pid, int test_conf, int run_as_root, int force_daemon);
+int fpm_init(int argc, char **argv, char *config, char *prefix, char *pid, int test_conf, int run_as_root, int force_daemon, int force_stderr);
struct fpm_globals_s {
pid_t parent_pid;
@@ -55,6 +55,7 @@ struct fpm_globals_s {
int test_successful;
int heartbeat;
int run_as_root;
+ int force_stderr;
int send_config_pipe[2];
};
diff --git a/sapi/fpm/fpm/fpm_conf.c b/sapi/fpm/fpm/fpm_conf.c
index cd5fc34d0f..9b699af99d 100644
--- a/sapi/fpm/fpm/fpm_conf.c
+++ b/sapi/fpm/fpm/fpm_conf.c
@@ -149,6 +149,9 @@ static struct ini_value_parser_s ini_fpm_pool_options[] = {
{ "chdir", &fpm_conf_set_string, WPO(chdir) },
{ "catch_workers_output", &fpm_conf_set_boolean, WPO(catch_workers_output) },
{ "security.limit_extensions", &fpm_conf_set_string, WPO(security_limit_extensions) },
+#ifdef HAVE_APPARMOR
+ { "apparmor_hat", &fpm_conf_set_string, WPO(apparmor_hat) },
+#endif
{ 0, 0, 0 }
};
@@ -644,6 +647,9 @@ int fpm_worker_pool_config_free(struct fpm_worker_pool_config_s *wpc) /* {{{ */
free(wpc->chroot);
free(wpc->chdir);
free(wpc->security_limit_extensions);
+#ifdef HAVE_APPARMOR
+ free(wpc->apparmor_hat);
+#endif
for (kv = wpc->php_values; kv; kv = kv_next) {
kv_next = kv->next;
diff --git a/sapi/fpm/fpm/fpm_conf.h b/sapi/fpm/fpm/fpm_conf.h
index efd65dc6d9..8cd8690f18 100644
--- a/sapi/fpm/fpm/fpm_conf.h
+++ b/sapi/fpm/fpm/fpm_conf.h
@@ -87,6 +87,9 @@ struct fpm_worker_pool_config_s {
struct key_value_s *env;
struct key_value_s *php_admin_values;
struct key_value_s *php_values;
+#ifdef HAVE_APPARMOR
+ char *apparmor_hat;
+#endif
};
struct ini_value_parser_s {
diff --git a/sapi/fpm/fpm/fpm_main.c b/sapi/fpm/fpm/fpm_main.c
index fb22a6840e..3d699e2e80 100644
--- a/sapi/fpm/fpm/fpm_main.c
+++ b/sapi/fpm/fpm/fpm_main.c
@@ -157,6 +157,7 @@ static const opt_struct OPTIONS[] = {
{'R', 0, "allow-to-run-as-root"},
{'D', 0, "daemonize"},
{'F', 0, "nodaemonize"},
+ {'O', 0, "force-stderr"},
{'-', 0, NULL} /* end of args */
};
@@ -921,7 +922,7 @@ static void php_cgi_usage(char *argv0)
prog = "php";
}
- php_printf( "Usage: %s [-n] [-e] [-h] [-i] [-m] [-v] [-t] [-p <prefix>] [-g <pid>] [-c <file>] [-d foo[=bar]] [-y <file>] [-D] [-F]\n"
+ php_printf( "Usage: %s [-n] [-e] [-h] [-i] [-m] [-v] [-t] [-p <prefix>] [-g <pid>] [-c <file>] [-d foo[=bar]] [-y <file>] [-D] [-F [-O]]\n"
" -c <path>|<file> Look for php.ini file in this directory\n"
" -n No php.ini file will be used\n"
" -d foo[=bar] Define INI entry foo with value 'bar'\n"
@@ -940,6 +941,8 @@ static void php_cgi_usage(char *argv0)
" -D, --daemonize force to run in background, and ignore daemonize option from config file\n"
" -F, --nodaemonize\n"
" force to stay in foreground, and ignore daemonize option from config file\n"
+ " -O, --force-stderr\n"
+ " force output to stderr in nodaemonize even if stderr is not a TTY\n"
" -R, --allow-to-run-as-root\n"
" Allow pool to run as root (disabled by default)\n",
prog, PHP_PREFIX);
@@ -1572,6 +1575,7 @@ int main(int argc, char *argv[])
char *fpm_pid = NULL;
int test_conf = 0;
int force_daemon = -1;
+ int force_stderr = 0;
int php_information = 0;
int php_allow_to_run_as_root = 0;
@@ -1700,6 +1704,10 @@ int main(int argc, char *argv[])
force_daemon = 0;
break;
+ case 'O': /* force stderr even on non tty */
+ force_stderr = 1;
+ break;
+
default:
case 'h':
case '?':
@@ -1827,7 +1835,7 @@ consult the installation file that came with this distribution, or visit \n\
}
}
- if (0 > fpm_init(argc, argv, fpm_config ? fpm_config : CGIG(fpm_config), fpm_prefix, fpm_pid, test_conf, php_allow_to_run_as_root, force_daemon)) {
+ if (0 > fpm_init(argc, argv, fpm_config ? fpm_config : CGIG(fpm_config), fpm_prefix, fpm_pid, test_conf, php_allow_to_run_as_root, force_daemon, force_stderr)) {
if (fpm_globals.send_config_pipe[1]) {
int writeval = 0;
diff --git a/sapi/fpm/fpm/fpm_stdio.c b/sapi/fpm/fpm/fpm_stdio.c
index d81e10150d..33b0e01c87 100644
--- a/sapi/fpm/fpm/fpm_stdio.c
+++ b/sapi/fpm/fpm/fpm_stdio.c
@@ -292,7 +292,7 @@ int fpm_stdio_open_error_log(int reopen) /* {{{ */
} else {
fpm_globals.error_log_fd = fd;
#if HAVE_UNISTD_H
- if (fpm_global_config.daemonize || !isatty(STDERR_FILENO)) {
+ if (fpm_global_config.daemonize || (!isatty(STDERR_FILENO) && !fpm_globals.force_stderr)) {
#else
if (fpm_global_config.daemonize) {
#endif
diff --git a/sapi/fpm/fpm/fpm_unix.c b/sapi/fpm/fpm/fpm_unix.c
index 48249e8a49..1159a13247 100644
--- a/sapi/fpm/fpm/fpm_unix.c
+++ b/sapi/fpm/fpm/fpm_unix.c
@@ -17,6 +17,10 @@
#include <sys/prctl.h>
#endif
+#ifdef HAVE_APPARMOR
+#include <sys/apparmor.h>
+#endif
+
#include "fpm.h"
#include "fpm_conf.h"
#include "fpm_cleanup.h"
@@ -222,6 +226,32 @@ int fpm_unix_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
if (0 > fpm_clock_init()) {
return -1;
}
+
+#ifdef HAVE_APPARMOR
+ if (wp->config->apparmor_hat) {
+ char *con, *new_con;
+ if (aa_getcon(&con, NULL) == -1) {
+ zlog(ZLOG_SYSERROR, "[pool %s] failed to query apparmor confinement. Please check if \"/proc/*/attr/current\" is read and writeable.", wp->config->name);
+ return -1;
+ }
+ new_con = malloc(strlen(con) + strlen(wp->config->apparmor_hat) + 3); // // + 0 Byte
+ if (!new_con) {
+ zlog(ZLOG_SYSERROR, "[pool %s] failed to allocate memory for apparmor hat change.", wp->config->name);
+ return -1;
+ }
+ if (0 > sprintf(new_con, "%s//%s", con, wp->config->apparmor_hat)) {
+ zlog(ZLOG_SYSERROR, "[pool %s] failed to construct apparmor confinement.", wp->config->name);
+ return -1;
+ }
+ if (0 > aa_change_profile(new_con)) {
+ zlog(ZLOG_SYSERROR, "[pool %s] failed to change to new confinement (%s). Please check if \"/proc/*/attr/current\" is read and writeable and \"change_profile -> %s//*\" is allowed.", wp->config->name, new_con, con);
+ return -1;
+ }
+ free(con);
+ free(new_con);
+ }
+#endif
+
return 0;
}
/* }}} */
diff --git a/sapi/fpm/tests/001.phpt b/sapi/fpm/tests/001.phpt
new file mode 100644
index 0000000000..b721bfa925
--- /dev/null
+++ b/sapi/fpm/tests/001.phpt
@@ -0,0 +1,21 @@
+--TEST--
+FPM: version string
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$php = get_fpm_path();
+
+var_dump(`$php -n -v`);
+
+echo "Done\n";
+?>
+--EXPECTF--
+string(%d) "PHP %s (fpm%s (built: %s
+Copyright (c) 1997-20%s The PHP Group
+Zend Engine v%s, Copyright (c) 1998-20%s Zend Technologies
+"
+Done
diff --git a/sapi/fpm/tests/002.phpt b/sapi/fpm/tests/002.phpt
new file mode 100644
index 0000000000..2ef6cedc38
--- /dev/null
+++ b/sapi/fpm/tests/002.phpt
@@ -0,0 +1,53 @@
+--TEST--
+FPM: Startup and connect
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = 127.0.0.1:9000
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ var_dump(fgets($tail));
+ var_dump(fgets($tail));
+ $i = 0;
+ while (($i++ < 30) && !($fp = @fsockopen('127.0.0.1', 9000))) {
+ usleep(10000);
+ }
+ if ($fp) {
+ echo "Done\n";
+ fclose($fp);
+ }
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+string(%d) "[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+"
+string(%d) "[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+"
+Done
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/apparmor.phpt b/sapi/fpm/tests/apparmor.phpt
new file mode 100644
index 0000000000..cf9bd71118
--- /dev/null
+++ b/sapi/fpm/tests/apparmor.phpt
@@ -0,0 +1,54 @@
+--TEST--
+FPM: Apparmor Test
+--DESCRIPTION--
+This test tries to launches a pool which tries to change to non existing
+apparmor hat a. Test succeeds if apparmor is not running or hat is non
+existant.
+--SKIPIF--
+<?php
+include "skipif.inc";
+include "skipapparmor.inc";
+
+?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[a]
+listen = 127.0.0.1:9001
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+apparmor_hat = a
+EOT;
+
+/* libapparmor has a bug which can cause SIGSEGV till Version 2.8.0-0ubuntu28
+ See https://bugs.launchpad.net/apparmor/+bug/1196880
+ Possible outcomes:
+
+ - SIGSEGV|failed to query apparmor confinement
+ apparmor not running
+ - failed to change to new confinement
+ something in apparmor went wrong
+ - exited with code 70
+ Change to successful; Hat not existant (Process gets killed by apparmor)
+ */
+var_dump(run_fpm_till('/(SIGSEGV|failed to query apparmor confinement|failed to change to new confinement|exited with code 70)/', $cfg));
+
+?>
+--EXPECTF--
+string(%d) "%s
+"
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/include.inc b/sapi/fpm/tests/include.inc
new file mode 100644
index 0000000000..983cbd3454
--- /dev/null
+++ b/sapi/fpm/tests/include.inc
@@ -0,0 +1,79 @@
+<?php
+
+function get_fpm_path() /* {{{ */
+{
+ $php_path = getenv("TEST_PHP_EXECUTABLE");
+
+ for ($i = 0; $i < 2; $i++) {
+ $slash_pos = strrpos($php_path, "/");
+ if ($slash_pos) {
+ $php_path = substr($php_path, 0, $slash_pos);
+ } else {
+ return false;
+ }
+ }
+
+ if ($php_path && is_dir($php_path) && file_exists($php_path."/fpm/php-fpm") && is_executable($php_path."/fpm/php-fpm")) {
+ /* gotcha */
+ return $php_path."/fpm/php-fpm";
+ }
+ return false;
+}
+/* }}} */
+
+function run_fpm($config, &$out = false, $extra_args = '') /* {{{ */
+{
+ $cfg = dirname(__FILE__).'/test-fpm-config.tmp';
+ file_put_contents($cfg, $config);
+ $desc = [];
+ if ($out !== false) {
+ $desc = [1 => array('pipe', 'w')];
+ }
+ /* Since it's not possible to spawn a process under linux without using a
+ * shell in php (why?!?) we need a little shell trickery, so that we can
+ * actually kill php-fpm */
+ $fpm = proc_open('killit () { kill $child; }; trap killit TERM; '.get_fpm_path().' -F -O -y '.$cfg.' '.$extra_args.' 2>&1 & child=$!; wait', $desc, $pipes);
+ register_shutdown_function(
+ function($fpm) use($cfg) {
+ @unlink($cfg);
+ if (is_resource($fpm)) {
+ @proc_terminate($fpm);
+ while (proc_get_status($fpm)['running']) {
+ usleep(10000);
+ }
+ }
+ },
+ $fpm
+ );
+ if ($out !== false) {
+ $out = $pipes[1];
+ }
+ return $fpm;
+}
+/* }}} */
+
+function run_fpm_till($needle, $config, $max = 10) /* {{{ */
+{
+ $i = 0;
+ $fpm = run_fpm($config, $tail);
+ if (is_resource($fpm)) {
+ while($i < $max) {
+ $i++;
+ $line = fgets($tail);
+ if(preg_match($needle, $line) === 1) {
+ break;
+ }
+ }
+ if ($i >= $max) {
+ $line = false;
+ }
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+ }
+ return $line;
+}
+/* }}} */
+
+?>
diff --git a/sapi/fpm/tests/skipapparmor.inc b/sapi/fpm/tests/skipapparmor.inc
new file mode 100644
index 0000000000..b286d0361d
--- /dev/null
+++ b/sapi/fpm/tests/skipapparmor.inc
@@ -0,0 +1,30 @@
+<?php
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[a]
+listen = 127.0.0.1:9001
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+apparmor_hat = a
+EOT;
+
+$fpm = run_fpm($cfg, $out, '-t');
+$ok = false;
+if (is_resource($fpm)) {
+ if (strpos(stream_get_contents($out), "test is successful") !== FALSE) {
+ $ok = true;
+ }
+ fclose($out);
+ proc_close($fpm);
+}
+if (!$ok) {
+ die("skip No apparmor support built in");
+}
+
+?>
diff --git a/sapi/fpm/tests/skipif.inc b/sapi/fpm/tests/skipif.inc
new file mode 100644
index 0000000000..8c569daafd
--- /dev/null
+++ b/sapi/fpm/tests/skipif.inc
@@ -0,0 +1,13 @@
+<?php
+
+if (substr(PHP_OS, 0, 3) == 'WIN') {
+ die ("skip not for Windows");
+}
+
+include dirname(__FILE__)."/include.inc";
+
+if (!get_fpm_path()) {
+ die("skip FPM not found");
+}
+
+?>