Unverified Commit a1ba72cc authored by Dominik Charousset's avatar Dominik Charousset Committed by GitHub

Merge pull request #1477

Validate UTF-8 on WebSocket text payloads
parents d0cdb60c 423bb432
......@@ -105,8 +105,8 @@ enum class sec : uint8_t {
incompatible_versions,
/// Connection refused because of incompatible application IDs.
incompatible_application_ids,
/// The middleman received a malformed BASP message from another node.
malformed_basp_message,
/// Received a malformed message from another node.
malformed_message,
/// The middleman closed a connection because it failed to serialize or
/// deserialize a payload.
serializing_basp_payload_failed = 50,
......
......@@ -42,9 +42,6 @@ constexpr bool is_continuation_byte(std::byte value) noexcept {
bool validate_rfc3629(const std::byte* first, const std::byte* last) {
while (first != last) {
auto x = *first++;
// Null byte (terminator) is not allowed.
if (x == 0_b)
return false;
// First bit is zero: ASCII character.
if (head<1>(x) == 0b0000'0000_b)
continue;
......@@ -72,13 +69,19 @@ bool validate_rfc3629(const std::byte* first, const std::byte* last) {
return false;
continue;
}
// 1111'0bxx: 4-byte sequence.
// 1111'0xxx: 4-byte sequence.
if (head<5>(x) == 0b1111'0000_b) {
// Check if code point is in the valid UTF range.
if (x > std::byte{0xf4})
return false;
if (first == last || !is_continuation_byte(*first))
return false;
// No non-shortest form.
if (x == 0b1111'0000_b && head<4>(*first) == 0b1000'0000_b)
return false;
// Check if code point is in the valid UTF range.
if (x == std::byte{0xf4} && *first >= std::byte{0x90})
return false;
++first;
if (first == last || !is_continuation_byte(*first++))
return false;
......
......@@ -113,8 +113,8 @@ std::string to_string(sec x) {
return "caf::sec::incompatible_versions";
case sec::incompatible_application_ids:
return "caf::sec::incompatible_application_ids";
case sec::malformed_basp_message:
return "caf::sec::malformed_basp_message";
case sec::malformed_message:
return "caf::sec::malformed_message";
case sec::serializing_basp_payload_failed:
return "caf::sec::serializing_basp_payload_failed";
case sec::redundant_connection:
......@@ -306,8 +306,8 @@ bool from_string(std::string_view in, sec& out) {
} else if (in == "caf::sec::incompatible_application_ids") {
out = sec::incompatible_application_ids;
return true;
} else if (in == "caf::sec::malformed_basp_message") {
out = sec::malformed_basp_message;
} else if (in == "caf::sec::malformed_message") {
out = sec::malformed_message;
return true;
} else if (in == "caf::sec::serializing_basp_payload_failed") {
out = sec::serializing_basp_payload_failed;
......@@ -429,7 +429,7 @@ bool from_integer(std::underlying_type_t<sec> in,
case sec::unavailable_or_would_block:
case sec::incompatible_versions:
case sec::incompatible_application_ids:
case sec::malformed_basp_message:
case sec::malformed_message:
case sec::serializing_basp_payload_failed:
case sec::redundant_connection:
case sec::remote_lookup_failed:
......
......@@ -96,11 +96,19 @@ constexpr std::byte valid_three_byte_1[] = {0xe0_b, 0xa0_b, 0x80_b};
// Largest valid 3-byte sequence.
constexpr std::byte valid_three_byte_2[] = {0xef_b, 0xbf_b, 0xbf_b};
// UTF-8 standard covers code points in the sequences [0x0 - 0x110000)
// Theoretically, a larger value can be encoded in the 4-byte sequence.
// Smallest valid 4-byte sequence.
constexpr std::byte valid_four_byte_1[] = {0xf0_b, 0x90_b, 0x80_b, 0x80_b};
// Largest valid 4-byte sequence.
constexpr std::byte valid_four_byte_2[] = {0xf7_b, 0xbf_b, 0xbf_b, 0xbf_b};
// Largest valid 4-byte sequence - code point 0x10FFFF.
constexpr std::byte valid_four_byte_2[] = {0xf4_b, 0x8f_b, 0xbf_b, 0xbf_b};
// Smallest invalid 4-byte sequence - code point 0x110000.
constexpr std::byte invalid_four_byte_9[] = {0xf4_b, 0x90_b, 0x80_b, 0x80_b};
// Largest invalid 4-byte sequence - invalid code point.
constexpr std::byte invalid_four_byte_10[] = {0xf7_b, 0xbf_b, 0xbf_b, 0xbf_b};
// Single line ASCII text.
constexpr std::string_view ascii_1 = "Hello World!";
......@@ -151,4 +159,6 @@ TEST_CASE("invalid UTF-8 input") {
CHECK(!valid_utf8(invalid_four_byte_6));
CHECK(!valid_utf8(invalid_four_byte_7));
CHECK(!valid_utf8(invalid_four_byte_8));
CHECK(!valid_utf8(invalid_four_byte_9));
CHECK(!valid_utf8(invalid_four_byte_10));
}
......@@ -27,8 +27,8 @@ enum connection_state {
incompatible_versions,
/// See `sec::incompatible_application_ids`.
incompatible_application_ids,
/// See `sec::malformed_basp_message`.
malformed_basp_message,
/// See `sec::malformed_message`.
malformed_message,
/// See `sec::serializing_basp_payload_failed`.
serializing_basp_payload_failed,
/// See `sec::redundant_connection`.
......@@ -55,8 +55,8 @@ inline sec to_sec(connection_state x) noexcept {
return sec::incompatible_versions;
case incompatible_application_ids:
return sec::incompatible_application_ids;
case malformed_basp_message:
return sec::malformed_basp_message;
case malformed_message:
return sec::malformed_message;
case serializing_basp_payload_failed:
return sec::serializing_basp_payload_failed;
case redundant_connection:
......
......@@ -55,17 +55,17 @@ connection_state instance::handle(execution_unit* ctx, new_data_msg& dm,
if (payload->size() != hdr.payload_len) {
CAF_LOG_WARNING("received invalid payload, expected"
<< hdr.payload_len << "bytes, got" << payload->size());
return err(malformed_basp_message);
return err(malformed_message);
}
} else {
binary_deserializer source{ctx, dm.buf};
if (!source.apply(hdr)) {
CAF_LOG_WARNING("failed to receive header:" << source.get_error());
return err(malformed_basp_message);
return err(malformed_message);
}
if (!valid(hdr)) {
CAF_LOG_WARNING("received invalid header:" << CAF_ARG(hdr));
return err(malformed_basp_message);
return err(malformed_message);
}
if (hdr.payload_len > 0) {
CAF_LOG_DEBUG("await payload before processing further");
......@@ -309,11 +309,11 @@ connection_state instance::handle(execution_unit* ctx, connection_handle hdl,
if (payload == nullptr) {
if (hdr.payload_len != 0) {
CAF_LOG_WARNING("missing payload");
return malformed_basp_message;
return malformed_message;
}
} else if (hdr.payload_len != payload->size()) {
CAF_LOG_WARNING("actual payload size differs from advertised size");
return malformed_basp_message;
return malformed_message;
}
// Dispatch by message type.
switch (hdr.operation) {
......@@ -506,7 +506,7 @@ connection_state instance::handle(execution_unit* ctx, connection_handle hdl,
}
default: {
CAF_LOG_ERROR("invalid operation");
return malformed_basp_message;
return malformed_message;
}
}
return await_header;
......
......@@ -4,6 +4,7 @@
#include "caf/net/web_socket/framing.hpp"
#include "caf/detail/rfc3629.hpp"
#include "caf/logger.hpp"
#include "caf/net/http/v1.hpp"
......@@ -202,6 +203,10 @@ ptrdiff_t framing::handle(uint8_t opcode, byte_span payload,
case detail::rfc6455::text_frame: {
std::string_view text{reinterpret_cast<const char*>(payload.data()),
payload.size()};
if (!detail::rfc3629::valid(text)) {
abort_and_shutdown(sec::malformed_message, "invalid UTF-8 sequence");
return -1;
}
if (up_->consume_text(text) < 0)
return -1;
break;
......
......@@ -24,6 +24,8 @@ void lower_layer::shutdown(const error& reason) {
shutdown(status::normal_close, to_string(reason));
} else if (reason.code() == static_cast<uint8_t>(sec::protocol_error)) {
shutdown(status::protocol_error, to_string(reason));
} else if (reason.code() == static_cast<uint8_t>(sec::malformed_message)) {
shutdown(status::inconsistent_data, to_string(reason));
} else {
shutdown(status::unexpected_condition, to_string(reason));
}
......
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