]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0344: channel: ch_listen() can bind to network interface v9.2.0344
authorZdenek Dohnal <zdohnal@redhat.com>
Tue, 14 Apr 2026 16:37:25 +0000 (16:37 +0000)
committerChristian Brabandt <cb@256bit.org>
Tue, 14 Apr 2026 16:37:25 +0000 (16:37 +0000)
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>
runtime/doc/builtin.txt
runtime/doc/channel.txt
src/channel.c
src/proto/channel.pro
src/testdir/test_channel.vim
src/version.c

index eb802ed75e89a8a6881daef8472f0556076f59b8..f929b9e4c640685407ba2d33a3bbad841e840c30 100644 (file)
@@ -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}])
index 55b95c632f168dc68f73fbd516816aafbd7c3a05..b8aaa92768ee3ad9ba0d7cc5ae9481ea80ca3647 100644 (file)
@@ -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|.
index 6b4bd3cf2b7c1e7ffe662ac0c191f5e709b9e90b..edf6d84c7af41f804a0547cbecf2432d8f27975e 100644 (file)
@@ -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))
-       return NULL;
-
-    address = tv_get_string(&argvars[0]);
-    if (argvars[1].v_type != VAR_UNKNOWN
-           && check_for_nonnull_dict_arg(argvars, 1) == FAIL)
+           && check_for_string_arg(argvars, 0) == 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);
index d0c2cd04245ee971cc5894970f69488acb9e7423..0b5d17a137421b42ca28a014bd1f5d11d915ad6e 100644 (file)
@@ -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);
index 6cd55ca9f928367abfa60b4fe99717ba54c80bd1..9110a141c7c0381532a1028a10fc330af1b1066b 100644 (file)
@@ -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:')
-
-    " 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:')
+    call assert_fails("call ch_listen('-1')", 'E475:')
+endfunc
 
-    " 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()
index 0028692f283f0d1bbd011f001ae4c0d92a155798..1694045111334a3d5c1613899e6f947ce1684ec4 100644 (file)
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    344,
 /**/
     343,
 /**/