aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjörn Linse <bjorn.linse@gmail.com>2021-03-23 20:09:36 +0100
committerBjörn Linse <bjorn.linse@gmail.com>2021-04-04 18:11:48 +0200
commit3d6831a30a6248b44be86e3b149eaaf4bb05c0a4 (patch)
treee30d21336ed332f8d36be172e9883d3b0fbff180
parent8e496b9dfd5b644f06536afea63e3088b9d83cf8 (diff)
downloadrneovim-3d6831a30a6248b44be86e3b149eaaf4bb05c0a4.tar.gz
rneovim-3d6831a30a6248b44be86e3b149eaaf4bb05c0a4.tar.bz2
rneovim-3d6831a30a6248b44be86e3b149eaaf4bb05c0a4.zip
api: fix use-after-free in nvim_chan_send
-rw-r--r--src/nvim/api/vim.c3
-rw-r--r--src/nvim/channel.c32
-rw-r--r--src/nvim/eval/funcs.c2
-rw-r--r--test/functional/core/job_spec.lua5
4 files changed, 26 insertions, 16 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 3ae944de4d..09496f5d0a 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1333,7 +1333,8 @@ void nvim_chan_send(Integer chan, String data, Error *err)
return;
}
- channel_send((uint64_t)chan, data.data, data.size, &error);
+ channel_send((uint64_t)chan, data.data, data.size,
+ false, &error);
if (error) {
api_set_error(err, kErrorTypeValidation, "%s", error);
}
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index 7a08ba58d0..22eb31513d 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -499,48 +499,54 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output,
}
/// @param data will be consumed
-size_t channel_send(uint64_t id, char *data, size_t len, const char **error)
+size_t channel_send(uint64_t id, char *data, size_t len,
+ bool data_owned, const char **error)
{
Channel *chan = find_channel(id);
+ size_t written = 0;
if (!chan) {
*error = _(e_invchan);
- goto err;
+ goto retfree;
}
if (chan->streamtype == kChannelStreamStderr) {
if (chan->stream.err.closed) {
*error = _("Can't send data to closed stream");
- goto err;
+ goto retfree;
}
// unbuffered write
- size_t written = fwrite(data, len, 1, stderr);
- xfree(data);
- return len * written;
+ written = len * fwrite(data, len, 1, stderr);
+ goto retfree;
}
if (chan->streamtype == kChannelStreamInternal && chan->term) {
terminal_receive(chan->term, data, len);
- return len;
+ written = len;
+ goto retfree;
}
Stream *in = channel_instream(chan);
if (in->closed) {
*error = _("Can't send data to closed stream");
- goto err;
+ goto retfree;
}
if (chan->is_rpc) {
*error = _("Can't send raw data to rpc channel");
- goto err;
+ goto retfree;
}
- WBuffer *buf = wstream_new_buffer(data, len, 1, xfree);
+ // write can be delayed indefinitely, so always use an allocated buffer
+ WBuffer *buf = wstream_new_buffer(data_owned ? data : xmemdup(data, len),
+ len, 1, xfree);
return wstream_write(in, buf) ? len : 0;
-err:
- xfree(data);
- return 0;
+retfree:
+ if (data_owned) {
+ xfree(data);
+ }
+ return written;
}
/// Convert binary byte array to a readfile()-style list
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 51e5a27348..a51618b994 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -916,7 +916,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
uint64_t id = argvars[0].vval.v_number;
const char *error = NULL;
- rettv->vval.v_number = channel_send(id, input, input_len, &error);
+ rettv->vval.v_number = channel_send(id, input, input_len, true, &error);
if (error) {
EMSG(error);
}
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua
index b59d87eb12..9de0d08e79 100644
--- a/test/functional/core/job_spec.lua
+++ b/test/functional/core/job_spec.lua
@@ -963,8 +963,10 @@ describe('jobs', function()
return rv
end
+ local j
local function send(str)
- nvim('command', 'call jobsend(j, "'..str..'")')
+ -- check no nvim_chan_free double free with pty job (#14198)
+ meths.chan_send(j, str)
end
before_each(function()
@@ -979,6 +981,7 @@ describe('jobs', function()
nvim('command', 'let g:job_opts.pty = 1')
nvim('command', 'let exec = [expand("<cfile>:p")]')
nvim('command', "let j = jobstart(exec, g:job_opts)")
+ j = eval'j'
eq('tty ready', next_chunk())
end)