diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 3ccd2bfa7d..1f703739ca 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -228,7 +228,7 @@ static Set(ptr_t) invalidated_terminals = SET_INIT; static void emit_termrequest(void **argv) { - Terminal *term = argv[0]; + handle_T buf_handle = (handle_T)(intptr_t)argv[0]; char *sequence = argv[1]; size_t sequence_length = (size_t)argv[2]; 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]; 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) { // 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 event onto the pending queue where it will be executed after the // terminal is refreshed and the pending scrollback is cleared. - multiqueue_put(term->pending.events, emit_termrequest, term, sequence, (void *)sequence_length, - pending_send, (void *)(intptr_t)row, (void *)(intptr_t)col, - (void *)(intptr_t)sb_deleted, (void *)(intptr_t)terminator); + multiqueue_put(term->pending.events, emit_termrequest, argv[0], argv[1], argv[2], + argv[3], argv[4], argv[5], argv[6], argv[7]); return; } @@ -262,7 +268,6 @@ static void emit_termrequest(void **argv) terminator == 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, &DICT_OBJ(data)); xfree(sequence); @@ -285,7 +290,7 @@ static void schedule_termrequest(Terminal *term) kv_init(*term->pending.send); 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), (void *)(intptr_t)term->termrequest_buffer.size, term->pending.send, (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->termrequest_buffer); vterm_free(term->vt); + xfree(term->pending.send); multiqueue_free(term->pending.events); xfree(term); *termpp = NULL; // coverity[dead-store] diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 24bf673db6..704ca3d793 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -658,6 +658,34 @@ describe(':terminal buffer', function() | ]]) 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) it('no heap-buffer-overflow when using jobstart("echo",{term=true}) #3161', function()