fix(autocmd): potential TabClosed UAF, always set abuf

Problem: win_free_mem can free w_buffer (via qf_free_all), which may cause a
heap use-after-free if used as TabClosed's <abuf>. I think TabClosed is also the
only event to conditionally set <abuf> not based on event type.

Solution: use the buffer saved by the bufref. Fall back to curbuf if invalid,
like WinResized/WinScrolled.

NOTE: Not always equivalent if close_buffer autocmds switch buffers at the last
moment; previously <abuf> would be set to that buffer. Fixed in next commit.

https://github.com/neovim/neovim/actions/runs/21765657455/job/62800643599?pr=37758#step:9:159
for an example of qf_free_all being a nuisance.
This commit is contained in:
Sean Dewar
2026-02-06 23:13:04 +00:00
parent 5c156fdc64
commit d945a93d69
2 changed files with 4 additions and 3 deletions

View File

@@ -3263,7 +3263,6 @@ bool win_close_othertab(win_T *win, int free_buf, tabpage_T *tp, bool force)
}
// Free the memory used for the window.
buf_T *buf = win->w_buffer;
int dir;
win_free_mem(win, &dir, tp);
@@ -3276,7 +3275,8 @@ bool win_close_othertab(win_T *win, int free_buf, tabpage_T *tp, bool force)
if (has_event(EVENT_TABCLOSED)) {
char prev_idx[NUMBUFLEN];
vim_snprintf(prev_idx, NUMBUFLEN, "%i", free_tp_idx);
apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, buf);
apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false,
bufref.br_buf && bufref_valid(&bufref) ? bufref.br_buf : curbuf);
}
}
return true;

View File

@@ -59,13 +59,14 @@ describe('TabClosed', function()
setlocal bufhidden=wipe
tabnew
au TabClosed * ++once let g:tp_valid = nvim_tabpage_is_valid(s:tp)
\| let g:curbuf = bufnr()
\| let g:abuf = expand('<abuf>')
call nvim_buf_delete(g:buf, #{force: 1})
]])
eq(false, eval('g:tp_valid'))
eq(false, eval('nvim_buf_is_valid(g:buf)'))
eq('', eval('g:abuf'))
eq(eval('g:curbuf'), tonumber(eval('g:abuf'))) -- Falls back to curbuf.
exec([[
tabnew