Description: Unable to transfer large files (more than 1 GB) with SFTP module.
Author: proftpd upstream
Forwarded: bug #4097, fixed in 1.3.5.b.
Bug-Debian: http://bugs.debian.org/809068

Index: proftpd-dfsg/contrib/mod_sftp/kex.c
===================================================================
--- proftpd-dfsg.orig/contrib/mod_sftp/kex.c	2016-08-25 12:25:34.000000000 +0200
+++ proftpd-dfsg/contrib/mod_sftp/kex.c	2016-09-04 15:06:00.000000000 +0200
@@ -39,8 +39,6 @@
 #include "interop.h"
 #include "tap.h"
 
-#define SFTP_DH_PRIV_KEY_RANDOM_BITS	2048
-
 extern module sftp_module;
 
 /* For managing the kexinit process */
@@ -621,8 +619,94 @@
   return 0;
 }
 
+static int get_dh_nbits(struct sftp_kex *kex) {
+  int dh_nbits = 0, dh_size = 0;
+  const char *algo;
+  const EVP_CIPHER *cipher;
+  const EVP_MD *digest;
+
+  algo = kex->session_names->c2s_encrypt_algo;
+  cipher = sftp_crypto_get_cipher(algo, NULL, NULL);
+  if (cipher != NULL) {
+    int block_size, key_len;
+
+    key_len = EVP_CIPHER_key_length(cipher);
+    if (dh_size < key_len) {
+      dh_size = key_len;
+      pr_trace_msg(trace_channel, 19,
+        "set DH size to %d bytes, matching client-to-server '%s' cipher "
+        "key length", dh_size, algo);
+    }
+
+    block_size = EVP_CIPHER_block_size(cipher);
+    if (dh_size < block_size) {
+      dh_size = block_size;
+      pr_trace_msg(trace_channel, 19,
+        "set DH size to %d bytes, matching client-to-server '%s' cipher "
+        "block size", dh_size, algo);
+    }
+  }
+
+  algo = kex->session_names->s2c_encrypt_algo;
+  cipher = sftp_crypto_get_cipher(algo, NULL, NULL);
+  if (cipher != NULL) {
+    int block_size, key_len;
+
+    key_len = EVP_CIPHER_key_length(cipher);
+    if (dh_size < key_len) {
+      dh_size = key_len;
+      pr_trace_msg(trace_channel, 19,
+        "set DH size to %d bytes, matching server-to-client '%s' cipher "
+        "key length", dh_size, algo);
+    }
+
+    block_size = EVP_CIPHER_block_size(cipher);
+    if (dh_size < block_size) {
+      dh_size = block_size;
+      pr_trace_msg(trace_channel, 19,
+        "set DH size to %d bytes, matching server-to-client '%s' cipher "
+        "block size", dh_size, algo);
+    }
+  }
+
+  algo = kex->session_names->c2s_mac_algo;
+  digest = sftp_crypto_get_digest(algo, NULL);
+  if (digest != NULL) {
+    int mac_len;
+
+    mac_len = EVP_MD_size(digest);
+    if (dh_size < mac_len) {
+      dh_size = mac_len;
+      pr_trace_msg(trace_channel, 19,
+        "set DH size to %d bytes, matching client-to-server '%s' digest size",
+        dh_size, algo);
+    }
+  }
+
+  algo = kex->session_names->s2c_mac_algo;
+  digest = sftp_crypto_get_digest(algo, NULL);
+  if (digest != NULL) {
+    int mac_len;
+
+    mac_len = EVP_MD_size(digest);
+    if (dh_size < mac_len) {
+      dh_size = mac_len;
+      pr_trace_msg(trace_channel, 19,
+        "set DH size to %d bytes, matching server-to-client '%s' digest size",
+        dh_size, algo);
+    }
+  }
+
+  /* We want to return bits, not bytes. */
+  dh_nbits = dh_size * 8;
+
+  pr_trace_msg(trace_channel, 8, "requesting DH size of %d bits", dh_nbits);
+  return dh_nbits;
+}
+
 static int create_dh(struct sftp_kex *kex, int type) {
   unsigned int attempts = 0;
+  int dh_nbits;
   DH *dh;
 
   if (type != SFTP_DH_GROUP1_SHA1 &&
@@ -656,6 +740,8 @@
     kex->dh = NULL;
   }
 
+  dh_nbits = get_dh_nbits(kex);
+
   /* We have 10 attempts to make a DH key which passes muster. */
   while (attempts <= 10) {
     pr_signals_handle();
@@ -665,7 +751,7 @@
       attempts);
 
     dh = DH_new();
-    if (!dh) {
+    if (dh == NULL) {
       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
         "error creating DH: %s", sftp_crypto_get_errors());
       return -1;
@@ -699,10 +785,11 @@
       return -1;
     }
 
-    if (!BN_rand(dh->priv_key, SFTP_DH_PRIV_KEY_RANDOM_BITS, 0, 0)) {
+    /* Generate a random private exponent of the desired size, in bits. */
+    if (!BN_rand(dh->priv_key, dh_nbits, 0, 0)) {
       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
-        "error generating DH random key (%d bytes): %s",
-        SFTP_DH_PRIV_KEY_RANDOM_BITS, sftp_crypto_get_errors());
+        "error generating DH random key (%d bits): %s", dh_nbits,
+        sftp_crypto_get_errors());
       DH_free(dh);
       return -1;
     }
@@ -787,6 +874,9 @@
 
 static int finish_dh(struct sftp_kex *kex) {
   unsigned int attempts = 0;
+  int dh_nbits;
+
+  dh_nbits = get_dh_nbits(kex);
 
   /* We have 10 attempts to make a DH key which passes muster. */
   while (attempts <= 10) {
@@ -797,11 +887,12 @@
       attempts);
 
     kex->dh->priv_key = BN_new();
-  
-    if (!BN_rand(kex->dh->priv_key, SFTP_DH_PRIV_KEY_RANDOM_BITS, 0, 0)) {
+ 
+    /* Generate a random private exponent of the desired size, in bits. */ 
+    if (!BN_rand(kex->dh->priv_key, dh_nbits, 0, 0)) {
       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
-        "error generating DH random key (%d bytes): %s",
-        SFTP_DH_PRIV_KEY_RANDOM_BITS, sftp_crypto_get_errors());
+        "error generating DH random key (%d bits): %s", dh_nbits,
+        sftp_crypto_get_errors());
       return -1;
     }
 
@@ -1514,77 +1605,71 @@
 }
 
 static int setup_c2s_encrypt_algo(struct sftp_kex *kex, const char *algo) {
-  (void) kex;
-
-  if (sftp_cipher_set_read_algo(algo) < 0)
+  if (sftp_cipher_set_read_algo(algo) < 0) {
     return -1;
+  }
 
+  kex->session_names->c2s_encrypt_algo = algo;
   return 0;
 }
 
 static int setup_s2c_encrypt_algo(struct sftp_kex *kex, const char *algo) {
-  (void) kex;
-
-  if (sftp_cipher_set_write_algo(algo) < 0)
+  if (sftp_cipher_set_write_algo(algo) < 0) {
     return -1;
+  }
 
+  kex->session_names->s2c_encrypt_algo = algo;
   return 0;
 }
 
 static int setup_c2s_mac_algo(struct sftp_kex *kex, const char *algo) {
-  (void) kex;
-
-  if (sftp_mac_set_read_algo(algo) < 0)
+  if (sftp_mac_set_read_algo(algo) < 0) {
     return -1;
+  }
 
+  kex->session_names->c2s_mac_algo = algo;
   return 0;
 }
 
 static int setup_s2c_mac_algo(struct sftp_kex *kex, const char *algo) {
-  (void) kex;
-
-  if (sftp_mac_set_write_algo(algo) < 0)
+  if (sftp_mac_set_write_algo(algo) < 0) {
     return -1;
+  }
 
+  kex->session_names->s2c_mac_algo = algo;
   return 0;
 }
 
 static int setup_c2s_comp_algo(struct sftp_kex *kex, const char *algo) {
-  (void) kex;
-
-  if (sftp_compress_set_read_algo(algo) < 0)
+  if (sftp_compress_set_read_algo(algo) < 0) {
     return -1;
+  }
 
+  kex->session_names->c2s_comp_algo = algo;
   return 0;
 }
 
 static int setup_s2c_comp_algo(struct sftp_kex *kex, const char *algo) {
-  (void) kex;
-
-  if (sftp_compress_set_write_algo(algo) < 0)
+  if (sftp_compress_set_write_algo(algo) < 0) {
     return -1;
+  }
 
+  kex->session_names->s2c_comp_algo = algo;
   return 0;
 }
 
 static int setup_c2s_lang(struct sftp_kex *kex, const char *lang) {
-  (void) kex;
-
-  /* XXX Need to implement the functionality here. */
-
+  kex->session_names->c2s_lang = lang;
   return 0;
 }
 
 static int setup_s2c_lang(struct sftp_kex *kex, const char *lang) {
-  (void) kex;
-
-  /* XXX Need to implement the functionality here. */
-
+  kex->session_names->s2c_lang = lang;
   return 0;
 }
 
 static int get_session_names(struct sftp_kex *kex, int *correct_guess) {
-  const char *shared, *client_list, *server_list;
+  const char *kex_algo, *shared, *client_list, *server_list;
   const char *client_pref, *server_pref;
   pool *tmp_pool;
 
@@ -1624,17 +1709,16 @@
     }
   }
 
-  shared = get_shared_name(kex_pool, client_list, server_list);
-  if (shared) {
-    if (setup_kex_algo(kex, shared) < 0) {
-      destroy_pool(tmp_pool);
-      return -1;
-    }
-
+  kex_algo = get_shared_name(kex_pool, client_list, server_list);
+  if (kex_algo != NULL) {
+    /* Unlike the following algorithms, we wait to setup the chosen kex algo
+     * until the end.  Why?  The kex algo setup may require knowledge of the
+     * ciphers chosen for encryption, MAC, etc (Bug#4097).
+     */
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
-      " + Session key exchange: %s", shared);
+      " + Session key exchange: %s", kex_algo);
     pr_trace_msg(trace_channel, 20, "session key exchange algorithm: %s",
-      shared);
+      kex_algo);
 
   } else {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
@@ -1902,6 +1986,14 @@
 #endif
   }
 
+  /* Now that we've finished setting up the other bits, we can set up the
+   * kex algo.
+   */
+  if (setup_kex_algo(kex, kex_algo) < 0) {
+    destroy_pool(tmp_pool);
+    return -1;
+  }
+
   destroy_pool(tmp_pool);
   return 0;
 }
Index: proftpd-dfsg/contrib/mod_sftp/keys.c
===================================================================
--- proftpd-dfsg.orig/contrib/mod_sftp/keys.c	2016-08-25 12:25:34.000000000 +0200
+++ proftpd-dfsg/contrib/mod_sftp/keys.c	2016-09-04 15:06:00.000000000 +0200
@@ -36,6 +36,7 @@
 extern xaset_t *server_list;
 extern module sftp_module;
 
+/* Note: Should this size be made bigger, in light of larger hostkeys? */
 #define SFTP_DEFAULT_HOSTKEY_SZ		4096
 #define SFTP_MAX_SIG_SZ			4096
 
@@ -2028,7 +2029,7 @@
       }
 
       /* XXX Is this buffer large enough?  Too large? */
-      ptr = buf = sftp_msg_getbuf(p, buflen);
+      ptr = buf = palloc(p, buflen);
       sftp_msg_write_string(&buf, &buflen, "ssh-rsa");
       sftp_msg_write_mpint(&buf, &buflen, rsa->e);
       sftp_msg_write_mpint(&buf, &buflen, rsa->n);
@@ -2048,7 +2049,7 @@
       }
 
       /* XXX Is this buffer large enough?  Too large? */
-      ptr = buf = sftp_msg_getbuf(p, buflen);
+      ptr = buf = palloc(p, buflen);
       sftp_msg_write_string(&buf, &buflen, "ssh-dss");
       sftp_msg_write_mpint(&buf, &buflen, dsa->p);
       sftp_msg_write_mpint(&buf, &buflen, dsa->q);
@@ -2071,8 +2072,7 @@
       }
 
       /* XXX Is this buffer large enough?  Too large? */
-      ptr = buf = sftp_msg_getbuf(p, buflen);
-
+      ptr = buf = palloc(p, buflen);
       sftp_msg_write_string(&buf, &buflen, "ecdsa-sha2-nistp256");
       sftp_msg_write_string(&buf, &buflen, "nistp256");
       sftp_msg_write_ecpoint(&buf, &buflen, EC_KEY_get0_group(ec),
@@ -2093,8 +2093,7 @@
       }
 
       /* XXX Is this buffer large enough?  Too large? */
-      ptr = buf = sftp_msg_getbuf(p, buflen);
-
+      ptr = buf = palloc(p, buflen);
       sftp_msg_write_string(&buf, &buflen, "ecdsa-sha2-nistp384");
       sftp_msg_write_string(&buf, &buflen, "nistp384");
       sftp_msg_write_ecpoint(&buf, &buflen, EC_KEY_get0_group(ec),
@@ -2115,8 +2114,7 @@
       }
 
       /* XXX Is this buffer large enough?  Too large? */
-      ptr = buf = sftp_msg_getbuf(p, buflen);
-
+      ptr = buf = palloc(p, buflen);
       sftp_msg_write_string(&buf, &buflen, "ecdsa-sha2-nistp521");
       sftp_msg_write_string(&buf, &buflen, "nistp521");
       sftp_msg_write_ecpoint(&buf, &buflen, EC_KEY_get0_group(ec),
@@ -2137,8 +2135,14 @@
   *datalen = SFTP_DEFAULT_HOSTKEY_SZ - buflen;
 
   /* If the caller provided a pool, make a copy of the data from the
-   * given pool, and return the copy.  Make sure the scrub the original
+   * given pool, and return the copy.  Make sure to scrub the original
    * after making the copy.
+   *
+   * Note that we do this copy, even though we use the given pool, since
+   * we only know the actual size of the data after the fact.  And we need
+   * to provide the size of the data to the caller, NOT the optimistic size
+   * we allocate out of the pool for writing the data in the first place.
+   * Hence the copy.
    */
   if (p) {
     buf = palloc(p, *datalen);
