CVE-2022-0261: Out-of-bounds Write (CWE-787)
value: Heap-based Buffer Overflow in GitHub repository vim/vim prior to 8.2.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 7.8
Published: 2022-01-18T16:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/ops.c, src/testdir/test_visual.vim, src/version.c
Commit date: 2022-01-17 17:30:21 +0000
Commit message: patch 8.2.4120: block insert goes over the end of the line

Problem:    Block insert goes over the end of the line.
Solution:   Handle invalid byte better.  Fix inserting the wrong text.

Diff: diff --git a/src/ops.c b/src/ops.c
index 6a378442c87..a9968024901 100644
--- a/src/ops.c
+++ b/src/ops.c
@@ -534,46 +534,58 @@ block_insert(
 
 	    // Avoid starting halfway a multi-byte character.
 	    if (b_insert)
 	    {
 		off = (*mb_head_off)(oldp, oldp + offset + spaces);
+		spaces -= off;
+		count -= off;
 	    }
 	    else
 	    {
-		off = (*mb_off_next)(oldp, oldp + offset);
-		offset += off;
+		// spaces fill the gap, the character that's at the edge moves
+		// right
+		off = (*mb_head_off)(oldp, oldp + offset);
+		offset -= off;
 	    }
-	    spaces -= off;
-	    count -= off;
 	}
 	if (spaces < 0)  // can happen when the cursor was moved
 	    spaces = 0;
 
-	newp = alloc(STRLEN(oldp) + s_len + count + 1);
+	// Make sure the allocated size matches what is actually copied below.
+	newp = alloc(STRLEN(oldp) + spaces + s_len
+		    + (spaces > 0 && !bdp->is_short ? ts_val - spaces : 0)
+								  + count + 1);
 	if (newp == NULL)
 	    continue;
 
 	// copy up to shifted part
-	mch_memmove(newp, oldp, (size_t)(offset));
+	mch_memmove(newp, oldp, (size_t)offset);
 	oldp += offset;
 
 	// insert pre-padding
 	vim_memset(newp + offset, ' ', (size_t)spaces);
 	startcol = offset + spaces;
 
 	// copy the new text
 	mch_memmove(newp + startcol, s, (size_t)s_len);
 	offset += s_len;
 
-	if (spaces && !bdp->is_short)
+	if (spaces > 0 && !bdp->is_short)
 	{
-	    // insert post-padding
-	    vim_memset(newp + offset + spaces, ' ', (size_t)(ts_val - spaces));
-	    // We're splitting a TAB, don't copy it.
-	    oldp++;
-	    // We allowed for that TAB, remember this now
-	    count++;
+	    if (*oldp == TAB)
+	    {
+		// insert post-padding
+		vim_memset(newp + offset + spaces, ' ',
+						    (size_t)(ts_val - spaces));
+		// we're splitting a TAB, don't copy it
+		oldp++;
+		// We allowed for that TAB, remember this now
+		count++;
+	    }
+	    else
+		// Not a TAB, no extra spaces
+		count = spaces;
 	}
 
 	if (spaces > 0)
 	    offset += count;
 	STRMOVE(newp + offset, oldp);
@@ -1596,11 +1608,11 @@ op_insert(oparg_T *oap, long count1)
 		    oap->start.col = curbuf->b_op_start_orig.col;
 		    pre_textlen -= t - oap->start_vcol;
 		    oap->start_vcol = t;
 		}
 		else if (oap->op_type == OP_APPEND
-			&& oap->end.col + oap->end.coladd
+			&& oap->start.col + oap->start.coladd
 				>= curbuf->b_op_start_orig.col
 					      + curbuf->b_op_start_orig.coladd)
 		{
 		    oap->start.col = curbuf->b_op_start_orig.col;
 		    // reset pre_textlen to the value of OP_INSERT
diff --git a/src/testdir/test_visual.vim b/src/testdir/test_visual.vim
index e40be5dede9..9ca0fa9c8e7 100644
--- a/src/testdir/test_visual.vim
+++ b/src/testdir/test_visual.vim
@@ -1276,10 +1276,19 @@ func Test_visual_block_ctrl_w_f()
   edit foo
 
   au! BufNew
 endfunc
 
+func Test_visual_block_append_invalid_char()
+  " this was going over the end of the line
+  new
+  call setline(1, ['	   let xxx', 'xxxxx', 'xxxxxxxxxxx'])
+  exe "normal 0\<C-V>jjA-\<Esc>"
+  call assert_equal(['	-   let xxx', 'xxxxx   -', 'xxxxxxxx-xxx'], getline(1, 3))
+  bwipe!
+endfunc
+
 func Test_visual_reselect_with_count()
   " this was causing an illegal memory access
   let lines =<< trim END
 
 
diff --git a/src/version.c b/src/version.c
index 01b8e5d3dc7..6fda77e9325 100644
--- a/src/version.c
+++ b/src/version.c
@@ -748,10 +748,12 @@ static char *(features[]) =
 	NULL
 };
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    4120,
 /**/
     4119,
 /**/
     4118,
 /**/

Vulnerable code: 		off = (*mb_off_next)(oldp, oldp + offset);
		offset += off;
	    spaces -= off;
	    count -= off;
	newp = alloc(STRLEN(oldp) + s_len + count + 1);
	mch_memmove(newp, oldp, (size_t)(offset));
	if (spaces && !bdp->is_short)
	    // insert post-padding
	    vim_memset(newp + offset + spaces, ' ', (size_t)(ts_val - spaces));
	    // We're splitting a TAB, don't copy it.
	    oldp++;
	    // We allowed for that TAB, remember this now
	    count++;
			&& oap->end.col + oap->end.coladd
Fixed code: 		spaces -= off;
		count -= off;
		// spaces fill the gap, the character that's at the edge moves
		// right
		off = (*mb_head_off)(oldp, oldp + offset);
		offset -= off;
	// Make sure the allocated size matches what is actually copied below.
	newp = alloc(STRLEN(oldp) + spaces + s_len
		    + (spaces > 0 && !bdp->is_short ? ts_val - spaces : 0)
								  + count + 1);
	mch_memmove(newp, oldp, (size_t)offset);
	if (spaces > 0 && !bdp->is_short)
	    if (*oldp == TAB)
	    {
		// insert post-padding
		vim_memset(newp + offset + spaces, ' ',
						    (size_t)(ts_val - spaces));
		// we're splitting a TAB, don't copy it
		oldp++;
		// We allowed for that TAB, remember this now
		count++;
	    }
	    else
		// Not a TAB, no extra spaces
		count = spaces;
			&& oap->start.col + oap->start.coladd
func Test_visual_block_append_invalid_char()
  " this was going over the end of the line
  new
  call setline(1, ['	   let xxx', 'xxxxx', 'xxxxxxxxxxx'])
  exe "normal 0\<C-V>jjA-\<Esc>"
  call assert_equal(['	-   let xxx', 'xxxxx   -', 'xxxxxxxx-xxx'], getline(1, 3))
  bwipe!
endfunc

/**/
    4120,