]> git.ipfire.org Git - thirdparty/tor.git/commitdiff
prop359: Add relay cell access functions
authorDavid Goulet <dgoulet@torproject.org>
Thu, 5 Oct 2023 15:13:35 +0000 (11:13 -0400)
committerNick Mathewson <nickm@torproject.org>
Mon, 5 May 2025 17:05:45 +0000 (13:05 -0400)
Author: David Goulet <dgoulet@torproject.org>

src/core/or/cell_st.h
src/core/or/include.am
src/core/or/relay.c
src/core/or/relay.h
src/core/or/relay_cell.c [new file with mode: 0644]
src/core/or/relay_cell.h [new file with mode: 0644]
src/test/test_sendme.c

index 77e12c0c2cc1bb7ad1ec700f71f61974025ea60a..b6ffff94374a7ac21a318c27e77e3e3e54ecde12 100644 (file)
@@ -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. */
 };
 
index d0dffd5d3b90260af4fbc7bba41f9e8f9dd731bc..904fcc137bc38de5681d4308a013c96bc3d65431 100644 (file)
@@ -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/scheduler.c                 \
        src/core/or/scheduler_kist.c            \
        src/core/or/scheduler_vanilla.c         \
@@ -104,6 +105,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_crypto_st.h                   \
        src/core/or/scheduler.h                         \
        src/core/or/sendme.h                            \
index b2904fc89f371358e0feb28865b17e0322ad565c..b78f8f4e000f9ced3d22b7be772c87467d0892ac 100644 (file)
@@ -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"
@@ -565,60 +566,6 @@ relay_command_to_string(uint8_t command)
  * payload. */
 #define CELL_PADDING_GAP 4
 
-/** Return the offset where the padding should start. The <b>data_len</b> 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 <b>relay_command</b> and <b>payload</b>, and send
  * it onto the open circuit <b>circ</b>. <b>stream_id</b> is the ID on
  * <b>circ</b> for the stream that's sending the relay cell, or 0 if it's a
@@ -684,7 +631,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");
index 12198ae9827e77a70de7be74e206f03ad4fa3bd9..492fee9c37f06c2edaf920bfea1d8bf6cfa7b9ec 100644 (file)
@@ -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 (file)
index 0000000..219b105
--- /dev/null
@@ -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 <stddef.h>
+#include <stdint.h>
+#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 <b>data_len</b> 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 (file)
index 0000000..9ae628c
--- /dev/null
@@ -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 */
+
index ea7ccd0b3c28ba3f1cd7dcad0fd4a0ed8bc29738..18e2bde164a18b911df5bddc632536e8ec8d7271 100644 (file)
@@ -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);