Skip to content

ext/standard: Refactor exec.c public APIs to use zend_string pointers #14353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,12 @@ PHP 8.4 INTERNALS UPGRADE NOTES
g. ext/standard
- Added the php_base64_encode_ex() API with flag parameters, value can be
PHP_BASE64_NO_PADDING to encode without the padding character '='.
- The php_escape_shell_cmd() now takes a zend_string* instead of a char*
Moreover, providing it with a binary safe string is the responsibility of
the caller now.
- The php_escape_shell_arg() now takes a zend_string* instead of a char*
Moreover, providing it with a binary safe string is the responsibility of
the caller now.

========================
4. OpCode changes
Expand Down
4 changes: 2 additions & 2 deletions ext/mbstring/mbstring.c
Original file line number Diff line number Diff line change
Expand Up @@ -4435,7 +4435,6 @@ PHP_FUNCTION(mb_send_mail)
zend_string *str_headers = NULL;
size_t i;
char *to_r = NULL;
char *force_extra_parameters = INI_STR("mail.force_extra_parameters");
bool suppress_content_type = false;
bool suppress_content_transfer_encoding = false;

Expand Down Expand Up @@ -4653,10 +4652,11 @@ PHP_FUNCTION(mb_send_mail)

str_headers = smart_str_extract(&str);

zend_string *force_extra_parameters = zend_ini_str_ex("mail.force_extra_parameters", strlen("mail.force_extra_parameters"), false, NULL);
if (force_extra_parameters) {
extra_cmd = php_escape_shell_cmd(force_extra_parameters);
} else if (extra_cmd) {
extra_cmd = php_escape_shell_cmd(ZSTR_VAL(extra_cmd));
extra_cmd = php_escape_shell_cmd(extra_cmd);
}

RETVAL_BOOL(php_mail(to_r, ZSTR_VAL(subject), message, ZSTR_VAL(str_headers), extra_cmd ? ZSTR_VAL(extra_cmd) : NULL));
Expand Down
47 changes: 22 additions & 25 deletions ext/standard/exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -279,19 +279,23 @@ PHP_FUNCTION(passthru)

*NOT* safe for binary strings
*/
PHPAPI zend_string *php_escape_shell_cmd(const char *str)
PHPAPI zend_string *php_escape_shell_cmd(const zend_string *unescaped_cmd)
{
size_t x, y;
size_t l = strlen(str);
uint64_t estimate = (2 * (uint64_t)l) + 1;
zend_string *cmd;
#ifndef PHP_WIN32
char *p = NULL;
#endif

ZEND_ASSERT(ZSTR_LEN(unescaped_cmd) == strlen(ZSTR_VAL(unescaped_cmd)) && "Must be a binary safe string");
size_t l = ZSTR_LEN(unescaped_cmd);
const char *str = ZSTR_VAL(unescaped_cmd);

uint64_t estimate = (2 * (uint64_t)l) + 1;

/* max command line length - two single quotes - \0 byte length */
if (l > cmd_max_len - 2 - 1) {
php_error_docref(NULL, E_ERROR, "Command exceeds the allowed length of %zu bytes", cmd_max_len);
zend_value_error("Command exceeds the allowed length of %zu bytes", cmd_max_len);
return ZSTR_EMPTY_ALLOC();
}

Expand Down Expand Up @@ -367,7 +371,7 @@ PHPAPI zend_string *php_escape_shell_cmd(const char *str)
ZSTR_VAL(cmd)[y] = '\0';

if (y > cmd_max_len + 1) {
php_error_docref(NULL, E_ERROR, "Escaped command exceeds the allowed length of %zu bytes", cmd_max_len);
zend_value_error("Escaped command exceeds the allowed length of %zu bytes", cmd_max_len);
zend_string_release_ex(cmd, 0);
return ZSTR_EMPTY_ALLOC();
}
Expand All @@ -385,16 +389,20 @@ PHPAPI zend_string *php_escape_shell_cmd(const char *str)
/* }}} */

/* {{{ php_escape_shell_arg */
PHPAPI zend_string *php_escape_shell_arg(const char *str)
PHPAPI zend_string *php_escape_shell_arg(const zend_string *unescaped_arg)
{
size_t x, y = 0;
size_t l = strlen(str);
zend_string *cmd;

ZEND_ASSERT(ZSTR_LEN(unescaped_arg) == strlen(ZSTR_VAL(unescaped_arg)) && "Must be a binary safe string");
size_t l = ZSTR_LEN(unescaped_arg);
const char *str = ZSTR_VAL(unescaped_arg);

uint64_t estimate = (4 * (uint64_t)l) + 3;

/* max command line length - two single quotes - \0 byte length */
if (l > cmd_max_len - 2 - 1) {
php_error_docref(NULL, E_ERROR, "Argument exceeds the allowed length of %zu bytes", cmd_max_len);
zend_value_error("Argument exceeds the allowed length of %zu bytes", cmd_max_len);
return ZSTR_EMPTY_ALLOC();
}

Expand Down Expand Up @@ -453,7 +461,7 @@ PHPAPI zend_string *php_escape_shell_arg(const char *str)
ZSTR_VAL(cmd)[y] = '\0';

if (y > cmd_max_len + 1) {
php_error_docref(NULL, E_ERROR, "Escaped argument exceeds the allowed length of %zu bytes", cmd_max_len);
zend_value_error("Escaped argument exceeds the allowed length of %zu bytes", cmd_max_len);
zend_string_release_ex(cmd, 0);
return ZSTR_EMPTY_ALLOC();
}
Expand All @@ -471,18 +479,13 @@ PHPAPI zend_string *php_escape_shell_arg(const char *str)
/* {{{ Escape shell metacharacters */
PHP_FUNCTION(escapeshellcmd)
{
char *command;
size_t command_len;
zend_string *command;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STRING(command, command_len)
Z_PARAM_PATH_STR(command)
ZEND_PARSE_PARAMETERS_END();

if (command_len) {
if (command_len != strlen(command)) {
zend_argument_value_error(1, "must not contain any null bytes");
RETURN_THROWS();
}
if (ZSTR_LEN(command)) {
RETVAL_STR(php_escape_shell_cmd(command));
} else {
RETVAL_EMPTY_STRING();
Expand All @@ -493,18 +496,12 @@ PHP_FUNCTION(escapeshellcmd)
/* {{{ Quote and escape an argument for use in a shell command */
PHP_FUNCTION(escapeshellarg)
{
char *argument;
size_t argument_len;
zend_string *argument;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STRING(argument, argument_len)
Z_PARAM_PATH_STR(argument)
ZEND_PARSE_PARAMETERS_END();

if (argument_len != strlen(argument)) {
zend_argument_value_error(1, "must not contain any null bytes");
RETURN_THROWS();
}

RETVAL_STR(php_escape_shell_arg(argument));
}
/* }}} */
Expand Down
4 changes: 2 additions & 2 deletions ext/standard/exec.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
PHP_MINIT_FUNCTION(proc_open);
PHP_MINIT_FUNCTION(exec);

PHPAPI zend_string *php_escape_shell_cmd(const char *str);
PHPAPI zend_string *php_escape_shell_arg(const char *str);
PHPAPI zend_string *php_escape_shell_cmd(const zend_string *unescaped_cmd);
PHPAPI zend_string *php_escape_shell_arg(const zend_string *unescaped_arg);
PHPAPI int php_exec(int type, const char *cmd, zval *array, zval *return_value);

#endif /* EXEC_H */
4 changes: 2 additions & 2 deletions ext/standard/mail.c
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,6 @@ PHP_FUNCTION(mail)
HashTable *headers_ht = NULL;
size_t to_len, message_len;
size_t subject_len, i;
char *force_extra_parameters = INI_STR("mail.force_extra_parameters");
char *to_r, *subject_r;

ZEND_PARSE_PARAMETERS_START(3, 5)
Expand Down Expand Up @@ -312,10 +311,11 @@ PHP_FUNCTION(mail)
subject_r = subject;
}

zend_string *force_extra_parameters = zend_ini_str_ex("mail.force_extra_parameters", strlen("mail.force_extra_parameters"), false, NULL);
if (force_extra_parameters) {
extra_cmd = php_escape_shell_cmd(force_extra_parameters);
} else if (extra_cmd) {
extra_cmd = php_escape_shell_cmd(ZSTR_VAL(extra_cmd));
extra_cmd = php_escape_shell_cmd(extra_cmd);
}

if (php_mail(to_r, subject_r, message, headers_str && ZSTR_LEN(headers_str) ? ZSTR_VAL(headers_str) : NULL, extra_cmd ? ZSTR_VAL(extra_cmd) : NULL)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@ Test escapeshellarg() allowed argument length
<?php
ini_set('memory_limit', -1);
$var_2 = str_repeat('A', 1024*1024*64);
escapeshellarg($var_2);

try {
escapeshellarg($var_2);
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}

?>
===DONE===
--EXPECTF--
Fatal error: escapeshellarg(): Argument exceeds the allowed length of %d bytes in %s on line %d
ValueError: Argument exceeds the allowed length of %d bytes
===DONE===
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@ Test escapeshellcmd() allowed argument length
<?php
ini_set('memory_limit', -1);
$var_2 = str_repeat('A', 1024*1024*64);
escapeshellcmd($var_2);

try {
escapeshellcmd($var_2);
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}

?>
===DONE===
--EXPECTF--
Fatal error: escapeshellcmd(): Command exceeds the allowed length of %d bytes in %s on line %d
ValueError: Command exceeds the allowed length of %d bytes
===DONE===
5 changes: 5 additions & 0 deletions main/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,11 @@ static PHP_INI_MH(OnUpdateMailLog)
/* {{{ PHP_INI_MH */
static PHP_INI_MH(OnChangeMailForceExtra)
{
/* Check that INI setting does not have any nul bytes */
if (new_value && ZSTR_LEN(new_value) != strlen(ZSTR_VAL(new_value))) {
/* TODO Emit warning? */
return FAILURE;
}
/* Don't allow changing it in htaccess */
if (stage == PHP_INI_STAGE_HTACCESS) {
return FAILURE;
Expand Down
Loading