[PATCH 2/5] sftp-server: implement limits at openssh.com extension

Mike Frysinger vapier at gentoo.org
Mon Nov 30 15:57:01 AEDT 2020


As discussed in bz#2949, 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.
---
 PROTOCOL      | 43 +++++++++++++++++++++++++++++++++++++++++++
 sftp-server.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 90 insertions(+), 1 deletion(-)

diff --git a/PROTOCOL b/PROTOCOL
index abb083af3ea0..33298613b3b2 100644
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -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 at 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 at 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
diff --git a/sftp-server.c b/sftp-server.c
index ec3274d24cf5..c9500d9900b2 100644
--- a/sftp-server.c
+++ b/sftp-server.c
@@ -18,6 +18,7 @@
 #include "includes.h"
 
 #include <sys/types.h>
+#include <sys/resource.h>
 #include <sys/stat.h>
 #ifdef HAVE_SYS_TIME_H
 # include <sys/time.h>
@@ -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 at openssh.com", 0, process_extended_hardlink, 1 },
 	{ "fsync", "fsync at openssh.com", 0, process_extended_fsync, 1 },
 	{ "lsetstat", "lsetstat at openssh.com", 0, process_extended_lsetstat, 1 },
+	{ "limits", "limits at openssh.com", 0, process_extended_limits, 1 },
 	{ NULL, NULL, 0, NULL, 0 }
 };
 
@@ -641,6 +647,36 @@ send_statvfs(u_int32_t id, struct statvfs *st)
 	sshbuf_free(msg);
 }
 
+static void
+send_limits(u_int32_t id)
+{
+	struct sshbuf *msg;
+	int r;
+	uint64_t nofiles = 0;
+
+#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE)
+	struct rlimit rlim;
+	if (getrlimit(RLIMIT_NOFILE, &rlim) != -1)
+		nofiles = rlim.rlim_cur;
+#endif
+
+	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, nofiles)) != 0)
+		fatal_fr(r, "compose");
+	send_msg(msg);
+	sshbuf_free(msg);
+}
+
 /* parse incoming */
 
 static void
@@ -673,6 +709,9 @@ process_init(void)
 	    (r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */
 	    /* lsetstat extension */
 	    (r = sshbuf_put_cstring(msg, "lsetstat at openssh.com")) != 0 ||
+	    (r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */
+	    /* limits extension */
+	    (r = sshbuf_put_cstring(msg, "limits at openssh.com")) != 0 ||
 	    (r = sshbuf_put_cstring(msg, "1")) != 0) /* version */
 		fatal_fr(r, "compose");
 	send_msg(msg);
@@ -739,7 +778,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 +1476,13 @@ process_extended_lsetstat(u_int32_t id)
 	free(name);
 }
 
+static void
+process_extended_limits(u_int32_t id)
+{
+	debug("request %u: limits", id);
+	send_limits(id);
+}
+
 static void
 process_extended(u_int32_t id)
 {
-- 
2.28.0



More information about the openssh-unix-dev mailing list