mirror of
https://github.com/neovim/neovim.git
synced 2026-04-24 08:15:41 +00:00
fix(terminal): handle closing terminal with pending TermRequest (#37227)
Problem: Destroying a terminal with pending TermRequest leads to
heap-use-after-free when processing TermRequest afterwards.
Solution: Store the buffer handle instead of the Terminal pointer in the
pending TermRequest event, and don't emit TermRequest if the
terminal is already closed.
This commit is contained in:
@@ -228,7 +228,7 @@ static Set(ptr_t) invalidated_terminals = SET_INIT;
|
|||||||
|
|
||||||
static void emit_termrequest(void **argv)
|
static void emit_termrequest(void **argv)
|
||||||
{
|
{
|
||||||
Terminal *term = argv[0];
|
handle_T buf_handle = (handle_T)(intptr_t)argv[0];
|
||||||
char *sequence = argv[1];
|
char *sequence = argv[1];
|
||||||
size_t sequence_length = (size_t)argv[2];
|
size_t sequence_length = (size_t)argv[2];
|
||||||
StringBuilder *pending_send = argv[3];
|
StringBuilder *pending_send = argv[3];
|
||||||
@@ -237,14 +237,20 @@ static void emit_termrequest(void **argv)
|
|||||||
size_t sb_deleted = (size_t)(intptr_t)argv[6];
|
size_t sb_deleted = (size_t)(intptr_t)argv[6];
|
||||||
VTermTerminator terminator = (VTermTerminator)(intptr_t)argv[7];
|
VTermTerminator terminator = (VTermTerminator)(intptr_t)argv[7];
|
||||||
|
|
||||||
|
buf_T *buf = handle_get_buffer(buf_handle);
|
||||||
|
if (!buf || buf->terminal == NULL) { // Terminal already closed.
|
||||||
|
xfree(sequence);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Terminal *term = buf->terminal;
|
||||||
|
|
||||||
if (term->sb_pending > 0) {
|
if (term->sb_pending > 0) {
|
||||||
// Don't emit the event while there is pending scrollback because we need
|
// Don't emit the event while there is pending scrollback because we need
|
||||||
// the buffer contents to be fully updated. If this is the case, schedule
|
// the buffer contents to be fully updated. If this is the case, schedule
|
||||||
// the event onto the pending queue where it will be executed after the
|
// the event onto the pending queue where it will be executed after the
|
||||||
// terminal is refreshed and the pending scrollback is cleared.
|
// terminal is refreshed and the pending scrollback is cleared.
|
||||||
multiqueue_put(term->pending.events, emit_termrequest, term, sequence, (void *)sequence_length,
|
multiqueue_put(term->pending.events, emit_termrequest, argv[0], argv[1], argv[2],
|
||||||
pending_send, (void *)(intptr_t)row, (void *)(intptr_t)col,
|
argv[3], argv[4], argv[5], argv[6], argv[7]);
|
||||||
(void *)(intptr_t)sb_deleted, (void *)(intptr_t)terminator);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +268,6 @@ static void emit_termrequest(void **argv)
|
|||||||
terminator ==
|
terminator ==
|
||||||
VTERM_TERMINATOR_BEL ? STATIC_CSTR_AS_OBJ("\x07") : STATIC_CSTR_AS_OBJ("\x1b\\"));
|
VTERM_TERMINATOR_BEL ? STATIC_CSTR_AS_OBJ("\x07") : STATIC_CSTR_AS_OBJ("\x1b\\"));
|
||||||
|
|
||||||
buf_T *buf = handle_get_buffer(term->buf_handle);
|
|
||||||
apply_autocmds_group(EVENT_TERMREQUEST, NULL, NULL, true, AUGROUP_ALL, buf, NULL,
|
apply_autocmds_group(EVENT_TERMREQUEST, NULL, NULL, true, AUGROUP_ALL, buf, NULL,
|
||||||
&DICT_OBJ(data));
|
&DICT_OBJ(data));
|
||||||
xfree(sequence);
|
xfree(sequence);
|
||||||
@@ -285,7 +290,7 @@ static void schedule_termrequest(Terminal *term)
|
|||||||
kv_init(*term->pending.send);
|
kv_init(*term->pending.send);
|
||||||
|
|
||||||
int line = row_to_linenr(term, term->cursor.row);
|
int line = row_to_linenr(term, term->cursor.row);
|
||||||
multiqueue_put(main_loop.events, emit_termrequest, term,
|
multiqueue_put(main_loop.events, emit_termrequest, (void *)(intptr_t)term->buf_handle,
|
||||||
xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size),
|
xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size),
|
||||||
(void *)(intptr_t)term->termrequest_buffer.size, term->pending.send,
|
(void *)(intptr_t)term->termrequest_buffer.size, term->pending.send,
|
||||||
(void *)(intptr_t)line, (void *)(intptr_t)term->cursor.col,
|
(void *)(intptr_t)line, (void *)(intptr_t)term->cursor.col,
|
||||||
@@ -1070,6 +1075,7 @@ void terminal_destroy(Terminal **termpp)
|
|||||||
kv_destroy(term->selection);
|
kv_destroy(term->selection);
|
||||||
kv_destroy(term->termrequest_buffer);
|
kv_destroy(term->termrequest_buffer);
|
||||||
vterm_free(term->vt);
|
vterm_free(term->vt);
|
||||||
|
xfree(term->pending.send);
|
||||||
multiqueue_free(term->pending.events);
|
multiqueue_free(term->pending.events);
|
||||||
xfree(term);
|
xfree(term);
|
||||||
*termpp = NULL; // coverity[dead-store]
|
*termpp = NULL; // coverity[dead-store]
|
||||||
|
|||||||
@@ -658,6 +658,34 @@ describe(':terminal buffer', function()
|
|||||||
|
|
|
|
||||||
]])
|
]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe('no heap-use-after-free after', function()
|
||||||
|
local term
|
||||||
|
|
||||||
|
before_each(function()
|
||||||
|
term = exec_lua(function()
|
||||||
|
vim.api.nvim_create_autocmd('TermRequest', { callback = function() end })
|
||||||
|
return vim.api.nvim_open_term(0, {})
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('wiping buffer with pending TermRequest #37226', function()
|
||||||
|
exec_lua(function()
|
||||||
|
vim.api.nvim_chan_send(term, '\027]8;;https://example.com\027\\')
|
||||||
|
vim.api.nvim_buf_delete(0, { force = true })
|
||||||
|
end)
|
||||||
|
assert_alive()
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('unloading buffer with pending TermRequest #37226', function()
|
||||||
|
api.nvim_create_buf(true, false) -- Create a buffer to switch to.
|
||||||
|
exec_lua(function()
|
||||||
|
vim.api.nvim_chan_send(term, '\027]8;;https://example.com\027\\')
|
||||||
|
vim.api.nvim_buf_delete(0, { force = true, unload = true })
|
||||||
|
end)
|
||||||
|
assert_alive()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('no heap-buffer-overflow when using jobstart("echo",{term=true}) #3161', function()
|
it('no heap-buffer-overflow when using jobstart("echo",{term=true}) #3161', function()
|
||||||
|
|||||||
Reference in New Issue
Block a user