api: fix use-after-free in nvim_chan_send

This commit is contained in:
Björn Linse
2021-03-23 20:09:36 +01:00
parent 8e496b9dfd
commit 3d6831a30a
4 changed files with 26 additions and 16 deletions

View File

@@ -1333,7 +1333,8 @@ void nvim_chan_send(Integer chan, String data, Error *err)
return; return;
} }
channel_send((uint64_t)chan, data.data, data.size, &error); channel_send((uint64_t)chan, data.data, data.size,
false, &error);
if (error) { if (error) {
api_set_error(err, kErrorTypeValidation, "%s", error); api_set_error(err, kErrorTypeValidation, "%s", error);
} }

View File

@@ -499,48 +499,54 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output,
} }
/// @param data will be consumed /// @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); Channel *chan = find_channel(id);
size_t written = 0;
if (!chan) { if (!chan) {
*error = _(e_invchan); *error = _(e_invchan);
goto err; goto retfree;
} }
if (chan->streamtype == kChannelStreamStderr) { if (chan->streamtype == kChannelStreamStderr) {
if (chan->stream.err.closed) { if (chan->stream.err.closed) {
*error = _("Can't send data to closed stream"); *error = _("Can't send data to closed stream");
goto err; goto retfree;
} }
// unbuffered write // unbuffered write
size_t written = fwrite(data, len, 1, stderr); written = len * fwrite(data, len, 1, stderr);
xfree(data); goto retfree;
return len * written;
} }
if (chan->streamtype == kChannelStreamInternal && chan->term) { if (chan->streamtype == kChannelStreamInternal && chan->term) {
terminal_receive(chan->term, data, len); terminal_receive(chan->term, data, len);
return len; written = len;
goto retfree;
} }
Stream *in = channel_instream(chan); Stream *in = channel_instream(chan);
if (in->closed) { if (in->closed) {
*error = _("Can't send data to closed stream"); *error = _("Can't send data to closed stream");
goto err; goto retfree;
} }
if (chan->is_rpc) { if (chan->is_rpc) {
*error = _("Can't send raw data to rpc channel"); *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; return wstream_write(in, buf) ? len : 0;
err: retfree:
xfree(data); if (data_owned) {
return 0; xfree(data);
}
return written;
} }
/// Convert binary byte array to a readfile()-style list /// Convert binary byte array to a readfile()-style list

View File

@@ -916,7 +916,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} }
uint64_t id = argvars[0].vval.v_number; uint64_t id = argvars[0].vval.v_number;
const char *error = NULL; 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) { if (error) {
EMSG(error); EMSG(error);
} }

View File

@@ -963,8 +963,10 @@ describe('jobs', function()
return rv return rv
end end
local j
local function send(str) 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 end
before_each(function() before_each(function()
@@ -979,6 +981,7 @@ describe('jobs', function()
nvim('command', 'let g:job_opts.pty = 1') nvim('command', 'let g:job_opts.pty = 1')
nvim('command', 'let exec = [expand("<cfile>:p")]') nvim('command', 'let exec = [expand("<cfile>:p")]')
nvim('command', "let j = jobstart(exec, g:job_opts)") nvim('command', "let j = jobstart(exec, g:job_opts)")
j = eval'j'
eq('tty ready', next_chunk()) eq('tty ready', next_chunk())
end) end)