From b52eb48c15a06fe198c7c5e0d765cfef8ff4fbdf Mon Sep 17 00:00:00 2001 From: Alexandre Erwin Ittner Date: Sun, 21 Sep 2014 11:37:49 -0300 Subject: [PATCH] Remove chattiness from avatar data transfers The chatty approach for the avatar data transfer was intended as a security feature to add explicit delays to the transfer and prevent amplification attacks among authenticated friends. This was deemed unnecessary in the code review and, therefore, replaced by a simpler approach that sends all data in a single burst. --- docs/Avatars.md | 81 +++++++-------------------- toxcore/Messenger.c | 133 +++++++++++++++++++++----------------------- toxcore/Messenger.h | 5 +- 3 files changed, 85 insertions(+), 134 deletions(-) diff --git a/docs/Avatars.md b/docs/Avatars.md index f0d7a1eb..97b65c10 100644 --- a/docs/Avatars.md +++ b/docs/Avatars.md @@ -474,19 +474,15 @@ types. - Packet `PACKET_ID_AVATAR_DATA_CONTROL` have the format: PACKET_ID_AVATAR_DATA_CONTROL (54) - Packet data size: 5 bytes - [1: uint8_t op][1: uint32_t bytes_received] + Packet data size: 1 byte + [1: uint8_t op] where 'op' is a code signaling both an operation request or a status return, which semantics are explained bellow. The following values are defined: 0 = AVATARDATACONTROL_REQ - 1 = AVATARDATACONTROL_MORE - 2 = AVATARDATACONTROL_ERROR - - and 'bytes_received' is the number of bytes already received by the - client when the operation is `MORE` or zero otherwise. + 1 = AVATARDATACONTROL_ERROR - Packet `PACKET_ID_AVATAR_DATA_START` have the following format: @@ -503,7 +499,9 @@ types. - Packet `PACKET_ID_AVATAR_DATA_PUSH` have no format structure, just up - to `AVATAR_DATA_MAX_CHUNK_SIZE` (56) bytes of raw avatar image data. + to `AVATAR_DATA_MAX_CHUNK_SIZE` bytes of raw avatar image data; this + value is defined according to the maximum amount of data a Tox crypted + packet can hold. @@ -513,8 +511,7 @@ from a client "B": - "A" must initialize its control structures and mark its data transfer as not yet started. Then it requests avatar data from "B" by sending a packet `PACKET_ID_AVATAR_DATA_CONTROL` with 'op' set to - `AVATARDATACONTROL_REQ`. The field 'bytes_received' must be present, but - should be set to zero and is ignored in this step. + `AVATARDATACONTROL_REQ`. - If "B" accepts this transfer, it answers by sending an `PACKET_ID_AVATAR_DATA_START` with the fields 'format', 'hash' and @@ -527,42 +524,21 @@ from a client "B": `AVATARDATACONTROL_ERROR` or simply ignore this request. "A" must cope with this. + If "B" have an avatar, it sends a variable number of + `PACKET_ID_AVATAR_DATA_PUSH` packets with the avatar data in a single + shot. + - Upon receiving a `PACKET_ID_AVATAR_DATA_START`, "A" checks if it has sent a data request to "B". If not, just ignores the packet. - If "A" really sent a request to "B", checks if the request was already - started. If true, it is an error and it just ignores the request. + If "A" really requested avatar data and the format is `AVATARFORMAT_NONE`, + it triggers the avatar data callback, and clears all the temporary data, + finishing the process. For other formats, "A" just waits for packets + of type `PACKET_ID_AVATAR_DATA_PUSH`. - Otherwise, "A" decodes the message data and checks if the avatar data - length stated in the field 'data_length' is acceptable (ie. less or - equal than `TOX_MAX_AVATAR_DATA_LENGTH`). If not, it replies with an - `PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' set to - `AVATARDATACONTROL_ERROR` (or just ignores this, "A" holds no state). - - If the size is acceptable, "A" marks the request as stated, stores the - format, hash, and data length in the local state for user "B", sets a - counter for the number of bytes received from the peer and replies with - a `PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' set to - `AVATARDATACONTROL_MORE` and 'bytes_received' set to zero (as no data - was received yet). - - - Upon receiving a `PACKET_ID_AVATAR_DATA_CONTROL` with op - `AVATARDATACONTROL_MORE`, "B" sends an `PACKET_ID_AVATAR_DATA_PUSH` - with up to `AVATAR_DATA_MAX_CHUNK_SIZE` bytes of data from the avatar, - starting from the offset stated in the field 'bytes_received'. - - If the requested offset is invalid, "B" replies with a - `PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' set to - `AVATARDATACONTROL_ERROR`. - - "B" must have full control of the amount of data it sends to "A" and - may, at any time, abort the transfer by sending a - `PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' set to - `AVATARDATACONTROL_ERROR`. This may happens, for example, if some limit - was hit or a network data usage throttle enabled. A rationale for this - procedures is available in section "Security considerations". - - - Upon receiving a `PACKET_ID_AVATAR_DATA_PUSH`, "A" checks if the total + - Upon receiving a `PACKET_ID_AVATAR_DATA_PUSH`, "A" checks if it really + sent an avatar data request and if the `PACKET_ID_AVATAR_DATA_START` was + already received. If this conditions are valid, it checks if the total length of the data already stored in the receiving buffer plus the data present in the push packet is still less or equal than `TOX_MAX_AVATAR_DATA_LENGTH`. If invalid, it replies with a @@ -582,22 +558,13 @@ from a client "B": triggers the avatar data callback, and clears all the temporary data, finishing the process. - If not all data was received, "A" requests more data by sending a - `PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' set to - `AVATARDATACONTROL_MORE` and 'bytes_received' set to the new offset. + If not all data was received, "A" simply waits for more data. Client "A" is always responsible for controlling the transfer and validating the data received. "B" don't need to keep any state for the protocol, have full control over the data sent and should implement some transfer limit for the data it sends. - This "chatty" protocol mitigates a potential amplification attack, - i.e., a malicious friend sending a very small data packet that causes - another user to send a larger amount of data. The hash validation - ensures that the avatar data is correct even if "B" changed its avatar - data in the middle of the transfer. A rationale for this procedures is - available in section "Security considerations". - - Any peer receiving a `PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' set to `AVATARDATACONTROL_ERROR` clears any existing control state and finishes sending or receiving data. @@ -624,12 +591,6 @@ The present proposal mitigates this situation by: - Providing an alternate, smaller, message to cooperative users refresh avatar information when nothing has changed (`PACKET_ID_AVATAR_INFO`); - - Making the avatar data transfer chatty: The user requesting avatar data - can not force a peer to send large amounts of data in a single shot and - must request new chunks as needed. The sender will never send more that - 1 kB of data in a single push and have ultimate control over the amount - of data sent in a chunk; - - Having per-friend data transfer limit. As the current protocol still allows an user to request an infinite data stream by asking the the same offset of the avatar again and again, the implementation limits @@ -640,10 +601,6 @@ The present proposal mitigates this situation by: - Making the requester responsible for storing partial data and state information; - - (currently not implemented) Treating avatar data transfers as a low - priority operation, handled only if no other packets are being - processed. - Another problem present in the avatars is the possibility of a friend send a maliciously crafted image intended to exploit vulnerabilities in image decoders. Without an intermediate server to recompress and validate and diff --git a/toxcore/Messenger.c b/toxcore/Messenger.c index 4f8eee6b..fb6aafa5 100644 --- a/toxcore/Messenger.c +++ b/toxcore/Messenger.c @@ -42,7 +42,7 @@ static void set_friend_status(Messenger *m, int32_t friendnumber, uint8_t status); static int write_cryptpacket_id(const Messenger *m, int32_t friendnumber, uint8_t packet_id, const uint8_t *data, uint32_t length, uint8_t congestion_control); -static int send_avatar_data_control(const Messenger *m, const uint32_t friendnumber, uint8_t op, uint32_t bytes_received); +static int send_avatar_data_control(const Messenger *m, const uint32_t friendnumber, uint8_t op); // friend_not_valid determines if the friendnumber passed is valid in the Messenger object static uint8_t friend_not_valid(const Messenger *m, int32_t friendnumber) @@ -672,6 +672,8 @@ int m_request_avatar_data(const Messenger *m, const int32_t friendnumber) avrd = malloc(sizeof(AVATARRECEIVEDATA)); if (avrd == NULL) return -1; + memset(avrd, 0, sizeof(AVATARRECEIVEDATA)); + avrd->started = 0; m->friendlist[friendnumber].avatar_recv_data = avrd; } @@ -680,13 +682,12 @@ int m_request_avatar_data(const Messenger *m, const int32_t friendnumber) "friendnumber == %u", friendnumber); } - memset(avrd, 0, sizeof(AVATARRECEIVEDATA)); avrd->started = 0; avrd->bytes_received = 0; avrd->total_length = 0; avrd->format = AVATARFORMAT_NONE; - return send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_REQ, 0); + return send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_REQ); } @@ -2118,18 +2119,12 @@ static int handle_status(void *object, int i, uint8_t status) * values or request data. */ static int send_avatar_data_control(const Messenger *m, const uint32_t friendnumber, - uint8_t op, uint32_t bytes_received) + uint8_t op) { - uint8_t data[1 + sizeof(uint32_t)]; - data[0] = op; - uint32_t tmp_recv = htonl(bytes_received); - memcpy(data+1, &tmp_recv, sizeof(uint32_t)); - int ret = write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_DATA_CONTROL, - data, sizeof(data), 0); - - LOGGER_DEBUG("friendnumber = %u, op = %u, bytes_received = %u, ret = %d", - friendnumber, op, bytes_received, ret); + &op, sizeof(op), 0); + LOGGER_DEBUG("friendnumber = %u, op = %u, ret = %d", + friendnumber, op, ret); return (ret >= 0) ? 0 : -1; } @@ -2137,11 +2132,11 @@ static int send_avatar_data_control(const Messenger *m, const uint32_t friendnum static int handle_avatar_data_control(Messenger *m, uint32_t friendnumber, uint8_t *data, uint32_t data_length) { - if (data_length != 1 + sizeof(uint32_t)) { + if (data_length != 1) { LOGGER_DEBUG("Error: PACKET_ID_AVATAR_DATA_CONTROL with bad " "data_length = %u, friendnumber = %u", data_length, friendnumber); - send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR, 0); + send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR); return -1; /* Error */ } @@ -2149,6 +2144,25 @@ static int handle_avatar_data_control(Messenger *m, uint32_t friendnumber, switch (data[0]) { case AVATARDATACONTROL_REQ: { + + /* Check data transfer limits for this friend */ + AVATARSENDDATA * const avsd = &(m->friendlist[friendnumber].avatar_send_data); + if (avsd->bytes_sent >= AVATAR_DATA_TRANSFER_LIMIT) { + /* User reached data limit. Check timeout */ + uint64_t now = unix_time(); + if (avsd->last_reset > 0 + && (avsd->last_reset + AVATAR_DATA_TRANSFER_TIMEOUT < now)) { + avsd->bytes_sent = 0; + avsd->last_reset = now; + } else { + /* Friend still rate-limitted. Send an error and stops. */ + LOGGER_DEBUG("Avatar data transfer limit reached. " + "friendnumber = %u", friendnumber); + send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR); + return 0; + } + } + /* Start the transmission with a DATA_START message. Format: * uint8_t format * uint8_t hash[AVATAR_HASH_LENGTH] @@ -2164,64 +2178,46 @@ static int handle_avatar_data_control(Messenger *m, uint32_t friendnumber, memcpy(start_data + 1, m->avatar_hash, AVATAR_HASH_LENGTH); memcpy(start_data + 1 + AVATAR_HASH_LENGTH, &avatar_len, sizeof(uint32_t)); + avsd->bytes_sent += sizeof(start_data); /* For rate limit */ + int ret = write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_DATA_START, start_data, sizeof(start_data), 0); - if (ret >= 0) - return 0; /* Ok */ - return -1; - } - - case AVATARDATACONTROL_MORE: { - /* Format: [uint8_t op][uint32_t position] - * Friend received a chunk of data and asked for more. - */ - uint32_t bytes_received; - memcpy(&bytes_received, data+1, sizeof(uint32_t)); - bytes_received = ntohl(bytes_received); - - if (m->avatar_format == AVATARFORMAT_NONE - || m->avatar_data == NULL - || m->avatar_data_length == 0 - || m->avatar_data_length < bytes_received) { - /* Avatar changed, or position became invalid, or we - * received a incorrect or malicious request. Abort. */ - LOGGER_DEBUG("Aborting. AVATARDATACONTROL_MORE with bad " - "offset. bytes_received = %u, friendnumber = %u", - bytes_received, friendnumber); - send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR, 0); + if (ret < 0) { + /* Something went wrong, try to signal the error so the friend + * can clear up the state. */ + send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR); return 0; } - /* Check data transfer limits for this friend */ - AVATARSENDDATA * const avsd = &(m->friendlist[friendnumber].avatar_send_data); - if (avsd->bytes_sent >= AVATAR_DATA_TRANSFER_LIMIT) { - /* User reached data limit. Check timeout */ - uint64_t now = unix_time(); - if (avsd->last_reset > 0 - && (avsd->last_reset + AVATAR_DATA_TRANSFER_TIMEOUT < now)) { - avsd->bytes_sent = 0; - avsd->last_reset = now; - } else { - /* Friend still rate-limitted. Send an error ad stops. */ - LOGGER_DEBUG("Avatar data transfer limit reached. " - "friendnumber = %u", friendnumber); - send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR, 0); - return 0; + /* User have no avatar data, nothing more to do. */ + if (m->avatar_format == AVATARFORMAT_NONE) + return 0; + + /* Send the actual avatar data. */ + uint32_t offset = 0; + while (offset < m->avatar_data_length) { + uint32_t chunk_len = m->avatar_data_length - offset; + if (chunk_len > AVATAR_DATA_MAX_CHUNK_SIZE) + chunk_len = AVATAR_DATA_MAX_CHUNK_SIZE; + + uint8_t chunk[AVATAR_DATA_MAX_CHUNK_SIZE]; + memcpy(chunk, m->avatar_data + offset, chunk_len); + offset += chunk_len; + avsd->bytes_sent += chunk_len; /* For rate limit */ + + int ret = write_cryptpacket_id(m, friendnumber, + PACKET_ID_AVATAR_DATA_PUSH, + chunk, chunk_len, 0); + if (ret < 0) { + LOGGER_DEBUG("write_cryptpacket_id failed. ret = %d, " + "friendnumber = %u, offset = %u", + ret, friendnumber, offset); + send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR); + return -1; } } - /* Send another chunk of data */ - uint32_t chunk_len = m->avatar_data_length - bytes_received; - if (chunk_len > AVATAR_DATA_MAX_CHUNK_SIZE) - chunk_len = AVATAR_DATA_MAX_CHUNK_SIZE; - - uint8_t chunk[AVATAR_DATA_MAX_CHUNK_SIZE]; - memcpy(chunk, m->avatar_data + bytes_received, chunk_len); - avsd->bytes_sent += chunk_len; /* For rate limit */ - - write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_DATA_PUSH, - chunk, chunk_len, 0); return 0; } @@ -2305,8 +2301,8 @@ static int handle_avatar_data_start(Messenger *m, uint32_t friendnumber, return 0; } - LOGGER_DEBUG("requesting more data, friendnumber = %u", friendnumber); - return send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_MORE, 0); + /* Waits for more data to be received */ + return 0; } @@ -2371,9 +2367,8 @@ static int handle_avatar_data_push(Messenger *m, uint32_t friendnumber, return 0; } - /* Request more data */ - return send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_MORE, - avrd->bytes_received); + /* Waits for more data to be received */ + return 0; } diff --git a/toxcore/Messenger.h b/toxcore/Messenger.h index 499964a7..ee8aaa5f 100644 --- a/toxcore/Messenger.h +++ b/toxcore/Messenger.h @@ -117,8 +117,8 @@ enum { /* If no packets are received from friend in this time interval, kill the connection. */ #define FRIEND_CONNECTION_TIMEOUT (FRIEND_PING_INTERVAL * 3) -/* Must be <= MAX_CRYPTO_DATA_SIZE */ -#define AVATAR_DATA_MAX_CHUNK_SIZE 1024 +/* Must be < MAX_CRYPTO_DATA_SIZE */ +#define AVATAR_DATA_MAX_CHUNK_SIZE (MAX_CRYPTO_DATA_SIZE-1) /* Per-friend data limit for avatar data requests */ #define AVATAR_DATA_TRANSFER_LIMIT (10*MAX_AVATAR_DATA_LENGTH) @@ -151,7 +151,6 @@ AVATARFORMAT; */ typedef enum { AVATARDATACONTROL_REQ, - AVATARDATACONTROL_MORE, AVATARDATACONTROL_ERROR } AVATARDATACONTROL;