Backported from 5.5 for 5.4 by Remi Collet From f4d7bbf4ace4ea21c8f95c2d1177bd56a21b86b9 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 28 Jan 2016 13:45:43 +0100 Subject: [PATCH] backport the escapeshell* functions hardening branch --- ext/standard/basic_functions.c | 1 + ext/standard/exec.c | 76 +++++++++++++++++++++++++++++++++++++++--- ext/standard/exec.h | 1 + 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index f8cb91e..50b6bc7 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -3613,6 +3613,7 @@ #ifdef PHP_CAN_SUPPORT_PROC_OPEN PHP_MINIT(proc_open)(INIT_FUNC_ARGS_PASSTHRU); #endif + PHP_MINIT(exec)(INIT_FUNC_ARGS_PASSTHRU); PHP_MINIT(user_streams)(INIT_FUNC_ARGS_PASSTHRU); PHP_MINIT(imagetypes)(INIT_FUNC_ARGS_PASSTHRU); diff --git a/ext/standard/exec.c b/ext/standard/exec.c index 66d4537..461bef4 100644 --- a/ext/standard/exec.c +++ b/ext/standard/exec.c @@ -46,10 +46,42 @@ #include #endif -#if HAVE_NICE && HAVE_UNISTD_H +#if HAVE_UNISTD_H #include #endif +#ifdef PHP_WIN32 +# include "win32/php_stdint.h" +#else +# if HAVE_INTTYPES_H +# include +# elif HAVE_STDINT_H +# include +# endif +#endif + +static int cmd_max_len; + +/* {{{ PHP_MINIT_FUNCTION(exec) */ +PHP_MINIT_FUNCTION(exec) +{ +#ifdef _SC_ARG_MAX + cmd_max_len = sysconf(_SC_ARG_MAX); +#elif defined(ARG_MAX) + cmd_max_len = ARG_MAX; +#elif defined(PHP_WIN32) + /* Executed commands will run through cmd.exe. As long as it's the case, + it's just the constant limit.*/ + cmd_max_len = 8192; +#else + /* This is just an arbitrary value for the fallback case. */ + cmd_max_len = 4096; +#endif + + return SUCCESS; +} +/* }}} */ + /* {{{ php_exec * If type==0, only last line of output is returned (exec) * If type==1, all lines will be printed and last lined returned (system) @@ -244,13 +276,20 @@ PHP_FUNCTION(passthru) */ PHPAPI char *php_escape_shell_cmd(char *str) { - register int x, y, l = strlen(str); + register int x, y; + size_t l = strlen(str); + uint64_t estimate = (2 * (uint64_t)l) + 1; char *cmd; char *p = NULL; - size_t estimate = (2 * l) + 1; TSRMLS_FETCH(); + /* max command line length - two single quotes - \0 byte length */ + if (l > cmd_max_len - 2 - 1) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Command exceeds the allowed length of %d bytes", cmd_max_len); + return NULL; + } + cmd = safe_emalloc(2, l, 1); for (x = 0, y = 0; x < l; x++) { @@ -322,6 +361,12 @@ PHPAPI char *php_escape_shell_cmd(char *str) } cmd[y] = '\0'; + if (y - 1 > cmd_max_len) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Escaped command exceeds the allowed length of %d bytes", cmd_max_len); + efree(cmd); + return NULL; + } + if ((estimate - y) > 4096) { /* realloc if the estimate was way overill * Arbitrary cutoff point of 4096 */ @@ -336,12 +381,19 @@ PHPAPI char *php_escape_shell_cmd(char *str) */ PHPAPI char *php_escape_shell_arg(char *str) { - int x, y = 0, l = strlen(str); + int x, y = 0; + size_t l = strlen(str); char *cmd; - size_t estimate = (4 * l) + 3; + uint64_t estimate = (4 * (uint64_t)l) + 3; TSRMLS_FETCH(); + /* max command line length - two single quotes - \0 byte length */ + if (l > cmd_max_len - 2 - 1) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Argument exceeds the allowed length of %d bytes", cmd_max_len); + return NULL; + } + cmd = safe_emalloc(4, l, 3); /* worst case */ #ifdef PHP_WIN32 @@ -396,6 +448,12 @@ PHPAPI char *php_escape_shell_arg(char *str) #endif cmd[y] = '\0'; + if (y - 1 > cmd_max_len) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Escaped argument exceeds the allowed length of %d bytes", cmd_max_len); + efree(cmd); + return NULL; + } + if ((estimate - y) > 4096) { /* realloc if the estimate was way overill * Arbitrary cutoff point of 4096 */ @@ -418,6 +476,10 @@ PHP_FUNCTION(escapeshellcmd) } if (command_len) { + if (command_len != strlen(command)) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Input string contains NULL bytes"); + return; + } cmd = php_escape_shell_cmd(command); RETVAL_STRING(cmd, 0); } else { @@ -439,6 +501,10 @@ PHP_FUNCTION(escapeshellarg) } if (argument) { + if (argument_len != strlen(argument)) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Input string contains NULL bytes"); + return; + } cmd = php_escape_shell_arg(argument); RETVAL_STRING(cmd, 0); } diff --git a/ext/standard/exec.h b/ext/standard/exec.h index 399325c..b106838 100644 --- a/ext/standard/exec.h +++ b/ext/standard/exec.h @@ -33,6 +33,7 @@ PHP_FUNCTION(proc_close); PHP_FUNCTION(proc_terminate); PHP_FUNCTION(proc_nice); PHP_MINIT_FUNCTION(proc_open); +PHP_MINIT_FUNCTION(exec); PHPAPI char *php_escape_shell_cmd(char *); PHPAPI char *php_escape_shell_arg(char *); From 828364e59ca08c2ff3328a648522f9eb05ebbaa3 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 28 Jan 2016 13:27:26 +0100 Subject: [PATCH] add tests --- .../tests/general_functions/escapeshellarg_bug71039.phpt | 10 ++++++++++ .../tests/general_functions/escapeshellarg_bug71270.phpt | 12 ++++++++++++ .../tests/general_functions/escapeshellcmd_bug71039.phpt | 10 ++++++++++ .../tests/general_functions/escapeshellcmd_bug71270.phpt | 12 ++++++++++++ 4 files changed, 44 insertions(+) create mode 100644 ext/standard/tests/general_functions/escapeshellarg_bug71039.phpt create mode 100644 ext/standard/tests/general_functions/escapeshellarg_bug71270.phpt create mode 100644 ext/standard/tests/general_functions/escapeshellcmd_bug71039.phpt create mode 100644 ext/standard/tests/general_functions/escapeshellcmd_bug71270.phpt diff --git a/ext/standard/tests/general_functions/escapeshellarg_bug71039.phpt b/ext/standard/tests/general_functions/escapeshellarg_bug71039.phpt new file mode 100644 index 0000000..cbb3f6f --- /dev/null +++ b/ext/standard/tests/general_functions/escapeshellarg_bug71039.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test escapeshellarg() string with \0 bytes +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: escapeshellarg(): Input string contains NULL bytes in %s on line %d diff --git a/ext/standard/tests/general_functions/escapeshellarg_bug71270.phpt b/ext/standard/tests/general_functions/escapeshellarg_bug71270.phpt new file mode 100644 index 0000000..c57da36 --- /dev/null +++ b/ext/standard/tests/general_functions/escapeshellarg_bug71270.phpt @@ -0,0 +1,12 @@ +--TEST-- +Test escapeshellarg() allowed argument length +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: escapeshellarg(): Argument exceeds the allowed length of %d bytes in %s on line %d diff --git a/ext/standard/tests/general_functions/escapeshellcmd_bug71039.phpt b/ext/standard/tests/general_functions/escapeshellcmd_bug71039.phpt new file mode 100644 index 0000000..0a4d7ea --- /dev/null +++ b/ext/standard/tests/general_functions/escapeshellcmd_bug71039.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test escapeshellcmd() string with \0 bytes +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: escapeshellcmd(): Input string contains NULL bytes in %s on line %d diff --git a/ext/standard/tests/general_functions/escapeshellcmd_bug71270.phpt b/ext/standard/tests/general_functions/escapeshellcmd_bug71270.phpt new file mode 100644 index 0000000..4686193 --- /dev/null +++ b/ext/standard/tests/general_functions/escapeshellcmd_bug71270.phpt @@ -0,0 +1,12 @@ +--TEST-- +Test escapeshellcmd() allowed argument length +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: escapeshellcmd(): Command exceeds the allowed length of %d bytes in %s on line %d From 377d353c9f8aad6f79f3cf84aad3e2f6d65fa456 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Tue, 2 Feb 2016 14:19:10 +0100 Subject: [PATCH] add error check to sysconf call --- ext/standard/exec.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ext/standard/exec.c b/ext/standard/exec.c index 461bef4..329066e 100644 --- a/ext/standard/exec.c +++ b/ext/standard/exec.c @@ -50,6 +50,10 @@ #include #endif +#if HAVE_LIMITS_H +#include +#endif + #ifdef PHP_WIN32 # include "win32/php_stdint.h" #else @@ -67,6 +71,13 @@ PHP_MINIT_FUNCTION(exec) { #ifdef _SC_ARG_MAX cmd_max_len = sysconf(_SC_ARG_MAX); + if (-1 == cmd_max_len) { +#ifdef _POSIX_ARG_MAX + cmd_max_len = _POSIX_ARG_MAX; +#else + cmd_max_len = 4096; +#endif + } #elif defined(ARG_MAX) cmd_max_len = ARG_MAX; #elif defined(PHP_WIN32)