Skip to content

Commit 48d5ae9

Browse files
authored
ext/standard: Refactor exec.c public APIs to use zend_string pointers (#14353)
* Pull zend_string* from INI directive * Ensure that mail.force_extra_parameters INI directive does not have any nul bytes * ext/standard: Make php_escape_shell_cmd() take a zend_string* instead of char* This saves on an expensive strlen() computation * Convert E_ERROR to ValueError in php_escape_shell_cmd() * ext/standard: Make php_escape_shell_arg() take a zend_string* instead of char* This saves on an expensive strlen() computation * Convert E_ERROR to ValueError in php_escape_shell_arg()
1 parent 06fcf3c commit 48d5ae9

File tree

8 files changed

+55
-35
lines changed

8 files changed

+55
-35
lines changed

UPGRADING.INTERNALS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,12 @@ PHP 8.4 INTERNALS UPGRADE NOTES
246246
g. ext/standard
247247
- Added the php_base64_encode_ex() API with flag parameters, value can be
248248
PHP_BASE64_NO_PADDING to encode without the padding character '='.
249+
- The php_escape_shell_cmd() now takes a zend_string* instead of a char*
250+
Moreover, providing it with a binary safe string is the responsibility of
251+
the caller now.
252+
- The php_escape_shell_arg() now takes a zend_string* instead of a char*
253+
Moreover, providing it with a binary safe string is the responsibility of
254+
the caller now.
249255

250256
========================
251257
4. OpCode changes

ext/mbstring/mbstring.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4435,7 +4435,6 @@ PHP_FUNCTION(mb_send_mail)
44354435
zend_string *str_headers = NULL;
44364436
size_t i;
44374437
char *to_r = NULL;
4438-
char *force_extra_parameters = INI_STR("mail.force_extra_parameters");
44394438
bool suppress_content_type = false;
44404439
bool suppress_content_transfer_encoding = false;
44414440

@@ -4653,10 +4652,11 @@ PHP_FUNCTION(mb_send_mail)
46534652

46544653
str_headers = smart_str_extract(&str);
46554654

4655+
zend_string *force_extra_parameters = zend_ini_str_ex("mail.force_extra_parameters", strlen("mail.force_extra_parameters"), false, NULL);
46564656
if (force_extra_parameters) {
46574657
extra_cmd = php_escape_shell_cmd(force_extra_parameters);
46584658
} else if (extra_cmd) {
4659-
extra_cmd = php_escape_shell_cmd(ZSTR_VAL(extra_cmd));
4659+
extra_cmd = php_escape_shell_cmd(extra_cmd);
46604660
}
46614661

46624662
RETVAL_BOOL(php_mail(to_r, ZSTR_VAL(subject), message, ZSTR_VAL(str_headers), extra_cmd ? ZSTR_VAL(extra_cmd) : NULL));

ext/standard/exec.c

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -279,19 +279,23 @@ PHP_FUNCTION(passthru)
279279
280280
*NOT* safe for binary strings
281281
*/
282-
PHPAPI zend_string *php_escape_shell_cmd(const char *str)
282+
PHPAPI zend_string *php_escape_shell_cmd(const zend_string *unescaped_cmd)
283283
{
284284
size_t x, y;
285-
size_t l = strlen(str);
286-
uint64_t estimate = (2 * (uint64_t)l) + 1;
287285
zend_string *cmd;
288286
#ifndef PHP_WIN32
289287
char *p = NULL;
290288
#endif
291289

290+
ZEND_ASSERT(ZSTR_LEN(unescaped_cmd) == strlen(ZSTR_VAL(unescaped_cmd)) && "Must be a binary safe string");
291+
size_t l = ZSTR_LEN(unescaped_cmd);
292+
const char *str = ZSTR_VAL(unescaped_cmd);
293+
294+
uint64_t estimate = (2 * (uint64_t)l) + 1;
295+
292296
/* max command line length - two single quotes - \0 byte length */
293297
if (l > cmd_max_len - 2 - 1) {
294-
php_error_docref(NULL, E_ERROR, "Command exceeds the allowed length of %zu bytes", cmd_max_len);
298+
zend_value_error("Command exceeds the allowed length of %zu bytes", cmd_max_len);
295299
return ZSTR_EMPTY_ALLOC();
296300
}
297301

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

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

387391
/* {{{ php_escape_shell_arg */
388-
PHPAPI zend_string *php_escape_shell_arg(const char *str)
392+
PHPAPI zend_string *php_escape_shell_arg(const zend_string *unescaped_arg)
389393
{
390394
size_t x, y = 0;
391-
size_t l = strlen(str);
392395
zend_string *cmd;
396+
397+
ZEND_ASSERT(ZSTR_LEN(unescaped_arg) == strlen(ZSTR_VAL(unescaped_arg)) && "Must be a binary safe string");
398+
size_t l = ZSTR_LEN(unescaped_arg);
399+
const char *str = ZSTR_VAL(unescaped_arg);
400+
393401
uint64_t estimate = (4 * (uint64_t)l) + 3;
394402

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

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

455463
if (y > cmd_max_len + 1) {
456-
php_error_docref(NULL, E_ERROR, "Escaped argument exceeds the allowed length of %zu bytes", cmd_max_len);
464+
zend_value_error("Escaped argument exceeds the allowed length of %zu bytes", cmd_max_len);
457465
zend_string_release_ex(cmd, 0);
458466
return ZSTR_EMPTY_ALLOC();
459467
}
@@ -471,18 +479,13 @@ PHPAPI zend_string *php_escape_shell_arg(const char *str)
471479
/* {{{ Escape shell metacharacters */
472480
PHP_FUNCTION(escapeshellcmd)
473481
{
474-
char *command;
475-
size_t command_len;
482+
zend_string *command;
476483

477484
ZEND_PARSE_PARAMETERS_START(1, 1)
478-
Z_PARAM_STRING(command, command_len)
485+
Z_PARAM_PATH_STR(command)
479486
ZEND_PARSE_PARAMETERS_END();
480487

481-
if (command_len) {
482-
if (command_len != strlen(command)) {
483-
zend_argument_value_error(1, "must not contain any null bytes");
484-
RETURN_THROWS();
485-
}
488+
if (ZSTR_LEN(command)) {
486489
RETVAL_STR(php_escape_shell_cmd(command));
487490
} else {
488491
RETVAL_EMPTY_STRING();
@@ -493,18 +496,12 @@ PHP_FUNCTION(escapeshellcmd)
493496
/* {{{ Quote and escape an argument for use in a shell command */
494497
PHP_FUNCTION(escapeshellarg)
495498
{
496-
char *argument;
497-
size_t argument_len;
499+
zend_string *argument;
498500

499501
ZEND_PARSE_PARAMETERS_START(1, 1)
500-
Z_PARAM_STRING(argument, argument_len)
502+
Z_PARAM_PATH_STR(argument)
501503
ZEND_PARSE_PARAMETERS_END();
502504

503-
if (argument_len != strlen(argument)) {
504-
zend_argument_value_error(1, "must not contain any null bytes");
505-
RETURN_THROWS();
506-
}
507-
508505
RETVAL_STR(php_escape_shell_arg(argument));
509506
}
510507
/* }}} */

ext/standard/exec.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
PHP_MINIT_FUNCTION(proc_open);
2121
PHP_MINIT_FUNCTION(exec);
2222

23-
PHPAPI zend_string *php_escape_shell_cmd(const char *str);
24-
PHPAPI zend_string *php_escape_shell_arg(const char *str);
23+
PHPAPI zend_string *php_escape_shell_cmd(const zend_string *unescaped_cmd);
24+
PHPAPI zend_string *php_escape_shell_arg(const zend_string *unescaped_arg);
2525
PHPAPI int php_exec(int type, const char *cmd, zval *array, zval *return_value);
2626

2727
#endif /* EXEC_H */

ext/standard/mail.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,6 @@ PHP_FUNCTION(mail)
247247
HashTable *headers_ht = NULL;
248248
size_t to_len, message_len;
249249
size_t subject_len, i;
250-
char *force_extra_parameters = INI_STR("mail.force_extra_parameters");
251250
char *to_r, *subject_r;
252251

253252
ZEND_PARSE_PARAMETERS_START(3, 5)
@@ -312,10 +311,11 @@ PHP_FUNCTION(mail)
312311
subject_r = subject;
313312
}
314313

314+
zend_string *force_extra_parameters = zend_ini_str_ex("mail.force_extra_parameters", strlen("mail.force_extra_parameters"), false, NULL);
315315
if (force_extra_parameters) {
316316
extra_cmd = php_escape_shell_cmd(force_extra_parameters);
317317
} else if (extra_cmd) {
318-
extra_cmd = php_escape_shell_cmd(ZSTR_VAL(extra_cmd));
318+
extra_cmd = php_escape_shell_cmd(extra_cmd);
319319
}
320320

321321
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)) {

ext/standard/tests/general_functions/escapeshellarg_bug71270.phpt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ Test escapeshellarg() allowed argument length
44
<?php
55
ini_set('memory_limit', -1);
66
$var_2 = str_repeat('A', 1024*1024*64);
7-
escapeshellarg($var_2);
7+
8+
try {
9+
escapeshellarg($var_2);
10+
} catch (Throwable $e) {
11+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
12+
}
813

914
?>
1015
===DONE===
1116
--EXPECTF--
12-
Fatal error: escapeshellarg(): Argument exceeds the allowed length of %d bytes in %s on line %d
17+
ValueError: Argument exceeds the allowed length of %d bytes
18+
===DONE===

ext/standard/tests/general_functions/escapeshellcmd_bug71270.phpt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ Test escapeshellcmd() allowed argument length
44
<?php
55
ini_set('memory_limit', -1);
66
$var_2 = str_repeat('A', 1024*1024*64);
7-
escapeshellcmd($var_2);
7+
8+
try {
9+
escapeshellcmd($var_2);
10+
} catch (Throwable $e) {
11+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
12+
}
813

914
?>
1015
===DONE===
1116
--EXPECTF--
12-
Fatal error: escapeshellcmd(): Command exceeds the allowed length of %d bytes in %s on line %d
17+
ValueError: Command exceeds the allowed length of %d bytes
18+
===DONE===

main/main.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,11 @@ static PHP_INI_MH(OnUpdateMailLog)
652652
/* {{{ PHP_INI_MH */
653653
static PHP_INI_MH(OnChangeMailForceExtra)
654654
{
655+
/* Check that INI setting does not have any nul bytes */
656+
if (new_value && ZSTR_LEN(new_value) != strlen(ZSTR_VAL(new_value))) {
657+
/* TODO Emit warning? */
658+
return FAILURE;
659+
}
655660
/* Don't allow changing it in htaccess */
656661
if (stage == PHP_INI_STAGE_HTACCESS) {
657662
return FAILURE;

0 commit comments

Comments
 (0)