Commit: patch 9.2.0843: [security]: popup: opacity mask indexed out of bounds
Christian Brabandt <[email protected]>
| Newsgroups | gmane.editors.vim.devel |
|---|---|
| Message-ID | <[email protected]> |
patch 9.2.0843: [security]: popup: opacity mask indexed out of bounds Commit: https://github.com/vim/vim/commit/975e191dc817d8d00abca7197c4529a417c2f805 Author: Hirohito Higashi <[email protected]> Date: Wed Jul 22 14:51:00 2026 +0900 patch 9.2.0843: [security]: popup: opacity mask indexed out of bounds Problem: [security]: A "clipwindow" popup with "opacity" anchored to a text property can keep a negative window row when the anchor scrolls above the top of the host window. The opacity mask loop then indexes the screen array before its start, causing an out-of-bounds read and a conditional out-of-bounds write (tdjackey). Solution: Keep the popup window row at the first visible row and record the clipped-off top rows only in the top offset, so no consumer has to clamp the row and the opacity mask loop stays in bounds (Hirohito Higashi). Github Security Advisory: https://github.com/vim/vim/security/advisories/GHSA-pmvp-6rcj-98p4 Signed-off-by: Hirohito Higashi <[email protected]> Signed-off-by: Christian Brabandt <[email protected]> diff --git a/src/popupwin.c b/src/popupwin.c index 0acb7453d..293305ed9 100644 --- a/src/popupwin.c +++ b/src/popupwin.c @@ -2313,10 +2313,8 @@ popup_apply_winupdate_clip(win_T *wp, popup_clip_T *cl) if (wp->w_height < 0) wp->w_height = 0; if (cl->clip_top_content > 0) - { + // w_winrow already points at the first visible row. wp->w_topline += cl->clip_top_content; - wp->w_winrow += cl->clip_top_content; - } } if (wp->w_popup_leftclip > 0 || wp->w_popup_rightclip > 0) { @@ -2982,6 +2980,10 @@ popup_adjust_position(win_T *wp) return; } + // Make w_winrow the first visible screen row (>= 0); the clipped-off top + // rows are recorded in w_popup_topoff. + wp->w_winrow += wp->w_popup_topoff; + #ifdef FEAT_IMAGE_SIXEL // Final winrow is now known: encode (or re-encode) the sixel image so it // fits within the screen and does not trigger sixel-scroll on terminals. @@ -6120,8 +6122,11 @@ popup_mark_opacity_zindex(win_T *wp) width = popup_width(wp); height = popup_height(wp); + // w_winrow/w_wincol are visible cells (>= 0); w_popup_topoff rows are + // clipped off the top. for (r = wp->w_winrow; - r < wp->w_winrow + height && r < screen_Rows; ++r) + r < wp->w_winrow + height - wp->w_popup_topoff + && r < screen_Rows; ++r) for (c = wp->w_wincol; c < wp->w_wincol + width - wp->w_popup_leftoff && c < screen_Columns; ++c) @@ -6417,7 +6422,7 @@ may_update_popup_mask(int type) continue; { - int mask_start = wp->w_winrow + wp->w_popup_topoff; + int mask_start = wp->w_winrow; int mask_end = mask_start + height; int mask_col_start = wp->w_wincol + wp->w_popup_leftclip; int mask_col_end = wp->w_wincol + width - wp->w_popup_leftoff @@ -7453,7 +7458,8 @@ update_popups(void (*win_update)(win_T *wp)) - wp->w_popup_leftoff; if (wp->w_wincol + left_extra < 0) left_extra = -wp->w_wincol; - wp->w_winrow += top_off; + // Move to the content top, skipping any clipped top border/padding. + wp->w_winrow += MAX(top_off - wp->w_popup_topoff, 0); wp->w_wincol += left_extra; // Draw the popup text, unless it's off screen. @@ -7486,15 +7492,9 @@ update_popups(void (*win_update)(win_T *wp)) wp->w_cursor.lnum = wp->w_botline - 1; } - wp->w_winrow -= top_off; + wp->w_winrow -= MAX(top_off - wp->w_popup_topoff, 0); wp->w_wincol -= left_extra; - // "clipwindow" with top-clip shifts all popup decorations down so the - // first visible row of the popup lands at the host window's top edge. - // Apply the shift before drawing borders/padding/etc. and restore at - // the end of this popup's iteration. - wp->w_winrow += wp->w_popup_topoff; - // Add offset for border and padding if not done already. if ((wp->w_flags & WFLAG_WCOL_OFF_ADDED) == 0) { @@ -7896,10 +7896,6 @@ update_popups(void (*win_update)(win_T *wp)) if (override_success) pop_highlight_overrides(); - // Undo the topoff shift applied before drawing the borders so the - // next iteration sees the popup's logical winrow. - wp->w_winrow -= wp->w_popup_topoff; - #ifdef FEAT_IMAGE // Emit the popup image right after this popup's decorations land in // ScreenLines. Popups are walked in zindex order, so a higher diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim index 364a50fe0..6aed7be3e 100644 --- a/src/testdir/test_popupwin.vim +++ b/src/testdir/test_popupwin.vim @@ -4685,6 +4685,37 @@ func Test_popup_clipwindow_hide_when_prop_off_screen() call prop_type_delete('clipprop') endfunc +func Test_popup_clipwindow_opacity_negative_winrow() + " A "clipwindow" popup with "opacity" whose textprop anchor scrolls above + " the window top must not index the opacity mask out of bounds. + call prop_type_add('clipprop', {}) + new + call setline(1, range(1, 200)->mapnew({_, v -> 'line ' .. v})) + call prop_add(5, 1, #{type: 'clipprop', length: 5}) + let host = win_getid() + + let id = popup_create(['aaa', 'bbb', 'ccc', 'ddd', 'eee'], #{ + \ textprop: 'clipprop', + \ textpropwin: host, + \ wrap: v:false, + \ fixed: v:true, + \ clipwindow: v:true, + \ opacity: 50, + \ }) + call assert_true(id > 0) + redraw + + " Scroll so the prop (line 5) sits a couple of lines above the top, so the + " popup is clipped at the host window's top edge. + call win_execute(host, 'normal! 8Gzt') + redraw + redraw + + call popup_close(id) + bwipe! + call prop_type_delete('clipprop') +endfunc + func Test_popup_clipwindow_top_clip() CheckScreendump diff --git a/src/version.c b/src/version.c index a5cca3061..a9bcdc88e 100644 --- a/src/version.c +++ b/src/version.c @@ -758,6 +758,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 843, /**/ 842, /**/ -- -- You received this message from the "vim_dev" maillist. Do not top-post! Type your reply below the text you are replying to. For more information, visit http://www.vim.org/maillist.php --- You received this message because you are subscribed to the Google Groups "vim_dev" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1wn0zT-00AUvO-SZ%40256bit.org.