fix(ui,db): harden NULL handling, fix CWE-134, optimize iterations

security(CWE-134): fix format string injections + add CI check
fix(ui): subwindow lifecycle, newwin/newpad guards, fallback timestamps
fix(db): sqlite cleanup on failures, sqlite3_close_v2
fix(xmpp): queued_messages loop, barejid leak
perf(core): g_hash_table_iter_init instead of g_hash_table_get_keys
refactor(ui): CLAMP macro in _check_subwin_width
test: XEP-0012 and XEP-0045 functional tests

Author: jabber.developer2
Closes #58, #85
This commit is contained in:
2026-02-06 19:27:40 +01:00
parent f8826b7c79
commit 467222d0ca
36 changed files with 656 additions and 238 deletions

View File

@@ -140,6 +140,10 @@ void
buffer_remove_entry(ProfBuff buffer, int entry)
{
GSList* node = g_slist_nth(buffer->entries, entry);
if (node == NULL) {
// Index out of range; nothing to remove
return;
}
ProfBuffEntry* e = node->data;
buffer->lines -= e->_lines;
_free_entry(e);
@@ -168,6 +172,9 @@ ProfBuffEntry*
buffer_get_entry(ProfBuff buffer, int entry)
{
GSList* node = g_slist_nth(buffer->entries, entry);
if (node == NULL) {
return NULL;
}
return node->data;
}
@@ -189,6 +196,7 @@ buffer_get_entry_by_id(ProfBuff buffer, const char* const id)
static ProfBuffEntry*
_create_entry(const char* show_char, int pad_indent, GDateTime* time, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message, DeliveryReceipt* receipt, const char* const id, int y_start_pos, int y_end_pos)
{
assert(time != NULL);
ProfBuffEntry* e = malloc(sizeof(struct prof_buff_entry_t));
e->show_char = STRDUP_OR_NULL(show_char);
e->pad_indent = pad_indent;

View File

@@ -592,8 +592,11 @@ chatwin_db_history(ProfChatWin* chatwin, const gchar* start_time, const gchar* e
{
auto_gchar gchar* _end_time = NULL;
if (!end_time && buffer_size(((ProfWin*)chatwin)->layout->buffer) > 0) {
_end_time = g_date_time_format_iso8601(buffer_get_entry(((ProfWin*)chatwin)->layout->buffer, 0)->time);
end_time = _end_time;
ProfBuffEntry* first = buffer_get_entry(((ProfWin*)chatwin)->layout->buffer, 0);
if (first && first->time) {
_end_time = g_date_time_format_iso8601(first->time);
end_time = _end_time;
}
}
GSList* history = NULL;

View File

@@ -152,7 +152,7 @@ cons_bad_cmd_usage(const char* const cmd)
g_string_printf(msg, "Invalid usage, see '/help %s' for details.", &cmd[1]);
cons_show("");
cons_show(msg->str);
cons_show("%s", msg->str);
g_string_free(msg, TRUE);
}
@@ -773,7 +773,7 @@ cons_show_disco_info(const char* jid, GSList* identities, GSList* features)
if (identity->category) {
identity_str = g_string_append(identity_str, identity->category);
}
cons_show(identity_str->str);
cons_show("%s", identity_str->str);
g_string_free(identity_str, TRUE);
identities = g_slist_next(identities);
}
@@ -938,7 +938,7 @@ cons_show_account_list(gchar** accounts)
theme_item_t presence_colour = theme_main_presence_attrs(string_from_resource_presence(presence));
win_println(console, presence_colour, "-", "%s", accounts[i]);
} else {
cons_show(accounts[i]);
cons_show("%s", accounts[i]);
}
}
cons_show("");
@@ -1019,7 +1019,7 @@ cons_show_account(ProfAccount* account)
}
curr = curr->next;
}
cons_show(manual->str);
cons_show("%s", manual->str);
g_string_free(manual, TRUE);
}
if (g_list_length(account->otr_opportunistic) > 0) {
@@ -1032,7 +1032,7 @@ cons_show_account(ProfAccount* account)
}
curr = curr->next;
}
cons_show(opportunistic->str);
cons_show("%s", opportunistic->str);
g_string_free(opportunistic, TRUE);
}
if (g_list_length(account->otr_always) > 0) {
@@ -1045,7 +1045,7 @@ cons_show_account(ProfAccount* account)
}
curr = curr->next;
}
cons_show(always->str);
cons_show("%s", always->str);
g_string_free(always, TRUE);
}
@@ -2297,7 +2297,7 @@ cons_show_themes(GSList* themes)
} else {
cons_show("Available themes:");
while (themes) {
cons_show(themes->data);
cons_show("%s", themes->data);
themes = g_slist_next(themes);
}
}
@@ -2315,7 +2315,7 @@ cons_show_scripts(GSList* scripts)
} else {
cons_show("Scripts:");
while (scripts) {
cons_show(scripts->data);
cons_show("%s", scripts->data);
scripts = g_slist_next(scripts);
}
}

View File

@@ -445,7 +445,7 @@ ui_handle_error(const char* const err_msg)
GString* msg = g_string_new("");
g_string_printf(msg, "Error %s", err_msg);
cons_show_error(msg->str);
cons_show_error("%s", msg->str);
g_string_free(msg, TRUE);
}
@@ -461,7 +461,7 @@ ui_invalid_command_usage(const char* const cmd, void (*setting_func)(void))
(*setting_func)();
} else {
cons_show("");
cons_show(msg->str);
cons_show("%s", msg->str);
ProfWin* current = wins_get_current();
if (current->type == WIN_CHAT) {
win_println(current, THEME_DEFAULT, "-", "%s", msg->str);

View File

@@ -144,10 +144,14 @@ void
create_input_window(void)
{
/* MB_CUR_MAX is evaluated at runtime depending on the current
* locale, therefore we check that our own version is big enough
* and bail out if it isn't.
* locale; ensure our own compiled-in maximum is sufficient.
* Fail gracefully instead of aborting in production.
*/
assert(MB_CUR_MAX <= PROF_MB_CUR_MAX);
if (MB_CUR_MAX > PROF_MB_CUR_MAX) {
log_error("Locale MB_CUR_MAX (%zu) exceeds compiled limit (%d)", (size_t)MB_CUR_MAX, PROF_MB_CUR_MAX);
cons_show_error("Unsupported locale. Before running, execute in terminal: export LC_ALL=C.UTF-8");
return;
}
#ifdef NCURSES_REENTRANT
set_escdelay(25);
#else
@@ -163,6 +167,10 @@ create_input_window(void)
rl_callback_handler_install(NULL, _inp_rl_linehandler);
inp_win = newpad(1, INP_WIN_MAX);
if (!inp_win) {
log_error("Failed to allocate input window pad");
return;
}
wbkgd(inp_win, theme_attrs(THEME_INPUT_TEXT));
keypad(inp_win, TRUE);
wmove(inp_win, 0, 0);
@@ -238,6 +246,9 @@ inp_readline(void)
void
inp_win_resize(void)
{
if (!inp_win) {
return;
}
int col = getcurx(inp_win);
int wcols = getmaxx(stdscr);
@@ -285,8 +296,10 @@ void
inp_close(void)
{
rl_callback_handler_remove();
delwin(inp_win);
inp_win = NULL;
if (inp_win) {
delwin(inp_win);
inp_win = NULL;
}
fclose(discard);
discard = NULL;
}
@@ -294,6 +307,9 @@ inp_close(void)
char*
inp_get_line(void)
{
if (!inp_win) {
return NULL;
}
werase(inp_win);
wmove(inp_win, 0, 0);
_inp_win_update_virtual();
@@ -318,6 +334,9 @@ inp_set_line(const char* const new_line)
char*
inp_get_password(void)
{
if (!inp_win) {
return NULL;
}
werase(inp_win);
wmove(inp_win, 0, 0);
_inp_win_update_virtual();

View File

@@ -40,6 +40,8 @@
#include <string.h>
#include <stdlib.h>
#include "log.h"
#ifdef HAVE_NCURSESW_NCURSES_H
#include <ncursesw/ncurses.h>
#elif HAVE_NCURSES_H
@@ -111,16 +113,24 @@ status_bar_init(void)
int row = screen_statusbar_row();
int cols = getmaxx(stdscr);
if (cols <= 0) {
log_warning("status_bar_init: invalid cols %d, defaulting to 1", cols);
cols = 1;
}
statusbar_win = newwin(1, cols, row, 0);
status_bar_draw();
if (statusbar_win) {
status_bar_draw();
}
}
void
status_bar_close(void)
{
delwin(statusbar_win);
statusbar_win = NULL;
if (statusbar_win) {
delwin(statusbar_win);
statusbar_win = NULL;
}
if (statusbar) {
if (statusbar->time) {
g_free(statusbar->time);
@@ -145,7 +155,14 @@ status_bar_close(void)
void
status_bar_resize(void)
{
if (!statusbar_win) {
return;
}
int cols = getmaxx(stdscr);
if (cols <= 0) {
log_warning("status_bar_resize: invalid cols %d, defaulting to 1", cols);
cols = 1;
}
werase(statusbar_win);
int row = screen_statusbar_row();
wresize(statusbar_win, 1, cols);
@@ -285,6 +302,9 @@ status_bar_clear_fulljid(void)
void
status_bar_draw(void)
{
if (!statusbar_win) {
return;
}
werase(statusbar_win);
wbkgd(statusbar_win, theme_attrs(THEME_STATUS_TEXT));
@@ -674,8 +694,13 @@ _display_name(StatusBarTab* tab)
fullname = g_strconcat(mucwin_title, " conf", NULL);
} else if (tab->window_type == WIN_PRIVATE) {
auto_jid Jid* jid = jid_create(tab->identifier);
auto_gchar gchar* mucwin_title = mucwin_generate_title(jid->barejid, PREF_STATUSBAR_ROOM_TITLE);
fullname = g_strconcat(mucwin_title, "/", jid->resourcepart, NULL);
if (jid) {
auto_gchar gchar* mucwin_title = mucwin_generate_title(jid->barejid, PREF_STATUSBAR_ROOM_TITLE);
fullname = g_strconcat(mucwin_title, "/", jid->resourcepart, NULL);
} else {
// Fallback: use identifier directly if JID parsing failed
fullname = strdup(tab->identifier);
}
} else {
fullname = strdup("window");
}

View File

@@ -73,9 +73,16 @@ void
create_title_bar(void)
{
int cols = getmaxx(stdscr);
if (cols <= 0) {
cols = 1;
}
int row = screen_titlebar_row();
win = newwin(1, cols, row, 0);
if (!win) {
// Failed to create title bar window; skip initialization to avoid NULL deref
return;
}
wbkgd(win, theme_attrs(THEME_TITLE_TEXT));
title_bar_console();
title_bar_set_presence(CONTACT_OFFLINE);
@@ -88,13 +95,18 @@ create_title_bar(void)
void
free_title_bar(void)
{
delwin(win);
win = NULL;
if (win) {
delwin(win);
win = NULL;
}
}
void
title_bar_update_virtual(void)
{
if (!win) {
return;
}
ProfWin* window = wins_get_current();
if (window->type != WIN_CONSOLE) {
if (typing_elapsed) {
@@ -114,7 +126,13 @@ title_bar_update_virtual(void)
void
title_bar_resize(void)
{
if (!win) {
return;
}
int cols = getmaxx(stdscr);
if (cols <= 0) {
cols = 1;
}
werase(win);
@@ -131,6 +149,9 @@ title_bar_resize(void)
void
title_bar_console(void)
{
if (!win) {
return;
}
werase(win);
if (typing_elapsed) {
g_timer_destroy(typing_elapsed);
@@ -192,6 +213,9 @@ title_bar_set_typing(gboolean is_typing)
static void
_title_bar_draw(void)
{
if (!win) {
return;
}
int pos;
int maxrightpos;
ProfWin* current = wins_get_current();

View File

@@ -75,12 +75,22 @@ static void _win_print_internal(ProfWin* window, const char* show_char, int pad_
int flags, theme_item_t theme_item, const char* const from, const char* const message, DeliveryReceipt* receipt);
static void _win_print_wrapped(WINDOW* win, const char* const message, size_t indent, int pad_indent);
// Helper: clamp a subwindow width to a sane range [1, cols-1] if possible
static int
_check_subwin_width(int cols, int width)
{
return cols <= 1 ? 1 : CLAMP(width, 1, cols - 1);
}
int
win_roster_cols(void)
{
int roster_win_percent = prefs_get_roster_size();
int cols = getmaxx(stdscr);
return CEILING((((double)cols) / 100) * roster_win_percent);
int width = CEILING((((double)cols) / 100) * roster_win_percent);
// Clamp to a sane range to avoid zero/full-width pads
width = _check_subwin_width(cols, width);
return width;
}
int
@@ -88,7 +98,10 @@ win_occpuants_cols(void)
{
int occupants_win_percent = prefs_get_occupants_size();
int cols = getmaxx(stdscr);
return CEILING((((double)cols) / 100) * occupants_win_percent);
int width = CEILING((((double)cols) / 100) * occupants_win_percent);
// Clamp to a sane range to avoid zero/full-width pads
width = _check_subwin_width(cols, width);
return width;
}
static ProfLayout*
@@ -144,6 +157,7 @@ win_create_console(void)
ProfWin*
win_create_chat(const char* const barejid)
{
assert(barejid != NULL);
ProfChatWin* new_win = malloc(sizeof(ProfChatWin));
new_win->window.type = WIN_CHAT;
new_win->window.scroll_state = WIN_SCROLL_INNER;
@@ -175,6 +189,7 @@ win_create_chat(const char* const barejid)
ProfWin*
win_create_muc(const char* const roomjid)
{
assert(roomjid != NULL);
ProfMucWin* new_win = malloc(sizeof(ProfMucWin));
int cols = getmaxx(stdscr);
@@ -233,6 +248,8 @@ win_create_muc(const char* const roomjid)
ProfWin*
win_create_config(const char* const roomjid, DataForm* form, ProfConfWinCallback submit, ProfConfWinCallback cancel, const void* userdata)
{
assert(roomjid != NULL);
assert(form != NULL);
ProfConfWin* new_win = malloc(sizeof(ProfConfWin));
new_win->window.type = WIN_CONFIG;
new_win->window.scroll_state = WIN_SCROLL_INNER;
@@ -251,6 +268,7 @@ win_create_config(const char* const roomjid, DataForm* form, ProfConfWinCallback
ProfWin*
win_create_private(const char* const fulljid)
{
assert(fulljid != NULL);
ProfPrivateWin* new_win = malloc(sizeof(ProfPrivateWin));
new_win->window.type = WIN_PRIVATE;
new_win->window.scroll_state = WIN_SCROLL_INNER;
@@ -281,6 +299,8 @@ win_create_xmlconsole(void)
ProfWin*
win_create_plugin(const char* const plugin_name, const char* const tag)
{
assert(plugin_name != NULL);
assert(tag != NULL);
ProfPluginWin* new_win = malloc(sizeof(ProfPluginWin));
new_win->window.type = WIN_PLUGIN;
new_win->window.scroll_state = WIN_SCROLL_INNER;
@@ -297,6 +317,7 @@ win_create_plugin(const char* const plugin_name, const char* const tag)
ProfWin*
win_create_vcard(vCard* vcard)
{
assert(vcard != NULL);
ProfVcardWin* new_win = malloc(sizeof(ProfVcardWin));
new_win->window.type = WIN_VCARD;
new_win->window.scroll_state = WIN_SCROLL_INNER;
@@ -348,7 +369,7 @@ win_get_title(ProfWin* window)
const ProfConfWin* confwin = (ProfConfWin*)window;
assert(confwin->memcheck == PROFCONFWIN_MEMCHECK);
auto_gchar gchar* mucwin_title = mucwin_generate_title(confwin->roomjid, PREF_TITLEBAR_MUC_TITLE);
if (confwin->form->modified) {
if (confwin->form && confwin->form->modified) {
return g_strconcat(mucwin_title, " config *", NULL);
}
return g_strconcat(mucwin_title, " config", NULL);
@@ -556,7 +577,25 @@ win_show_subwin(ProfWin* window)
}
ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
// If a subwindow already exists (e.g. repeated call), destroy it to avoid leaks
if (layout->subwin) {
delwin(layout->subwin);
layout->subwin = NULL;
}
// Ensure minimum width to avoid creating a zero-width pad
if (subwin_cols <= 0) {
subwin_cols = 1;
}
layout->subwin = newpad(PAD_SIZE, subwin_cols);
if (layout->subwin == NULL) {
// Failed to allocate subwindow; keep base window resized to full width
log_error("Failed to create subwindow pad (cols=%d)", subwin_cols);
wresize(layout->base.win, PAD_SIZE, cols);
win_redraw(window);
return;
}
wbkgd(layout->subwin, theme_attrs(THEME_TEXT));
wresize(layout->base.win, PAD_SIZE, cols - subwin_cols);
win_redraw(window);
@@ -910,6 +949,11 @@ win_refresh_with_subwin(ProfWin* window)
int row_end = screen_mainwin_row_end();
ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
// Safety: if subwindow is not active, nothing to refresh
if (layout == NULL || layout->subwin == NULL) {
return;
}
if (window->type == WIN_MUC) {
subwin_cols = win_occpuants_cols();
} else if (window->type == WIN_CONSOLE) {
@@ -2038,7 +2082,14 @@ win_print_loading_history(ProfWin* window)
gboolean is_buffer_empty = buffer_size(window->layout->buffer) == 0;
if (!is_buffer_empty) {
timestamp = buffer_get_entry(window->layout->buffer, 0)->time;
ProfBuffEntry* first = buffer_get_entry(window->layout->buffer, 0);
if (first && first->time) {
timestamp = first->time;
} else {
// Fallback to current time if entry/time is unavailable
timestamp = g_date_time_new_now_local();
is_buffer_empty = TRUE; // ensure we unref fallback timestamp below
}
} else {
timestamp = g_date_time_new_now_local();
}
@@ -2238,7 +2289,7 @@ void
win_handle_command_exec_result_note(ProfWin* window, const char* const type, const char* const value)
{
assert(window != NULL);
win_println(window, THEME_DEFAULT, "!", value);
win_println(window, THEME_DEFAULT, "!", "%s", value);
}
void

View File

@@ -407,8 +407,10 @@ wins_get_by_string(const char* str)
if (barejid) {
ProfChatWin* chatwin = wins_get_chat(barejid);
if (chatwin) {
free(barejid);
return (ProfWin*)chatwin;
}
free(barejid);
}
}
@@ -603,6 +605,9 @@ wins_new_xmlconsole(void)
{
int result = _wins_get_next_available_num(keys);
ProfWin* newwin = win_create_xmlconsole();
if (!newwin) {
return NULL;
}
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
autocomplete_add(wins_ac, "xmlconsole");
autocomplete_add(wins_close_ac, "xmlconsole");
@@ -614,6 +619,9 @@ wins_new_chat(const char* const barejid)
{
int result = _wins_get_next_available_num(keys);
ProfWin* newwin = win_create_chat(barejid);
if (!newwin) {
return NULL;
}
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
autocomplete_add(wins_ac, barejid);
@@ -637,6 +645,9 @@ wins_new_muc(const char* const roomjid)
{
int result = _wins_get_next_available_num(keys);
ProfWin* newwin = win_create_muc(roomjid);
if (!newwin) {
return NULL;
}
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
autocomplete_add(wins_ac, roomjid);
autocomplete_add(wins_close_ac, roomjid);
@@ -651,6 +662,9 @@ wins_new_config(const char* const roomjid, DataForm* form, ProfConfWinCallback s
{
int result = _wins_get_next_available_num(keys);
ProfWin* newwin = win_create_config(roomjid, form, submit, cancel, userdata);
if (!newwin) {
return NULL;
}
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
return newwin;
@@ -661,6 +675,9 @@ wins_new_private(const char* const fulljid)
{
int result = _wins_get_next_available_num(keys);
ProfWin* newwin = win_create_private(fulljid);
if (!newwin) {
return NULL;
}
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
autocomplete_add(wins_ac, fulljid);
autocomplete_add(wins_close_ac, fulljid);
@@ -675,6 +692,9 @@ wins_new_plugin(const char* const plugin_name, const char* const tag)
{
int result = _wins_get_next_available_num(keys);
ProfWin* newwin = win_create_plugin(plugin_name, tag);
if (!newwin) {
return NULL;
}
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
autocomplete_add(wins_ac, tag);
autocomplete_add(wins_close_ac, tag);
@@ -686,6 +706,9 @@ wins_new_vcard(vCard* vcard)
{
int result = _wins_get_next_available_num(keys);
ProfWin* newwin = win_create_vcard(vcard);
if (!newwin) {
return NULL;
}
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
return newwin;