summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKrzysztof Grobelny <krzysztof.grobelny@intel.com>2022-08-24 10:24:33 +0300
committerEd Tanous <edtanous@google.com>2022-10-05 05:07:38 +0300
commit18e3f7fb9d6888a09c0d5f5ab9275f314cc22b40 (patch)
tree862edbd8b38a6cc4b2cf9d062ded8f24313b6940
parent22d268cb2c0bc00676d08c79f6ab8958bee74a25 (diff)
downloadbmcweb-18e3f7fb9d6888a09c0d5f5ab9275f314cc22b40.tar.xz
Fixed issues with multipart parser
- Index was not checked against size before dereference. Which cased to override memory. - Header without colon could put parser into invalid state. Now it will return with error. - Content after boundary was not correctly discarded. - Parser did not check body for final boudary. Now missing final boundary will return with error. Tested: - Tested that payload with header without colon doesn't cause memory corruption anymore. Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> Change-Id: I12f496ab5f53e6c088cdfdf2e96be636d66f7c7f
-rw-r--r--include/multipart_parser.hpp43
-rw-r--r--test/include/multipart_test.cpp192
2 files changed, 218 insertions, 17 deletions
diff --git a/include/multipart_parser.hpp b/include/multipart_parser.hpp
index 2fe3679452..a2f63bd167 100644
--- a/include/multipart_parser.hpp
+++ b/include/multipart_parser.hpp
@@ -16,7 +16,10 @@ enum class ParserError
ERROR_EMPTY_HEADER,
ERROR_HEADER_NAME,
ERROR_HEADER_VALUE,
- ERROR_HEADER_ENDING
+ ERROR_HEADER_ENDING,
+ ERROR_UNEXPECTED_END_OF_HEADER,
+ ERROR_UNEXPECTED_END_OF_INPUT,
+ ERROR_OUT_OF_RANGE
};
enum class State
@@ -72,7 +75,6 @@ class MultipartParser
const char* buffer = req.body.data();
size_t len = req.body.size();
- size_t prevIndex = index;
char cl = 0;
for (size_t i = 0; i < len; i++)
@@ -182,6 +184,10 @@ class MultipartParser
{
return ParserError::ERROR_HEADER_ENDING;
}
+ if (index > 0)
+ {
+ return ParserError::ERROR_UNEXPECTED_END_OF_HEADER;
+ }
state = State::PART_DATA_START;
break;
case State::PART_DATA_START:
@@ -189,6 +195,7 @@ class MultipartParser
partDataMark = i;
[[fallthrough]];
case State::PART_DATA:
+ {
if (index == 0)
{
skipNonBoundary(buffer, len, boundary.size() - 1, i);
@@ -196,12 +203,23 @@ class MultipartParser
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
c = buffer[i];
}
- processPartData(prevIndex, buffer, i, c);
+ const ParserError ec = processPartData(buffer, i, c);
+ if (ec != ParserError::PARSER_SUCCESS)
+ {
+ return ec;
+ }
break;
+ }
case State::END:
break;
}
}
+
+ if (state != State::END)
+ {
+ return ParserError::ERROR_UNEXPECTED_END_OF_INPUT;
+ }
+
return ParserError::PARSER_SUCCESS;
}
std::vector<FormPart> mime_fields;
@@ -242,10 +260,9 @@ class MultipartParser
}
}
- void processPartData(size_t& prevIndex, const char* buffer, size_t& i,
- char c)
+ ParserError processPartData(const char* buffer, size_t& i, char c)
{
- prevIndex = index;
+ size_t prevIndex = index;
if (index < boundary.size())
{
@@ -295,7 +312,7 @@ class MultipartParser
flags = Boundary::NON_BOUNDARY;
mime_fields.push_back({});
state = State::HEADER_FIELD_START;
- return;
+ return ParserError::PARSER_SUCCESS;
}
}
if (flags == Boundary::END_BOUNDARY)
@@ -304,11 +321,21 @@ class MultipartParser
{
state = State::END;
}
+ else
+ {
+ flags = Boundary::NON_BOUNDARY;
+ index = 0;
+ }
}
}
if (index > 0)
{
+ if ((index - 1) >= lookbehind.size())
+ {
+ // Should never happen, but when it does it won't cause crash
+ return ParserError::ERROR_OUT_OF_RANGE;
+ }
lookbehind[index - 1] = c;
}
else if (prevIndex > 0)
@@ -317,13 +344,13 @@ class MultipartParser
// lookbehind belongs to partData
mime_fields.rbegin()->content += lookbehind.substr(0, prevIndex);
- prevIndex = 0;
partDataMark = i;
// reconsider the current character even so it interrupted
// the sequence it could be the beginning of a new sequence
i--;
}
+ return ParserError::PARSER_SUCCESS;
}
std::string currentHeaderName;
diff --git a/test/include/multipart_test.cpp b/test/include/multipart_test.cpp
index 7ad7d98f89..f29bcfe5c8 100644
--- a/test/include/multipart_test.cpp
+++ b/test/include/multipart_test.cpp
@@ -83,11 +83,8 @@ TEST_F(MultipartTest, TestBadMultipartParser1)
crow::Request reqIn(req, ec);
ParserError rc = parser.parse(reqIn);
- ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
- EXPECT_EQ(parser.boundary,
- "\r\n-----------------------------d74496d66958873e");
- EXPECT_EQ(parser.mime_fields.size(), 1);
+ EXPECT_EQ(rc, ParserError::ERROR_UNEXPECTED_END_OF_INPUT);
}
TEST_F(MultipartTest, TestBadMultipartParser2)
@@ -103,11 +100,8 @@ TEST_F(MultipartTest, TestBadMultipartParser2)
crow::Request reqIn(req, ec);
ParserError rc = parser.parse(reqIn);
- ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
- EXPECT_EQ(parser.boundary,
- "\r\n-----------------------------d74496d66958873e");
- EXPECT_EQ(parser.mime_fields.size(), 1);
+ EXPECT_EQ(rc, ParserError::ERROR_UNEXPECTED_END_OF_INPUT);
}
TEST_F(MultipartTest, TestErrorBoundaryFormat)
@@ -253,4 +247,184 @@ TEST_F(MultipartTest, TestErrorHeaderEnding)
crow::Request reqIn(req, ec);
EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_HEADER_ENDING);
}
-} // namespace \ No newline at end of file
+
+TEST_F(MultipartTest, TestGoodMultipartParserMultipleHeaders)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n"
+ "Other-Header: value=\"v1\"\r\n"
+ "\r\n"
+ "Data1\r\n"
+ "-----------------------------d74496d66958873e--";
+
+ crow::Request reqIn(req, ec);
+ ParserError rc = parser.parse(reqIn);
+ ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
+
+ EXPECT_EQ(parser.boundary,
+ "\r\n-----------------------------d74496d66958873e");
+ ASSERT_EQ(parser.mime_fields.size(), 1);
+
+ EXPECT_EQ(parser.mime_fields[0].fields.at("Content-Disposition"),
+ "form-data; name=\"Test1\"");
+ EXPECT_EQ(parser.mime_fields[0].fields.at("Other-Header"), "value=\"v1\"");
+ EXPECT_EQ(parser.mime_fields[0].content, "Data1");
+}
+
+TEST_F(MultipartTest, TestErrorHeaderWithoutColon)
+{
+ req.set("Content-Type", "multipart/form-data; "
+ "boundary=--end");
+
+ req.body() = "----end\r\n"
+ "abc\r\n"
+ "\r\n"
+ "Data1\r\n"
+ "----end--\r\n";
+
+ crow::Request reqIn(req, ec);
+ EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_UNEXPECTED_END_OF_HEADER);
+}
+
+TEST_F(MultipartTest, TestUnknownHeaderIsCorrectlyParsed)
+{
+ req.set("Content-Type", "multipart/form-data; "
+ "boundary=--end");
+
+ req.body() =
+ "----end\r\n"
+ "t-DiPpcccc:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccgcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccaaaaaa\r\n"
+ "\r\n"
+ "Data1\r\n"
+ "----end--\r\n";
+
+ crow::Request reqIn(req, ec);
+ ParserError rc = parser.parse(reqIn);
+
+ ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
+
+ EXPECT_EQ(parser.boundary, "\r\n----end");
+ ASSERT_EQ(parser.mime_fields.size(), 1);
+
+ EXPECT_EQ(
+ parser.mime_fields[0].fields.at("t-DiPpcccc"),
+ "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccgcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccaaaaaa");
+ EXPECT_EQ(parser.mime_fields[0].content, "Data1");
+}
+
+TEST_F(MultipartTest, TestErrorMissingSeparatorBetweenMimeFieldsAndData)
+{
+ req.set(
+ "Content-Type",
+ "multipart/form-data; boundary=---------------------------d74496d66958873e");
+
+ req.body() =
+ "-----------------------------d74496d66958873e\r\n"
+ "t-DiPpcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccgcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccaaaaaa\r\n"
+ "Data1"
+ "-----------------------------d74496d66958873e--";
+
+ crow::Request reqIn(req, ec);
+ ParserError rc = parser.parse(reqIn);
+
+ EXPECT_EQ(rc, ParserError::ERROR_UNEXPECTED_END_OF_HEADER);
+}
+
+TEST_F(MultipartTest, TestDataWithoutMimeFields)
+{
+ req.set(
+ "Content-Type",
+ "multipart/form-data; boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "\r\n"
+ "Data1\r\n"
+ "-----------------------------d74496d66958873e--";
+
+ crow::Request reqIn(req, ec);
+ ParserError rc = parser.parse(reqIn);
+
+ ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
+
+ EXPECT_EQ(parser.boundary,
+ "\r\n-----------------------------d74496d66958873e");
+ ASSERT_EQ(parser.mime_fields.size(), 1);
+
+ EXPECT_EQ(std::distance(parser.mime_fields[0].fields.begin(),
+ parser.mime_fields[0].fields.end()),
+ 0);
+ EXPECT_EQ(parser.mime_fields[0].content, "Data1");
+}
+
+TEST_F(MultipartTest, TestErrorMissingFinalBoundry)
+{
+ req.set("Content-Type", "multipart/form-data; boundary=--XX");
+
+ req.body() =
+ "----XX\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "t-DiPpccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccAAAAAAAAAAAAAAABCDz\r\n"
+ "\335\r\n\r\n";
+
+ crow::Request reqIn(req, ec);
+ ParserError rc = parser.parse(reqIn);
+
+ EXPECT_EQ(rc, ParserError::ERROR_UNEXPECTED_END_OF_INPUT);
+}
+
+TEST_F(MultipartTest, TestIgnoreDataAfterFinalBoundary)
+{
+ req.set("Content-Type", "multipart/form-data; boundary=--XX");
+
+ req.body() = "----XX\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
+ "Data1\r\n"
+ "----XX--\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "Data2\r\n"
+ "----XX--\r\n";
+
+ crow::Request reqIn(req, ec);
+ ParserError rc = parser.parse(reqIn);
+
+ ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
+
+ EXPECT_EQ(parser.boundary, "\r\n----XX");
+ EXPECT_EQ(parser.mime_fields.size(), 1);
+
+ EXPECT_EQ(parser.mime_fields[0].fields.at("Content-Disposition"),
+ "form-data; name=\"Test1\"");
+ EXPECT_EQ(parser.mime_fields[0].content, "Data1");
+}
+
+TEST_F(MultipartTest, TestFinalBoundaryIsCorrectlyRecognized)
+{
+ req.set("Content-Type", "multipart/form-data; boundary=--XX");
+
+ req.body() = "----XX\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
+ "Data1\r\n"
+ "----XX-abc-\r\n"
+ "StillData1\r\n"
+ "----XX--\r\n";
+
+ crow::Request reqIn(req, ec);
+ ParserError rc = parser.parse(reqIn);
+
+ ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
+
+ EXPECT_EQ(parser.boundary, "\r\n----XX");
+ EXPECT_EQ(parser.mime_fields.size(), 1);
+
+ EXPECT_EQ(parser.mime_fields[0].fields.at("Content-Disposition"),
+ "form-data; name=\"Test1\"");
+ EXPECT_EQ(parser.mime_fields[0].content, "Data1\r\n"
+ "----XX-abc-\r\n"
+ "StillData1");
+}
+
+} // namespace