refactor: clean narrowing conversions and harden unsigned arithmetic

Cleanup of the conversion-safety warnings exposed by enabling
-Wconversion / -Wsign-compare in the previous commit, plus guard
clauses at the few places where unsigned arithmetic could actually
misbehave.

Build:
- configure.ac drops -Wno-error=conversion and
  -Wno-error=float-conversion. Only -Wno-error=sign-conversion and
  -Wno-error=sign-compare remain, gating the ~230 sign warnings
  inherited from upstream that will be cleaned up in follow-ups.

Type / conversion fixes (no behaviour change):
- Length-like locals in command/cmd_ac.c, command/cmd_funcs.c,
  pgp/gpg.c, tools/autocomplete.c, tools/parser.c and ui/mucwin.c
  switched from int to size_t / glong (matching strlen /
  g_utf8_strlen return type) so we no longer need an (int) cast and
  loop counters / array sizes stay in their natural unsigned domain.
- g_timer_elapsed / GTimeSpan -> int casts in session.c, iq.c,
  core.c, server_events.c, window.c.
- _win_print_wrapped: indent parameter and local curx/maxx switched
  from size_t to int to match _win_indent / getcurx / getmaxx.
- Port casts (int -> unsigned short) at the libstrophe boundary in
  connection.c and session.c, each preceded by
  g_assert(port >= 0 && port <= UINT16_MAX) so the truncation is
  documented at the call-site.
- curl_off_t / fread size_t results cast at usage in http_upload.c,
  http_download.c, omemo/crypto.c.
- strtoul results cast to uint32_t in xmpp/omemo.c and omemo/omemo.c
  where device/prekey IDs are genuinely 32-bit.
- config/color.c: fg/bg/palette indices switched to `short`
  end-to-end (find_col, color_hash, find_closest_col,
  _color_pair_cache_get, cache.pairs), so the ncurses init_pair
  boundary needs at most one (short)i cast for the cache index. Also
  TODO-noted: init_extended_pair is needed for >15-bit palettes.
- xmpp/avatar.c: float arithmetic explicitly casts its int operands.
- tests/functionaltests/proftest.c: read() result handling uses
  size_t for the accumulator, _read_output returns ssize_t, and the
  buffer-shift check happens before space subtraction so the
  expression cannot underflow.

Real-risk guard clauses (the part that actually fixes bugs):
- src/ui/statusbar.c _tabs_width: `end > opened_tabs - 1` rewritten
  as `end < opened_tabs` so opened_tabs == 0 no longer underflows.
- src/ui/statusbar.c _status_bar_draw_extended_tabs: the mirror
  comparison rewritten as `end >= opened_tabs`.
- src/ui/statusbar.c status_bar_draw: replaced
  `MAX(0, getmaxx - (int)_tabs_width)` with an explicit precheck
  before subtraction.
- src/omemo/omemo.c prekey selection: prekey_index is now uint32_t
  and randomized into an unsigned buffer, so modulo with prekeys_len
  cannot yield a negative index for g_list_nth_data.
- src/omemo/crypto.c omemo_decrypt_func: PKCS#5/PKCS#7 unpadding
  reads `plaintext[plaintext_len - 1]`, which would underflow on a
  malformed empty ciphertext and read past the heap buffer. Reject
  plaintext_len == 0 before the padding peek and validate the
  padding byte against the buffer length before the unpad loop.
  Initialise plaintext = NULL so the early `goto out` cannot free
  uninitialised memory.
- src/ui/inputwin.c (4 mbrlen sites) and src/ui/window.c
  _win_print_wrapped: mbrlen() returns 0 for the null wide
  character. The existing checks rejected (size_t)-1 / -2 but
  treated 0 as a valid step, so the surrounding loops would either
  advance by SIZE_MAX (i += ch_len - 1) or spin in place
  (word_pos += 0 forever). Add `|| ch_len == 0` to each guard;
  inside the spell-check word-emission loop also fall back to a
  one-byte advance.
- Defensive `len > 0 ? len - 1 : 0` prechecks at the strlen-based
  g_strndup / loop sites in ui/console.c, plugins/c_api.c and
  plugins/python_plugins.c.
This commit is contained in:
2026-04-27 15:03:52 +03:00
parent 5459e78e82
commit 9feff00ead
28 changed files with 193 additions and 133 deletions

View File

@@ -92,8 +92,11 @@ cons_show_help(const char* const cmd, CommandHelp* help)
win_println(console, THEME_HELP_HEADER, "-", "%s", &cmd[1]);
win_print(console, THEME_HELP_HEADER, "-", "");
size_t i;
for (i = 0; i < strlen(cmd) - 1; i++) {
win_append(console, THEME_HELP_HEADER, "-");
size_t cmd_len = strlen(cmd);
if (cmd_len > 0) {
for (i = 0; i < cmd_len - 1; i++) {
win_append(console, THEME_HELP_HEADER, "-");
}
}
win_appendln(console, THEME_HELP_HEADER, "");
cons_show("");
@@ -115,7 +118,7 @@ cons_show_help(const char* const cmd, CommandHelp* help)
cons_show("");
win_println(console, THEME_HELP_HEADER, "-", "Arguments");
for (i = 0; help->args[i][0] != NULL; i++) {
win_println_indent(console, maxlen + 3, "%-*s: %s", (int)(maxlen + 1), help->args[i][0], help->args[i][1]);
win_println_indent(console, (int)(maxlen + 3), "%-*s: %s", (int)(maxlen + 1), help->args[i][0], help->args[i][1]);
}
}

View File

@@ -166,7 +166,7 @@ ui_get_idle_time(void)
// if no libxss or xss idle time failed, use profanity idle time
#endif
gdouble seconds_elapsed = g_timer_elapsed(ui_idle_time, NULL);
unsigned long ms_elapsed = seconds_elapsed * 1000.0;
unsigned long ms_elapsed = (unsigned long)(seconds_elapsed * 1000.0);
return ms_elapsed;
}

View File

@@ -385,7 +385,10 @@ _inp_write(char* line, int offset)
char retc[PROF_MB_CUR_MAX] = { 0 };
size_t ch_len = mbrlen(c, MB_CUR_MAX, NULL);
if ((ch_len == (size_t)-2) || (ch_len == (size_t)-1)) {
// ch_len == 0 means mbrlen consumed the null wide character;
// skipping it here keeps `i += ch_len - 1` below from
// underflowing to SIZE_MAX.
if ((ch_len == (size_t)-2) || (ch_len == (size_t)-1) || ch_len == 0) {
waddch(inp_win, ' ');
continue;
}
@@ -409,7 +412,9 @@ _inp_write(char* line, int offset)
size_t end = start + ch_len;
while (line[end] != '\0') {
size_t next_ch_len = mbrlen(&line[end], MB_CUR_MAX, NULL);
if (next_ch_len == (size_t)-2 || next_ch_len == (size_t)-1)
// ch_len == 0 means embedded NUL — stop, otherwise
// `end += next_ch_len` loops forever.
if (next_ch_len == (size_t)-2 || next_ch_len == (size_t)-1 || next_ch_len == 0)
break;
gunichar next_uc = g_utf8_get_char(&line[end]);
if (!g_unichar_isalnum(next_uc) && next_uc != '\'')
@@ -428,7 +433,13 @@ _inp_write(char* line, int offset)
size_t word_pos = start;
while (word_pos < end) {
size_t cur_ch_len = mbrlen(&line[word_pos], MB_CUR_MAX, NULL);
waddnstr(inp_win, &line[word_pos], cur_ch_len);
// Defensive: invalid or zero-length step would loop
// forever; advance one byte and continue.
if (cur_ch_len == (size_t)-2 || cur_ch_len == (size_t)-1 || cur_ch_len == 0) {
word_pos++;
continue;
}
waddnstr(inp_win, &line[word_pos], (int)cur_ch_len);
word_pos += cur_ch_len;
}
@@ -441,7 +452,7 @@ _inp_write(char* line, int offset)
}
}
waddnstr(inp_win, c, ch_len);
waddnstr(inp_win, c, (int)ch_len);
}
wmove(inp_win, 0, col);
@@ -494,11 +505,13 @@ _inp_offset_to_col(char* str, int offset)
while (i < offset && str[i] != '\0') {
gunichar uni = g_utf8_get_char(&str[i]);
size_t ch_len = mbrlen(&str[i], MB_CUR_MAX, NULL);
if ((ch_len == (size_t)-2) || (ch_len == (size_t)-1)) {
// ch_len == 0 (embedded NUL) would freeze the loop on the same
// byte; treat it like an invalid step.
if ((ch_len == (size_t)-2) || (ch_len == (size_t)-1) || ch_len == 0) {
i++;
continue;
}
i += ch_len;
i += (int)ch_len;
col++;
if (g_unichar_iswide(uni)) {
col++;

View File

@@ -382,7 +382,7 @@ _mucwin_print_mention(ProfWin* window, const char* const message, const char* co
auto_gchar gchar* mynick_str = g_utf8_substring(message, pos, pos + mynick_len);
win_append_highlight(window, THEME_ROOMMENTION_TERM, "%s", mynick_str);
last_pos = pos + mynick_len;
last_pos = pos + (int)mynick_len;
curr = g_slist_next(curr);
}
@@ -400,8 +400,8 @@ _mucwin_print_mention(ProfWin* window, const char* const message, const char* co
gint
_cmp_trigger_weight(gconstpointer a, gconstpointer b)
{
int alen = strlen((char*)a);
int blen = strlen((char*)b);
size_t alen = strlen((char*)a);
size_t blen = strlen((char*)b);
if (alen > blen)
return -1;
@@ -438,10 +438,10 @@ _mucwin_print_triggers(ProfWin* window, const char* const message, GList* trigge
}
// found, replace vars if earlier than previous
int trigger_pos = trigger_ptr - message_lower;
int trigger_pos = (int)(trigger_ptr - message_lower);
if (first_trigger_pos == -1 || trigger_pos < first_trigger_pos) {
first_trigger_pos = trigger_pos;
first_trigger_len = strlen(trigger_lower);
first_trigger_len = (int)strlen(trigger_lower);
}
curr = g_list_next(curr);

View File

@@ -304,8 +304,13 @@ _status_bar_draw_tabs(guint pos)
gboolean is_static = g_strcmp0(tabmode, "dynamic") != 0;
_get_range_bounds(&start, &end, is_static);
// if the result of the calc is negative we take 0
pos = MAX(0, (getmaxx(stdscr) - (int)_tabs_width(start, end)));
guint tabs_w = _tabs_width(start, end);
int max_x = getmaxx(stdscr);
if (max_x > 0 && (guint)max_x > tabs_w) {
pos = (guint)max_x - tabs_w;
} else {
pos = 0;
}
pos = _status_bar_draw_extended_tabs(pos, TRUE, start, end, is_static);
@@ -392,7 +397,9 @@ _status_bar_draw_extended_tabs(guint pos, gboolean prefix, guint start, guint en
if (prefix && start < 2) {
return pos;
}
if (!prefix && end > opened_tabs - 1) {
// Guard against underflow when opened_tabs == 0 (reachable if the
// earlier `opened_tabs <= max_tabs` early-return no longer holds).
if (!prefix && end >= opened_tabs) {
return pos;
}
gboolean is_current = is_static && statusbar->current_tab > max_tabs;
@@ -504,7 +511,7 @@ _status_bar_draw_time(guint pos)
int bracket_attrs = theme_attrs(THEME_STATUS_BRACKET);
int time_attrs = theme_attrs(THEME_STATUS_TIME);
size_t len = strlen(statusbar->time);
guint len = (guint)strlen(statusbar->time);
wattron(statusbar_win, bracket_attrs);
mvwaddch(statusbar_win, 0, pos, '[');
pos++;
@@ -599,7 +606,7 @@ _tabs_width(guint start, guint end)
guint opened_tabs = g_hash_table_size(statusbar->tabs);
int width = start < 2 ? 1 : 4;
width += end > opened_tabs - 1 ? 0 : 3;
width += (end < opened_tabs) ? 3 : 0;
if (show_name && show_number) {
for (guint i = start; i <= end; i++) {

View File

@@ -224,7 +224,7 @@ _title_bar_draw(void)
auto_gchar gchar* title = win_get_title(current);
mvwprintw(win, 0, 0, " %s", title);
pos = strlen(title) + 1;
pos = (int)strlen(title) + 1;
// presence is written from the right side
// calculate it first so we have a maxposition
@@ -596,7 +596,7 @@ _show_contact_presence(ProfChatWin* chatwin, int pos, int maxpos)
}
if (resource && prefs_get_boolean(PREF_RESOURCE_TITLE)) {
int needed = strlen(resource) + 1;
int needed = (int)strlen(resource) + 1;
if (pos + needed < maxpos) {
wprintw(win, "/");
wprintw(win, "%s", resource);

View File

@@ -75,7 +75,7 @@ static void
_win_printf(ProfWin* window, const char* show_char, int pad_indent, GDateTime* timestamp, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message_id, const char* const message, ...);
static void _win_print_internal(ProfWin* window, const char* show_char, int pad_indent, GDateTime* time,
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);
static void _win_print_wrapped(WINDOW* win, const char* const message, int indent, int pad_indent);
// Helper: clamp a subwindow width to a sane range [1, cols-1] if possible
static int
@@ -1048,11 +1048,11 @@ win_show_contact(ProfWin* window, PContact contact)
GTimeSpan span = g_date_time_difference(now, last_activity);
g_date_time_unref(now);
int hours = span / G_TIME_SPAN_HOUR;
int hours = (int)(span / G_TIME_SPAN_HOUR);
span = span - hours * G_TIME_SPAN_HOUR;
int minutes = span / G_TIME_SPAN_MINUTE;
int minutes = (int)(span / G_TIME_SPAN_MINUTE);
span = span - minutes * G_TIME_SPAN_MINUTE;
int seconds = span / G_TIME_SPAN_SECOND;
int seconds = (int)(span / G_TIME_SPAN_SECOND);
if (hours > 0) {
win_append(window, presence_colour, ", idle %dh%dm%ds", hours, minutes, seconds);
@@ -1173,11 +1173,11 @@ win_show_info(ProfWin* window, PContact contact)
GDateTime* now = g_date_time_new_now_local();
GTimeSpan span = g_date_time_difference(now, last_activity);
int hours = span / G_TIME_SPAN_HOUR;
int hours = (int)(span / G_TIME_SPAN_HOUR);
span = span - hours * G_TIME_SPAN_HOUR;
int minutes = span / G_TIME_SPAN_MINUTE;
int minutes = (int)(span / G_TIME_SPAN_MINUTE);
span = span - minutes * G_TIME_SPAN_MINUTE;
int seconds = span / G_TIME_SPAN_SECOND;
int seconds = (int)(span / G_TIME_SPAN_SECOND);
if (hours > 0) {
win_println(window, THEME_DEFAULT, "-", "Last activity: %dh%dm%ds", hours, minutes, seconds);
@@ -1846,7 +1846,7 @@ _win_print_internal(ProfWin* window, const char* show_char, int pad_indent, GDat
gboolean me_message = FALSE;
int offset = 0;
int colour = theme_attrs(THEME_ME);
size_t indent = 0;
int indent = 0;
auto_gchar gchar* time_pref = NULL;
switch (window->type) {
@@ -1879,7 +1879,7 @@ _win_print_internal(ProfWin* window, const char* show_char, int pad_indent, GDat
assert(date_fmt != NULL);
if (strlen(date_fmt) != 0) {
indent = 3 + strlen(date_fmt);
indent = 3 + (int)strlen(date_fmt);
}
if ((flags & NO_DATE) == 0) {
@@ -1975,7 +1975,7 @@ _win_indent(WINDOW* win, int size)
}
static void
_win_print_wrapped(WINDOW* win, const char* const message, size_t indent, int pad_indent)
_win_print_wrapped(WINDOW* win, const char* const message, int indent, int pad_indent)
{
int starty = getcury(win);
int wordi = 0;
@@ -2002,7 +2002,8 @@ _win_print_wrapped(WINDOW* win, const char* const message, size_t indent, int pa
int wordlen = 0;
while (*curr_ch != ' ' && *curr_ch != '\n' && *curr_ch != '\0') {
size_t ch_len = mbrlen(curr_ch, MB_CUR_MAX, NULL);
if ((ch_len == (size_t)-2) || (ch_len == (size_t)-1)) {
// 0 / invalid step: advance one byte to avoid spinning.
if ((ch_len == (size_t)-2) || (ch_len == (size_t)-1) || ch_len == 0) {
curr_ch++;
continue;
}
@@ -2015,9 +2016,9 @@ _win_print_wrapped(WINDOW* win, const char* const message, size_t indent, int pa
word[wordi] = '\0';
wordlen = utf8_display_len(word);
size_t curx = (size_t)getcurx(win);
int curx = getcurx(win);
int cury;
size_t maxx = getmaxx(win);
int maxx = getmaxx(win);
// wrap required
if (curx + wordlen > maxx) {
@@ -2027,7 +2028,7 @@ _win_print_wrapped(WINDOW* win, const char* const message, size_t indent, int pa
if (wordlen > linelen) {
gchar* word_ch = g_utf8_offset_to_pointer(word, 0);
while (*word_ch != '\0') {
curx = (size_t)getcurx(win);
curx = getcurx(win);
cury = getcury(win);
gboolean firstline = cury == starty;
@@ -2048,7 +2049,7 @@ _win_print_wrapped(WINDOW* win, const char* const message, size_t indent, int pa
// newline and print word
} else {
waddch(win, '\n');
curx = (size_t)getcurx(win);
curx = getcurx(win);
cury = getcury(win);
gboolean firstline = cury == starty;
@@ -2063,7 +2064,7 @@ _win_print_wrapped(WINDOW* win, const char* const message, size_t indent, int pa
// no wrap required
} else {
curx = (size_t)getcurx(win);
curx = getcurx(win);
cury = getcury(win);
gboolean firstline = cury == starty;