diff options
author | Gregory Anders <8965202+gpanders@users.noreply.github.com> | 2023-11-30 08:04:33 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-30 08:04:33 -0600 |
commit | 884a83049b2c33e2b3b6cc5c9c7f6bf820b24a3d (patch) | |
tree | 85eb49d678e664927ca48a107e8089d863a647e5 | |
parent | 01b91deec7b6113ae728ac3f7a55f64f57ddb3e0 (diff) | |
download | rneovim-884a83049b2c33e2b3b6cc5c9c7f6bf820b24a3d.tar.gz rneovim-884a83049b2c33e2b3b6cc5c9c7f6bf820b24a3d.tar.bz2 rneovim-884a83049b2c33e2b3b6cc5c9c7f6bf820b24a3d.zip |
fix(tui): grow termkey's internal buffer for large escape sequences (#26309)
Some escape sequences (in particular, OSC 52 paste responses) can be
very large, even unbounded in length. These can easily overflow
termkey's internal buffer. In order to process these long sequences,
dynamically grow termkey's internal buffer.
-rw-r--r-- | src/nvim/tui/input.c | 41 | ||||
-rw-r--r-- | test/functional/terminal/tui_spec.lua | 25 |
2 files changed, 59 insertions, 7 deletions
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 639bfc0f79..7ef10c0acd 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -27,6 +27,11 @@ #define READ_STREAM_SIZE 0xfff #define KEY_BUFFER_SIZE 0xfff +/// Size of libtermkey's internal input buffer. The buffer may grow larger than +/// this when processing very long escape sequences, but will shrink back to +/// this size afterward +#define INPUT_BUFFER_SIZE 256 + static const struct kitty_key_map_entry { int key; const char *name; @@ -139,6 +144,7 @@ void tinput_init(TermInput *input, Loop *loop) input->tk = termkey_new_abstract(term, TERMKEY_FLAG_UTF8 | TERMKEY_FLAG_NOSTART); + termkey_set_buffer_size(input->tk, INPUT_BUFFER_SIZE); termkey_hook_terminfo_getstr(input->tk, input->tk_ti_hook_fn, input); termkey_start(input->tk); @@ -147,7 +153,6 @@ void tinput_init(TermInput *input, Loop *loop) // setup input handle rstream_init_fd(loop, &input->read_stream, input->in_fd, READ_STREAM_SIZE); - termkey_set_buffer_size(input->tk, rbuffer_capacity(input->read_stream.buffer)); // initialize a timer handle for handling ESC with libtermkey time_watcher_init(loop, &input->timer_handle, input); @@ -691,20 +696,42 @@ static void handle_raw_buffer(TermInput *input, bool force) } // Push through libtermkey (translates to "<keycode>" strings, etc.). RBUFFER_UNTIL_EMPTY(input->read_stream.buffer, ptr, len) { - size_t consumed = termkey_push_bytes(input->tk, ptr, MIN(count, len)); - // termkey_push_bytes can return (size_t)-1, so it is possible that - // `consumed > rbuffer_size(input->read_stream.buffer)`, but since tk_getkeys is - // called soon, it shouldn't happen. + const size_t size = MIN(count, len); + if (size > termkey_get_buffer_remaining(input->tk)) { + // We are processing a very long escape sequence. Increase termkey's + // internal buffer size. We don't handle out of memory situations so + // assert the result + const size_t delta = size - termkey_get_buffer_remaining(input->tk); + const size_t bufsize = termkey_get_buffer_size(input->tk); + const bool success = termkey_set_buffer_size(input->tk, MAX(bufsize + delta, bufsize * 2)); + assert(success); + } + + size_t consumed = termkey_push_bytes(input->tk, ptr, size); + + // We resize termkey's buffer when it runs out of space, so this should + // never happen assert(consumed <= rbuffer_size(input->read_stream.buffer)); rbuffer_consumed(input->read_stream.buffer, consumed); - // Process the keys now: there is no guarantee `count` will - // fit into libtermkey's input buffer. + + // Process the input buffer now for any keys tk_getkeys(input, false); + if (!(count -= consumed)) { break; } } } while (rbuffer_size(input->read_stream.buffer)); + + const size_t tk_size = termkey_get_buffer_size(input->tk); + const size_t tk_remaining = termkey_get_buffer_remaining(input->tk); + const size_t tk_count = tk_size - tk_remaining; + if (tk_count < INPUT_BUFFER_SIZE && tk_size > INPUT_BUFFER_SIZE) { + // If the termkey buffer was resized to handle a large input sequence then + // shrink it back down to its original size. + const bool success = termkey_set_buffer_size(input->tk, INPUT_BUFFER_SIZE); + assert(success); + } } static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, void *data, bool eof) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index b17eed00f9..96ae0c4662 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -2557,6 +2557,31 @@ describe("TUI", function() end) end) + it('does not crash on large inputs #26099', function() + nvim_tui() + + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + | + {3:-- TERMINAL --} | + ]]) + + feed_data(string.format('\027]52;c;%s\027\\', string.rep('A', 8192))) + + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + | + {3:-- TERMINAL --} | + ]], unchanged=true} + end) end) -- See test/unit/tui_spec.lua for unit tests. |