patch 9.2.0315: missing bound-checks

Problem:  missing bound-checks
Solution: Add defensive guards against potential buffer overflow
          (Yasuhiro Matsumoto)

Add bounds checking and integer overflow guards across multiple files
as a defensive measure. While these code paths are unlikely to be
exploitable in practice, the guards prevent undefined behavior in
edge cases.

- libvterm/vterm.c: use heap tmpbuffer instead of stack buffer in
  vsprintf() fallback path
- channel.c: validate len in channel_consume() before mch_memmove()
- spell.c: use long instead of int for addlen to avoid signed overflow
  in size_t subtraction
- alloc.c: add SIZE_MAX overflow check in ga_grow_inner() before
  itemsize multiplication
- list.c: add overflow check before count * sizeof(listitem_T)
- popupwin.c: add overflow check before width * height allocation
- insexpand.c: add overflow check before compl_num_bests multiplication
- regexp_bt.c: replace sprintf() with vim_snprintf() in regprop()
- spellfile.c: use SIZE_MAX instead of LONG_MAX for allocation overflow
  check

closes: #19904

Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>

Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Yasuhiro Matsumoto
2026-04-06 13:53:31 +00:00
committed by Christian Brabandt
parent c3c3478810
commit 8d23fcb603
10 changed files with 35 additions and 20 deletions
+2
View File
@@ -746,6 +746,8 @@ ga_grow_inner(garray_T *gap, int n)
if (n < gap->ga_len / 2)
n = gap->ga_len / 2;
if (n > 0 && (size_t)(gap->ga_len + n) > SIZE_MAX / gap->ga_itemsize)
return FAIL;
new_len = (size_t)gap->ga_itemsize * (gap->ga_len + n);
pp = vim_realloc(gap->ga_data, new_len);
if (pp == NULL)
+2
View File
@@ -2290,6 +2290,8 @@ channel_consume(channel_T *channel, ch_part_T part, int len)
readq_T *node = head->rq_next;
char_u *buf = node->rq_buffer;
if (len < 0 || (long_u)len > node->rq_buflen)
return;
mch_memmove(buf, buf + len, node->rq_buflen - len);
node->rq_buflen -= len;
node->rq_buffer[node->rq_buflen] = NUL;
+2
View File
@@ -4538,6 +4538,8 @@ fuzzy_longest_match(void)
return;
}
if ((size_t)compl_num_bests > SIZE_MAX / sizeof(compl_T *))
return;
compl_best_matches = (compl_T **)alloc(compl_num_bests * sizeof(compl_T *));
if (compl_best_matches == NULL)
return;
+7 -8
View File
@@ -181,18 +181,17 @@ INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len)
INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args)
{
size_t len;
#ifndef VSNPRINTF
// When vsnprintf() is not available (C90) fall back to vsprintf().
char buffer[1024]; // 1Kbyte is enough for everybody, right?
#endif
#ifdef VSNPRINTF
len = VSNPRINTF(vt->tmpbuffer, vt->tmpbuffer_len, format, args);
vterm_push_output_bytes(vt, vt->tmpbuffer, len);
#else
len = vsprintf(buffer, format, args);
vterm_push_output_bytes(vt, buffer, len);
// When vsnprintf() is not available (C90) fall back to vsprintf().
// Use the heap-allocated tmpbuffer (default 4096 bytes) instead of a small
// stack buffer to reduce the risk of overflow.
len = vsprintf(vt->tmpbuffer, format, args);
if (len >= vt->tmpbuffer_len)
len = vt->tmpbuffer_len - 1;
#endif
vterm_push_output_bytes(vt, vt->tmpbuffer, len);
}
INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...)
+3
View File
@@ -118,6 +118,9 @@ list_alloc_with_items(int count)
{
list_T *l;
if (count > 0
&& (size_t)count > (SIZE_MAX - sizeof(list_T)) / sizeof(listitem_T))
return NULL;
l = (list_T *)alloc_clear(sizeof(list_T) + count * sizeof(listitem_T));
if (l == NULL)
return NULL;
+5
View File
@@ -4103,6 +4103,11 @@ popup_update_mask(win_T *wp, int width, int height)
return; // cache is still valid
vim_free(wp->w_popup_mask_cells);
if (width > 0 && (size_t)height > SIZE_MAX / (size_t)width)
{
wp->w_popup_mask_cells = NULL;
return;
}
wp->w_popup_mask_cells = alloc_clear((size_t)width * height);
if (wp->w_popup_mask_cells == NULL)
return;
+9 -9
View File
@@ -5525,7 +5525,7 @@ regprop(char_u *op)
case MOPEN + 7:
case MOPEN + 8:
case MOPEN + 9:
buflen += sprintf(buf + buflen, "MOPEN%d", OP(op) - MOPEN);
buflen += vim_snprintf(buf + buflen, sizeof(buf) - buflen, "MOPEN%d", OP(op) - MOPEN);
p = NULL;
break;
case MCLOSE + 0:
@@ -5540,7 +5540,7 @@ regprop(char_u *op)
case MCLOSE + 7:
case MCLOSE + 8:
case MCLOSE + 9:
buflen += sprintf(buf + buflen, "MCLOSE%d", OP(op) - MCLOSE);
buflen += vim_snprintf(buf + buflen, sizeof(buf) - buflen, "MCLOSE%d", OP(op) - MCLOSE);
p = NULL;
break;
case BACKREF + 1:
@@ -5552,7 +5552,7 @@ regprop(char_u *op)
case BACKREF + 7:
case BACKREF + 8:
case BACKREF + 9:
buflen += sprintf(buf + buflen, "BACKREF%d", OP(op) - BACKREF);
buflen += vim_snprintf(buf + buflen, sizeof(buf) - buflen, "BACKREF%d", OP(op) - BACKREF);
p = NULL;
break;
case NOPEN:
@@ -5571,7 +5571,7 @@ regprop(char_u *op)
case ZOPEN + 7:
case ZOPEN + 8:
case ZOPEN + 9:
buflen += sprintf(buf + buflen, "ZOPEN%d", OP(op) - ZOPEN);
buflen += vim_snprintf(buf + buflen, sizeof(buf) - buflen, "ZOPEN%d", OP(op) - ZOPEN);
p = NULL;
break;
case ZCLOSE + 1:
@@ -5583,7 +5583,7 @@ regprop(char_u *op)
case ZCLOSE + 7:
case ZCLOSE + 8:
case ZCLOSE + 9:
buflen += sprintf(buf + buflen, "ZCLOSE%d", OP(op) - ZCLOSE);
buflen += vim_snprintf(buf + buflen, sizeof(buf) - buflen, "ZCLOSE%d", OP(op) - ZCLOSE);
p = NULL;
break;
case ZREF + 1:
@@ -5595,7 +5595,7 @@ regprop(char_u *op)
case ZREF + 7:
case ZREF + 8:
case ZREF + 9:
buflen += sprintf(buf + buflen, "ZREF%d", OP(op) - ZREF);
buflen += vim_snprintf(buf + buflen, sizeof(buf) - buflen, "ZREF%d", OP(op) - ZREF);
p = NULL;
break;
# endif
@@ -5636,7 +5636,7 @@ regprop(char_u *op)
case BRACE_COMPLEX + 7:
case BRACE_COMPLEX + 8:
case BRACE_COMPLEX + 9:
buflen += sprintf(buf + buflen, "BRACE_COMPLEX%d", OP(op) - BRACE_COMPLEX);
buflen += vim_snprintf(buf + buflen, sizeof(buf) - buflen, "BRACE_COMPLEX%d", OP(op) - BRACE_COMPLEX);
p = NULL;
break;
case MULTIBYTECODE:
@@ -5646,12 +5646,12 @@ regprop(char_u *op)
p = "NEWL";
break;
default:
buflen += sprintf(buf + buflen, "corrupt %d", OP(op));
buflen += vim_snprintf(buf + buflen, sizeof(buf) - buflen, "corrupt %d", OP(op));
p = NULL;
break;
}
if (p != NULL)
STRCPY(buf + buflen, p);
vim_strncpy((char_u *)buf + buflen, (char_u *)p, sizeof(buf) - buflen - 1);
return (char_u *)buf;
}
#endif // DEBUG
+2 -2
View File
@@ -2965,7 +2965,7 @@ ex_spellrepall(exarg_T *eap UNUSED)
}
size_t repl_from_len = STRLEN(repl_from);
size_t repl_to_len = STRLEN(repl_to);
int addlen = (int)(repl_to_len - repl_from_len);
long addlen = (long)repl_to_len - (long)repl_from_len;
frompat = alloc(repl_from_len + 7);
if (frompat == NULL)
@@ -2999,7 +2999,7 @@ ex_spellrepall(exarg_T *eap UNUSED)
#if defined(FEAT_PROP_POPUP)
if (curbuf->b_has_textprop && addlen != 0)
adjust_prop_columns(curwin->w_cursor.lnum,
curwin->w_cursor.col, addlen, APC_SUBSTITUTE);
curwin->w_cursor.col, (int)addlen, APC_SUBSTITUTE);
#endif
if (curwin->w_cursor.lnum != prev_lnum)
+1 -1
View File
@@ -1589,7 +1589,7 @@ spell_read_tree(
len = get4c(fd);
if (len < 0)
return SP_TRUNCERROR;
if (len >= LONG_MAX / (long)sizeof(int))
if ((size_t)len > SIZE_MAX / sizeof(int))
// Invalid length, multiply with sizeof(int) would overflow.
return SP_FORMERROR;
if (len <= 0)
+2
View File
@@ -734,6 +734,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
315,
/**/
314,
/**/