upstream: sftp-server: implement limits@openssh.com extension

This is a simple extension that allows the server to clearly
communicate transfer limits it is imposing so the client doesn't
have to guess, or force the user to manually tune.  This is
particularly useful when an attempt to use too large of a value
causes the server to abort the connection.

Patch from Mike Frysinger; ok dtucker@

OpenBSD-Commit-ID: f96293221e5aa24102d9bf30e4f4ef04d5f4fb51
This commit is contained in:
djm@openbsd.org 2021-02-18 00:30:17 +00:00 committed by Damien Miller
parent 324449a68d
commit 788cbc5b74
2 changed files with 85 additions and 3 deletions

View File

@ -481,6 +481,49 @@ See the "setstat" command for more details.
This extension is advertised in the SSH_FXP_VERSION hello with version
"1".
3.8. sftp: Extension request "limits@openssh.com"
This request is used to determine various limits the server might impose.
Clients should not attempt to exceed these limits as the server might sever
the connection immediately.
uint32 id
string "limits@openssh.com"
The server will respond with a SSH_FXP_EXTENDED_REPLY reply:
uint32 id
uint64 max-packet-length
uint64 max-read-length
uint64 max-write-length
uint64 max-open-handles
The 'max-packet-length' applies to the total number of bytes in a
single SFTP packet. Servers SHOULD set this at least to 34000.
The 'max-read-length' is the largest length in a SSH_FXP_READ packet.
Even if the client requests a larger size, servers will usually respond
with a shorter SSH_FXP_DATA packet. Servers SHOULD set this at least to
32768.
The 'max-write-length' is the largest length in a SSH_FXP_WRITE packet
the server will accept. Servers SHOULD set this at least to 32768.
The 'max-open-handles' is the maximum number of active handles that the
server allows (e.g. handles created by SSH_FXP_OPEN and SSH_FXP_OPENDIR
packets). Servers MAY count internal file handles against this limit
(e.g. system logging or stdout/stderr), so clients SHOULD NOT expect to
open this many handles in practice.
If the server doesn't enforce a specific limit, then the field may be
set to 0. This implies the server relies on the OS to enforce limits
(e.g. available memory or file handles), and such limits might be
dynamic. The client SHOULD take care to not try to exceed reasonable
limits.
This extension is advertised in the SSH_FXP_VERSION hello with version
"1".
4. Miscellaneous changes
4.1 Public key format
@ -512,4 +555,4 @@ OpenSSH's connection multiplexing uses messages as described in
PROTOCOL.mux over a Unix domain socket for communications between a
master instance and later clients.
$OpenBSD: PROTOCOL,v 1.39 2021/02/12 03:49:09 djm Exp $
$OpenBSD: PROTOCOL,v 1.40 2021/02/18 00:30:17 djm Exp $

View File

@ -1,4 +1,4 @@
/* $OpenBSD: sftp-server.c,v 1.121 2021/02/12 03:49:09 djm Exp $ */
/* $OpenBSD: sftp-server.c,v 1.122 2021/02/18 00:30:17 djm Exp $ */
/*
* Copyright (c) 2000-2004 Markus Friedl. All rights reserved.
*
@ -19,6 +19,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
@ -53,6 +54,9 @@
char *sftp_realpath(const char *, char *); /* sftp-realpath.c */
/* Maximum data read that we are willing to accept */
#define SFTP_MAX_READ_LENGTH (64 * 1024)
/* Our verbosity */
static LogLevel log_level = SYSLOG_LEVEL_ERROR;
@ -110,6 +114,7 @@ static void process_extended_fstatvfs(u_int32_t id);
static void process_extended_hardlink(u_int32_t id);
static void process_extended_fsync(u_int32_t id);
static void process_extended_lsetstat(u_int32_t id);
static void process_extended_limits(u_int32_t id);
static void process_extended(u_int32_t id);
struct sftp_handler {
@ -152,6 +157,7 @@ static const struct sftp_handler extended_handlers[] = {
{ "hardlink", "hardlink@openssh.com", 0, process_extended_hardlink, 1 },
{ "fsync", "fsync@openssh.com", 0, process_extended_fsync, 1 },
{ "lsetstat", "lsetstat@openssh.com", 0, process_extended_lsetstat, 1 },
{ "limits", "limits@openssh.com", 0, process_extended_limits, 1 },
{ NULL, NULL, 0, NULL, 0 }
};
@ -673,6 +679,9 @@ process_init(void)
(r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */
/* lsetstat extension */
(r = sshbuf_put_cstring(msg, "lsetstat@openssh.com")) != 0 ||
(r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */
/* limits extension */
(r = sshbuf_put_cstring(msg, "limits@openssh.com")) != 0 ||
(r = sshbuf_put_cstring(msg, "1")) != 0) /* version */
fatal_fr(r, "compose");
send_msg(msg);
@ -739,7 +748,7 @@ process_close(u_int32_t id)
static void
process_read(u_int32_t id)
{
u_char buf[64*1024];
u_char buf[SFTP_MAX_READ_LENGTH];
u_int32_t len;
int r, handle, fd, ret, status = SSH2_FX_FAILURE;
u_int64_t off;
@ -1437,6 +1446,36 @@ process_extended_lsetstat(u_int32_t id)
free(name);
}
static void
process_extended_limits(u_int32_t id)
{
struct sshbuf *msg;
int r;
uint64_t nfiles = 0;
struct rlimit rlim;
debug("request %u: limits", id);
if (getrlimit(RLIMIT_NOFILE, &rlim) != -1 && rlim.rlim_cur > 5)
nfiles = rlim.rlim_cur - 5; /* stdio(3) + syslog + spare */
if ((msg = sshbuf_new()) == NULL)
fatal_f("sshbuf_new failed");
if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED_REPLY)) != 0 ||
(r = sshbuf_put_u32(msg, id)) != 0 ||
/* max-packet-length */
(r = sshbuf_put_u64(msg, SFTP_MAX_MSG_LENGTH)) != 0 ||
/* max-read-length */
(r = sshbuf_put_u64(msg, SFTP_MAX_READ_LENGTH)) != 0 ||
/* max-write-length */
(r = sshbuf_put_u64(msg, SFTP_MAX_MSG_LENGTH - 1024)) != 0 ||
/* max-open-handles */
(r = sshbuf_put_u64(msg, nfiles)) != 0)
fatal_fr(r, "compose");
send_msg(msg);
sshbuf_free(msg);
}
static void
process_extended(u_int32_t id)
{