]> git.ipfire.org Git - thirdparty/openssh-portable.git/commitdiff
upstream: add a "global" ChannelTimeout type to ssh(1) and sshd(8)
authordjm@openbsd.org <djm@openbsd.org>
Tue, 9 Jan 2024 22:19:00 +0000 (22:19 +0000)
committerDamien Miller <djm@mindrot.org>
Tue, 9 Jan 2024 22:24:53 +0000 (09:24 +1100)
that watches all open channels and will close all open channels if there is
no traffic on any of them for the specified interval. This is in addition to
the existing per-channel timeouts added a few releases ago.

This supports use-cases like having a session + x11 forwarding channel
open where one may be idle for an extended period but the other is
actively used. The global timeout would allow closing both channels when
both have been idle for too long.

ok dtucker@

OpenBSD-Commit-ID: 0054157d24d2eaa5dc1a9a9859afefc13d1d7eb3

channels.c
ssh_config.5
sshd_config.5

index 20f31dadd782659d66cc609715710c34fb11377b..6862556be74f9b8837af7354648acfb82258120a 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.c,v 1.435 2023/12/18 14:47:20 djm Exp $ */
+/* $OpenBSD: channels.c,v 1.436 2024/01/09 22:19:00 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -214,6 +214,9 @@ struct ssh_channels {
        /* Channel timeouts by type */
        struct ssh_channel_timeout *timeouts;
        size_t ntimeouts;
+       /* Global timeout for all OPEN channels */
+       int global_deadline;
+       time_t lastused;
 };
 
 /* helper */
@@ -316,6 +319,11 @@ channel_add_timeout(struct ssh *ssh, const char *type_pattern,
 {
        struct ssh_channels *sc = ssh->chanctxt;
 
+       if (strcmp(type_pattern, "global") == 0) {
+               debug2_f("global channel timeout %d seconds", timeout_secs);
+               sc->global_deadline = timeout_secs;
+               return;
+       }
        debug2_f("channel type \"%s\" timeout %d seconds",
            type_pattern, timeout_secs);
        sc->timeouts = xrecallocarray(sc->timeouts, sc->ntimeouts,
@@ -376,6 +384,38 @@ channel_set_xtype(struct ssh *ssh, int id, const char *xctype)
            c->inactive_deadline);
 }
 
+/*
+ * update "last used" time on a channel.
+ * NB. nothing else should update lastused except to clear it.
+ */
+static void
+channel_set_used_time(struct ssh *ssh, Channel *c)
+{
+       ssh->chanctxt->lastused = monotime();
+       if (c != NULL)
+               c->lastused = ssh->chanctxt->lastused;
+}
+
+/*
+ * Get the time at which a channel is due to time out for inactivity.
+ * Returns 0 if the channel is not due to time out ever.
+ */
+static time_t
+channel_get_expiry(struct ssh *ssh, Channel *c)
+{
+       struct ssh_channels *sc = ssh->chanctxt;
+       time_t expiry = 0, channel_expiry;
+
+       if (sc->lastused != 0 && sc->global_deadline != 0)
+               expiry = sc->lastused + sc->global_deadline;
+       if (c->lastused != 0 && c->inactive_deadline != 0) {
+               channel_expiry = c->lastused + c->inactive_deadline;
+               if (expiry == 0 || channel_expiry < expiry)
+                       expiry = channel_expiry;
+       }
+       return expiry;
+}
+
 /*
  * Register filedescriptors for a channel, used when allocating a channel or
  * when the channel consumer/producer is ready, e.g. shell exec'd
@@ -441,6 +481,8 @@ channel_register_fds(struct ssh *ssh, Channel *c, int rfd, int wfd, int efd,
                if (efd != -1)
                        set_nonblock(efd);
        }
+       /* channel might be entering a larval state, so reset global timeout */
+       channel_set_used_time(ssh, NULL);
 }
 
 /*
@@ -1197,7 +1239,7 @@ channel_set_fds(struct ssh *ssh, int id, int rfd, int wfd, int efd,
 
        channel_register_fds(ssh, c, rfd, wfd, efd, extusage, nonblock, is_tty);
        c->type = SSH_CHANNEL_OPEN;
-       c->lastused = monotime();
+       channel_set_used_time(ssh, c);
        c->local_window = c->local_window_max = window_max;
 
        if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_WINDOW_ADJUST)) != 0 ||
@@ -1368,7 +1410,7 @@ channel_pre_x11_open(struct ssh *ssh, Channel *c)
 
        if (ret == 1) {
                c->type = SSH_CHANNEL_OPEN;
-               c->lastused = monotime();
+               channel_set_used_time(ssh, c);
                channel_pre_open(ssh, c);
        } else if (ret == -1) {
                logit("X11 connection rejected because of wrong "
@@ -2016,7 +2058,7 @@ channel_post_connecting(struct ssh *ssh, Channel *c)
                    c->self, c->connect_ctx.host, c->connect_ctx.port);
                channel_connect_ctx_free(&c->connect_ctx);
                c->type = SSH_CHANNEL_OPEN;
-               c->lastused = monotime();
+               channel_set_used_time(ssh, c);
                if (isopen) {
                        /* no message necessary */
                } else {
@@ -2108,7 +2150,7 @@ channel_handle_rfd(struct ssh *ssh, Channel *c)
                        goto rfail;
                }
                if (nr != 0)
-                       c->lastused = monotime();
+                       channel_set_used_time(ssh, c);
                return 1;
        }
 
@@ -2134,7 +2176,7 @@ channel_handle_rfd(struct ssh *ssh, Channel *c)
                }
                return -1;
        }
-       c->lastused = monotime();
+       channel_set_used_time(ssh, c);
        if (c->input_filter != NULL) {
                if (c->input_filter(ssh, c, buf, len) == -1) {
                        debug2("channel %d: filter stops", c->self);
@@ -2215,7 +2257,7 @@ channel_handle_wfd(struct ssh *ssh, Channel *c)
                }
                return -1;
        }
-       c->lastused = monotime();
+       channel_set_used_time(ssh, c);
 #ifndef BROKEN_TCGETATTR_ICANON
        if (c->isatty && dlen >= 1 && buf[0] != '\r') {
                if (tcgetattr(c->wfd, &tio) == 0 &&
@@ -2264,7 +2306,7 @@ channel_handle_efd_write(struct ssh *ssh, Channel *c)
                if ((r = sshbuf_consume(c->extended, len)) != 0)
                        fatal_fr(r, "channel %i: consume", c->self);
                c->local_consumed += len;
-               c->lastused = monotime();
+               channel_set_used_time(ssh, c);
        }
        return 1;
 }
@@ -2291,7 +2333,7 @@ channel_handle_efd_read(struct ssh *ssh, Channel *c)
                channel_close_fd(ssh, c, &c->efd);
                return 1;
        }
-       c->lastused = monotime();
+       channel_set_used_time(ssh, c);
        if (c->extended_usage == CHAN_EXTENDED_IGNORE)
                debug3("channel %d: discard efd", c->self);
        else if ((r = sshbuf_put(c->extended, buf, len)) != 0)
@@ -2581,10 +2623,9 @@ channel_handler(struct ssh *ssh, int table, struct timespec *timeout)
                                continue;
                }
                if (ftab[c->type] != NULL) {
-                       if (table == CHAN_PRE &&
-                           c->type == SSH_CHANNEL_OPEN &&
-                           c->inactive_deadline != 0 && c->lastused != 0 &&
-                           now >= c->lastused + c->inactive_deadline) {
+                       if (table == CHAN_PRE && c->type == SSH_CHANNEL_OPEN &&
+                           channel_get_expiry(ssh, c) != 0 &&
+                           now >= channel_get_expiry(ssh, c)) {
                                /* channel closed for inactivity */
                                verbose("channel %d: closing after %u seconds "
                                    "of inactivity", c->self,
@@ -2596,10 +2637,9 @@ channel_handler(struct ssh *ssh, int table, struct timespec *timeout)
                                /* inactivity timeouts must interrupt poll() */
                                if (timeout != NULL &&
                                    c->type == SSH_CHANNEL_OPEN &&
-                                   c->lastused != 0 &&
-                                   c->inactive_deadline != 0) {
+                                   channel_get_expiry(ssh, c) != 0) {
                                        ptimeout_deadline_monotime(timeout,
-                                           c->lastused + c->inactive_deadline);
+                                           channel_get_expiry(ssh, c));
                                }
                        } else if (timeout != NULL) {
                                /*
@@ -3558,7 +3598,7 @@ channel_input_open_confirmation(int type, u_int32_t seq, struct ssh *ssh)
                c->open_confirm(ssh, c->self, 1, c->open_confirm_ctx);
                debug2_f("channel %d: callback done", c->self);
        }
-       c->lastused = monotime();
+       channel_set_used_time(ssh, c);
        debug2("channel %d: open confirm rwindow %u rmax %u", c->self,
            c->remote_window, c->remote_maxpacket);
        return 0;
index 4bbdfefd12aa1ff5963ab7702cdd0be559929656..15ad012f8879d717b35c10905510100e811acdc7 100644 (file)
@@ -33,8 +33,8 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: ssh_config.5,v 1.391 2023/10/12 02:18:18 djm Exp $
-.Dd $Mdocdate: October 12 2023 $
+.\" $OpenBSD: ssh_config.5,v 1.392 2024/01/09 22:19:00 djm Exp $
+.Dd $Mdocdate: January 9 2024 $
 .Dt SSH_CONFIG 5
 .Os
 .Sh NAME
@@ -463,8 +463,10 @@ Timeouts are specified as one or more
 .Dq type=interval
 pairs separated by whitespace, where the
 .Dq type
-must be a channel type name (as described in the table below), optionally
-containing wildcard characters.
+must be the special keyword
+.Dq global
+or a channel type name from the list below, optionally containing
+wildcard characters.
 .Pp
 The timeout value
 .Dq interval
@@ -473,11 +475,19 @@ is specified in seconds or may use any of the units documented in the
 section.
 For example,
 .Dq session=5m
-would cause the interactive session to terminate after five minutes of
+would cause interactive sessions to terminate after five minutes of
 inactivity.
 Specifying a zero value disables the inactivity timeout.
 .Pp
-The available channel types include:
+The special timeout
+.Dq global
+Applies to all active channels, taken together.
+Traffic on any active channel will reset the timeout, but when the timeout
+expires then all open channels will be closed.
+Note that this global timeout is not matched by wildcards and must be
+specified explicitly.
+.Pp
+The available channel type names include:
 .Bl -tag -width Ds
 .It Cm agent-connection
 Open connections to
index 7e1a56cd0e647678de0d770a9e0ffdeeb48562d8..ca5eeb59d95731bc43acfc386d63f6f4f3f26691 100644 (file)
@@ -33,8 +33,8 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: sshd_config.5,v 1.350 2023/07/28 05:42:36 jmc Exp $
-.Dd $Mdocdate: July 28 2023 $
+.\" $OpenBSD: sshd_config.5,v 1.351 2024/01/09 22:19:00 djm Exp $
+.Dd $Mdocdate: January 9 2024 $
 .Dt SSHD_CONFIG 5
 .Os
 .Sh NAME
@@ -409,8 +409,10 @@ Timeouts are specified as one or more
 .Dq type=interval
 pairs separated by whitespace, where the
 .Dq type
-must be a channel type name (as described in the table below), optionally
-containing wildcard characters.
+must be the special keyword
+.Dq global
+or a channel type name from the list below, optionally containing
+wildcard characters.
 .Pp
 The timeout value
 .Dq interval
@@ -418,11 +420,20 @@ is specified in seconds or may use any of the units documented in the
 .Sx TIME FORMATS
 section.
 For example,
-.Dq session:*=5m
-would cause all sessions to terminate after five minutes of inactivity.
+.Dq session=5m
+would cause interactive sessions to terminate after five minutes of
+inactivity.
 Specifying a zero value disables the inactivity timeout.
 .Pp
-The available channel types include:
+The special timeout
+.Dq global
+Applies to all active channels, taken together.
+Traffic on any active channel will reset the timeout, but when the timeout
+expires then all open channels will be closed.
+Note that this global timeout is not matched by wildcards and must be
+specified explicitly.
+.Pp
+The available channel type names include:
 .Bl -tag -width Ds
 .It Cm agent-connection
 Open connections to
@@ -443,15 +454,15 @@ listening on behalf of a
 .Xr ssh 1
 remote forwarding, i.e.\&
 .Cm RemoteForward .
-.It Cm session:command
-Command execution sessions.
-.It Cm session:shell
-Interactive shell sessions.
-.It Cm session:subsystem:...
-Subsystem sessions, e.g. for
+.It Cm session
+The interactive main session, including shell session, command execution,
+.Xr scp 1 ,
 .Xr sftp 1 ,
-which could be identified as
-.Cm session:subsystem:sftp .
+etc.
+.It Cm tun-connection
+Open
+.Cm TunnelForward
+connections.
 .It Cm x11-connection
 Open X11 forwarding sessions.
 .El
@@ -465,9 +476,6 @@ close the SSH connection, nor does it prevent a client from
 requesting another channel of the same type.
 In particular, expiring an inactive forwarding session does not prevent
 another identical forwarding from being subsequently created.
-See also
-.Cm UnusedConnectionTimeout ,
-which may be used in conjunction with this option.
 .Pp
 The default is not to expire channels of any type for inactivity.
 .It Cm ChrootDirectory