diff options
| -rwxr-xr-x | run-tests.php | 2 | ||||
| -rw-r--r-- | sapi/fpm/config.m4 | 17 | ||||
| -rw-r--r-- | sapi/fpm/fpm/fpm.c | 4 | ||||
| -rw-r--r-- | sapi/fpm/fpm/fpm.h | 3 | ||||
| -rw-r--r-- | sapi/fpm/fpm/fpm_conf.c | 6 | ||||
| -rw-r--r-- | sapi/fpm/fpm/fpm_conf.h | 3 | ||||
| -rw-r--r-- | sapi/fpm/fpm/fpm_main.c | 12 | ||||
| -rw-r--r-- | sapi/fpm/fpm/fpm_stdio.c | 2 | ||||
| -rw-r--r-- | sapi/fpm/fpm/fpm_unix.c | 30 | ||||
| -rw-r--r-- | sapi/fpm/tests/001.phpt | 21 | ||||
| -rw-r--r-- | sapi/fpm/tests/002.phpt | 53 | ||||
| -rw-r--r-- | sapi/fpm/tests/apparmor.phpt | 54 | ||||
| -rw-r--r-- | sapi/fpm/tests/include.inc | 79 | ||||
| -rw-r--r-- | sapi/fpm/tests/skipapparmor.inc | 30 | ||||
| -rw-r--r-- | sapi/fpm/tests/skipif.inc | 13 |
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"); +} + +?> |
