From: David Goulet Date: Thu, 5 Oct 2023 15:13:35 +0000 (-0400) Subject: prop340: Add relay cell access functions X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=147af5e99992f5f03d3fcc02c31b6e10ce928c0f;p=thirdparty%2Ftor.git prop340: Add relay cell access functions Signed-off-by: David Goulet --- diff --git a/src/core/or/cell_st.h b/src/core/or/cell_st.h index 77e12c0c2c..b6ffff9437 100644 --- a/src/core/or/cell_st.h +++ b/src/core/or/cell_st.h @@ -18,6 +18,9 @@ struct cell_t { circid_t circ_id; /**< Circuit which received the cell. */ uint8_t command; /**< Type of the cell: one of CELL_PADDING, CELL_CREATE, * CELL_DESTROY, etc */ + /* Relay cell protocol version. This tells us which format to use when + * parsing the payload. */ + uint8_t relay_cell_proto; uint8_t payload[CELL_PAYLOAD_SIZE]; /**< Cell body. */ }; diff --git a/src/core/or/include.am b/src/core/or/include.am index 473ab84f77..d20d5b7ec1 100644 --- a/src/core/or/include.am +++ b/src/core/or/include.am @@ -30,6 +30,7 @@ LIBTOR_APP_A_SOURCES += \ src/core/or/protover.c \ 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 \ @@ -105,6 +106,7 @@ noinst_HEADERS += \ src/core/or/protover.h \ src/core/or/reasons.h \ src/core/or/relay.h \ + src/core/or/relay_cell.h \ src/core/or/relay_msg.h \ src/core/or/relay_msg_st.h \ src/core/or/relay_crypto_st.h \ diff --git a/src/core/or/relay.c b/src/core/or/relay.c index 36e94820fa..05da061be4 100644 --- a/src/core/or/relay.c +++ b/src/core/or/relay.c @@ -45,6 +45,7 @@ * types of relay cells, launching requests or transmitting data as needed. **/ +#include "core/or/relay_cell.h" #define RELAY_PRIVATE #include "core/or/or.h" #include "feature/client/addressmap.h" @@ -561,60 +562,6 @@ relay_command_to_string(uint8_t command) * payload. */ #define CELL_PADDING_GAP 4 -/** 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 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. - * - * 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) -{ - /* 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_PAYLOAD_SIZE); - - /* 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_HEADER_SIZE + data_len + 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. */ -static void -pad_cell_payload(uint8_t *cell_payload, size_t data_len) -{ - size_t pad_offset, pad_len; - - tor_assert(cell_payload); - - pad_offset = get_pad_cell_offset(data_len); - 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); -} - /** Make a relay cell out of relay_command and payload, and send * it onto the open circuit circ. stream_id is the ID on * circ for the stream that's sending the relay cell, or 0 if it's a @@ -680,7 +627,7 @@ relay_send_command_from_edge_,(streamid_t stream_id, circuit_t *orig_circ, memcpy(cell.payload+RELAY_HEADER_SIZE, payload, payload_len); /* Add random padding to the cell if we can. */ - pad_cell_payload(cell.payload, payload_len); + relay_cell_pad_payload(&cell, payload_len); log_debug(LD_OR,"delivering %d cell %s.", relay_command, cell_direction == CELL_DIRECTION_OUT ? "forward" : "backward"); diff --git a/src/core/or/relay.h b/src/core/or/relay.h index 3a1f3b0ae5..58ce971fad 100644 --- a/src/core/or/relay.h +++ b/src/core/or/relay.h @@ -137,7 +137,6 @@ STATIC int cell_queues_check_size(void); STATIC int connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, edge_connection_t *conn, crypt_path_t *layer_hint); -STATIC size_t get_pad_cell_offset(size_t payload_len); STATIC size_t connection_edge_get_inbuf_bytes_to_package(size_t n_available, int package_partial, circuit_t *on_circuit); diff --git a/src/core/or/relay_cell.c b/src/core/or/relay_cell.c new file mode 100644 index 0000000000..219b105b24 --- /dev/null +++ b/src/core/or/relay_cell.c @@ -0,0 +1,185 @@ +/* Copyright (c) 2023, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file relay_cell.c + * \brief This file handles most of the encoding and parsing of relay cells for + * all supported versions. + **/ + +#include +#include +#define RELAY_CELL_PRIVATE + +#include "core/or/relay_cell.h" +#include "core/or/relay.h" + +#include "lib/arch/bytes.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/log/util_bug.h" + +/* + * NOTE: As a starter of this file, it is important to note that trunnel is not + * used due to its heavy reliance on memory allocation. Parsing and encoding + * cells is a critical piece of the fast path and MUST be done with little + * as possible memory allocation or copy. + * + * And so, you will notice the direct access into a cell_t memory and relying + * on offset instead of copying data into an object representing a header. + */ + +/* + * Relay cell header static values. + * + * We don't copy the header back into a structure for performance reason. When + * accessing the header, we simply use offset within the cell_t payload. + * + * NOTE: We might want to move to a nicer static data structure containing all + * these and indexed by version but for now with the very few relay cell + * protocol and considering the future of C-tor this is simpler and enough. + */ + +/* The "recognized" field by version. */ +#define RECOGNIZED_OFFSET_V0 (1) +#define RECOGNIZED_OFFSET_V1 (0) + +/* The "digest" field by version. */ +#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. + * + * 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. */ +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); +} + +/** Return true iff the given cell recognized field is zero. */ +bool +relay_cell_is_recognized(const cell_t *cell) +{ + switch (cell->relay_cell_proto) { + case 0: return get_uint16(cell->payload + RECOGNIZED_OFFSET_V0) == 0; + case 1: return get_uint16(cell->payload + RECOGNIZED_OFFSET_V1) == 0; + default: + /* Reaching this means we've failed to validate the supported relay cell + * version. */ + tor_fragile_assert(); + return false; + } +} + +/** Return a pointer from inside the given cell pointing to the start of the + * relay cell digest for the given protocol version. + * + * This is part of the fast path. No memory allocation. */ +uint8_t * +relay_cell_get_digest(cell_t *cell) +{ + switch (cell->relay_cell_proto) { + case 0: return cell->payload + DIGEST_OFFSET_V0; + case 1: return cell->payload + DIGEST_OFFSET_V1; + default: + /* Reaching this means we've failed to validate the supported relay cell + * version. Return the start of the payload, it will simply never + * validate and ultimately will close the circuit. */ + tor_fragile_assert(); + return cell->payload; + } +} + +/** Return the relay cell digest length based on the given protocol version. */ +size_t +relay_cell_get_digest_len(const cell_t *cell) +{ +/* Specified in tor-spec.txt */ +#define RELAY_CELL_DIGEST_LEN_V0 (4) +#define RELAY_CELL_DIGEST_LEN_V1 (14) + + switch (cell->relay_cell_proto) { + case 0: return RELAY_CELL_DIGEST_LEN_V0; + case 1: return RELAY_CELL_DIGEST_LEN_V1; + default: + /* Reaching this means we've failed to validate the supported relay cell + * version. This length will simply never validate and ultimately the + * circuit will be closed. */ + tor_fragile_assert(); + return 0; + } +} + +/** Set the given cell_digest value into the cell for the given relay cell + * protocol version. + * + * This is part of the fast path. No memory allocation. */ +void +relay_cell_set_digest(cell_t *cell, uint8_t *new_cell_digest) +{ + uint8_t *cell_digest_ptr = relay_cell_get_digest(cell); + size_t cell_digest_len = relay_cell_get_digest_len(cell); + + 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 new file mode 100644 index 0000000000..9ae628c5bb --- /dev/null +++ b/src/core/or/relay_cell.h @@ -0,0 +1,68 @@ +/* Copyright (c) 2023, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file relay_cell.h + * \brief Header file for relay_cell.c. + **/ + +#ifndef TOR_RELAY_CELL_H +#define TOR_RELAY_CELL_H + +#include "core/or/or.h" + +#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 + +/* Getters. */ +bool relay_cell_is_recognized(const cell_t *cell); +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); + +/* + * 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) +{ + /* Specified in tor-spec.txt. */ + switch (relay_cell_proto) { + case 0: return (1 + 2 + 2 + 4 + 2); // 11 + case 1: return (2 + 14); // 16 + default: + tor_fragile_assert(); + return 0; + } +} + +/** Return the size of the relay cell payload for the given relay cell + * protocol version. */ +static inline size_t +relay_cell_get_payload_size(uint8_t relay_cell_proto) +{ + return CELL_PAYLOAD_SIZE - relay_cell_get_header_size(relay_cell_proto); +} + +#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/test/test_sendme.c b/src/test/test_sendme.c index ea7ccd0b3c..18e2bde164 100644 --- a/src/test/test_sendme.c +++ b/src/test/test_sendme.c @@ -7,12 +7,14 @@ #define NETWORKSTATUS_PRIVATE #define SENDME_PRIVATE #define RELAY_PRIVATE +#define RELAY_CELL_PRIVATE #include "core/or/circuit_st.h" #include "core/or/or_circuit_st.h" #include "core/or/origin_circuit_st.h" #include "core/or/circuitlist.h" #include "core/or/relay.h" +#include "core/or/relay_cell.h" #include "core/or/sendme.h" #include "feature/nodelist/networkstatus.h" @@ -210,32 +212,32 @@ test_cell_payload_pad(void *arg) /* Offset should be 0, not enough room for padding. */ payload_len = RELAY_PAYLOAD_SIZE; - pad_offset = get_pad_cell_offset(payload_len); + 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); + 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); + 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); + 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); + 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);