aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregory Anders <8965202+gpanders@users.noreply.github.com>2023-11-30 08:04:33 -0600
committerGitHub <noreply@github.com>2023-11-30 08:04:33 -0600
commit884a83049b2c33e2b3b6cc5c9c7f6bf820b24a3d (patch)
tree85eb49d678e664927ca48a107e8089d863a647e5
parent01b91deec7b6113ae728ac3f7a55f64f57ddb3e0 (diff)
downloadrneovim-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.c41
-rw-r--r--test/functional/terminal/tui_spec.lua25
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.