Skip to content

Commit 12dbafb

Browse files
committed
fix: memory leak during conference load
This was found in our continous fuzzing setup.
1 parent 6a6bc02 commit 12dbafb

1 file changed

Lines changed: 133 additions & 61 deletions

File tree

toxcore/group.c

Lines changed: 133 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3392,105 +3392,156 @@ uint8_t *conferences_save(const Group_Chats *g_c, uint8_t *data)
33923392
return data;
33933393
}
33943394

3395+
/**
3396+
* @brief load_group Load a Group section from a save file
3397+
* @param g Group to load
3398+
* @param g_c Reference to all groupchats, need for utility functions
3399+
* @param data Start of the data to deserialze
3400+
* @param length Length of data
3401+
* @return 0 on error, number of consumed bytes otherwise
3402+
*/
33953403
non_null()
3396-
static State_Load_Status load_conferences(Group_Chats *g_c, const uint8_t *data, uint32_t length)
3404+
static uint32_t load_group(Group_c *g, const Group_Chats *g_c, const uint8_t *data, uint32_t length)
33973405
{
33983406
const uint8_t *init_data = data;
33993407

3400-
while (length >= (uint32_t)(data - init_data) + SAVED_CONF_SIZE_CONSTANT) {
3401-
const int groupnumber = create_group_chat(g_c);
3408+
// Initialize to default values so we can unconditionally free in case of an error
3409+
setup_conference(g);
34023410

3403-
if (groupnumber == -1) {
3404-
return STATE_LOAD_STATUS_ERROR;
3411+
g->type = *data;
3412+
++data;
3413+
3414+
memcpy(g->id, data, GROUP_ID_LENGTH);
3415+
data += GROUP_ID_LENGTH;
3416+
3417+
lendian_bytes_to_host32(&g->message_number, data);
3418+
data += sizeof(uint32_t);
3419+
3420+
lendian_bytes_to_host16(&g->lossy_message_number, data);
3421+
data += sizeof(uint16_t);
3422+
3423+
lendian_bytes_to_host16(&g->peer_number, data);
3424+
data += sizeof(uint16_t);
3425+
3426+
lendian_bytes_to_host32(&g->numfrozen, data);
3427+
data += sizeof(uint32_t);
3428+
3429+
if (g->numfrozen > 0) {
3430+
g->frozen = (Group_Peer *)calloc(g->numfrozen, sizeof(Group_Peer));
3431+
3432+
if (g->frozen == nullptr) {
3433+
// Memory allocation failure
3434+
return 0;
34053435
}
3436+
}
34063437

3407-
Group_c *g = &g_c->chats[groupnumber];
3438+
g->title_len = *data;
34083439

3409-
g->type = *data;
3410-
++data;
3440+
if (g->title_len > MAX_NAME_LENGTH) {
3441+
return 0;
3442+
}
34113443

3412-
memcpy(g->id, data, GROUP_ID_LENGTH);
3413-
data += GROUP_ID_LENGTH;
3444+
++data;
34143445

3415-
lendian_bytes_to_host32(&g->message_number, data);
3416-
data += sizeof(uint32_t);
3446+
assert((data - init_data) < UINT32_MAX);
34173447

3418-
lendian_bytes_to_host16(&g->lossy_message_number, data);
3419-
data += sizeof(uint16_t);
3448+
if (length < (uint32_t)(data - init_data) + g->title_len) {
3449+
return 0;
3450+
}
34203451

3421-
lendian_bytes_to_host16(&g->peer_number, data);
3422-
data += sizeof(uint16_t);
3452+
memcpy(g->title, data, g->title_len);
3453+
data += g->title_len;
34233454

3424-
lendian_bytes_to_host32(&g->numfrozen, data);
3425-
data += sizeof(uint32_t);
3455+
for (uint32_t j = 0; j < g->numfrozen; ++j) {
34263456

3427-
if (g->numfrozen > 0) {
3428-
g->frozen = (Group_Peer *)calloc(g->numfrozen, sizeof(Group_Peer));
3457+
assert((data - init_data) < UINT32_MAX);
34293458

3430-
if (g->frozen == nullptr) {
3431-
return STATE_LOAD_STATUS_ERROR;
3432-
}
3459+
if (length < (uint32_t)(data - init_data) + SAVED_PEER_SIZE_CONSTANT) {
3460+
return 0;
34333461
}
34343462

3435-
g->title_len = *data;
3463+
Group_Peer *peer = &g->frozen[j];
3464+
memset(peer, 0, sizeof(Group_Peer));
34363465

3437-
if (g->title_len > MAX_NAME_LENGTH) {
3438-
return STATE_LOAD_STATUS_ERROR;
3466+
id_copy(peer->real_pk, data);
3467+
data += CRYPTO_PUBLIC_KEY_SIZE;
3468+
id_copy(peer->temp_pk, data);
3469+
data += CRYPTO_PUBLIC_KEY_SIZE;
3470+
3471+
lendian_bytes_to_host16(&peer->peer_number, data);
3472+
data += sizeof(uint16_t);
3473+
3474+
lendian_bytes_to_host64(&peer->last_active, data);
3475+
data += sizeof(uint64_t);
3476+
3477+
peer->nick_len = *data;
3478+
3479+
if (peer->nick_len > MAX_NAME_LENGTH) {
3480+
return 0;
34393481
}
34403482

34413483
++data;
3484+
assert((data - init_data) < UINT32_MAX);
34423485

3443-
if (length < (uint32_t)(data - init_data) + g->title_len) {
3444-
return STATE_LOAD_STATUS_ERROR;
3486+
if (length < (uint32_t)(data - init_data) + peer->nick_len) {
3487+
return 0;
34453488
}
34463489

3447-
memcpy(g->title, data, g->title_len);
3448-
data += g->title_len;
3490+
memcpy(peer->nick, data, peer->nick_len);
3491+
data += peer->nick_len;
34493492

3450-
for (uint32_t j = 0; j < g->numfrozen; ++j) {
3451-
if (length < (uint32_t)(data - init_data) + SAVED_PEER_SIZE_CONSTANT) {
3452-
return STATE_LOAD_STATUS_ERROR;
3453-
}
3454-
3455-
Group_Peer *peer = &g->frozen[j];
3456-
memset(peer, 0, sizeof(Group_Peer));
3493+
// NOTE: this relies on friends being loaded before conferences.
3494+
peer->is_friend = (getfriend_id(g_c->m, peer->real_pk) != -1);
3495+
}
34573496

3458-
id_copy(peer->real_pk, data);
3459-
data += CRYPTO_PUBLIC_KEY_SIZE;
3460-
id_copy(peer->temp_pk, data);
3461-
data += CRYPTO_PUBLIC_KEY_SIZE;
3497+
if (g->numfrozen > g->maxfrozen) {
3498+
g->maxfrozen = g->numfrozen;
3499+
}
34623500

3463-
lendian_bytes_to_host16(&peer->peer_number, data);
3464-
data += sizeof(uint16_t);
3501+
g->status = GROUPCHAT_STATUS_CONNECTED;
34653502

3466-
lendian_bytes_to_host64(&peer->last_active, data);
3467-
data += sizeof(uint64_t);
3503+
id_copy(g->real_pk, nc_get_self_public_key(g_c->m->net_crypto));
34683504

3469-
peer->nick_len = *data;
3505+
assert((data - init_data) < UINT32_MAX);
34703506

3471-
if (peer->nick_len > MAX_NAME_LENGTH) {
3472-
return STATE_LOAD_STATUS_ERROR;
3473-
}
3507+
return (uint32_t)(data - init_data);
3508+
}
34743509

3475-
++data;
3510+
non_null()
3511+
static State_Load_Status load_conferences_helper(Group_Chats *g_c, const uint8_t *data, uint32_t length)
3512+
{
3513+
const uint8_t *init_data = data;
34763514

3477-
if (length < (uint32_t)(data - init_data) + peer->nick_len) {
3478-
return STATE_LOAD_STATUS_ERROR;
3479-
}
3515+
while (length >= (uint32_t)(data - init_data) + SAVED_CONF_SIZE_CONSTANT) {
3516+
const int groupnumber = create_group_chat(g_c);
34803517

3481-
memcpy(peer->nick, data, peer->nick_len);
3482-
data += peer->nick_len;
3518+
// Helpful for testing
3519+
assert(groupnumber != -1);
34833520

3484-
// NOTE: this relies on friends being loaded before conferences.
3485-
peer->is_friend = (getfriend_id(g_c->m, peer->real_pk) != -1);
3521+
if (groupnumber == -1) {
3522+
// If this fails there's a serious problem, don't bother with cleanup
3523+
return STATE_LOAD_STATUS_ERROR;
34863524
}
34873525

3488-
if (g->numfrozen > g->maxfrozen) {
3489-
g->maxfrozen = g->numfrozen;
3526+
Group_c *g = &g_c->chats[groupnumber];
3527+
3528+
const uint32_t consumed = load_group(g, g_c, data, length - (uint32_t)(data - init_data));
3529+
3530+
if (consumed == 0) {
3531+
// remove partially loaded stuff, wipe_group_chat must be able to wipe a partially loaded group
3532+
const bool ret = wipe_group_chat(g_c, groupnumber);
3533+
3534+
// HACK: suppress unused variable warning
3535+
if (!ret) {
3536+
// wipe_group_chat(...) must be able to wipe partially allocated groups
3537+
assert(ret == true);
3538+
}
3539+
3540+
return STATE_LOAD_STATUS_ERROR;
34903541
}
34913542

3492-
g->status = GROUPCHAT_STATUS_CONNECTED;
3493-
memcpy(g->real_pk, nc_get_self_public_key(g_c->m->net_crypto), CRYPTO_PUBLIC_KEY_SIZE);
3543+
data += consumed;
3544+
34943545
const int peer_index = addpeer(g_c, groupnumber, g->real_pk, dht_get_self_public_key(g_c->m->dht), g->peer_number,
34953546
nullptr, true, false);
34963547

@@ -3504,6 +3555,27 @@ static State_Load_Status load_conferences(Group_Chats *g_c, const uint8_t *data,
35043555
return STATE_LOAD_STATUS_CONTINUE;
35053556
}
35063557

3558+
non_null()
3559+
static State_Load_Status load_conferences(Group_Chats *g_c, const uint8_t *data, uint32_t length)
3560+
{
3561+
const State_Load_Status res = load_conferences_helper(g_c, data, length);
3562+
3563+
if (res == STATE_LOAD_STATUS_CONTINUE) {
3564+
return res;
3565+
}
3566+
3567+
// Loading failed, cleanup all Group_c
3568+
3569+
// save locally, because wipe_group_chat(...) modifies it
3570+
const uint16_t num_groups = g_c->num_chats;
3571+
3572+
for (uint16_t i = 0; i < num_groups; ++i) {
3573+
wipe_group_chat(g_c, i);
3574+
}
3575+
3576+
return res;
3577+
}
3578+
35073579
bool conferences_load_state_section(Group_Chats *g_c, const uint8_t *data, uint32_t length, uint16_t type,
35083580
State_Load_Status *status)
35093581
{

0 commit comments

Comments
 (0)