Skip to content

Commit

Permalink
Merge pull request #5205 from rsto/cyr-1302-message-c-parse-nul-mime-…
Browse files Browse the repository at this point in the history
…header

message.c: reject to parse MIME headers with NUL byte
  • Loading branch information
rsto authored Jan 10, 2025
2 parents cfa1fc6 + f56c25f commit c12ad0b
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 54 deletions.
59 changes: 33 additions & 26 deletions cassandane/tiny-tests/JMAPEmail/email_import_zerobyte
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
use Cassandane::Tiny;

sub test_email_import_zerobyte
:min_version_3_1 :needs_component_sieve
:needs_component_sieve
{
my ($self) = @_;
my $jmap = $self->{jmap};

# A bogus email with an unencoded zero byte
my $email = <<"EOF";
my @testCases = ({
desc => "unencoded zero byte in MIME body",
mime => <<"EOF",
From: \"Some Example Sender\" <example\@local>\r\n
To: baseball\@local\r\n
Subject: test email\r\n
Expand All @@ -18,32 +19,38 @@ Content-Type: text/plain; charset="UTF-8"\r\n
\r\n
This is a test email with a \x{0}-byte.\r\n
EOF
}, {
desc => "unencoded zero byte in MIME headers",
mime => <<"EOF",
From: from\@local\r\n
To: to\@local\r\n
Subject: subject with \x{0}-byte\r\n
Date: Wed, 7 Dec 2016 22:11:11 +1100\r\n
MIME-Version: 1.0\r\n
Content-Type: text/plain; charset="UTF-8"\r\n
\r\n
This is a test email.\r\n
EOF
});

my $data = $jmap->Upload($email, "message/rfc822");
my $blobid = $data->{blobId};

xlog $self, "create drafts mailbox";
my $res = $jmap->CallMethods([
['Mailbox/set', { create => { "1" => {
name => "drafts",
parentId => undef,
role => "drafts"
}}}, "R1"]
]);
my $draftsmbox = $res->[0][1]{created}{"1"}{id};
$self->assert_not_null($draftsmbox);
foreach (@testCases) {
xlog $self, "Upload MIME blob for test '$_->{desc}'";
my $blobId = $jmap->Upload($_->{mime}, "message/rfc822")->{blobId};

xlog $self, "import email from blob $blobid";
$res = $jmap->CallMethods([['Email/import', {
emails => {
"1" => {
blobId => $blobid,
mailboxIds => {$draftsmbox => JSON::true},
keywords => {
'$draft' => JSON::true,
xlog $self, "Import email from blob $blobId";
my $res = $jmap->CallMethods([ [
'Email/import',
{
emails => {
"1" => {
blobId => $blobId,
mailboxIds => { '$inbox' => JSON::true },
},
},
},
}, "R1"]]);
$self->assert_str_equals("invalidEmail", $res->[0][1]{notCreated}{1}{type});
"R1"
] ]);
$self->assert_str_equals("invalidEmail",
$res->[0][1]{notCreated}{1}{type});
}
}
70 changes: 42 additions & 28 deletions imap/message.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ static int message_parse_headers(struct msg *msg,
struct body *body,
const char *defaultContentType,
strarray_t *boundaries,
const char *efname);
const char *efname,
int *sawboundaryp);

static void message_parse_address(const char *hdr, struct address **addrp);
static void message_parse_encoding(const char *hdr, char **hdrp);
Expand Down Expand Up @@ -522,36 +523,38 @@ EXPORTED int message_parse_mapped(const char *msg_base, unsigned long msg_len,
struct body *body, const char *efname)
{
struct msg msg;
int r = 0;

msg.base = msg_base;
msg.len = msg_len;
msg.offset = 0;
msg.encode = 0;

message_parse_body(&msg, body, DEFAULT_CONTENT_TYPE, NULL, efname);
r = message_parse_body(&msg, body, DEFAULT_CONTENT_TYPE, NULL, efname);
if (r) goto done;

body->filesize = msg_len;

message_guid_generate(&body->guid, msg_base, msg_len);

if (body->filesize != body->header_size + body->content_size) {
if (efname)
/* XXX IOERROR but only LOG_NOTICE?? */
xsyslog(LOG_NOTICE, "IOERROR: size mismatch on parse",
"guid=<%s> filename=<%s> "
"filesize=<%" PRIu32 "> bodysize=<%" PRIu32 ">",
message_guid_encode(&body->guid), efname,
body->filesize,
body->header_size + body->content_size);
xsyslog(LOG_ERR, "IOERROR: size mismatch on parse",
"guid=<%s> filename=<%s>"
" filesize=<%" PRIu32 "> bodysize=<%" PRIu32 ">",
message_guid_encode(&body->guid), efname,
body->filesize,
body->header_size + body->content_size);
else
xsyslog(LOG_NOTICE, "IOERROR: size mismatch on parse",
"guid=<%s> "
"filesize=<%" PRIu32 "> bodysize=<%" PRIu32 ">",
message_guid_encode(&body->guid), body->filesize,
body->header_size + body->content_size);
xsyslog(LOG_ERR, "IOERROR: size mismatch on parse",
"guid=<%s>"
" filesize=<%" PRIu32 "> bodysize=<%" PRIu32 ">",
message_guid_encode(&body->guid), body->filesize,
body->header_size + body->content_size);
}

return 0;
done:
return r;
}

/*
Expand Down Expand Up @@ -777,7 +780,8 @@ static int message_parse_body(struct msg *msg, struct body *body,
const char *efname)
{
strarray_t newboundaries = STRARRAY_INITIALIZER;
int sawboundary;
int sawboundary = 0;
int r = 0;

memset(body, 0, sizeof(struct body));

Expand All @@ -788,9 +792,9 @@ static int message_parse_body(struct msg *msg, struct body *body,
buf_ensure(&body->cacheheaders, 1024);
}


sawboundary = message_parse_headers(msg, body, defaultContentType,
boundaries, efname);
r = message_parse_headers(msg, body, defaultContentType, boundaries,
efname, &sawboundary);
if (r) goto done;

/* Charset id and encoding id are stored in the binary
* bodystructure, but we don't have that one here. */
Expand Down Expand Up @@ -846,10 +850,10 @@ static int message_parse_body(struct msg *msg, struct body *body,
}
}

done:
/* Free up boundary storage if necessary */
strarray_fini(&newboundaries);

return 0;
return r;
}

/*
Expand All @@ -858,15 +862,17 @@ static int message_parse_body(struct msg *msg, struct body *body,
static int message_parse_headers(struct msg *msg, struct body *body,
const char *defaultContentType,
strarray_t *boundaries,
const char *efname)
const char *efname,
int *sawboundaryp)
{
struct buf headers = BUF_INITIALIZER;
char *next;
int len;
int sawboundary = 0;
size_t len;
if (sawboundaryp) *sawboundaryp = 0;
uint32_t maxlines = config_getint(IMAPOPT_MAXHEADERLINES);
int have_max = 0;
const char *value;
int r = 0;

body->header_offset = msg->offset;

Expand All @@ -877,7 +883,12 @@ static int message_parse_headers(struct msg *msg, struct body *body,
(next[-1] != '\n' ||
(*next != '\r' || next[1] != '\n'))) {

len = strlen(next);
len = buf_len(&headers) - (next - buf_base(&headers));
if (len != strlen(next)) {
// NUL in the MIME headers is a fatal error
r = IMAP_MESSAGE_CONTAINSNULL;
goto done;
}

if (next[-1] == '\n' && *next == '-' &&
message_pendingboundary(next, len, boundaries)) {
Expand All @@ -891,7 +902,7 @@ static int message_parse_headers(struct msg *msg, struct body *body,
else {
*next = '\0';
}
sawboundary = 1;
if (sawboundaryp) *sawboundaryp = 1;
break;
}
}
Expand Down Expand Up @@ -1038,8 +1049,10 @@ static int message_parse_headers(struct msg *msg, struct body *body,
if (!body->type) {
message_parse_bodytype(defaultContentType, body);
}

done:
buf_free(&headers);
return sawboundary;
return r;
}

/*
Expand Down Expand Up @@ -4956,7 +4969,8 @@ static int body_foreach_section(struct body *body, struct message *message,
msg.len = body->header_size;
msg.offset = 0;
msg.encode = 0;
message_parse_headers(&msg, tmpbody, "text/plain", &boundaries, NULL);
message_parse_headers(&msg, tmpbody, "text/plain", &boundaries,
NULL, NULL);

disposition = tmpbody->disposition;
disposition_params = tmpbody->disposition_params;
Expand Down

0 comments on commit c12ad0b

Please sign in to comment.