mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-23 11:54:42 +00:00
Grow a WebSocket payload only as it arrives
read_websocket_frame() resized the payload to the length a frame header declared before reading any of it, so a 14-byte header made the peer allocate and zero-fill up to CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH and hold it until the read timed out. 70 such headers took a server to over 1 GB, and a client reading from a hostile server was exposed the same way. Read the payload in CPPHTTPLIB_RECV_BUFSIZ chunks and grow it by each one, so memory follows the bytes that actually came in.
This commit is contained in:
1 parent
2e278fd6e9
commit
344311c1c3
2 files changed
+44
-11
No files matched your search
@@ -5766,12 +5766,17 @@ inline FrameRead read_websocket_frame(Stream &strm, Opcode &opcode,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read payload
|
// Grow the payload only as it arrives, since a header alone can announce
|
||||||
payload.resize(static_cast<size_t>(payload_len));
|
// max_len bytes that never come.
|
||||||
if (payload_len > 0 &&
|
payload.clear();
|
||||||
read_exact(strm, &payload[0], static_cast<size_t>(payload_len)) !=
|
auto len = static_cast<size_t>(payload_len);
|
||||||
FrameRead::Ok) {
|
while (payload.size() < len) {
|
||||||
return FrameRead::Fail;
|
auto offset = payload.size();
|
||||||
|
auto n = (std::min)(len - offset, CPPHTTPLIB_RECV_BUFSIZ);
|
||||||
|
payload.resize(offset + n);
|
||||||
|
if (read_exact(strm, &payload[offset], n) != FrameRead::Ok) {
|
||||||
|
return FrameRead::Fail;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmask if needed
|
// Unmask if needed
|
||||||
|
|||||||
+33
-5
@@ -25543,10 +25543,14 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
TEST(WebSocketFrameTest, MultiByteFieldsSplitAcrossReads) {
|
TEST(WebSocketFrameTest, MultiByteFieldsSplitAcrossReads) {
|
||||||
// A 200-byte payload uses the 16-bit extended length, so the header, the
|
// The payload uses the 16-bit extended length, so the header, the length
|
||||||
// length and the mask key are all multi-byte fields here. Each used to be
|
// and the mask key are all multi-byte fields here. Each used to be read
|
||||||
// read with a single read() call, which fails as soon as one is split.
|
// with a single read() call, which fails as soon as one is split. The
|
||||||
std::string body(200, 'x');
|
// payload is also a little over one read chunk, so it is read in two.
|
||||||
|
std::string body;
|
||||||
|
for (size_t i = 0; i < CPPHTTPLIB_RECV_BUFSIZ + 3; i++) {
|
||||||
|
body += static_cast<char>(i % 251);
|
||||||
|
}
|
||||||
const uint8_t mask[4] = {0x0a, 0x0b, 0x0c, 0x0d};
|
const uint8_t mask[4] = {0x0a, 0x0b, 0x0c, 0x0d};
|
||||||
|
|
||||||
std::string frame;
|
std::string frame;
|
||||||
@@ -25566,13 +25570,37 @@ TEST(WebSocketFrameTest, MultiByteFieldsSplitAcrossReads) {
|
|||||||
std::string payload;
|
std::string payload;
|
||||||
bool fin = false;
|
bool fin = false;
|
||||||
ASSERT_EQ(ws::impl::read_websocket_frame(strm, opcode, payload, fin,
|
ASSERT_EQ(ws::impl::read_websocket_frame(strm, opcode, payload, fin,
|
||||||
/*expect_masked=*/true, 1024),
|
/*expect_masked=*/true, body.size()),
|
||||||
ws::impl::FrameRead::Ok);
|
ws::impl::FrameRead::Ok);
|
||||||
EXPECT_TRUE(fin);
|
EXPECT_TRUE(fin);
|
||||||
EXPECT_EQ(opcode, ws::Opcode::Text);
|
EXPECT_EQ(opcode, ws::Opcode::Text);
|
||||||
EXPECT_EQ(payload, body);
|
EXPECT_EQ(payload, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(WebSocketFrameTest, PayloadGrowsOnlyAsItArrives) {
|
||||||
|
// The header announces the largest payload allowed, and only 3 bytes of it
|
||||||
|
// ever arrive.
|
||||||
|
uint64_t len = CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH;
|
||||||
|
std::string frame;
|
||||||
|
frame += static_cast<char>(0x82); // FIN + Binary
|
||||||
|
frame += static_cast<char>(127); // 64-bit length follows
|
||||||
|
for (int shift = 56; shift >= 0; shift -= 8) {
|
||||||
|
frame += static_cast<char>((len >> shift) & 0xff);
|
||||||
|
}
|
||||||
|
frame += "abc";
|
||||||
|
|
||||||
|
detail::BufferStream strm;
|
||||||
|
strm.write(frame.data(), frame.size());
|
||||||
|
ws::Opcode opcode;
|
||||||
|
std::string payload;
|
||||||
|
bool fin;
|
||||||
|
EXPECT_EQ(
|
||||||
|
ws::impl::read_websocket_frame(strm, opcode, payload, fin, false,
|
||||||
|
CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH),
|
||||||
|
ws::impl::FrameRead::Fail);
|
||||||
|
EXPECT_LT(payload.capacity(), 2 * CPPHTTPLIB_RECV_BUFSIZ);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(WebSocketTest, QueryStringInHandshake) {
|
TEST(WebSocketTest, QueryStringInHandshake) {
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user