mirror of
https://github.com/m1ngsama/TNT.git
synced 2026-02-08 08:54:05 +00:00
- Enhance room_broadcast() reference counting: * Check client state (connected, show_help, command_output) before rendering * Perform state check while holding client ref_lock * Prevents rendering to disconnected/invalid clients * Ensures safe cleanup when ref count reaches zero - Fix tui_render_screen() message array TOCTOU: * Acquire all data (online count, message count, messages) in single lock * Create snapshot of messages to display * Calculate message range while holding lock * Render from snapshot without holding lock * Prevents inconsistencies from concurrent message additions * Eliminates race between two separate lock acquisitions - Fix handle_key() scroll position TOCTOU: * Get message count atomically when calculating scroll bounds * Calculate max_scroll properly accounting for message height * Apply consistent bounds checking for 'j' (down) and 'G' (bottom) * Prevents out-of-bounds access from concurrent message changes These changes address: - Race condition in broadcast rendering to disconnecting clients - TOCTOU between message count read and message access - Scroll position bounds check race conditions Prevents: - Use-after-free in client cleanup - Array out-of-bounds access - Inconsistent UI rendering - Crashes from concurrent message list modifications Improves thread safety without introducing deadlocks by: - Using snapshot approach to avoid long lock holds - Acquiring data in consistent lock order - Minimizing critical sections
181 lines
4.9 KiB
C
181 lines
4.9 KiB
C
#include "chat_room.h"
|
|
#include "ssh_server.h"
|
|
#include "tui.h"
|
|
#include <unistd.h>
|
|
|
|
/* Global chat room instance */
|
|
chat_room_t *g_room = NULL;
|
|
|
|
/* Initialize chat room */
|
|
chat_room_t* room_create(void) {
|
|
chat_room_t *room = calloc(1, sizeof(chat_room_t));
|
|
if (!room) return NULL;
|
|
|
|
pthread_rwlock_init(&room->lock, NULL);
|
|
|
|
room->client_capacity = MAX_CLIENTS;
|
|
room->clients = calloc(room->client_capacity, sizeof(client_t*));
|
|
if (!room->clients) {
|
|
free(room);
|
|
return NULL;
|
|
}
|
|
|
|
/* Load messages from file */
|
|
room->message_count = message_load(&room->messages, MAX_MESSAGES);
|
|
|
|
return room;
|
|
}
|
|
|
|
/* Destroy chat room */
|
|
void room_destroy(chat_room_t *room) {
|
|
if (!room) return;
|
|
|
|
pthread_rwlock_wrlock(&room->lock);
|
|
|
|
free(room->clients);
|
|
free(room->messages);
|
|
|
|
pthread_rwlock_unlock(&room->lock);
|
|
pthread_rwlock_destroy(&room->lock);
|
|
|
|
free(room);
|
|
}
|
|
|
|
/* Add client to room */
|
|
int room_add_client(chat_room_t *room, client_t *client) {
|
|
pthread_rwlock_wrlock(&room->lock);
|
|
|
|
if (room->client_count >= room->client_capacity) {
|
|
pthread_rwlock_unlock(&room->lock);
|
|
return -1;
|
|
}
|
|
|
|
room->clients[room->client_count++] = client;
|
|
|
|
pthread_rwlock_unlock(&room->lock);
|
|
return 0;
|
|
}
|
|
|
|
/* Remove client from room */
|
|
void room_remove_client(chat_room_t *room, client_t *client) {
|
|
pthread_rwlock_wrlock(&room->lock);
|
|
|
|
for (int i = 0; i < room->client_count; i++) {
|
|
if (room->clients[i] == client) {
|
|
/* Shift remaining clients */
|
|
for (int j = i; j < room->client_count - 1; j++) {
|
|
room->clients[j] = room->clients[j + 1];
|
|
}
|
|
room->client_count--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
pthread_rwlock_unlock(&room->lock);
|
|
}
|
|
|
|
/* Broadcast message to all clients */
|
|
void room_broadcast(chat_room_t *room, const message_t *msg) {
|
|
pthread_rwlock_wrlock(&room->lock);
|
|
|
|
/* Add to history */
|
|
room_add_message(room, msg);
|
|
|
|
/* Get copy of client list and increment ref counts */
|
|
client_t **clients_copy = calloc(room->client_count, sizeof(client_t*));
|
|
int count = room->client_count;
|
|
memcpy(clients_copy, room->clients, count * sizeof(client_t*));
|
|
|
|
/* Increment reference count for each client */
|
|
for (int i = 0; i < count; i++) {
|
|
pthread_mutex_lock(&clients_copy[i]->ref_lock);
|
|
clients_copy[i]->ref_count++;
|
|
pthread_mutex_unlock(&clients_copy[i]->ref_lock);
|
|
}
|
|
|
|
pthread_rwlock_unlock(&room->lock);
|
|
|
|
/* Render to each client (outside of lock) */
|
|
for (int i = 0; i < count; i++) {
|
|
client_t *client = clients_copy[i];
|
|
|
|
/* Check client state before rendering (while holding ref) */
|
|
bool should_render = false;
|
|
pthread_mutex_lock(&client->ref_lock);
|
|
if (client->ref_count > 0) {
|
|
should_render = client->connected &&
|
|
!client->show_help &&
|
|
client->command_output[0] == '\0';
|
|
}
|
|
pthread_mutex_unlock(&client->ref_lock);
|
|
|
|
if (should_render) {
|
|
tui_render_screen(client);
|
|
}
|
|
|
|
/* Decrement reference count and free if needed */
|
|
pthread_mutex_lock(&client->ref_lock);
|
|
client->ref_count--;
|
|
int ref = client->ref_count;
|
|
pthread_mutex_unlock(&client->ref_lock);
|
|
|
|
if (ref == 0) {
|
|
/* Safe to free now */
|
|
if (client->channel) {
|
|
ssh_channel_close(client->channel);
|
|
ssh_channel_free(client->channel);
|
|
}
|
|
if (client->session) {
|
|
ssh_disconnect(client->session);
|
|
ssh_free(client->session);
|
|
}
|
|
pthread_mutex_destroy(&client->ref_lock);
|
|
free(client);
|
|
}
|
|
}
|
|
|
|
free(clients_copy);
|
|
}
|
|
|
|
/* Add message to room history */
|
|
void room_add_message(chat_room_t *room, const message_t *msg) {
|
|
/* Caller should hold write lock */
|
|
|
|
if (room->message_count >= MAX_MESSAGES) {
|
|
/* Shift messages to make room */
|
|
memmove(&room->messages[0], &room->messages[1],
|
|
(MAX_MESSAGES - 1) * sizeof(message_t));
|
|
room->message_count = MAX_MESSAGES - 1;
|
|
}
|
|
|
|
room->messages[room->message_count++] = *msg;
|
|
}
|
|
|
|
/* Get message by index */
|
|
const message_t* room_get_message(chat_room_t *room, int index) {
|
|
pthread_rwlock_rdlock(&room->lock);
|
|
|
|
const message_t *msg = NULL;
|
|
if (index >= 0 && index < room->message_count) {
|
|
msg = &room->messages[index];
|
|
}
|
|
|
|
pthread_rwlock_unlock(&room->lock);
|
|
return msg;
|
|
}
|
|
|
|
/* Get total message count */
|
|
int room_get_message_count(chat_room_t *room) {
|
|
pthread_rwlock_rdlock(&room->lock);
|
|
int count = room->message_count;
|
|
pthread_rwlock_unlock(&room->lock);
|
|
return count;
|
|
}
|
|
|
|
/* Get online client count */
|
|
int room_get_client_count(chat_room_t *room) {
|
|
pthread_rwlock_rdlock(&room->lock);
|
|
int count = room->client_count;
|
|
pthread_rwlock_unlock(&room->lock);
|
|
return count;
|
|
}
|