From b1c1e5a89429a8fdb444bf9eddafd5a4b8e1f13f Mon Sep 17 00:00:00 2001 From: m1ngsama Date: Sun, 19 Apr 2026 18:18:21 +0800 Subject: [PATCH] fix: deadlock in whisper, use-after-free in callbacks, log rotation, tail parsing - Whisper: copy target client ref out of room lock before calling client_send, preventing lock-ordering inversion deadlock - Channel callbacks: call ssh_remove_channel_callbacks before releasing refs to prevent use-after-free if a callback fires during cleanup - Log rotation: rotate messages.log to messages.log.1 when it exceeds 10 MiB, preventing unbounded growth on public servers - tail -nN: accept both "tail -n5" and "tail -n 5" forms, matching standard Unix tail behavior Closes #36 --- include/common.h | 1 + src/message.c | 9 +++++++++ src/ssh_server.c | 27 ++++++++++++++++++++------- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/include/common.h b/include/common.h index 5d10ca0..627c306 100644 --- a/include/common.h +++ b/include/common.h @@ -22,6 +22,7 @@ #define MAX_EXEC_COMMAND_LEN 1024 #define MAX_CLIENTS 64 #define LOG_FILE "messages.log" +#define MAX_LOG_SIZE (10 * 1024 * 1024) /* 10 MiB */ #define HOST_KEY_FILE "host_key" #define TNT_DEFAULT_STATE_DIR "." diff --git a/src/message.c b/src/message.c index fff9f1f..8e8667c 100644 --- a/src/message.c +++ b/src/message.c @@ -227,7 +227,16 @@ int message_save(const message_t *msg) { rc = -1; } + /* Rotate if the log exceeds MAX_LOG_SIZE */ + long file_size = ftell(fp); fclose(fp); + + if (file_size > MAX_LOG_SIZE) { + char backup_path[PATH_MAX + 4]; + snprintf(backup_path, sizeof(backup_path), "%s.1", log_path); + rename(log_path, backup_path); + } + pthread_mutex_unlock(&g_message_file_lock); return rc; } diff --git a/src/ssh_server.c b/src/ssh_server.c index 9275b91..6ed9a14 100644 --- a/src/ssh_server.c +++ b/src/ssh_server.c @@ -895,7 +895,7 @@ static int parse_tail_count(const char *args, int *count) { return 0; } - if (strncmp(args, "-n", 2) == 0 && isspace((unsigned char)args[2])) { + if (strncmp(args, "-n", 2) == 0) { args += 2; while (*args && isspace((unsigned char)*args)) { args++; @@ -1171,21 +1171,28 @@ static void execute_command(client_t *client) { " w \n"); } else { bool found = false; + client_t *target = NULL; pthread_rwlock_rdlock(&g_room->lock); for (int i = 0; i < g_room->client_count; i++) { if (strcmp(g_room->clients[i]->username, target_name) == 0) { - char whisper[MAX_MESSAGE_LEN]; - snprintf(whisper, sizeof(whisper), - "\r\n\033[35m[whisper from %s]: %s\033[0m\r\n", - client->username, rest); - client_send(g_room->clients[i], whisper, strlen(whisper)); - g_room->clients[i]->redraw_pending = true; + target = g_room->clients[i]; + client_addref(target); found = true; break; } } pthread_rwlock_unlock(&g_room->lock); + if (target) { + char whisper[MAX_MESSAGE_LEN]; + snprintf(whisper, sizeof(whisper), + "\r\n\033[35m[whisper from %s]: %s\033[0m\r\n", + client->username, rest); + client_send(target, whisper, strlen(whisper)); + target->redraw_pending = true; + client_release(target); + } + if (found) { buffer_appendf(output, sizeof(output), &pos, "Whisper sent to %s\n", target_name); @@ -1649,6 +1656,12 @@ cleanup: release_ip_connection(client->client_ip); + /* Remove channel callbacks before releasing refs to prevent use-after-free + * if a callback fires between the two releases. */ + if (client->channel && client->channel_cb) { + ssh_remove_channel_callbacks(client->channel, client->channel_cb); + } + /* Release the callback reference (paired with addref before install_client_channel_callbacks) */ client_release(client);