Added validation of \n in $additional_headers of mail()

When $additional_headers of mail() is an array, the same validation as
`\r\n` is now applied to `\n` alone too.
This commit is contained in:
Saki Takamachi 2024-03-04 21:30:07 +09:00
parent 7c8a3e426e
commit 04e8e55f47
No known key found for this signature in database
GPG Key ID: E4A36F6D37931A8B
6 changed files with 109 additions and 9 deletions

2
NEWS
View File

@ -15,6 +15,8 @@ PHP NEWS
- Standard:
. Fixed bug GH-11808 (Live filesystem modified by tests). (nielsdos)
. Fixed GH-13402: Added validation of `\n` in `$additional_headers` of `mail()`
(SakiTakamachi)
- XML:
. Fixed bug GH-13517 (Multiple test failures when building with

View File

@ -243,6 +243,8 @@ PHP 8.2 UPGRADE NOTES
objects.
. mail() function reverts back to the mixed LF and CRLF new lines (behavior
before PHP 8.0) if mail.mixed_lf_and_crlf INI is on.
. When $additional_headers of mail() is an array, the same validation as
`\r\n` is now applied to `\n` alone too.
- XML
. xml_parser_set_option() now actually returns false when attempting to set a

View File

@ -57,7 +57,7 @@
extern zend_long php_getuid(void);
static bool php_mail_build_headers_check_field_value(zval *val)
static php_mail_header_value_error_type php_mail_build_headers_check_field_value(zval *val)
{
size_t len = 0;
zend_string *value = Z_STR_P(val);
@ -66,20 +66,39 @@ static bool php_mail_build_headers_check_field_value(zval *val)
/* https://tools.ietf.org/html/rfc2822#section-2.2.3 */
while (len < value->len) {
if (*(value->val+len) == '\r') {
if (*(value->val+len+1) != '\n') {
return CONTAINS_CR_ONLY;
}
if (value->len - len >= 3
&& *(value->val+len+1) == '\n'
&& (*(value->val+len+2) == ' ' || *(value->val+len+2) == '\t')) {
len += 3;
continue;
}
return FAILURE;
return CONTAINS_CRLF;
}
/**
* The RFC does not allow using LF alone for folding. However, LF is
* often treated similarly to CRLF, and there are likely many user
* environments that use LF for folding.
* Therefore, considering such an environment, folding with LF alone
* is allowed.
*/
if (*(value->val+len) == '\n') {
if (value->len - len >= 2
&& (*(value->val+len+1) == ' ' || *(value->val+len+1) == '\t')) {
len += 2;
continue;
}
return CONTAINS_LF_ONLY;
}
if (*(value->val+len) == '\0') {
return FAILURE;
return CONTAINS_NULL;
}
len++;
}
return SUCCESS;
return NO_HEADER_ERROR;
}
@ -108,9 +127,27 @@ static void php_mail_build_headers_elem(smart_str *s, zend_string *key, zval *va
zend_value_error("Header name \"%s\" contains invalid characters", ZSTR_VAL(key));
return;
}
if (php_mail_build_headers_check_field_value(val) != SUCCESS) {
zend_value_error("Header \"%s\" has invalid format, or contains invalid characters", ZSTR_VAL(key));
return;
php_mail_header_value_error_type error_type = php_mail_build_headers_check_field_value(val);
switch (error_type) {
case NO_HEADER_ERROR:
break;
case CONTAINS_LF_ONLY:
zend_value_error("Header \"%s\" contains LF character that is not allowed in the header", ZSTR_VAL(key));
return;
case CONTAINS_CR_ONLY:
zend_value_error("Header \"%s\" contains CR character that is not allowed in the header", ZSTR_VAL(key));
return;
case CONTAINS_CRLF:
zend_value_error("Header \"%s\" contains CRLF characters that are used as a line separator and are not allowed in the header", ZSTR_VAL(key));
return;
case CONTAINS_NULL:
zend_value_error("Header \"%s\" contains NULL character that is not allowed in the header", ZSTR_VAL(key));
return;
default:
// fallback
zend_value_error("Header \"%s\" has invalid format, or contains invalid characters", ZSTR_VAL(key));
return;
}
smart_str_append(s, key);
smart_str_appendl(s, ": ", 2);

View File

@ -49,5 +49,12 @@ do { \
} \
} while(0)
typedef enum {
NO_HEADER_ERROR,
CONTAINS_LF_ONLY,
CONTAINS_CR_ONLY,
CONTAINS_CRLF,
CONTAINS_NULL
} php_mail_header_value_error_type;
#endif /* PHP_MAIL_H */

View File

@ -0,0 +1,52 @@
--TEST--
GH-13415 (Added validation of line breaks \n in $additional_headers of mail())
--INI--
sendmail_path={MAIL:gh13415.out}
--FILE--
<?php
echo "LF only:\n";
try {
mail('to@example.com', 'Test Subject', 'A Message', ['Reply-To' => "foo@example.com \nCc: hacker@example.com"]);
} catch (Throwable $e) {
echo $e->getMessage()."\n\n";
}
echo "CR only:\n";
try {
mail('to@example.com', 'Test Subject', 'A Message', ['Reply-To' => "foo@example.com \rCc: hacker@example.com"]);
} catch (Throwable $e) {
echo $e->getMessage()."\n\n";
}
echo "CRLF:\n";
try {
mail('to@example.com', 'Test Subject', 'A Message', ['Reply-To' => "foo@example.com \r\nCc: hacker@example.com"]);
} catch (Throwable $e) {
echo $e->getMessage()."\n\n";
}
echo "NULL:\n";
try {
mail('to@example.com', 'Test Subject', 'A Message', ['Reply-To' => "foo@example.com \0Cc: hacker@example.com"]);
} catch (Throwable $e) {
echo $e->getMessage()."\n\n";
}
?>
--CLEAN--
<?php
if (file_exists('gh13415.out')) {
unlink('gh13415.out');
}
?>
--EXPECTF--
LF only:
Header "Reply-To" contains LF character that is not allowed in the header
CR only:
Header "Reply-To" contains CR character that is not allowed in the header
CRLF:
Header "Reply-To" contains CRLF characters that are used as a line separator and are not allowed in the header
NULL:
Header "Reply-To" contains NULL character that is not allowed in the header

View File

@ -258,4 +258,4 @@ Subject: Test Subject
foo9: %&$#!
A Message
ValueError: Header "foo10" has invalid format, or contains invalid characters
ValueError: Header "foo10" contains NULL character that is not allowed in the header