]> git.ipfire.org Git - thirdparty/tor.git/commitdiff
prop340: Add relay msg decoding capability
authorDavid Goulet <dgoulet@torproject.org>
Thu, 5 Oct 2023 15:18:31 +0000 (11:18 -0400)
committerDavid Goulet <dgoulet@torproject.org>
Wed, 31 Jan 2024 15:16:02 +0000 (10:16 -0500)
Signed-off-by: David Goulet <dgoulet@torproject.org>
src/core/or/or.h
src/core/or/relay_msg.c
src/core/or/relay_msg.h

index 39b9a254c3e4330277e701b483f128b0d8966be5..87176d15b9a07a5792e51560b95a0602889f06ba 100644 (file)
@@ -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 */
index 864a6066f2bdc40c9e3c5acafd78e820eda8ef48..e4152cb159d81b5da8605dd0ea3c743e3cdaef53 100644 (file)
 
 #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;
+  }
+}
index b265b8d459c62f2f0c950664f298c572d726b1ec..ed0e7f188e191feeeec7da9ccfb3b4caca8ac0c4 100644 (file)
@@ -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 */