mirror of
https://github.com/macvim-dev/macvim.git
synced 2026-06-11 15:37:29 +02:00
patch 9.2.0344: channel: ch_listen() can bind to network interface
Problem: channel: ch_listen() can bind to network interface
Solution: Only allow to use Unix domain sockets or localhost interface
(Zdenek Dohnal)
related: #19231
related: #19799
closes: #19973
Signed-off-by: Zdenek Dohnal <zdohnal@redhat.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
committed by
Christian Brabandt
parent
4b6f3f1d16
commit
962a540d76
@@ -1,4 +1,4 @@
|
||||
*builtin.txt* For Vim version 9.2. Last change: 2026 Apr 10
|
||||
*builtin.txt* For Vim version 9.2. Last change: 2026 Apr 14
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@@ -107,7 +107,8 @@ ch_getbufnr({handle}, {what}) Number get buffer number for {handle}/{what}
|
||||
ch_getjob({channel}) Job get the Job of {channel}
|
||||
ch_info({handle}) Dict info about channel {handle}
|
||||
ch_listen({address} [, {options}])
|
||||
Channel listen on {address}
|
||||
Channel listen on {address} - port on loopback
|
||||
or UNIX domain socket
|
||||
ch_log({msg} [, {handle}]) none write {msg} in the channel log file
|
||||
ch_logfile({fname} [, {mode}]) none start logging channel activity
|
||||
ch_open({address} [, {options}])
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
*channel.txt* For Vim version 9.2. Last change: 2026 Apr 06
|
||||
*channel.txt* For Vim version 9.2. Last change: 2026 Apr 14
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@@ -132,7 +132,7 @@ Start Vim and create a listening channel: >
|
||||
endfunc
|
||||
|
||||
" Start listening on port 8765
|
||||
let server = ch_listen('localhost:8765', {"callback": "OnAccept"})
|
||||
let server = ch_listen('8765', {"callback": "OnAccept"})
|
||||
|
||||
From another Vim instance (or any program) you can connect to it: >
|
||||
let channel = ch_open('localhost:8765')
|
||||
@@ -637,8 +637,7 @@ ch_info({handle}) *ch_info()*
|
||||
"status" "open", "buffered" or "closed", like
|
||||
ch_status()
|
||||
When opened with ch_open():
|
||||
"hostname" the hostname of the address
|
||||
"port" the port of the address
|
||||
"port" the port on loopback
|
||||
"path" the path of the Unix-domain socket
|
||||
"sock_status" "open" or "closed"
|
||||
"sock_mode" "NL", "RAW", "JSON" or "JS"
|
||||
@@ -668,14 +667,15 @@ ch_info({handle}) *ch_info()*
|
||||
Return type: dict<any>
|
||||
|
||||
ch_listen({address} [, {options}]) *E1573* *E1574* *ch_listen()*
|
||||
Listen on {address} for incoming channel connections.
|
||||
This creates a server-side channel, unlike |ch_open()|
|
||||
which connects to an existing server.
|
||||
Listen on {address} - port on loopback or UNIX domain socket
|
||||
for incoming channel connections. This creates a server-side
|
||||
channel, unlike |ch_open()|which connects to an existing server.
|
||||
Returns a Channel. Use |ch_status()| to check for failure.
|
||||
|
||||
{address} is a String, see |channel-address| for the possible
|
||||
accepted forms, however binding to all interfaces is not
|
||||
allowed for security reasons.
|
||||
accepted forms, however in case of TCP sockets it allows to
|
||||
set only a port and binds to loopback address for
|
||||
security reasons.
|
||||
Note: IPv6 is not yet supported.
|
||||
|
||||
If {options} is given it must be a |Dictionary|.
|
||||
|
||||
+14
-91
@@ -1400,8 +1400,7 @@ theend:
|
||||
channel_T *
|
||||
channel_listen_func(typval_T *argvars)
|
||||
{
|
||||
char_u *address;
|
||||
char_u *p;
|
||||
char_u *arg;
|
||||
char *rest;
|
||||
int port;
|
||||
int is_unix = FALSE;
|
||||
@@ -1409,62 +1408,31 @@ channel_listen_func(typval_T *argvars)
|
||||
channel_T *channel = NULL;
|
||||
|
||||
if (in_vim9script()
|
||||
&& (check_for_string_arg(argvars, 0) == FAIL
|
||||
|| check_for_opt_dict_arg(argvars, 1) == FAIL))
|
||||
&& check_for_string_arg(argvars, 0) == FAIL)
|
||||
return NULL;
|
||||
|
||||
address = tv_get_string(&argvars[0]);
|
||||
if (argvars[1].v_type != VAR_UNKNOWN
|
||||
&& check_for_nonnull_dict_arg(argvars, 1) == FAIL)
|
||||
return NULL;
|
||||
|
||||
if (*address == NUL)
|
||||
arg = tv_get_string(&argvars[0]);
|
||||
if (*arg == NUL)
|
||||
{
|
||||
semsg(_(e_invalid_argument_str), address);
|
||||
semsg(_(e_invalid_argument_str), arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!STRNCMP(address, "unix:", 5))
|
||||
if (!STRNCMP(arg, "unix:", 5))
|
||||
{
|
||||
is_unix = TRUE;
|
||||
address += 5;
|
||||
arg += 5;
|
||||
port = 0;
|
||||
}
|
||||
else if (*address == '[')
|
||||
{
|
||||
// ipv6 address
|
||||
p = vim_strchr(address + 1, ']');
|
||||
if (p == NULL || *++p != ':')
|
||||
{
|
||||
semsg(_(e_invalid_argument_str), address);
|
||||
return NULL;
|
||||
}
|
||||
port = strtol((char *)(p + 1), &rest, 10);
|
||||
if (port < 0 || port >= 65536 || *rest != NUL)
|
||||
{
|
||||
semsg(_(e_invalid_argument_str), address);
|
||||
return NULL;
|
||||
}
|
||||
// strip '[' and ']'
|
||||
++address;
|
||||
*(p - 1) = NUL;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ipv4 address
|
||||
p = vim_strchr(address, ':');
|
||||
if (p == NULL)
|
||||
{
|
||||
semsg(_(e_invalid_argument_str), address);
|
||||
return NULL;
|
||||
}
|
||||
port = strtol((char *)(p + 1), &rest, 10);
|
||||
port = strtol((char *)(arg), &rest, 10);
|
||||
if (port < 0 || port >= 65536 || *rest != NUL)
|
||||
{
|
||||
semsg(_(e_invalid_argument_str), address);
|
||||
semsg(_(e_invalid_argument_str), arg);
|
||||
return NULL;
|
||||
}
|
||||
*p = NUL;
|
||||
*arg = NUL;
|
||||
}
|
||||
|
||||
// parse options
|
||||
@@ -1481,9 +1449,9 @@ channel_listen_func(typval_T *argvars)
|
||||
}
|
||||
|
||||
if (is_unix)
|
||||
channel = channel_listen_unix((char *)address, NULL);
|
||||
channel = channel_listen_unix((char *)arg, NULL);
|
||||
else
|
||||
channel = channel_listen((char *)address, port, NULL);
|
||||
channel = channel_listen(port, NULL);
|
||||
if (channel != NULL)
|
||||
{
|
||||
opt.jo_set = JO_ALL;
|
||||
@@ -1501,7 +1469,6 @@ theend:
|
||||
*/
|
||||
channel_T *
|
||||
channel_listen(
|
||||
char *hostname,
|
||||
int port_in,
|
||||
void (*nb_close_cb)(void))
|
||||
{
|
||||
@@ -1513,12 +1480,6 @@ channel_listen(
|
||||
int val = 1;
|
||||
channel_T *channel;
|
||||
|
||||
if (hostname == NULL || *hostname == NUL)
|
||||
{
|
||||
ch_error(NULL, "Hostname/address not defined.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef MSWIN
|
||||
channel_init_winsock();
|
||||
#endif
|
||||
@@ -1535,42 +1496,7 @@ channel_listen(
|
||||
vim_memset((char *)&server, 0, sizeof(server));
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_port = htons(port_in);
|
||||
|
||||
#ifdef FEAT_IPV6
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *res = NULL;
|
||||
int err;
|
||||
|
||||
CLEAR_FIELD(hints);
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
if ((err = getaddrinfo(hostname, NULL, &hints, &res)) != 0)
|
||||
{
|
||||
ch_error(channel, "in getaddrinfo() in channel_listen()");
|
||||
PERROR(_(e_gethostbyname_in_channel_listen));
|
||||
channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(&server.sin_addr,
|
||||
&((struct sockaddr_in *)res->ai_addr)->sin_addr,
|
||||
sizeof(server.sin_addr));
|
||||
freeaddrinfo(res);
|
||||
#else
|
||||
if ((host = gethostbyname(hostname)) == NULL)
|
||||
{
|
||||
ch_error(channel, "in gethostbyname() in channel_listen()");
|
||||
PERROR(_(e_gethostbyname_in_channel_listen));
|
||||
channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *p;
|
||||
|
||||
// When using host->h_addr_list[0] directly ubsan warns for it to
|
||||
// not be aligned. First copy the pointer to avoid that.
|
||||
memcpy(&p, &host->h_addr_list[0], sizeof(p));
|
||||
memcpy((char *)&server.sin_addr, p, host->h_length);
|
||||
#endif
|
||||
server.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
|
||||
sd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sd == -1)
|
||||
@@ -1632,7 +1558,7 @@ channel_listen(
|
||||
channel->ch_listen = TRUE;
|
||||
channel->CH_SOCK_FD = (sock_T)sd;
|
||||
channel->ch_nb_close_cb = nb_close_cb;
|
||||
channel->ch_hostname = (char *)vim_strsave((char_u *)hostname);
|
||||
channel->ch_hostname = (char *)vim_strsave((char_u *)"");
|
||||
channel->ch_port = port_in;
|
||||
channel->ch_to_be_closed |= (1U << PART_SOCK);
|
||||
|
||||
@@ -3796,10 +3722,7 @@ channel_info(channel_T *channel, dict_T *dict)
|
||||
if (channel->ch_hostname != NULL)
|
||||
{
|
||||
if (channel->ch_port)
|
||||
{
|
||||
dict_add_string(dict, "hostname", (char_u *)channel->ch_hostname);
|
||||
dict_add_number(dict, "port", channel->ch_port);
|
||||
}
|
||||
else
|
||||
// Unix-domain socket.
|
||||
dict_add_string(dict, "path", (char_u *)channel->ch_hostname);
|
||||
|
||||
@@ -9,7 +9,7 @@ void free_unused_channels(int copyID, int mask);
|
||||
void channel_gui_register_all(void);
|
||||
channel_T *channel_open(const char *hostname, int port, int waittime, void (*nb_close_cb)(void));
|
||||
channel_T *channel_listen_func(typval_T *argvars);
|
||||
channel_T *channel_listen(char *hostname, int port_in, void (*nb_close_cb)(void));
|
||||
channel_T *channel_listen(int port_in, void (*nb_close_cb)(void));
|
||||
channel_T *channel_listen_unix(char *path, void (*nb_close_cb)(void));
|
||||
void ch_close_part(channel_T *channel, ch_part_T part);
|
||||
void channel_set_pipes(channel_T *channel, sock_T in, sock_T out, sock_T err);
|
||||
|
||||
@@ -2778,7 +2778,7 @@ endfunction
|
||||
|
||||
func Test_listen()
|
||||
call ch_log('Test_listen()')
|
||||
let server = ch_listen('127.0.0.1:12345', {'callback': function('s:test_listen_accept')})
|
||||
let server = ch_listen('12345', {'callback': function('s:test_listen_accept')})
|
||||
if ch_status(server) == 'fail'
|
||||
call assert_report("Can't listen channel")
|
||||
return
|
||||
@@ -2799,35 +2799,29 @@ func Test_listen()
|
||||
call assert_match('127.0.0.1:', g:server_received_addr)
|
||||
endfunc
|
||||
|
||||
func Test_listen_invalid_address()
|
||||
call ch_log('Test_listen_invalid_address()')
|
||||
|
||||
" empty address
|
||||
call assert_fails("call ch_listen('')", 'E475:')
|
||||
func Test_listen_invalid_port()
|
||||
call ch_log('Test_listen_invalid_port()')
|
||||
|
||||
" missing port
|
||||
call assert_fails("call ch_listen('localhost')", 'E475:')
|
||||
call assert_fails("call ch_listen('')", 'E475:')
|
||||
|
||||
" port number too large
|
||||
call assert_fails("call ch_listen('localhost:99999')", 'E475:')
|
||||
call assert_fails("call ch_listen('99999')", 'E475:')
|
||||
|
||||
" port number zero should let the OS assign an available port
|
||||
let ch = ch_listen('localhost:0')
|
||||
let ch = ch_listen('0')
|
||||
call assert_equal('open', ch_status(ch))
|
||||
call assert_notequal(0, ch_info(ch).port)
|
||||
call ch_close(ch)
|
||||
|
||||
" port number negative
|
||||
call assert_fails("call ch_listen('localhost:-1')", 'E475:')
|
||||
call assert_fails("call ch_listen('-1')", 'E475:')
|
||||
endfunc
|
||||
|
||||
" invalid ipv6 format (missing closing bracket)
|
||||
call assert_fails("call ch_listen('[::1:8765')", 'E475:')
|
||||
|
||||
" invalid ipv6 format (missing port)
|
||||
call assert_fails("call ch_listen('[::1]')", 'E475:')
|
||||
|
||||
" TODO: IPv6 should actually work
|
||||
call assert_fails("call ch_listen('[::1]:9999')", 'E1574:')
|
||||
func Test_listen_info_no_hostname()
|
||||
let ch = ch_listen('0')
|
||||
call assert_fails("call ch_info(ch).hostname", 'E716:')
|
||||
call ch_close(ch)
|
||||
endfunc
|
||||
|
||||
func Test_channel_lsp_mode()
|
||||
|
||||
@@ -734,6 +734,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
344,
|
||||
/**/
|
||||
343,
|
||||
/**/
|
||||
|
||||
Reference in New Issue
Block a user