From 6e5bc9dd6c477a71020dcbe6c7a387673825d941 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Wed, 28 Aug 2024 15:47:27 +0200 Subject: [PATCH] Fix use-after-free InsertCompletionHide touches used register Before performing the insertion, InsertCompleter::insert calls try_accept() to accept any selected completion candidate. If there is one, we fire InsertCompletionHide. If that one modifies the register used by , the inserted StringViews will be dangling. Fix this by running try_insert first, and read from the register later. Note that we call try_accept() twice but that's fine. It would probably make more sense to copy the register before calling insert() but I don't think it matters. Closes #5220 --- src/input_handler.cc | 10 +++++++++- test/hooks/completion-hide-using-register/cmd | 1 + test/hooks/completion-hide-using-register/in | 1 + test/hooks/completion-hide-using-register/out | 2 ++ test/hooks/completion-hide-using-register/rc | 8 ++++++++ test/hooks/completion-hide-using-register/script | 7 +++++++ 6 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 test/hooks/completion-hide-using-register/cmd create mode 100644 test/hooks/completion-hide-using-register/in create mode 100644 test/hooks/completion-hide-using-register/out create mode 100644 test/hooks/completion-hide-using-register/rc create mode 100644 test/hooks/completion-hide-using-register/script diff --git a/src/input_handler.cc b/src/input_handler.cc index aa43783ea..85a8fbc17 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -15,6 +15,7 @@ #include "window.hh" #include "word_db.hh" +#include #include #include @@ -1310,7 +1311,7 @@ public: auto cp = key.codepoint(); if (not cp or key == Key::Escape) return; - insert(RegisterManager::instance()[*cp].get(context())); + insert([&] { return RegisterManager::instance()[*cp].get(context()); }); }, "enter register name", register_doc.str()); update_completions = false; } @@ -1430,6 +1431,13 @@ private: }, false); } + template + void insert(Func&& lazy_strings) + { + m_completer.try_accept(); + insert(std::forward(lazy_strings)()); + } + void insert(Codepoint key) { String str{key}; diff --git a/test/hooks/completion-hide-using-register/cmd b/test/hooks/completion-hide-using-register/cmd new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/test/hooks/completion-hide-using-register/cmd @@ -0,0 +1 @@ + diff --git a/test/hooks/completion-hide-using-register/in b/test/hooks/completion-hide-using-register/in new file mode 100644 index 000000000..fa11a6a9c --- /dev/null +++ b/test/hooks/completion-hide-using-register/in @@ -0,0 +1 @@ +echo diff --git a/test/hooks/completion-hide-using-register/out b/test/hooks/completion-hide-using-register/out new file mode 100644 index 000000000..97e70d260 --- /dev/null +++ b/test/hooks/completion-hide-using-register/out @@ -0,0 +1,2 @@ +echofoobar +echo diff --git a/test/hooks/completion-hide-using-register/rc b/test/hooks/completion-hide-using-register/rc new file mode 100644 index 000000000..8b579f63a --- /dev/null +++ b/test/hooks/completion-hide-using-register/rc @@ -0,0 +1,8 @@ +set-option global autocomplete insert +hook global InsertCompletionHide .+ %{ + evaluate-commands -draft -save-regs '"' %{ + select %val{hook_param} + set-register dquote foo bar + execute-keys + } +} diff --git a/test/hooks/completion-hide-using-register/script b/test/hooks/completion-hide-using-register/script new file mode 100644 index 000000000..a6096a993 --- /dev/null +++ b/test/hooks/completion-hide-using-register/script @@ -0,0 +1,7 @@ +ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }' +ui_in '{ "jsonrpc": "2.0", "method": "keys", "params": [ "Oe" ] }' +ui_out -until '{ "jsonrpc": "2.0", "method": "menu_show", "params": [[[{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "echo" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "cyan", "bg": "default", "underline": "default", "attributes": [] }, "contents": "out" }]], { "line": 0, "column": 0 }, { "fg": "white", "bg": "blue", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "white", "underline": "default", "attributes": [] }, "inline"] }' +ui_in '{ "jsonrpc": "2.0", "method": "keys", "params": [ "" ] }' +ui_out -until '{ "jsonrpc": "2.0", "method": "menu_select", "params": [1] }' +ui_in '{ "jsonrpc": "2.0", "method": "keys", "params": [ "\"" ] }' +ui_out -until '{ "jsonrpc": "2.0", "method": "menu_hide", "params": [] }'