Skip to content

Commit

Permalink
Websocket: Validate UTF-8 in text payloads (#418)
Browse files Browse the repository at this point in the history
Validate text payloads, as required by [RFC-6455 Section 5.6](https://www.rfc-editor.org/rfc/rfc6455#section-5.6)
  • Loading branch information
graebm authored Jan 3, 2023
1 parent 9bd58ca commit 62a03c5
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 8 deletions.
13 changes: 11 additions & 2 deletions include/aws/http/private/websocket_decoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum aws_websocket_decoder_state {
AWS_WEBSOCKET_DECODER_STATE_MASKING_KEY,
AWS_WEBSOCKET_DECODER_STATE_PAYLOAD_CHECK,
AWS_WEBSOCKET_DECODER_STATE_PAYLOAD,
AWS_WEBSOCKET_DECODER_STATE_FRAME_END,
AWS_WEBSOCKET_DECODER_STATE_DONE,
};

Expand All @@ -38,6 +39,11 @@ struct aws_websocket_decoder {

bool expecting_continuation_data_frame; /* True when the next data frame must be CONTINUATION frame */

/* True while processing a TEXT "message" (from the start of a TEXT frame,
* until the end of the TEXT or CONTINUATION frame with the FIN bit set). */
bool processing_text_message;
struct aws_utf8_validator *text_message_validator;

void *user_data;
aws_websocket_decoder_frame_fn *on_frame;
aws_websocket_decoder_payload_fn *on_payload;
Expand All @@ -48,19 +54,22 @@ AWS_EXTERN_C_BEGIN
AWS_HTTP_API
void aws_websocket_decoder_init(
struct aws_websocket_decoder *decoder,
struct aws_allocator *alloc,
aws_websocket_decoder_frame_fn *on_frame,
aws_websocket_decoder_payload_fn *on_payload,
void *user_data);

AWS_HTTP_API
void aws_websocket_decoder_clean_up(struct aws_websocket_decoder *decoder);

/**
* Returns when all data is processed, or a frame and its payload have completed.
* `data` will be advanced to reflect the amount of data processed by this call.
* `frame_complete` will be set true if this call returned due to completion of a frame.
* The `on_frame` and `on_payload` callbacks may each be invoked once as a result of this call.
* If an error occurs, the decoder is invalid forevermore.
*/
AWS_HTTP_API
int aws_websocket_decoder_process(
AWS_HTTP_API int aws_websocket_decoder_process(
struct aws_websocket_decoder *decoder,
struct aws_byte_cursor *data,
bool *frame_complete);
Expand Down
4 changes: 3 additions & 1 deletion source/websocket.c
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,8 @@ struct aws_websocket *aws_websocket_handler_new(const struct aws_websocket_handl

aws_websocket_encoder_init(&websocket->thread_data.encoder, s_encoder_stream_outgoing_payload, websocket);

aws_websocket_decoder_init(&websocket->thread_data.decoder, s_decoder_on_frame, s_decoder_on_payload, websocket);
aws_websocket_decoder_init(
&websocket->thread_data.decoder, options->allocator, s_decoder_on_frame, s_decoder_on_payload, websocket);

aws_linked_list_init(&websocket->synced_data.outgoing_frame_list);

Expand Down Expand Up @@ -346,6 +347,7 @@ static void s_handler_destroy(struct aws_channel_handler *handler) {

AWS_LOGF_TRACE(AWS_LS_HTTP_WEBSOCKET, "id=%p: Destroying websocket.", (void *)websocket);

aws_websocket_decoder_clean_up(&websocket->thread_data.decoder);
aws_byte_buf_clean_up(&websocket->thread_data.incoming_ping_payload);
aws_mutex_clean_up(&websocket->synced_data.lock);
aws_mem_release(websocket->alloc, websocket);
Expand Down
52 changes: 49 additions & 3 deletions source/websocket_decoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include <aws/http/private/websocket_decoder.h>

#include <aws/common/encoding.h>

#include <inttypes.h>

typedef int(state_fn)(struct aws_websocket_decoder *decoder, struct aws_byte_cursor *data);
Expand Down Expand Up @@ -86,6 +88,10 @@ static int s_state_opcode_byte(struct aws_websocket_decoder *decoder, struct aws
}
}

if (decoder->current_frame.opcode == AWS_WEBSOCKET_OPCODE_TEXT) {
decoder->processing_text_message = true;
}

decoder->state = AWS_WEBSOCKET_DECODER_STATE_LENGTH_BYTE;
return AWS_OP_SUCCESS;
}
Expand Down Expand Up @@ -234,7 +240,7 @@ static int s_state_payload_check(struct aws_websocket_decoder *decoder, struct a
decoder->state_bytes_processed = 0;
decoder->state = AWS_WEBSOCKET_DECODER_STATE_PAYLOAD;
} else {
decoder->state = AWS_WEBSOCKET_DECODER_STATE_DONE;
decoder->state = AWS_WEBSOCKET_DECODER_STATE_FRAME_END;
}

return AWS_OP_SUCCESS;
Expand Down Expand Up @@ -266,9 +272,16 @@ static int s_state_payload(struct aws_websocket_decoder *decoder, struct aws_byt
}
}

/* TODO: validate utf-8 */
/* TODO: validate payload of CLOSE frame */

/* Validate the UTF-8 for TEXT messages (a TEXT frame and any subsequent CONTINUATION frames) */
if (decoder->processing_text_message && aws_websocket_is_data_frame(decoder->current_frame.opcode)) {
if (aws_utf8_validator_update(decoder->text_message_validator, payload)) {
AWS_LOGF_ERROR(AWS_LS_HTTP_WEBSOCKET, "id=%p: Received invalid UTF-8", (void *)decoder->user_data);
return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR);
}
}

/* Invoke on_payload() callback to inform user of payload data */
int err = decoder->on_payload(payload, decoder->user_data);
if (err) {
Expand All @@ -280,9 +293,34 @@ static int s_state_payload(struct aws_websocket_decoder *decoder, struct aws_byt

/* If all data consumed, proceed to next state. */
if (decoder->state_bytes_processed == decoder->current_frame.payload_length) {
decoder->state++;
decoder->state = AWS_WEBSOCKET_DECODER_STATE_FRAME_END;
}

return AWS_OP_SUCCESS;
}

/* FRAME_END: Perform checks once we reach the end of the frame. */
static int s_state_frame_end(struct aws_websocket_decoder *decoder, struct aws_byte_cursor *data) {
(void)data;

/* If we're done processing a text message (a TEXT frame and any subsequent CONTINUATION frames),
* complete the UTF-8 validation */
if (decoder->processing_text_message && aws_websocket_is_data_frame(decoder->current_frame.opcode) &&
decoder->current_frame.fin) {

if (aws_utf8_validator_finalize(decoder->text_message_validator)) {
AWS_LOGF_ERROR(
AWS_LS_HTTP_WEBSOCKET,
"id=%p: Received invalid UTF-8 (incomplete encoding)",
(void *)decoder->user_data);
return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR);
}

decoder->processing_text_message = false;
}

/* Done! */
decoder->state = AWS_WEBSOCKET_DECODER_STATE_DONE;
return AWS_OP_SUCCESS;
}

Expand All @@ -295,6 +333,7 @@ static state_fn *s_state_functions[AWS_WEBSOCKET_DECODER_STATE_DONE] = {
s_state_masking_key,
s_state_payload_check,
s_state_payload,
s_state_frame_end,
};

int aws_websocket_decoder_process(
Expand Down Expand Up @@ -330,6 +369,7 @@ int aws_websocket_decoder_process(

void aws_websocket_decoder_init(
struct aws_websocket_decoder *decoder,
struct aws_allocator *alloc,
aws_websocket_decoder_frame_fn *on_frame,
aws_websocket_decoder_payload_fn *on_payload,
void *user_data) {
Expand All @@ -338,4 +378,10 @@ void aws_websocket_decoder_init(
decoder->user_data = user_data;
decoder->on_frame = on_frame;
decoder->on_payload = on_payload;
decoder->text_message_validator = aws_utf8_validator_new(alloc);
}

void aws_websocket_decoder_clean_up(struct aws_websocket_decoder *decoder) {
aws_utf8_validator_destroy(decoder->text_message_validator);
AWS_ZERO_STRUCT(*decoder);
}
4 changes: 4 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ add_test_case(websocket_decoder_fail_on_unknown_opcode)
add_test_case(websocket_decoder_fragmented_message)
add_test_case(websocket_decoder_fail_on_bad_fragmentation)
add_test_case(websocket_decoder_control_frame_cannot_be_fragmented)
add_test_case(websocket_decoder_utf8_text)
add_test_case(websocket_decoder_fail_on_bad_utf8_text)
add_test_case(websocket_decoder_fragmented_utf8_text)
add_test_case(websocket_decoder_fail_on_fragmented_bad_utf8_text)
add_test_case(websocket_decoder_on_frame_callback_can_fail_decoder)
add_test_case(websocket_decoder_on_payload_callback_can_fail_decoder)
add_test_case(websocket_encoder_sanity_check)
Expand Down
Loading

0 comments on commit 62a03c5

Please sign in to comment.