mirror of
https://github.com/neovim/neovim.git
synced 2025-09-11 22:08:18 +00:00
fix(api): fix nvim_buf_set_text heap-use-after-free (#19644)
The line returned but ml_get_buf() may be freed by another call to ml_get_buf(), so it is necessary to make a copy.
This commit is contained in:
@@ -565,27 +565,33 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *str_at_start = (char *)ml_get_buf(buf, (linenr_T)start_row, false);
|
char *str_at_start = NULL;
|
||||||
if (start_col < 0 || (size_t)start_col > strlen(str_at_start)) {
|
char *str_at_end = NULL;
|
||||||
|
|
||||||
|
// Another call to ml_get_buf() may free the line, so make a copy.
|
||||||
|
str_at_start = xstrdup((char *)ml_get_buf(buf, (linenr_T)start_row, false));
|
||||||
|
size_t len_at_start = strlen(str_at_start);
|
||||||
|
if (start_col < 0 || (size_t)start_col > len_at_start) {
|
||||||
api_set_error(err, kErrorTypeValidation, "start_col out of bounds");
|
api_set_error(err, kErrorTypeValidation, "start_col out of bounds");
|
||||||
return;
|
goto early_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *str_at_end = (char *)ml_get_buf(buf, (linenr_T)end_row, false);
|
// Another call to ml_get_buf() may free the line, so make a copy.
|
||||||
|
str_at_end = xstrdup((char *)ml_get_buf(buf, (linenr_T)end_row, false));
|
||||||
size_t len_at_end = strlen(str_at_end);
|
size_t len_at_end = strlen(str_at_end);
|
||||||
if (end_col < 0 || (size_t)end_col > len_at_end) {
|
if (end_col < 0 || (size_t)end_col > len_at_end) {
|
||||||
api_set_error(err, kErrorTypeValidation, "end_col out of bounds");
|
api_set_error(err, kErrorTypeValidation, "end_col out of bounds");
|
||||||
return;
|
goto early_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start_row > end_row || (end_row == start_row && start_col > end_col)) {
|
if (start_row > end_row || (end_row == start_row && start_col > end_col)) {
|
||||||
api_set_error(err, kErrorTypeValidation, "start is higher than end");
|
api_set_error(err, kErrorTypeValidation, "start is higher than end");
|
||||||
return;
|
goto early_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool disallow_nl = (channel_id != VIML_INTERNAL_CALL);
|
bool disallow_nl = (channel_id != VIML_INTERNAL_CALL);
|
||||||
if (!check_string_array(replacement, disallow_nl, err)) {
|
if (!check_string_array(replacement, disallow_nl, err)) {
|
||||||
return;
|
goto early_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t new_len = replacement.size;
|
size_t new_len = replacement.size;
|
||||||
@@ -597,7 +603,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
|
|||||||
if (start_row == end_row) {
|
if (start_row == end_row) {
|
||||||
old_byte = (bcount_t)end_col - start_col;
|
old_byte = (bcount_t)end_col - start_col;
|
||||||
} else {
|
} else {
|
||||||
old_byte += (bcount_t)strlen(str_at_start) - start_col;
|
old_byte += (bcount_t)len_at_start - start_col;
|
||||||
for (int64_t i = 1; i < end_row - start_row; i++) {
|
for (int64_t i = 1; i < end_row - start_row; i++) {
|
||||||
int64_t lnum = start_row + i;
|
int64_t lnum = start_row + i;
|
||||||
|
|
||||||
@@ -611,7 +617,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
|
|||||||
String last_item = replacement.items[replacement.size - 1].data.string;
|
String last_item = replacement.items[replacement.size - 1].data.string;
|
||||||
|
|
||||||
size_t firstlen = (size_t)start_col + first_item.size;
|
size_t firstlen = (size_t)start_col + first_item.size;
|
||||||
size_t last_part_len = strlen(str_at_end) - (size_t)end_col;
|
size_t last_part_len = len_at_end - (size_t)end_col;
|
||||||
if (replacement.size == 1) {
|
if (replacement.size == 1) {
|
||||||
firstlen += last_part_len;
|
firstlen += last_part_len;
|
||||||
}
|
}
|
||||||
@@ -751,6 +757,10 @@ end:
|
|||||||
xfree(lines);
|
xfree(lines);
|
||||||
aucmd_restbuf(&aco);
|
aucmd_restbuf(&aco);
|
||||||
try_end(err);
|
try_end(err);
|
||||||
|
|
||||||
|
early_end:
|
||||||
|
xfree(str_at_start);
|
||||||
|
xfree(str_at_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a range from the buffer.
|
/// Gets a range from the buffer.
|
||||||
|
@@ -7,6 +7,7 @@ local meths = helpers.meths
|
|||||||
local funcs = helpers.funcs
|
local funcs = helpers.funcs
|
||||||
local request = helpers.request
|
local request = helpers.request
|
||||||
local exc_exec = helpers.exc_exec
|
local exc_exec = helpers.exc_exec
|
||||||
|
local exec_lua = helpers.exec_lua
|
||||||
local feed_command = helpers.feed_command
|
local feed_command = helpers.feed_command
|
||||||
local insert = helpers.insert
|
local insert = helpers.insert
|
||||||
local NIL = helpers.NIL
|
local NIL = helpers.NIL
|
||||||
@@ -565,6 +566,17 @@ describe('api/buf', function()
|
|||||||
eq('start is higher than end', pcall_err(set_text, 1, 0, 0, 0, {}))
|
eq('start is higher than end', pcall_err(set_text, 1, 0, 0, 0, {}))
|
||||||
eq('start is higher than end', pcall_err(set_text, 0, 1, 0, 0, {}))
|
eq('start is higher than end', pcall_err(set_text, 0, 1, 0, 0, {}))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('no heap-use-after-free when called consecutively #19643', function()
|
||||||
|
set_text(0, 0, 0, 0, {'one', '', '', 'two'})
|
||||||
|
eq({'one', '', '', 'two'}, get_lines(0, 4, true))
|
||||||
|
meths.win_set_cursor(0, {1, 0})
|
||||||
|
exec_lua([[
|
||||||
|
vim.api.nvim_buf_set_text(0, 0, 3, 1, 0, {''})
|
||||||
|
vim.api.nvim_buf_set_text(0, 0, 3, 1, 0, {''})
|
||||||
|
]])
|
||||||
|
eq({'one', 'two'}, get_lines(0, 2, true))
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('nvim_buf_get_text', function()
|
describe('nvim_buf_get_text', function()
|
||||||
|
Reference in New Issue
Block a user