upstream: add a "global" ChannelTimeout type to ssh(1) and sshd(8)

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
This commit is contained in:
djm@openbsd.org 2024-01-09 22:19:00 +00:00 committed by Damien Miller
parent 602f4beeed
commit b31b12d28d
No known key found for this signature in database
3 changed files with 99 additions and 41 deletions

View 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> * Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -214,6 +214,9 @@ struct ssh_channels {
/* Channel timeouts by type */ /* Channel timeouts by type */
struct ssh_channel_timeout *timeouts; struct ssh_channel_timeout *timeouts;
size_t ntimeouts; size_t ntimeouts;
/* Global timeout for all OPEN channels */
int global_deadline;
time_t lastused;
}; };
/* helper */ /* helper */
@ -316,6 +319,11 @@ channel_add_timeout(struct ssh *ssh, const char *type_pattern,
{ {
struct ssh_channels *sc = ssh->chanctxt; 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", debug2_f("channel type \"%s\" timeout %d seconds",
type_pattern, timeout_secs); type_pattern, timeout_secs);
sc->timeouts = xrecallocarray(sc->timeouts, sc->ntimeouts, 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); 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 * Register filedescriptors for a channel, used when allocating a channel or
* when the channel consumer/producer is ready, e.g. shell exec'd * 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) if (efd != -1)
set_nonblock(efd); 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); channel_register_fds(ssh, c, rfd, wfd, efd, extusage, nonblock, is_tty);
c->type = SSH_CHANNEL_OPEN; c->type = SSH_CHANNEL_OPEN;
c->lastused = monotime(); channel_set_used_time(ssh, c);
c->local_window = c->local_window_max = window_max; c->local_window = c->local_window_max = window_max;
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_WINDOW_ADJUST)) != 0 || 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) { if (ret == 1) {
c->type = SSH_CHANNEL_OPEN; c->type = SSH_CHANNEL_OPEN;
c->lastused = monotime(); channel_set_used_time(ssh, c);
channel_pre_open(ssh, c); channel_pre_open(ssh, c);
} else if (ret == -1) { } else if (ret == -1) {
logit("X11 connection rejected because of wrong " 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); c->self, c->connect_ctx.host, c->connect_ctx.port);
channel_connect_ctx_free(&c->connect_ctx); channel_connect_ctx_free(&c->connect_ctx);
c->type = SSH_CHANNEL_OPEN; c->type = SSH_CHANNEL_OPEN;
c->lastused = monotime(); channel_set_used_time(ssh, c);
if (isopen) { if (isopen) {
/* no message necessary */ /* no message necessary */
} else { } else {
@ -2108,7 +2150,7 @@ channel_handle_rfd(struct ssh *ssh, Channel *c)
goto rfail; goto rfail;
} }
if (nr != 0) if (nr != 0)
c->lastused = monotime(); channel_set_used_time(ssh, c);
return 1; return 1;
} }
@ -2134,7 +2176,7 @@ channel_handle_rfd(struct ssh *ssh, Channel *c)
} }
return -1; return -1;
} }
c->lastused = monotime(); channel_set_used_time(ssh, c);
if (c->input_filter != NULL) { if (c->input_filter != NULL) {
if (c->input_filter(ssh, c, buf, len) == -1) { if (c->input_filter(ssh, c, buf, len) == -1) {
debug2("channel %d: filter stops", c->self); debug2("channel %d: filter stops", c->self);
@ -2215,7 +2257,7 @@ channel_handle_wfd(struct ssh *ssh, Channel *c)
} }
return -1; return -1;
} }
c->lastused = monotime(); channel_set_used_time(ssh, c);
#ifndef BROKEN_TCGETATTR_ICANON #ifndef BROKEN_TCGETATTR_ICANON
if (c->isatty && dlen >= 1 && buf[0] != '\r') { if (c->isatty && dlen >= 1 && buf[0] != '\r') {
if (tcgetattr(c->wfd, &tio) == 0 && 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) if ((r = sshbuf_consume(c->extended, len)) != 0)
fatal_fr(r, "channel %i: consume", c->self); fatal_fr(r, "channel %i: consume", c->self);
c->local_consumed += len; c->local_consumed += len;
c->lastused = monotime(); channel_set_used_time(ssh, c);
} }
return 1; return 1;
} }
@ -2291,7 +2333,7 @@ channel_handle_efd_read(struct ssh *ssh, Channel *c)
channel_close_fd(ssh, c, &c->efd); channel_close_fd(ssh, c, &c->efd);
return 1; return 1;
} }
c->lastused = monotime(); channel_set_used_time(ssh, c);
if (c->extended_usage == CHAN_EXTENDED_IGNORE) if (c->extended_usage == CHAN_EXTENDED_IGNORE)
debug3("channel %d: discard efd", c->self); debug3("channel %d: discard efd", c->self);
else if ((r = sshbuf_put(c->extended, buf, len)) != 0) 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; continue;
} }
if (ftab[c->type] != NULL) { if (ftab[c->type] != NULL) {
if (table == CHAN_PRE && if (table == CHAN_PRE && c->type == SSH_CHANNEL_OPEN &&
c->type == SSH_CHANNEL_OPEN && channel_get_expiry(ssh, c) != 0 &&
c->inactive_deadline != 0 && c->lastused != 0 && now >= channel_get_expiry(ssh, c)) {
now >= c->lastused + c->inactive_deadline) {
/* channel closed for inactivity */ /* channel closed for inactivity */
verbose("channel %d: closing after %u seconds " verbose("channel %d: closing after %u seconds "
"of inactivity", c->self, "of inactivity", c->self,
@ -2596,10 +2637,9 @@ channel_handler(struct ssh *ssh, int table, struct timespec *timeout)
/* inactivity timeouts must interrupt poll() */ /* inactivity timeouts must interrupt poll() */
if (timeout != NULL && if (timeout != NULL &&
c->type == SSH_CHANNEL_OPEN && c->type == SSH_CHANNEL_OPEN &&
c->lastused != 0 && channel_get_expiry(ssh, c) != 0) {
c->inactive_deadline != 0) {
ptimeout_deadline_monotime(timeout, ptimeout_deadline_monotime(timeout,
c->lastused + c->inactive_deadline); channel_get_expiry(ssh, c));
} }
} else if (timeout != NULL) { } 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); c->open_confirm(ssh, c->self, 1, c->open_confirm_ctx);
debug2_f("channel %d: callback done", c->self); 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, debug2("channel %d: open confirm rwindow %u rmax %u", c->self,
c->remote_window, c->remote_maxpacket); c->remote_window, c->remote_maxpacket);
return 0; return 0;

View File

@ -33,8 +33,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" 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 $ .\" $OpenBSD: ssh_config.5,v 1.392 2024/01/09 22:19:00 djm Exp $
.Dd $Mdocdate: October 12 2023 $ .Dd $Mdocdate: January 9 2024 $
.Dt SSH_CONFIG 5 .Dt SSH_CONFIG 5
.Os .Os
.Sh NAME .Sh NAME
@ -463,8 +463,10 @@ Timeouts are specified as one or more
.Dq type=interval .Dq type=interval
pairs separated by whitespace, where the pairs separated by whitespace, where the
.Dq type .Dq type
must be a channel type name (as described in the table below), optionally must be the special keyword
containing wildcard characters. .Dq global
or a channel type name from the list below, optionally containing
wildcard characters.
.Pp .Pp
The timeout value The timeout value
.Dq interval .Dq interval
@ -473,11 +475,19 @@ is specified in seconds or may use any of the units documented in the
section. section.
For example, For example,
.Dq session=5m .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. inactivity.
Specifying a zero value disables the inactivity timeout. Specifying a zero value disables the inactivity timeout.
.Pp .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 .Bl -tag -width Ds
.It Cm agent-connection .It Cm agent-connection
Open connections to Open connections to

View File

@ -33,8 +33,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" 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 $ .\" $OpenBSD: sshd_config.5,v 1.351 2024/01/09 22:19:00 djm Exp $
.Dd $Mdocdate: July 28 2023 $ .Dd $Mdocdate: January 9 2024 $
.Dt SSHD_CONFIG 5 .Dt SSHD_CONFIG 5
.Os .Os
.Sh NAME .Sh NAME
@ -409,8 +409,10 @@ Timeouts are specified as one or more
.Dq type=interval .Dq type=interval
pairs separated by whitespace, where the pairs separated by whitespace, where the
.Dq type .Dq type
must be a channel type name (as described in the table below), optionally must be the special keyword
containing wildcard characters. .Dq global
or a channel type name from the list below, optionally containing
wildcard characters.
.Pp .Pp
The timeout value The timeout value
.Dq interval .Dq interval
@ -418,11 +420,20 @@ is specified in seconds or may use any of the units documented in the
.Sx TIME FORMATS .Sx TIME FORMATS
section. section.
For example, For example,
.Dq session:*=5m .Dq session=5m
would cause all sessions to terminate after five minutes of inactivity. would cause interactive sessions to terminate after five minutes of
inactivity.
Specifying a zero value disables the inactivity timeout. Specifying a zero value disables the inactivity timeout.
.Pp .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 .Bl -tag -width Ds
.It Cm agent-connection .It Cm agent-connection
Open connections to Open connections to
@ -443,15 +454,15 @@ listening on behalf of a
.Xr ssh 1 .Xr ssh 1
remote forwarding, i.e.\& remote forwarding, i.e.\&
.Cm RemoteForward . .Cm RemoteForward .
.It Cm session:command .It Cm session
Command execution sessions. The interactive main session, including shell session, command execution,
.It Cm session:shell .Xr scp 1 ,
Interactive shell sessions.
.It Cm session:subsystem:...
Subsystem sessions, e.g. for
.Xr sftp 1 , .Xr sftp 1 ,
which could be identified as etc.
.Cm session:subsystem:sftp . .It Cm tun-connection
Open
.Cm TunnelForward
connections.
.It Cm x11-connection .It Cm x11-connection
Open X11 forwarding sessions. Open X11 forwarding sessions.
.El .El
@ -465,9 +476,6 @@ close the SSH connection, nor does it prevent a client from
requesting another channel of the same type. requesting another channel of the same type.
In particular, expiring an inactive forwarding session does not prevent In particular, expiring an inactive forwarding session does not prevent
another identical forwarding from being subsequently created. another identical forwarding from being subsequently created.
See also
.Cm UnusedConnectionTimeout ,
which may be used in conjunction with this option.
.Pp .Pp
The default is not to expire channels of any type for inactivity. The default is not to expire channels of any type for inactivity.
.It Cm ChrootDirectory .It Cm ChrootDirectory