From: Alhuda Khan Date: Thu, 2 Jul 2026 10:53:36 +0000 (+0530) Subject: mime: reject CR and LF in mail part name and filename X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c3f9ef13f62a2f09466cbfec8676eb6fbb361313;p=thirdparty%2Fcurl.git mime: reject CR and LF in mail part name and filename Closes #22247 --- diff --git a/lib/mime.c b/lib/mime.c index 077e599e43..11eb66207d 100644 --- a/lib/mime.c +++ b/lib/mime.c @@ -1708,6 +1708,16 @@ static CURLcode add_content_disposition(struct Curl_easy *data, CURLcode result = CURLE_OK; char *name = NULL; char *filename = NULL; + /* The mail (and legacy mime_formescape) strategy quotes the name and + filename with a backslash and has no in-band way to represent a CR or + LF, so one embedded in the value would split the generated header. The + form strategy percent-encodes CR/LF (see escape_string) and is safe. */ + bool backslash = (strategy == MIMESTRATEGY_MAIL) || + (data && data->set.mime_formescape); + if(backslash && + ((part->name && part->name[strcspn(part->name, "\r\n")]) || + (part->filename && part->filename[strcspn(part->filename, "\r\n")]))) + return CURLE_BAD_FUNCTION_ARGUMENT; if(part->name) { name = escape_string(data, part->name, strategy); diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 53a1927146..e124958874 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -284,7 +284,7 @@ test3100 test3101 test3102 test3103 test3104 test3105 test3106 \ test3200 test3201 test3202 test3203 test3204 test3205 test3206 test3207 \ test3208 test3209 test3210 test3211 test3212 test3213 test3214 test3215 \ test3216 test3217 test3218 test3219 test3220 test3221 test3222 \ -test3223 test3224 test3225 test3226 \ +test3223 test3224 test3225 test3226 test3227 \ \ test3300 test3301 test3302 test3303 test3304 test3305 \ \ diff --git a/tests/data/test3227 b/tests/data/test3227 new file mode 100644 index 0000000000..cd9d74b30f --- /dev/null +++ b/tests/data/test3227 @@ -0,0 +1,20 @@ + + + + +unittest +mime +SMTP + + + +# Client-side + + +unittest + + +reject CR and LF in mail mime part name and filename + + + diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc index d1fec804ee..c292e7a198 100644 --- a/tests/unit/Makefile.inc +++ b/tests/unit/Makefile.inc @@ -48,4 +48,5 @@ TESTS_C = \ unit2600.c unit2601.c unit2602.c unit2603.c unit2604.c unit2605.c \ unit3200.c unit3205.c \ unit3211.c unit3212.c unit3213.c unit3214.c unit3216.c unit3219.c \ + unit3227.c \ unit3300.c unit3301.c unit3302.c unit3303.c unit3304.c unit3400.c diff --git a/tests/unit/unit3227.c b/tests/unit/unit3227.c new file mode 100644 index 0000000000..42d068e99d --- /dev/null +++ b/tests/unit/unit3227.c @@ -0,0 +1,133 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" +#include "urldata.h" +#include "mime.h" + +#if !defined(CURL_DISABLE_MIME) && (!defined(CURL_DISABLE_HTTP) || \ + !defined(CURL_DISABLE_SMTP) || !defined(CURL_DISABLE_IMAP)) + +/* does any generated part header carry a raw CR or LF, i.e. a split line? */ +static bool headers_have_ctrl(const curl_mimepart *part) +{ + const struct curl_slist *h; + for(h = part->curlheaders; h; h = h->next) + if(h->data[strcspn(h->data, "\r\n")]) + return TRUE; + return FALSE; +} + +static CURLcode t3227_setup(void) +{ + CURLcode result = CURLE_OK; + global_init(CURL_GLOBAL_ALL); + return result; +} + +static void t3227_stop(CURL *easy, curl_mime *mime) +{ + curl_mime_free(mime); + if(easy) + curl_easy_cleanup(easy); + curl_global_cleanup(); +} + +static CURLcode test_unit3227(const char *arg) +{ + CURL *easy = NULL; + curl_mime *mime = NULL; + curl_mimepart *part; + + UNITTEST_BEGIN(t3227_setup()) + + easy = curl_easy_init(); + abort_unless(easy, "curl_easy_init()"); + mime = curl_mime_init(easy); + abort_unless(mime, "curl_mime_init()"); + + /* A CR or LF in the mail-part name or filename would split the generated + Content-Disposition header. The mail strategy must reject it. */ + + part = curl_mime_addpart(mime); + abort_unless(part, "curl_mime_addpart()"); + curl_mime_data(part, "x", 1); + curl_mime_filename(part, "a\r\nX-Injected: 1"); + fail_unless(Curl_mime_prepare_headers(easy, part, NULL, NULL, + MIMESTRATEGY_MAIL) == + CURLE_BAD_FUNCTION_ARGUMENT, + "CRLF filename accepted for mail"); + + part = curl_mime_addpart(mime); + abort_unless(part, "curl_mime_addpart()"); + curl_mime_data(part, "x", 1); + curl_mime_name(part, "a\nb"); + fail_unless(Curl_mime_prepare_headers(easy, part, NULL, NULL, + MIMESTRATEGY_MAIL) == + CURLE_BAD_FUNCTION_ARGUMENT, + "LF name accepted for mail"); + + part = curl_mime_addpart(mime); + abort_unless(part, "curl_mime_addpart()"); + curl_mime_data(part, "x", 1); + curl_mime_filename(part, "a\rb"); + fail_unless(Curl_mime_prepare_headers(easy, part, NULL, NULL, + MIMESTRATEGY_MAIL) == + CURLE_BAD_FUNCTION_ARGUMENT, + "CR filename accepted for mail"); + + /* A regular name and filename are accepted and produce a clean header. */ + part = curl_mime_addpart(mime); + abort_unless(part, "curl_mime_addpart()"); + curl_mime_data(part, "x", 1); + curl_mime_name(part, "field"); + curl_mime_filename(part, "readme.txt"); + fail_unless(Curl_mime_prepare_headers(easy, part, NULL, NULL, + MIMESTRATEGY_MAIL) == CURLE_OK, + "plain mail part rejected"); + fail_if(headers_have_ctrl(part), "clean mail header split"); + + /* The HTTP form strategy percent-encodes CR/LF, so it stays accepted and + produces no split line. */ + part = curl_mime_addpart(mime); + abort_unless(part, "curl_mime_addpart()"); + curl_mime_data(part, "x", 1); + curl_mime_filename(part, "a\r\nX-Injected: 1"); + fail_unless(Curl_mime_prepare_headers(easy, part, NULL, NULL, + MIMESTRATEGY_FORM) == CURLE_OK, + "CRLF filename rejected for form"); + fail_if(headers_have_ctrl(part), "form header split despite encoding"); + + UNITTEST_END(t3227_stop(easy, mime)) +} + +#else /* mime disabled */ + +static CURLcode test_unit3227(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + puts("nothing to do when mime is disabled"); + UNITTEST_END_SIMPLE +} + +#endif