From: David Goulet Date: Thu, 5 Oct 2023 15:18:31 +0000 (-0400) Subject: prop340: Add relay msg decoding capability X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8fe1c503;p=thirdparty%2Ftor.git prop340: Add relay msg decoding capability Signed-off-by: David Goulet --- diff --git a/src/core/or/or.h b/src/core/or/or.h index 39b9a254c3..87176d15b9 100644 --- a/src/core/or/or.h +++ b/src/core/or/or.h @@ -222,6 +222,53 @@ struct curve25519_public_key_t; #define RELAY_COMMAND_XOFF 43 #define RELAY_COMMAND_XON 44 +/* NOTE: Any new command from above MUST be added to this function. */ + +/** Helper to learn if we know the relay command. Unfortuantely, they are not + * contigous and so we need this kind of big switch. We could do better but for + * now, we'll run with this. */ +static inline bool +is_known_relay_command(const uint8_t cmd) +{ + switch (cmd) { + case RELAY_COMMAND_BEGIN: + case RELAY_COMMAND_BEGIN_DIR: + case RELAY_COMMAND_CONFLUX_LINK: + case RELAY_COMMAND_CONFLUX_LINKED: + case RELAY_COMMAND_CONFLUX_LINKED_ACK: + case RELAY_COMMAND_CONFLUX_SWITCH: + case RELAY_COMMAND_CONNECTED: + case RELAY_COMMAND_DATA: + case RELAY_COMMAND_DROP : + case RELAY_COMMAND_END: + case RELAY_COMMAND_ESTABLISH_INTRO: + case RELAY_COMMAND_ESTABLISH_RENDEZVOUS: + case RELAY_COMMAND_EXTEND2: + case RELAY_COMMAND_EXTEND: + case RELAY_COMMAND_EXTENDED2: + case RELAY_COMMAND_EXTENDED: + case RELAY_COMMAND_INTRODUCE1: + case RELAY_COMMAND_INTRODUCE2: + case RELAY_COMMAND_INTRODUCE_ACK: + case RELAY_COMMAND_INTRO_ESTABLISHED: + case RELAY_COMMAND_PADDING_NEGOTIATE: + case RELAY_COMMAND_PADDING_NEGOTIATED: + case RELAY_COMMAND_RENDEZVOUS1: + case RELAY_COMMAND_RENDEZVOUS2: + case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED: + case RELAY_COMMAND_RESOLVE: + case RELAY_COMMAND_RESOLVED: + case RELAY_COMMAND_SENDME: + case RELAY_COMMAND_TRUNCATE: + case RELAY_COMMAND_TRUNCATED: + case RELAY_COMMAND_XOFF: + case RELAY_COMMAND_XON: + return true; + default: + return false; + } +} + /* Reasons why an OR connection is closed. */ #define END_OR_CONN_REASON_DONE 1 #define END_OR_CONN_REASON_REFUSED 2 /* connection refused */ diff --git a/src/core/or/relay_msg.c b/src/core/or/relay_msg.c index 864a6066f2..e4152cb159 100644 --- a/src/core/or/relay_msg.c +++ b/src/core/or/relay_msg.c @@ -10,10 +10,23 @@ #include "app/config/config.h" +#include "core/or/cell_st.h" +#include "core/or/relay.h" +#include "core/or/relay_cell.h" #include "core/or/relay_msg.h" #include "feature/nodelist/networkstatus.h" +#include "lib/log/util_bug.h" + +/* Size of a relay message header which is up to the payload. */ +#define RELAY_MSG_HEADER_SIZE_V0 (0) +#define RELAY_MSG_HEADER_SIZE_V1 (1 + 2) +/* Size of a relay message routing header. */ +#define RELAY_MSG_ROUTING_HEADER_SIZE_V1 (2) +/* End of relay message marker as per the specification. */ +#define RELAY_MSG_END_MARKER_V1 (0) + /* Consensus parameters. Updated when we get a new consensus. */ static bool relay_msg_enabled = false; @@ -36,6 +49,114 @@ get_param_enabled(const networkstatus_t *ns) RELAY_MSG_PARAM_ENABLED_MAX) != 0; } +/** Return true iff the given command is allowed to have a stream ID of 0. */ +static inline bool +relay_command_requires_stream_id(const uint8_t cmd) +{ + /* In accordance to proposal 340. */ + switch (cmd) { + case RELAY_COMMAND_BEGIN: + case RELAY_COMMAND_BEGIN_DIR: + case RELAY_COMMAND_CONNECTED: + case RELAY_COMMAND_DATA: + case RELAY_COMMAND_END: + case RELAY_COMMAND_RESOLVE: + case RELAY_COMMAND_RESOLVED: + case RELAY_COMMAND_XOFF: + case RELAY_COMMAND_XON: + return true; + default: + return false; + } +} + +/** Return the relay message header size based on the given command and relay + * cell protocol version. */ +static size_t +get_relay_msg_header_size(const uint8_t cmd, const uint8_t relay_cell_proto) +{ + switch (relay_cell_proto) { + case 0: + return RELAY_MSG_HEADER_SIZE_V0; + case 1: + { + size_t size = RELAY_MSG_HEADER_SIZE_V1; + if (relay_command_requires_stream_id(cmd)) { + size += RELAY_MSG_ROUTING_HEADER_SIZE_V1; + } + return size; + } + default: + tor_assert_unreached(); + return 0; + } +} + +/** Return the maximum size of a relay message body that is allowed for the + * given command and the relay cell protocol. */ +static size_t +get_relay_msg_max_body(const uint8_t cmd, const uint8_t relay_cell_proto) +{ + switch (cmd) { + /* DATAGRAM command will be allowed to be above a full cell. */ + default: + return relay_cell_get_payload_size(relay_cell_proto) - + get_relay_msg_header_size(cmd, relay_cell_proto); + } +} + +/** Parse the relay message header from the given payload and sets the value in + * the relay message. + * + * Return the length parsed from the payload or -1 on error. */ +static ssize_t +parse_relay_msg_hdr_v1(const uint8_t *payload, relay_msg_t *msg) +{ + size_t offset = 0; + + /* Relay Command. */ + msg->command = get_uint8(payload); + offset += (sizeof(msg->command)); + /* Error immediately if this is an unknown relay command. Don't parse + * anything else as we have an invalid payload in our hands. */ + if (!is_known_relay_command(msg->command)) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Unknown relay command %d. Invalid v1 cell.", msg->command); + return -1; + } + /* Message body length. */ + msg->length = ntohs(get_uint16(payload + offset)); + offset += (sizeof(msg->length)); + /* Message body length validation. */ + if (msg->length > get_relay_msg_max_body(msg->command, 1)) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Relay message body length is too big: %u vs %zu. Invalid v1 cell.", + msg->length, get_relay_msg_max_body(msg->command, 1)); + return -1; + } + + return offset; +} + +/** Parse the relay message routing header and set the relay message with it if + * need be. + * + * Return the length parsed from the payload. */ +static size_t +parse_relay_msg_routing_hdr_v1(const uint8_t *payload, relay_msg_t *msg) +{ + size_t offset = 0; + + if (relay_command_requires_stream_id(msg->command)) { + msg->stream_id = ntohs(get_uint16(payload)); + offset += sizeof(msg->stream_id); + } else { + msg->stream_id = 0; + } + + return offset; +} + /** Initialize the given encoder for the relay cell protocol version. */ static void encoder_relay_msg_init(relay_msg_encoder_t *encoder, uint8_t relay_cell_proto) @@ -54,18 +175,6 @@ encoder_relay_msg_clear(relay_msg_encoder_t *encoder) smartlist_free(encoder->ready_cells); } -/** Reset a given encoder that is set it back to its initial state. */ -#if 0 -static void -encoder_relay_msg_reset(relay_msg_encoder_t *encoder) -{ - tor_assert(encoder); - uint8_t relay_cell_proto = encoder->relay_cell_proto; - encoder_relay_msg_clear(encoder); - encoder_relay_msg_init(encoder, relay_cell_proto); -} -#endif - /** Initialize a given decoder object for the relay cell protocol version. */ static void decoder_relay_msg_init(relay_msg_decoder_t *decoder, uint8_t relay_cell_proto) @@ -89,7 +198,6 @@ decoder_relay_msg_clear(relay_msg_decoder_t *decoder) /** Clear the given decoder as in free all its content and initialize it to * factory default. */ -#if 0 static void decoder_relay_msg_reset(relay_msg_decoder_t *decoder) { @@ -97,7 +205,235 @@ decoder_relay_msg_reset(relay_msg_decoder_t *decoder) decoder_relay_msg_clear(decoder); decoder_relay_msg_init(decoder, relay_cell_proto); } -#endif + +/** Move the pending relay message to the ready list. Final validation is done + * to the relay message and returns true if valid or false if not. */ +static bool +decoder_mark_pending_ready(relay_msg_decoder_t *decoder) +{ + tor_assert(decoder); + tor_assert(decoder->pending_len == 0); + tor_assert(decoder->pending); + + smartlist_add(decoder->ready, decoder->pending); + decoder->pending = NULL; + + return true; +} + +/** Unframe a cell for relay cell protocol v0, which is anything before + * RelayCell protocol version appeared, and put it into the decoder. + * + * Return true on success else false indicating the cell is invalid. */ +static bool +decoder_unframe_cell_v0(const cell_t *cell, relay_msg_decoder_t *decoder) +{ + bool ret = false; + relay_header_t rh; + + /* First thing in the payload is the header. Notice that we use this function + * here and our own and reason is that this function is used also to get the + * digest and recognized field and so instead of doing a new clean one, we + * stick to the same interface for header parsing. */ + relay_header_unpack(&rh, cell->payload); + + /* Invalid length. */ + if (rh.length > RELAY_PAYLOAD_SIZE) { + goto end; + } + + /* Invalid relay command. */ + if (!is_known_relay_command(rh.command)) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Unknown relay command %d. Invalid cell.", rh.command); + goto end; + } + + /* Allocate new relay message. We'll copy data from the given cell. */ + decoder->pending = tor_malloc_zero(sizeof(relay_msg_t)); + decoder->pending_len = 0; + decoder->pending->relay_cell_proto = 0; + decoder->pending->is_relay_early = cell->command == CELL_RELAY_EARLY; + + /* Set up the header information and body. */ + decoder->pending->command = rh.command; + decoder->pending->length = rh.length; + decoder->pending->stream_id = rh.stream_id; + decoder->pending->body = tor_malloc_zero(rh.length); + + /* Copy the cell payload into the message body. */ + const size_t header_size = + relay_cell_get_header_size(decoder->relay_cell_proto); + memcpy(decoder->pending->body, cell->payload + header_size, rh.length); + + /* This pending is now ready. */ + decoder_mark_pending_ready(decoder); + + /* Success. */ + ret = true; + + end: + return ret; +} + +/** Unframe a cell for relay cell protocol v1 and put it into the decoder. + * + * Return true on success else false indicating the cell is invalid. */ +static bool +decoder_unframe_cell_v1(const cell_t *cell, relay_msg_decoder_t *decoder) +{ +/** Helper macro: Used ONLY in relay_msg_decoder_add_cell() to make sure the + * length of data we are about to parse fits within the maximum size of a + * payload. On error, it protocol warns and goto end. */ +#undef CHECK_AVAILABLE_ROOM +#define CHECK_AVAILABLE_ROOM(wanted_len) \ + do { \ + if ((processed_len + wanted_len) > PAYLOAD_MAX_SIZE) { \ + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, \ + "Cell is malformed. Missing data to build a relay message"); \ + goto end; \ + } \ + } while (0) + + /* Returned value defaults on false as in a failure. */ + bool ret = false; + + tor_assert(cell); + tor_assert(decoder); + + /* Maximum size the relay message payload can be (this includes the message + * header(s). */ + const uint16_t PAYLOAD_MAX_SIZE = + relay_cell_get_payload_size(decoder->relay_cell_proto); + /* Pointer to the cell payload that is the start to the relay message data + * Its content and position can't be changed, we always only offset in it. */ + const uint8_t * const payload = cell->payload + + relay_cell_get_header_size(decoder->relay_cell_proto); + + /* Processed length of the payload. This is used to offset in the payload and + * know if we have reached the end of the payload so to not overrun. */ + size_t processed_len = 0; + /* This is the current relay message we are processing during the loop. */ + relay_msg_t *current = NULL; + + /* As long as we have bytes to consume. */ + while (processed_len < PAYLOAD_MAX_SIZE) { + /* Peek in the payload for an end-of-message marker. + * + * We first check if we have available room to do this and won't buffer + * overrun. + * + * Also, we don't check if this is the start of the payload because a + * marker on the first byte is not allowed. Furthermore, we can't do it + * with a pending length because it means the pending message is not + * complete and thus it can NOT be an end marker if we find one. */ + CHECK_AVAILABLE_ROOM(sizeof(uint8_t)); + if (get_uint8(payload + processed_len) == RELAY_MSG_END_MARKER_V1) { + /* End-of-message marker are not allowed at the very start of a cell + * because this means a cell without a message. Invalid. */ + if (processed_len == 0) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "End-of-message marker at the start. Invalid v1 cell."); + goto end; + } + /* We done parsing this cell. */ + break; + } + + /* Use the pending message if any. If not, we'll build a new one. */ + current = decoder->pending; + if (!current) { + /* No pending message, the length has to be 0 else bad code flow. */ + tor_assert(decoder->pending_len == 0); + + /* Without a pending message, we are about to parse a new relay message + * header and so make sure we have enough room for this else this is a + * malformed cell. A header can't be fragmented. */ + CHECK_AVAILABLE_ROOM(RELAY_MSG_HEADER_SIZE_V1); + + /* We have enough room to parse the header, allocate the object. At this + * stage, the message body is NOT allocated yet. */ + current = tor_malloc_zero(sizeof(relay_msg_t)); + current->relay_cell_proto = 1; + + /* Only consider this flag if we haven't already flagged it. Reason is + * that we only allow one relay_early command per message else we would + * deplete our bucket for a fragmented message over multiple cells. */ + if (!current->is_relay_early) { + current->is_relay_early = cell->command == CELL_RELAY_EARLY; + } + + /* Parse the relay message header into the relay message. Again, no body + * allocated yet. */ + ssize_t parsed_len = + parse_relay_msg_hdr_v1(payload + processed_len, current); + if (parsed_len < 0) { + goto end; + } + processed_len += (size_t) parsed_len; + + /* Then, check if the command requires a stream ID for which we parse the + * message routing header. */ + if (relay_command_requires_stream_id(current->command)) { + /* Validate we have enough room for the routing header. */ + CHECK_AVAILABLE_ROOM(RELAY_MSG_ROUTING_HEADER_SIZE_V1); + + /* Parse the routing header into the relay message. */ + parsed_len = + parse_relay_msg_routing_hdr_v1(payload + processed_len, current); + processed_len += parsed_len; + } + + /* Allocate the message body. The length is bound by its type of 16 bit + * and so the maximum value is UINT16_MAX which is allowed. */ + current->body = tor_malloc_zero(current->length); + + /* Set this new message as pending. Next step is to get the body. */ + decoder->pending_len = current->length; + decoder->pending = current; + } + + /* The body length is the minimum between what is left and what we want as + * in the pending length in the decoder. */ + uint16_t body_len = MIN(decoder->pending_len, + PAYLOAD_MAX_SIZE - processed_len); + /* Safety: The body_len should always fit within what was processed and the + * maximum size. However, this function is so delicate that there are never + * too many checks for buffer overrun. Future proof ourselves. */ + CHECK_AVAILABLE_ROOM(body_len); + + /* We can end up in a situation where the length is 0 because the relay + * command doesn't require a body or the body is fragmented in other cells + * because we reached the end of the cell. In that case, avoid useless + * mempcy of 0 length. */ + if (body_len > 0) { + memcpy(current->body, payload + processed_len, body_len); + processed_len += body_len; + decoder->pending_len -= body_len; + } + + /* If we have the entire message, mark it ready and continue processing. */ + if (decoder->pending_len == 0) { + decoder_mark_pending_ready(decoder); + } + } + + /* Success. */ + ret = true; + + end: + /* On error, we cleanup the decoder and anything allocated so the caller + * can't by mistake use that data. */ + if (!ret) { + /* Make sure we were not processing the pending message. */ + if (decoder->pending == current) { + decoder->pending = NULL; + } + relay_msg_free(current); + decoder_relay_msg_reset(decoder); + } + return ret; +} /* * Public API @@ -161,3 +497,37 @@ relay_msg_codec_clear(relay_msg_codec_t *codec) relay_msg_free(msg)); smartlist_free(codec->pending_packable_msg); } + +/** Add a new cell to the decoder which unframes it and creates relay + * message(s). They are accumulated inside the decoder and can be taken after + * this process. + * + * This is a core function of the fast path processing all relay cells and + * building messages. The caller should query the decoder for any completed + * messages. If none are ready, it means more cells are needed to complete any + * pending messages. + * + * Return true if the cell was successfully unframed and validated. Else return + * false meaning the cell or the constructed message is invalid. */ +bool +relay_msg_decode_cell(relay_msg_codec_t *codec, const cell_t *cell) +{ + tor_assert(cell); + tor_assert(codec); + + /* This is set during decryption and so reaching this should always match. */ + tor_assert(cell->relay_cell_proto == codec->relay_cell_proto); + + switch (codec->relay_cell_proto) { + case 0: + return decoder_unframe_cell_v0(cell, &codec->decoder); + case 1: + return decoder_unframe_cell_v1(cell, &codec->decoder); + default: + /* In theory, we can't negotiate a protocol version we don't know but, + * again, this is C and 20+ year old code base so be extra safe. */ + tor_assert_nonfatal_unreached(); + /* Consider invalid of course. */ + return false; + } +} diff --git a/src/core/or/relay_msg.h b/src/core/or/relay_msg.h index b265b8d459..ed0e7f188e 100644 --- a/src/core/or/relay_msg.h +++ b/src/core/or/relay_msg.h @@ -25,6 +25,9 @@ void relay_msg_clear(relay_msg_t *msg); void relay_msg_codec_init(relay_msg_codec_t *codec, uint8_t relay_cell_proto); void relay_msg_codec_clear(relay_msg_codec_t *codec); +/* Decoder/Encoder. */ +bool relay_msg_decode_cell(relay_msg_codec_t *codec, const cell_t *cell); + #ifdef RELAY_MSG_PRIVATE #endif /* RELAY_MSG_PRIVATE */