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.
This commit is contained in:
Alexandre Erwin Ittner 2014-09-21 11:37:49 -03:00
parent 70b4018069
commit b52eb48c15
3 changed files with 85 additions and 134 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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;