Skip to content

Commit 04e8e55

Browse files
committed
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.
1 parent 7c8a3e4 commit 04e8e55

File tree

6 files changed

+109
-9
lines changed

6 files changed

+109
-9
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ PHP NEWS
1515

1616
- Standard:
1717
. Fixed bug GH-11808 (Live filesystem modified by tests). (nielsdos)
18+
. Fixed GH-13402: Added validation of `\n` in `$additional_headers` of `mail()`
19+
(SakiTakamachi)
1820

1921
- XML:
2022
. Fixed bug GH-13517 (Multiple test failures when building with

UPGRADING

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ PHP 8.2 UPGRADE NOTES
243243
objects.
244244
. mail() function reverts back to the mixed LF and CRLF new lines (behavior
245245
before PHP 8.0) if mail.mixed_lf_and_crlf INI is on.
246+
. When $additional_headers of mail() is an array, the same validation as
247+
`\r\n` is now applied to `\n` alone too.
246248

247249
- XML
248250
. xml_parser_set_option() now actually returns false when attempting to set a

ext/standard/mail.c

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757

5858
extern zend_long php_getuid(void);
5959

60-
static bool php_mail_build_headers_check_field_value(zval *val)
60+
static php_mail_header_value_error_type php_mail_build_headers_check_field_value(zval *val)
6161
{
6262
size_t len = 0;
6363
zend_string *value = Z_STR_P(val);
@@ -66,20 +66,39 @@ static bool php_mail_build_headers_check_field_value(zval *val)
6666
/* https://tools.ietf.org/html/rfc2822#section-2.2.3 */
6767
while (len < value->len) {
6868
if (*(value->val+len) == '\r') {
69+
if (*(value->val+len+1) != '\n') {
70+
return CONTAINS_CR_ONLY;
71+
}
72+
6973
if (value->len - len >= 3
70-
&& *(value->val+len+1) == '\n'
7174
&& (*(value->val+len+2) == ' ' || *(value->val+len+2) == '\t')) {
7275
len += 3;
7376
continue;
7477
}
75-
return FAILURE;
78+
79+
return CONTAINS_CRLF;
80+
}
81+
/**
82+
* The RFC does not allow using LF alone for folding. However, LF is
83+
* often treated similarly to CRLF, and there are likely many user
84+
* environments that use LF for folding.
85+
* Therefore, considering such an environment, folding with LF alone
86+
* is allowed.
87+
*/
88+
if (*(value->val+len) == '\n') {
89+
if (value->len - len >= 2
90+
&& (*(value->val+len+1) == ' ' || *(value->val+len+1) == '\t')) {
91+
len += 2;
92+
continue;
93+
}
94+
return CONTAINS_LF_ONLY;
7695
}
7796
if (*(value->val+len) == '\0') {
78-
return FAILURE;
97+
return CONTAINS_NULL;
7998
}
8099
len++;
81100
}
82-
return SUCCESS;
101+
return NO_HEADER_ERROR;
83102
}
84103

85104

@@ -108,9 +127,27 @@ static void php_mail_build_headers_elem(smart_str *s, zend_string *key, zval *va
108127
zend_value_error("Header name \"%s\" contains invalid characters", ZSTR_VAL(key));
109128
return;
110129
}
111-
if (php_mail_build_headers_check_field_value(val) != SUCCESS) {
112-
zend_value_error("Header \"%s\" has invalid format, or contains invalid characters", ZSTR_VAL(key));
113-
return;
130+
131+
php_mail_header_value_error_type error_type = php_mail_build_headers_check_field_value(val);
132+
switch (error_type) {
133+
case NO_HEADER_ERROR:
134+
break;
135+
case CONTAINS_LF_ONLY:
136+
zend_value_error("Header \"%s\" contains LF character that is not allowed in the header", ZSTR_VAL(key));
137+
return;
138+
case CONTAINS_CR_ONLY:
139+
zend_value_error("Header \"%s\" contains CR character that is not allowed in the header", ZSTR_VAL(key));
140+
return;
141+
case CONTAINS_CRLF:
142+
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));
143+
return;
144+
case CONTAINS_NULL:
145+
zend_value_error("Header \"%s\" contains NULL character that is not allowed in the header", ZSTR_VAL(key));
146+
return;
147+
default:
148+
// fallback
149+
zend_value_error("Header \"%s\" has invalid format, or contains invalid characters", ZSTR_VAL(key));
150+
return;
114151
}
115152
smart_str_append(s, key);
116153
smart_str_appendl(s, ": ", 2);

ext/standard/php_mail.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,12 @@ do { \
4949
} \
5050
} while(0)
5151

52+
typedef enum {
53+
NO_HEADER_ERROR,
54+
CONTAINS_LF_ONLY,
55+
CONTAINS_CR_ONLY,
56+
CONTAINS_CRLF,
57+
CONTAINS_NULL
58+
} php_mail_header_value_error_type;
5259

5360
#endif /* PHP_MAIL_H */

ext/standard/tests/mail/gh13415.phpt

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
--TEST--
2+
GH-13415 (Added validation of line breaks \n in $additional_headers of mail())
3+
--INI--
4+
sendmail_path={MAIL:gh13415.out}
5+
--FILE--
6+
<?php
7+
echo "LF only:\n";
8+
try {
9+
mail('to@example.com', 'Test Subject', 'A Message', ['Reply-To' => "foo@example.com \nCc: hacker@example.com"]);
10+
} catch (Throwable $e) {
11+
echo $e->getMessage()."\n\n";
12+
}
13+
14+
echo "CR only:\n";
15+
try {
16+
mail('to@example.com', 'Test Subject', 'A Message', ['Reply-To' => "foo@example.com \rCc: hacker@example.com"]);
17+
} catch (Throwable $e) {
18+
echo $e->getMessage()."\n\n";
19+
}
20+
21+
echo "CRLF:\n";
22+
try {
23+
mail('to@example.com', 'Test Subject', 'A Message', ['Reply-To' => "foo@example.com \r\nCc: hacker@example.com"]);
24+
} catch (Throwable $e) {
25+
echo $e->getMessage()."\n\n";
26+
}
27+
28+
echo "NULL:\n";
29+
try {
30+
mail('to@example.com', 'Test Subject', 'A Message', ['Reply-To' => "foo@example.com \0Cc: hacker@example.com"]);
31+
} catch (Throwable $e) {
32+
echo $e->getMessage()."\n\n";
33+
}
34+
?>
35+
--CLEAN--
36+
<?php
37+
if (file_exists('gh13415.out')) {
38+
unlink('gh13415.out');
39+
}
40+
?>
41+
--EXPECTF--
42+
LF only:
43+
Header "Reply-To" contains LF character that is not allowed in the header
44+
45+
CR only:
46+
Header "Reply-To" contains CR character that is not allowed in the header
47+
48+
CRLF:
49+
Header "Reply-To" contains CRLF characters that are used as a line separator and are not allowed in the header
50+
51+
NULL:
52+
Header "Reply-To" contains NULL character that is not allowed in the header

ext/standard/tests/mail/mail_basic7.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,4 +258,4 @@ Subject: Test Subject
258258
foo9: %&$#!
259259

260260
A Message
261-
ValueError: Header "foo10" has invalid format, or contains invalid characters
261+
ValueError: Header "foo10" contains NULL character that is not allowed in the header

0 commit comments

Comments
 (0)