From: Nick Mathewson Date: Thu, 17 Apr 2025 17:15:04 +0000 (-0400) Subject: prop359: Implement relay cell encoder/decoders X-Git-Tag: tor-0.4.9.3-alpha~58^2~22 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3c5d23d1b6313e6dfe2a931a24039fb5466d0921;p=thirdparty%2Ftor.git prop359: Implement relay cell encoder/decoders I decided not to use a codec-based approach here. Since we aren't implementing prop340, there is exactly one cell per message, so we don't need to keep any state in between cells or messages. --- diff --git a/src/core/or/include.am b/src/core/or/include.am index b4a1ce6653..09ec617ce2 100644 --- a/src/core/or/include.am +++ b/src/core/or/include.am @@ -31,6 +31,7 @@ LIBTOR_APP_A_SOURCES += \ src/core/or/reasons.c \ src/core/or/relay.c \ src/core/or/relay_cell.c \ + src/core/or/relay_msg.c \ src/core/or/scheduler.c \ src/core/or/scheduler_kist.c \ src/core/or/scheduler_vanilla.c \ diff --git a/src/core/or/or.h b/src/core/or/or.h index 1b3accfa38..8204554245 100644 --- a/src/core/or/or.h +++ b/src/core/or/or.h @@ -557,6 +557,15 @@ static inline int get_circ_id_size(int wide_circ_ids) /** Largest number of bytes that can fit in a relay cell payload. */ #define RELAY_PAYLOAD_SIZE (CELL_PAYLOAD_SIZE-RELAY_HEADER_SIZE) +/** Number of bytes used for a relay cell's header, in the v0 format. */ +#define RELAY_HEADER_SIZE_V0 (1+2+2+4+2) +/** Number of bytes used for a relay cell's header, in the v1 format, + * if no StreamID is used. */ +#define RELAY_HEADER_SIZE_V1_NO_STREAM_ID (16+1+2) +/** Number of bytes used for a relay cell's header, in the v1 format, + * if a StreamID is used. */ +#define RELAY_HEADER_SIZE_V1_WITH_STREAM_ID (16+1+2+2) + /** Identifies a circuit on an or_connection */ typedef uint32_t circid_t; /** Identifies a stream on a circuit */ diff --git a/src/core/or/relay_cell.c b/src/core/or/relay_cell.c index 219b105b24..32fcfae0f9 100644 --- a/src/core/or/relay_cell.c +++ b/src/core/or/relay_cell.c @@ -47,58 +47,18 @@ #define DIGEST_OFFSET_V0 (5) #define DIGEST_OFFSET_V1 (2) -/** Return the offset where the padding should start. The data_len is - * the relay payload length expected to be put in the cell. It can not be - * bigger than the relay payload size else this function assert(). - * - * Value will always be smaller than CELL_PAYLOAD_SIZE because this offset is - * for the entire cell length not just the data payload length. Zero is - * returned if there is no room for padding. +/** + * TODO #41051: Remove this entirely. * - * This function always skips the first 4 bytes after the payload because - * having some unused zero bytes has saved us a lot of times in the past. */ -STATIC size_t -get_pad_cell_offset(size_t data_len, uint8_t relay_cell_proto) -{ - /* This is never supposed to happen but in case it does, stop right away - * because if tor is tricked somehow into not adding random bytes to the - * payload with this function returning 0 for a bad data_len, the entire - * authenticated SENDME design can be bypassed leading to bad denial of - * service attacks. */ - tor_assert(data_len <= relay_cell_get_payload_size(relay_cell_proto)); - - /* If the offset is larger than the cell payload size, we return an offset - * of zero indicating that no padding needs to be added. */ - size_t offset = relay_cell_get_header_size(relay_cell_proto) + data_len + - RELAY_CELL_PADDING_GAP; - if (offset >= CELL_PAYLOAD_SIZE) { - return 0; - } - return offset; -} - -/* Add random bytes to the unused portion of the payload, to foil attacks - * where the other side can predict all of the bytes in the payload and thus - * compute the authenticated SENDME cells without seeing the traffic. See - * proposal 289. */ + * I've moved this functionality into relay_msg.c, where it lives + * more happily. This function shouldn't have any callsites left + * at the end of this branch. + **/ void relay_cell_pad_payload(cell_t *cell, size_t data_len) { - size_t pad_offset, pad_len; - - tor_assert(cell); - - pad_offset = get_pad_cell_offset(data_len, cell->relay_cell_proto); - if (pad_offset == 0) { - /* We can't add padding so we are done. */ - return; - } - - /* Remember here that the cell_payload is the length of the header and - * payload size so we offset it using the full length of the cell. */ - pad_len = CELL_PAYLOAD_SIZE - pad_offset; - crypto_fast_rng_getbytes(get_thread_fast_rng(), - cell->payload + pad_offset, pad_len); + (void)cell; + (void)data_len; } /** Return true iff the given cell recognized field is zero. */ @@ -167,19 +127,3 @@ relay_cell_set_digest(cell_t *cell, uint8_t *new_cell_digest) memcpy(cell_digest_ptr, new_cell_digest, cell_digest_len); } - -/** Set the payload in the given cell for the given relay cell protocol - * version. This also takes care of the padding. - * - * This is part of the fast path. No memory allocation. */ -void -relay_cell_set_payload(cell_t *cell, const uint8_t *payload, - size_t payload_len) -{ - if (payload_len) { - memcpy(cell->payload + relay_cell_get_header_size(cell->relay_cell_proto), - payload, payload_len); - } - /* Add random padding to the cell if we can. */ - relay_cell_pad_payload(cell, payload_len); -} diff --git a/src/core/or/relay_cell.h b/src/core/or/relay_cell.h index 05f0e9f10d..9468355196 100644 --- a/src/core/or/relay_cell.h +++ b/src/core/or/relay_cell.h @@ -13,11 +13,6 @@ #include "core/or/cell_st.h" -/** When padding a cell with randomness, leave this many zeros after the - * payload. Historically, it has paid off to keep unused bytes after the - * payload for the future of our C-tor maze and protocol. */ -#define RELAY_CELL_PADDING_GAP 4 - /* TODO #41051: Most of these functions no longer make sense under CGO, * and we are only going to use the new proto format with CGO. */ @@ -27,53 +22,59 @@ uint8_t *relay_cell_get_digest(cell_t *cell); size_t relay_cell_get_digest_len(const cell_t *cell); /* Setters. */ -void relay_cell_set_payload(cell_t *cell, const uint8_t *payload, - size_t payload_len); void relay_cell_set_digest(cell_t *cell, uint8_t *cell_digest); -void relay_cell_pad_payload(cell_t *cell, size_t payload_len); +void relay_cell_pad_payload(cell_t *cell, size_t data_len); /* * NOTE: The following are inlined for performance reasons. These values are * accessed everywhere and so, even if not expensive, we avoid a function call. */ -/** Return the size of the relay cell header for the given relay cell - * protocol version. */ -static inline size_t -relay_cell_get_header_size(uint8_t relay_cell_proto) +/** Return true iff 'cmd' uses a stream ID when using + * the v1 relay message format. */ +static bool +relay_cmd_expects_streamid_in_v1(uint8_t relay_command) { - /* Specified in tor-spec.txt. */ - switch (relay_cell_proto) { - case 0: return (1 + 2 + 2 + 4 + 2); // 11 - // TODO #41051: This doesn't really make sense, for two reasons. - // First, we're not going to do this protocol without CGO, - // so we no longer have separate "recognized" and "digest" fields. - // Second, the 16-byte tag under CGO does not include - // the length and command fields, - // which are counted above. - case 1: return (2 + 14); // 16 - default: - tor_fragile_assert(); - return 0; + switch (relay_command) { + 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 size of the relay cell payload for the given relay cell - * protocol version. */ -/* TODO #41051: This depends on the command too, since the stream ID is - conditional */ -/* TODO #41051: This is a _maximum_. */ +/** Return the size of the relay cell payload for the given relay + * cell format. */ static inline size_t -relay_cell_get_payload_size(uint8_t relay_cell_proto) +relay_cell_max_payload_size(relay_cell_fmt_t format, + uint8_t relay_command) { - return CELL_PAYLOAD_SIZE - relay_cell_get_header_size(relay_cell_proto); + switch (format) { + case RELAY_CELL_FORMAT_V0: + return CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V0; + case RELAY_CELL_FORMAT_V1: { + if (relay_cmd_expects_streamid_in_v1(relay_command)) { + return CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_WITH_STREAM_ID; + } else { + return CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_NO_STREAM_ID; + } + } + default: + tor_fragile_assert(); + return 0; + } } #ifdef RELAY_CELL_PRIVATE -STATIC size_t get_pad_cell_offset(size_t payload_len, - uint8_t relay_cell_proto); - #endif /* RELAY_CELL_PRIVATE */ #endif /* TOR_RELAY_CELL_H */ diff --git a/src/core/or/relay_msg.c b/src/core/or/relay_msg.c index fa25019c5e..2fe1d1c622 100644 --- a/src/core/or/relay_msg.c +++ b/src/core/or/relay_msg.c @@ -10,18 +10,16 @@ #include "core/or/relay_msg.h" +#include "core/or/relay_cell.h" +#include "lib/crypt_ops/crypto_rand.h" + +#include "core/or/cell_st.h" +#include "core/or/relay_msg_st.h" + /* * Public API */ -/** Called just before the consensus is changed with the given networkstatus_t - * object. */ -void -relay_msg_consensus_has_changed(const networkstatus_t *ns) -{ - relay_msg_enabled = get_param_enabled(ns); -} - /** Free the given relay message. */ void relay_msg_free_(relay_msg_t *msg) @@ -42,3 +40,208 @@ relay_msg_clear(relay_msg_t *msg) tor_free(msg->body); memset(msg, 0, sizeof(*msg)); } + +/* Positions of fields within a v0 message. */ +#define V0_CMD_OFFSET 0 +#define V0_STREAM_ID_OFFSET 3 +#define V0_LEN_OFFSET 9 +#define V0_PAYLOAD_OFFSET 11 + +/* Positions of fields within a v1 message. */ +#define V1_CMD_OFFSET 16 +#define V1_LEN_OFFSET 17 +#define V1_STREAM_ID_OFFSET 19 +#define V1_PAYLOAD_OFFSET_NO_STREAM_ID 19 +#define V1_PAYLOAD_OFFSET_WITH_STREAM_ID 21 + +/* Add random bytes to the unused portion of the payload, to foil attacks + * where the other side can predict all of the bytes in the payload and thus + * compute the authenticated SENDME cells without seeing the traffic. See + * proposal 289. */ +static void +relay_cell_pad(cell_t *cell, size_t end_of_message) +{ + // We add 4 bytes of zero before padding, for forward-compatibility. + const size_t skip = 4; + + if (end_of_message + skip >= CELL_PAYLOAD_SIZE) { + /* nothing to do. */ + return; + } + + crypto_fast_rng_getbytes(get_thread_fast_rng(), + &cell->payload[end_of_message + skip], + CELL_PAYLOAD_SIZE - (end_of_message + skip)); +} + +/** Encode the relay message in 'msg' into cell, according to the + * v0 rules. */ +static int +encode_v0_cell(const relay_msg_t *msg, + cell_t *cell_out) +{ + if (BUG(msg->length > CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V0)) + return -1; + + uint8_t *out = cell_out->payload; + + out[V0_CMD_OFFSET] = (uint8_t) msg->command; + set_uint16(out+V0_STREAM_ID_OFFSET, htons(msg->stream_id)); + set_uint16(out+V0_LEN_OFFSET, htons(msg->length)); + memcpy(out + RELAY_HEADER_SIZE_V0, msg->body, msg->length); + relay_cell_pad(cell_out, RELAY_HEADER_SIZE_V0 + msg->length); + + return 0; +} + +/** Encode the relay message in 'msg' into cell, according to the + * v0 rules. */ +static int +encode_v1_cell(const relay_msg_t *msg, + cell_t *cell_out) +{ + bool expects_streamid = relay_cmd_expects_streamid_in_v1(msg->command); + size_t maxlen; + if (expects_streamid) { + maxlen = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_WITH_STREAM_ID; + } else { + maxlen = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_NO_STREAM_ID; + } + + if (BUG(msg->length > maxlen)) + return -1; + + uint8_t *out = cell_out->payload; + out[V1_CMD_OFFSET] = msg->command; + set_uint16(out+V1_LEN_OFFSET, htons(msg->length)); + size_t payload_offset; + if (expects_streamid) { + if (BUG(msg->stream_id == 0)) + return -1; + set_uint16(out+V1_STREAM_ID_OFFSET, htons(msg->stream_id)); + payload_offset = V1_PAYLOAD_OFFSET_WITH_STREAM_ID; + } else { + if (BUG(msg->stream_id != 0)) + return -1; + + payload_offset = V1_PAYLOAD_OFFSET_NO_STREAM_ID; + } + + memcpy(out + payload_offset, msg->body, msg->length); + relay_cell_pad(cell_out, payload_offset + msg->length); + return 0; +} + +/** Try to decode 'cell' into a newly allocated V0 relay message. + * + * Return NULL on error. + */ +static relay_msg_t * +decode_v0_cell(const cell_t *cell) +{ + relay_msg_t *out = tor_malloc_zero(sizeof(relay_msg_t)); + out->is_relay_early = (cell->command == CELL_RELAY_EARLY); + + const uint8_t *body = cell->payload; + out->command = get_uint8(body + V0_CMD_OFFSET); + out->stream_id = ntohs(get_uint16(body + V0_STREAM_ID_OFFSET)); + out->length = ntohs(get_uint16(body + V0_LEN_OFFSET)); + + if (out->length > CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V0) { + goto err; + } + out->body = tor_memdup_nulterm(body + V0_PAYLOAD_OFFSET, out->length); + + return out; + err: + relay_msg_free(out); + return NULL; +} + +/** Try to decode 'cell' into a newly allocated V0 relay message. + * + * Return NULL on error. + */ +static relay_msg_t * +decode_v1_cell(const cell_t *cell) +{ + relay_msg_t *out = tor_malloc_zero(sizeof(relay_msg_t)); + out->is_relay_early = (cell->command == CELL_RELAY_EARLY); + + const uint8_t *body = cell->payload; + out->command = get_uint8(body + V1_CMD_OFFSET); + if (! is_known_relay_command(out->command)) + goto err; + out->length = ntohs(get_uint16(body + V1_LEN_OFFSET)); + size_t payload_offset; + if (relay_cmd_expects_streamid_in_v1(out->command)) { + out->stream_id = ntohs(get_uint16(body + V1_STREAM_ID_OFFSET)); + payload_offset = V1_PAYLOAD_OFFSET_WITH_STREAM_ID; + } else { + payload_offset = V1_PAYLOAD_OFFSET_NO_STREAM_ID; + } + + if (out->length > CELL_PAYLOAD_SIZE - payload_offset) + goto err; + out->body = tor_memdup_nulterm(body + payload_offset, out->length); + + return out; + err: + relay_msg_free(out); + return NULL; +} +/** + * Encode 'msg' into 'cell' according to the rules of 'format'. + * + * Does not set any "recognized", "digest" or "tag" fields, + * since those are necessarily part of the crypto logic. + * + * Clears the circuit ID on the cell. + * + * Return 0 on success, and -1 if 'msg' is not well-formed. + */ +int +relay_msg_encode_cell(relay_cell_fmt_t format, + const relay_msg_t *msg, + cell_t *cell_out) +{ + memset(cell_out, 0, sizeof(cell_t)); + cell_out->relay_cell_proto = format; + cell_out->command = msg->is_relay_early ? + CELL_RELAY_EARLY : CELL_RELAY; + + switch (format) { + case RELAY_CELL_FORMAT_V0: + return encode_v0_cell(msg, cell_out); + case RELAY_CELL_FORMAT_V1: + return encode_v1_cell(msg, cell_out); + default: + tor_fragile_assert(); + return -1; + } +} + +/** + * Decode 'cell' (which must be RELAY or RELAY_EARLY) into a newly allocated + * 'relay_msg_t'. + * + * Return NULL on error. + */ +relay_msg_t * +relay_msg_decode_cell(relay_cell_fmt_t format, + const cell_t *cell) +{ + // TODO #41051: Either remove the format argument here, + // or the format field in cell_t. + tor_assert(cell->relay_cell_proto == format); + + switch (format) { + case RELAY_CELL_FORMAT_V0: + return decode_v0_cell(cell); + case RELAY_CELL_FORMAT_V1: + return decode_v1_cell(cell); + default: + tor_fragile_assert(); + return NULL; + } +} diff --git a/src/core/or/relay_msg.h b/src/core/or/relay_msg.h index 4dc6c109f7..a0647330e7 100644 --- a/src/core/or/relay_msg.h +++ b/src/core/or/relay_msg.h @@ -17,6 +17,13 @@ void relay_msg_free_(relay_msg_t *msg); void relay_msg_clear(relay_msg_t *msg); +int relay_msg_encode_cell(relay_cell_fmt_t format, + const relay_msg_t *msg, + cell_t *cell_out) ATTR_WUR; +relay_msg_t *relay_msg_decode_cell( + relay_cell_fmt_t format, + const cell_t *cell) ATTR_WUR; + #define relay_msg_free(msg) \ FREE_AND_NULL(relay_msg_t, relay_msg_free_, (msg)) diff --git a/src/test/test_cell_formats.c b/src/test/test_cell_formats.c index e01a3461fc..d3ad68f89a 100644 --- a/src/test/test_cell_formats.c +++ b/src/test/test_cell_formats.c @@ -17,9 +17,11 @@ #include "core/crypto/onion_fast.h" #include "core/crypto/onion_ntor.h" #include "core/or/relay.h" +#include "core/or/relay_msg.h" #include "core/or/cell_st.h" #include "core/or/cell_queue_st.h" +#include "core/or/relay_msg_st.h" #include "core/or/var_cell_st.h" #include "test/test.h" @@ -1200,6 +1202,411 @@ test_cfmt_is_destroy(void *arg) tor_free(chan); } +static void +test_cfmt_relay_msg_encoding_simple(void *arg) +{ + (void)arg; + relay_msg_t *msg1 = NULL; + cell_t cell; + char *mem_op_hex_tmp = NULL; + int r; + + /* Simple message: Data, fits easily in cell. */ + msg1 = tor_malloc_zero(sizeof(relay_msg_t)); + msg1->command = RELAY_COMMAND_DATA; + msg1->stream_id = 0x250; + msg1->length = 11; + msg1->body = tor_memdup("hello world", 11); + + r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V0, msg1, &cell); + tt_int_op(r, OP_EQ, 0); + tt_int_op(cell.command, OP_EQ, CELL_RELAY); + tt_int_op(cell.circ_id, OP_EQ, 0); + // command, recognized, streamid, digest, len, payload, zero-padding. + test_memeq_hex(cell.payload, + "02" "0000" "0250" "00000000" "000B" + "68656c6c6f20776f726c64" "00000000"); + // random padding + size_t used = RELAY_HEADER_SIZE_V0 + 11 + 4; + tt_assert(!fast_mem_is_zero((char*)cell.payload + used, + CELL_PAYLOAD_SIZE - used)); + + r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V1, msg1, &cell); + tt_int_op(r, OP_EQ, 0); + tt_int_op(cell.command, OP_EQ, CELL_RELAY); + tt_int_op(cell.circ_id, OP_EQ, 0); + // tag, command, len, optional streamid, payload, zero-padding + test_memeq_hex(cell.payload, + "00000000000000000000000000000000" + "02" "000B" "0250" + "68656c6c6f20776f726c64" "00000000"); + // random padding. + used = RELAY_HEADER_SIZE_V1_WITH_STREAM_ID + 11 + 4; + tt_assert(!fast_mem_is_zero((char*)cell.payload + used, + CELL_PAYLOAD_SIZE - used)); + + /* Message without stream ID: SENDME, fits easily in cell. */ + relay_msg_clear(msg1); + msg1->command = RELAY_COMMAND_SENDME; + msg1->stream_id = 0; + msg1->length = 20; + msg1->body = tor_memdup("hello i am a tag....", 20); + + r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V0, msg1, &cell); + tt_int_op(r, OP_EQ, 0); + tt_int_op(cell.command, OP_EQ, CELL_RELAY); + tt_int_op(cell.circ_id, OP_EQ, 0); + // command, recognized, streamid, digest, len, payload, zero-padding. + test_memeq_hex(cell.payload, + "05" "0000" "0000" "00000000" "0014" + "68656c6c6f206920616d2061207461672e2e2e2e" "00000000"); + // random padding + used = RELAY_HEADER_SIZE_V0 + 20 + 4; + tt_assert(!fast_mem_is_zero((char*)cell.payload + used, + CELL_PAYLOAD_SIZE - used)); + + r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V1, msg1, &cell); + tt_int_op(r, OP_EQ, 0); + tt_int_op(cell.command, OP_EQ, CELL_RELAY); + tt_int_op(cell.circ_id, OP_EQ, 0); + // tag, command, len, optional streamid, payload, zero-padding + test_memeq_hex(cell.payload, + "00000000000000000000000000000000" + "05" "0014" + "68656c6c6f206920616d2061207461672e2e2e2e" "00000000"); + // random padding. + used = RELAY_HEADER_SIZE_V1_NO_STREAM_ID + 20 + 4; + tt_assert(!fast_mem_is_zero((char*)cell.payload + used, + CELL_PAYLOAD_SIZE - used)); + + done: + relay_msg_free(msg1); + tor_free(mem_op_hex_tmp); +} + +/** Helper for test_cfmt_relay_cell_padding. + * Requires that that the body of 'msg' ends with 'pre_padding_byte', + * and that when encoded, the zero-padding (if any) will appear at + * offset 'zeros_begin_at' in the message. + */ +static void +msg_encoder_padding_test(const relay_msg_t *msg, + relay_cell_fmt_t fmt, + uint8_t pre_padding_byte, + size_t zeros_begin_at) +{ + cell_t cell; + int n = 16, i; + /* We set this to 0 as soon as we find that the first byte of + * random padding has been set. */ + bool padded_first = false; + /* We set this to true as soon as we find that the last byte of + * random padding has been set */ + bool padded_last = false; + + tt_int_op(zeros_begin_at, OP_LE, CELL_PAYLOAD_SIZE); + + size_t expect_n_zeros = MIN(4, CELL_PAYLOAD_SIZE - zeros_begin_at); + ssize_t first_random_at = -1; + if (CELL_PAYLOAD_SIZE - zeros_begin_at > 4) { + first_random_at = CELL_PAYLOAD_SIZE - zeros_begin_at + 4; + } + + for (i = 0; i < n; ++i) { + memset(&cell, 0, sizeof(cell)); + tt_int_op(0, OP_EQ, + relay_msg_encode_cell(fmt, msg, &cell)); + + const uint8_t *body = cell.payload; + tt_int_op(body[zeros_begin_at - 1], OP_EQ, pre_padding_byte); + + if (expect_n_zeros) { + tt_assert(fast_mem_is_zero((char*)body + zeros_begin_at, + expect_n_zeros)); + } + if (first_random_at >= 0) { + if (body[first_random_at]) + padded_first = true; + if (body[CELL_PAYLOAD_SIZE-1]) + padded_last = true; + } + } + + if (first_random_at >= 0) { + tt_assert(padded_first); + tt_assert(padded_last); + } + + done: + ; +} + +static void +test_cfmt_relay_cell_padding(void *arg) +{ + (void)arg; + relay_msg_t *msg1 = NULL; + + /* Simple message; we'll adjust the length and encode it. */ + msg1 = tor_malloc_zero(sizeof(relay_msg_t)); + msg1->command = RELAY_COMMAND_DATA; + msg1->stream_id = 0x250; + msg1->body = tor_malloc(500); // Longer than it needs to be. + memset(msg1->body, 0xff, 500); + + // Empty message + msg1->length = 0; + msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V0, 0x00, + RELAY_HEADER_SIZE_V0); + msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0x50, + RELAY_HEADER_SIZE_V1_WITH_STREAM_ID); + + // Short message + msg1->length = 10; + msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V0, 0xff, + RELAY_HEADER_SIZE_V0 + 10); + msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff, + RELAY_HEADER_SIZE_V1_WITH_STREAM_ID + 10); + + // Message where zeros extend exactly up to the end of the cell. + msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V0 - 4; + msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V0, 0xff, + RELAY_HEADER_SIZE_V0 + msg1->length); + msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_WITH_STREAM_ID - 4; + msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff, + RELAY_HEADER_SIZE_V1_WITH_STREAM_ID + msg1->length); + + // Message where zeros would intersect with the end of the cell. + msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V0 - 3; + msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V0, 0xff, + RELAY_HEADER_SIZE_V0 + msg1->length); + msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_WITH_STREAM_ID - 3; + msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff, + RELAY_HEADER_SIZE_V1_WITH_STREAM_ID + msg1->length); + + // Message with no room for zeros + msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V0; + msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V0, 0xff, + RELAY_HEADER_SIZE_V0 + msg1->length); + msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_WITH_STREAM_ID; + msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff, + RELAY_HEADER_SIZE_V1_WITH_STREAM_ID + msg1->length); + + /////////////// + // V1 cases with no stream ID. + msg1->stream_id = 0; + msg1->command = RELAY_COMMAND_EXTENDED; + + msg1->length = 0; + msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0x00, + RELAY_HEADER_SIZE_V1_NO_STREAM_ID); + msg1->length = 10; + msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff, + RELAY_HEADER_SIZE_V1_NO_STREAM_ID + 10); + msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_NO_STREAM_ID - 4; + msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff, + RELAY_HEADER_SIZE_V1_NO_STREAM_ID + msg1->length); + msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_NO_STREAM_ID - 3; + msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff, + RELAY_HEADER_SIZE_V1_NO_STREAM_ID + msg1->length); + msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_NO_STREAM_ID; + msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff, + RELAY_HEADER_SIZE_V1_NO_STREAM_ID + msg1->length); + + relay_msg_free(msg1); +} + +static void +test_cfmt_relay_msg_encoding_error(void *arg) +{ + (void)arg; + relay_msg_t *msg1 = NULL; + int r; + cell_t cell; + + msg1 = tor_malloc_zero(sizeof(relay_msg_t)); + msg1->command = RELAY_COMMAND_DATA; + msg1->stream_id = 0x250; + msg1->body = tor_malloc(500); // Longer than it needs to be. + memset(msg1->body, 0xff, 500); + + tor_capture_bugs_(5); + // Too long for v0. + msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V0 + 1; + r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V0, msg1, &cell); + tt_int_op(r, OP_EQ, -1); + + // Too long for v1, with stream ID. + msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_WITH_STREAM_ID + 1; + r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V1, msg1, &cell); + tt_int_op(r, OP_EQ, -1); + + // Too long for v1 with no stream ID. + msg1->command = RELAY_COMMAND_EXTENDED; + msg1->stream_id = 0; + msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_NO_STREAM_ID + 1; + r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V1, msg1, &cell); + tt_int_op(r, OP_EQ, -1); + + // Invalid (present) stream ID for V1. + msg1->stream_id = 10; + msg1->length = 20; + r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V1, msg1, &cell); + tt_int_op(r, OP_EQ, -1); + + // Invalid (absent) stream ID for V1. + msg1->stream_id = 0; + msg1->command = RELAY_COMMAND_DATA; + r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V1, msg1, &cell); + tt_int_op(r, OP_EQ, -1); + + done: + tor_end_capture_bugs_(); + relay_msg_free(msg1); +} + +static void +test_cfmt_relay_msg_decoding_simple(void *arg) +{ + (void) arg; + cell_t cell; + relay_msg_t *msg1 = NULL; + const char *s; + + memset(&cell, 0, sizeof(cell)); + cell.command = CELL_RELAY; + + // V0 decoding, short message. + s = "02" "0000" "0250" "00000000" "000B" + "68656c6c6f20776f726c64" "00000000"; + base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s)); + cell.relay_cell_proto = RELAY_CELL_FORMAT_V0; + msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V0, &cell); + tt_assert(msg1); + + tt_int_op(msg1->command, OP_EQ, RELAY_COMMAND_DATA); + tt_int_op(msg1->stream_id, OP_EQ, 0x250); + tt_int_op(msg1->length, OP_EQ, 11); + tt_mem_op(msg1->body, OP_EQ, "hello world", 11); + relay_msg_free(msg1); + + // V0 decoding, message up to length of cell. + memset(cell.payload, 0, sizeof(cell.payload)); + s = "02" "0000" "0250" "00000000" "01F2"; + base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s)); + msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V0, &cell); + tt_assert(msg1); + + tt_int_op(msg1->command, OP_EQ, RELAY_COMMAND_DATA); + tt_int_op(msg1->stream_id, OP_EQ, 0x250); + tt_int_op(msg1->length, OP_EQ, 498); + tt_assert(fast_mem_is_zero((char*)msg1->body, 498)); + relay_msg_free(msg1); + + // V1 decoding, short message, no stream ID. + s = "00000000000000000000000000000000" + "05" "0014" + "68656c6c6f206920616d2061207461672e2e2e2e" "00000000"; + base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s)); + cell.relay_cell_proto = RELAY_CELL_FORMAT_V1; + + msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V1, &cell); + tt_assert(msg1); + tt_int_op(msg1->command, OP_EQ, RELAY_COMMAND_SENDME); + tt_int_op(msg1->stream_id, OP_EQ, 0); + tt_int_op(msg1->length, OP_EQ, 20); + tt_mem_op(msg1->body, OP_EQ, "hello i am a tag....", 20); + relay_msg_free(msg1); + + // V1 decoding, up to length of cell, no stream ID. + memset(cell.payload, 0, sizeof(cell.payload)); + s = "00000000000000000000000000000000" + "05" "01EA"; + base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s)); + + msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V1, &cell); + tt_assert(msg1); + tt_int_op(msg1->command, OP_EQ, RELAY_COMMAND_SENDME); + tt_int_op(msg1->stream_id, OP_EQ, 0); + tt_int_op(msg1->length, OP_EQ, 490); + tt_assert(fast_mem_is_zero((char*)msg1->body, 490)); + relay_msg_free(msg1); + + // V1 decoding, short message, with stream ID. + s = "00000000000000000000000000000000" + "02" "000B" "0250" + "68656c6c6f20776f726c64" "00000000"; + base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s)); + + msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V1, &cell); + tt_assert(msg1); + tt_int_op(msg1->command, OP_EQ, RELAY_COMMAND_DATA); + tt_int_op(msg1->stream_id, OP_EQ, 0x250); + tt_int_op(msg1->length, OP_EQ, 11); + tt_mem_op(msg1->body, OP_EQ, "hello world", 11); + relay_msg_free(msg1); + + // V1 decoding, up to length of cell, with stream ID. + memset(cell.payload, 0, sizeof(cell.payload)); + s = "00000000000000000000000000000000" + "02" "01E8" "0250"; + base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s)); + + msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V1, &cell); + tt_assert(msg1); + tt_int_op(msg1->command, OP_EQ, RELAY_COMMAND_DATA); + tt_int_op(msg1->stream_id, OP_EQ, 0x250); + tt_int_op(msg1->length, OP_EQ, 488); + tt_assert(fast_mem_is_zero((char*)msg1->body, 488)); + relay_msg_free(msg1); + + done: + relay_msg_free(msg1); +} + +static void +test_cfmt_relay_msg_decoding_error(void *arg) +{ + (void) arg; + relay_msg_t *msg1 = NULL; + cell_t cell; + const char *s; + memset(&cell, 0, sizeof(cell)); + + // V0, too long. + cell.command = CELL_RELAY; + cell.relay_cell_proto = RELAY_CELL_FORMAT_V0; + s = "02" "0000" "0250" "00000000" "01F3"; + base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s)); + msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V0, &cell); + tt_ptr_op(msg1, OP_EQ, NULL); + + // V1, command unrecognized. + cell.relay_cell_proto = RELAY_CELL_FORMAT_V1; + s = "00000000000000000000000000000000" + "F0" "000C" "0250"; + base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s)); + msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V1, &cell); + tt_ptr_op(msg1, OP_EQ, NULL); + + // V1, too long (with stream ID) + s = "00000000000000000000000000000000" + "02" "01E9" "0250"; + base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s)); + msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V1, &cell); + tt_ptr_op(msg1, OP_EQ, NULL); + + // V1, too long (without stream ID) + s = "00000000000000000000000000000000" + "05" "01EB"; + base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s)); + msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V1, &cell); + tt_ptr_op(msg1, OP_EQ, NULL); + + done: + relay_msg_free(msg1); +} + #define TEST(name, flags) \ { #name, test_cfmt_ ## name, flags, 0, NULL } @@ -1213,5 +1620,10 @@ struct testcase_t cell_format_tests[] = { TEST(extended_cells, 0), TEST(resolved_cells, 0), TEST(is_destroy, 0), + TEST(relay_msg_encoding_simple, 0), + TEST(relay_cell_padding, 0), + TEST(relay_msg_encoding_error, 0), + TEST(relay_msg_decoding_simple, 0), + TEST(relay_msg_decoding_error, 0), END_OF_TESTCASES }; diff --git a/src/test/test_sendme.c b/src/test/test_sendme.c index 18e2bde164..6dd1842501 100644 --- a/src/test/test_sendme.c +++ b/src/test/test_sendme.c @@ -203,48 +203,6 @@ test_v1_build_cell(void *arg) circuit_free_(circ); } -static void -test_cell_payload_pad(void *arg) -{ - size_t pad_offset, payload_len, expected_offset; - - (void) arg; - - /* Offset should be 0, not enough room for padding. */ - payload_len = RELAY_PAYLOAD_SIZE; - pad_offset = get_pad_cell_offset(payload_len, 0); - tt_int_op(pad_offset, OP_EQ, 0); - tt_int_op(CELL_PAYLOAD_SIZE - pad_offset, OP_LE, CELL_PAYLOAD_SIZE); - - /* Still no room because we keep 4 extra bytes. */ - pad_offset = get_pad_cell_offset(payload_len - 4, 0); - tt_int_op(pad_offset, OP_EQ, 0); - tt_int_op(CELL_PAYLOAD_SIZE - pad_offset, OP_LE, CELL_PAYLOAD_SIZE); - - /* We should have 1 byte of padding. Meaning, the offset should be the - * CELL_PAYLOAD_SIZE minus 1 byte. */ - expected_offset = CELL_PAYLOAD_SIZE - 1; - pad_offset = get_pad_cell_offset(payload_len - 5, 0); - tt_int_op(pad_offset, OP_EQ, expected_offset); - tt_int_op(CELL_PAYLOAD_SIZE - pad_offset, OP_LE, CELL_PAYLOAD_SIZE); - - /* Now some arbitrary small payload length. The cell size is header + 10 + - * extra 4 bytes we keep so the offset should be there. */ - expected_offset = RELAY_HEADER_SIZE + 10 + 4; - pad_offset = get_pad_cell_offset(10, 0); - tt_int_op(pad_offset, OP_EQ, expected_offset); - tt_int_op(CELL_PAYLOAD_SIZE - pad_offset, OP_LE, CELL_PAYLOAD_SIZE); - - /* Data length of 0. */ - expected_offset = RELAY_HEADER_SIZE + 4; - pad_offset = get_pad_cell_offset(0, 0); - tt_int_op(pad_offset, OP_EQ, expected_offset); - tt_int_op(CELL_PAYLOAD_SIZE - pad_offset, OP_LE, CELL_PAYLOAD_SIZE); - - done: - ; -} - static void test_cell_version_validation(void *arg) { @@ -401,8 +359,6 @@ struct testcase_t sendme_tests[] = { NULL, NULL }, { "v1_build_cell", test_v1_build_cell, TT_FORK, NULL, NULL }, - { "cell_payload_pad", test_cell_payload_pad, TT_FORK, - NULL, NULL }, { "cell_version_validation", test_cell_version_validation, TT_FORK, NULL, NULL }, { "package_payload_len", test_package_payload_len, 0, NULL, NULL },