Unverified Commit 15ab3fd7 authored by Samir Halilčević's avatar Samir Halilčević Committed by GitHub

Decode and validate WebSocket closing payload

parent 52668376
...@@ -11,6 +11,45 @@ ...@@ -11,6 +11,45 @@
namespace caf::net::web_socket { namespace caf::net::web_socket {
// -- static utility functions -------------------------------------------------
error framing::validate_closing_payload(const_byte_span payload) {
if (payload.empty())
return {};
if (payload.size() == 1)
return make_error(caf::sec::protocol_error,
"non empty closing payload must have at least two bytes");
auto status = (std::to_integer<uint16_t>(payload[0]) << 8)
+ std::to_integer<uint16_t>(payload[1]);
if (!detail::rfc3629::valid(payload.subspan(2)))
return make_error(sec::protocol_error,
"malformed UTF-8 text message in closing payload");
// statuses between 3000 and 4999 are allowed and application specific
if (status >= 3000 && status < 5000)
return {};
// statuses between 1000 and 2999 need to be protocol defined, and status
// codes lower then 1000 and greater or equal then 5000 are invalid.
auto status_code = web_socket::status{0};
if (from_integer(status, status_code)) {
switch (status_code) {
case status::normal_close:
case status::going_away:
case status::protocol_error:
case status::invalid_data:
case status::inconsistent_data:
case status::policy_violation:
case status::message_too_big:
case status::missing_extensions:
case status::unexpected_condition:
return {};
default:
break;
}
}
return make_error(sec::protocol_error,
"invalid status code in closing payload");
}
// -- octet_stream::upper_layer implementation --------------------------------- // -- octet_stream::upper_layer implementation ---------------------------------
error framing::start(octet_stream::lower_layer* down) { error framing::start(octet_stream::lower_layer* down) {
...@@ -212,6 +251,10 @@ ptrdiff_t framing::handle(uint8_t opcode, byte_span payload, ...@@ -212,6 +251,10 @@ ptrdiff_t framing::handle(uint8_t opcode, byte_span payload,
// opcodes are checked for validity when decoding the header // opcodes are checked for validity when decoding the header
switch (opcode) { switch (opcode) {
case detail::rfc6455::connection_close: case detail::rfc6455::connection_close:
if (auto err = validate_closing_payload(payload); err) {
abort_and_shutdown(err);
return -1;
}
abort_and_shutdown(sec::connection_closed); abort_and_shutdown(sec::connection_closed);
return -1; return -1;
case detail::rfc6455::text_frame: { case detail::rfc6455::text_frame: {
......
...@@ -44,6 +44,14 @@ public: ...@@ -44,6 +44,14 @@ public:
/// Stored as currently active opcode to mean "no opcode received yet". /// Stored as currently active opcode to mean "no opcode received yet".
static constexpr size_t nil_code = 0xFF; static constexpr size_t nil_code = 0xFF;
// -- static utility functions -----------------------------------------------
/// Checks whether the payload of a closing frame contains a valid status
/// code and an UTF-8 formatted message.
/// @returns A default constructed `error` if the payload is valid, error kind
/// otherwise.
static error validate_closing_payload(const_byte_span payload);
// -- constructors, destructors, and assignment operators -------------------- // -- constructors, destructors, and assignment operators --------------------
/// Creates a new framing protocol for client mode. /// Creates a new framing protocol for client mode.
...@@ -148,7 +156,10 @@ private: ...@@ -148,7 +156,10 @@ private:
// with closing message // with closing message
template <class... Ts> template <class... Ts>
void abort_and_shutdown(sec reason, Ts&&... xs) { void abort_and_shutdown(sec reason, Ts&&... xs) {
auto err = make_error(reason, std::forward<Ts>(xs)...); abort_and_shutdown(make_error(reason, std::forward<Ts>(xs)...));
}
void abort_and_shutdown(const error& err) {
up_->abort(err); up_->abort(err);
shutdown(err); shutdown(err);
} }
......
...@@ -50,6 +50,15 @@ int fetch_status(const_byte_span payload) { ...@@ -50,6 +50,15 @@ int fetch_status(const_byte_span payload) {
+ std::to_integer<int>(payload[3]); + std::to_integer<int>(payload[3]);
} }
byte_buffer make_closing_payload(uint16_t code_val, std::string_view msg) {
byte_buffer payload;
payload.push_back(static_cast<std::byte>((code_val & 0xFF00) >> 8));
payload.push_back(static_cast<std::byte>(code_val & 0x00FF));
auto* first = reinterpret_cast<const std::byte*>(msg.data());
payload.insert(payload.end(), first, first + msg.size());
return payload;
}
} // namespace } // namespace
BEGIN_FIXTURE_SCOPE(fixture) BEGIN_FIXTURE_SCOPE(fixture)
...@@ -171,6 +180,25 @@ SCENARIO("the client closes the connection with a closing handshake") { ...@@ -171,6 +180,25 @@ SCENARIO("the client closes the connection with a closing handshake") {
CHECK_EQ(fetch_status(transport->output_buffer()), CHECK_EQ(fetch_status(transport->output_buffer()),
static_cast<int>(net::web_socket::status::normal_close)); static_cast<int>(net::web_socket::status::normal_close));
} }
WHEN("the client sends an invalid closing handshake") {
reset();
std::vector<std::byte> handshake;
// invalid status code
auto payload = make_closing_payload(1016, ""sv);
detail::rfc6455::assemble_frame(detail::rfc6455::connection_close, 0x0,
payload, handshake);
transport->push(handshake);
}
THEN("the server closes the connection with protocol error") {
CHECK_EQ(transport->handle_input(), 0l);
detail::rfc6455::header hdr;
detail::rfc6455::decode_header(transport->output_buffer(), hdr);
CHECK_EQ(app->abort_reason, sec::protocol_error);
CHECK_EQ(hdr.opcode, detail::rfc6455::connection_close);
CHECK(hdr.fin);
CHECK_EQ(fetch_status(transport->output_buffer()),
static_cast<int>(net::web_socket::status::protocol_error));
}
} }
} }
...@@ -585,3 +613,41 @@ SCENARIO("the application shuts down on invalid frame fragments") { ...@@ -585,3 +613,41 @@ SCENARIO("the application shuts down on invalid frame fragments") {
} }
END_FIXTURE_SCOPE(); END_FIXTURE_SCOPE();
TEST_CASE("empty closing payload is valid") {
auto error
= net::web_socket::framing::validate_closing_payload(byte_buffer{});
CHECK(!error);
}
TEST_CASE("decode valid closing codes") {
auto valid_status_codes = std::array{1000, 1001, 1002, 1003, 1007, 1008, 1009,
1010, 1011, 3000, 3999, 4000, 4999};
for (auto status_code : valid_status_codes) {
auto payload = make_closing_payload(status_code, ""sv);
auto error = net::web_socket::framing::validate_closing_payload(payload);
CHECK(!error);
}
}
TEST_CASE("fail on invalid closing codes") {
auto valid_status_codes = std::array{0, 999, 1004, 1005, 1006, 1016,
1100, 2000, 2999, 5000, 65535};
for (auto status_code : valid_status_codes) {
auto payload = make_closing_payload(status_code, ""sv);
auto result = net::web_socket::framing::validate_closing_payload(payload);
CHECK_EQ(result, sec::protocol_error);
}
}
TEST_CASE("fail on invalid utf8 closing message") {
auto payload = make_closing_payload(1000, "\xf4\x80"sv);
auto result = net::web_socket::framing::validate_closing_payload(payload);
CHECK_EQ(result, sec::protocol_error);
}
TEST_CASE("fail on single byte payload") {
auto payload = byte_buffer{std::byte{0}};
auto result = net::web_socket::framing::validate_closing_payload(payload);
CHECK_EQ(result, sec::protocol_error);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment