Version in base suite: 1.3.8.c+dfsg-4+deb13u2 Base version: proftpd-dfsg_1.3.8.c+dfsg-4+deb13u2 Target version: proftpd-dfsg_1.3.8.c+dfsg-4+deb13u3 Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/proftpd-dfsg/proftpd-dfsg_1.3.8.c+dfsg-4+deb13u2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/p/proftpd-dfsg/proftpd-dfsg_1.3.8.c+dfsg-4+deb13u3.dsc changelog | 9 gitlab-ci.yml | 4 patches/07797aba88dca902da7eaf1dfe262c8896943de7.diff | 103 +++++++++ patches/226c85b97aa75cef4bdd74e7012b313a801796b5.diff | 45 ++++ patches/8685930f5e2e448563ef31d8871553308b954785.diff | 29 ++ patches/8ccd66576da8c3e4ca678335268905e00bc71bdb.diff | 188 ++++++++++++++++++ patches/a237fa62341bf882c7edc4e5e8cc492cec851d0b.diff | 43 ++++ patches/baf4b7929758c72cdb6cf16325fa25f435d23db6.diff | 49 ++++ patches/ce13286900a7e25f1e3403620496868d73292f6b.diff | 92 ++++++++ patches/mod_sftp-1.3.8d-issue2115.diff | 44 ++++ patches/mod_sftp-1.3.8d-issue2190.diff | 81 +++++++ patches/series | 13 + 12 files changed, 699 insertions(+), 1 deletion(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp2b5qjz1x/proftpd-dfsg_1.3.8.c+dfsg-4+deb13u2.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp2b5qjz1x/proftpd-dfsg_1.3.8.c+dfsg-4+deb13u3.dsc: no acceptable signature found diff -Nru proftpd-dfsg-1.3.8.c+dfsg/debian/changelog proftpd-dfsg-1.3.8.c+dfsg/debian/changelog --- proftpd-dfsg-1.3.8.c+dfsg/debian/changelog 2026-05-01 10:48:15.000000000 +0000 +++ proftpd-dfsg-1.3.8.c+dfsg/debian/changelog 2026-07-22 20:58:45.000000000 +0000 @@ -1,3 +1,12 @@ +proftpd-dfsg (1.3.8.c+dfsg-4+deb13u3) trixie; urgency=medium + + * Add patch for CVE-2026-44331 (Closes: #1135840). + * Add patch for CVE-2026-53994. + * Add patch for CVE-2026-63091. + * Add patch for CVE-2026-63090. + + -- Hilmar Preuße Wed, 22 Jul 2026 22:58:45 +0200 + proftpd-dfsg (1.3.8.c+dfsg-4+deb13u2) trixie; urgency=medium * Add patch for CVE-2026-42167 (Closes: #1135119). diff -Nru proftpd-dfsg-1.3.8.c+dfsg/debian/gitlab-ci.yml proftpd-dfsg-1.3.8.c+dfsg/debian/gitlab-ci.yml --- proftpd-dfsg-1.3.8.c+dfsg/debian/gitlab-ci.yml 2026-05-01 10:47:52.000000000 +0000 +++ proftpd-dfsg-1.3.8.c+dfsg/debian/gitlab-ci.yml 2026-07-22 20:56:40.000000000 +0000 @@ -7,9 +7,11 @@ variables: SALSA_CI_DISABLE_APTLY: 0 SALSA_CI_DISABLE_AUTOPKGTEST: 1 + SALSA_CI_DISABLE_DEBREBUILD: 1 SALSA_CI_DISABLE_BLHC: 1 SALSA_CI_DISABLE_LINTIAN: 0 SALSA_CI_DISABLE_PIUPARTS: 0 SALSA_CI_DISABLE_REPROTEST: 1 SALSA_CI_DISABLE_BUILD_PACKAGE_ALL: 0 - SALSA_CI_DISABLE_BUILD_PACKAGE_ANY: 0 \ No newline at end of file + SALSA_CI_DISABLE_BUILD_PACKAGE_ANY: 0 + SALSA_CI_DISABLE_USCAN: 1 diff -Nru proftpd-dfsg-1.3.8.c+dfsg/debian/patches/07797aba88dca902da7eaf1dfe262c8896943de7.diff proftpd-dfsg-1.3.8.c+dfsg/debian/patches/07797aba88dca902da7eaf1dfe262c8896943de7.diff --- proftpd-dfsg-1.3.8.c+dfsg/debian/patches/07797aba88dca902da7eaf1dfe262c8896943de7.diff 1970-01-01 00:00:00.000000000 +0000 +++ proftpd-dfsg-1.3.8.c+dfsg/debian/patches/07797aba88dca902da7eaf1dfe262c8896943de7.diff 2026-05-23 08:50:34.000000000 +0000 @@ -0,0 +1,103 @@ +From 07797aba88dca902da7eaf1dfe262c8896943de7 Mon Sep 17 00:00:00 2001 +From: TJ Saunders +Date: Tue, 5 May 2026 09:56:33 -0700 +Subject: [PATCH] Issue #2057: Properly escape the "name" provided when doing + SQL lookups for allowed/denied client IP addresses or DNS names. + +--- + contrib/mod_wrap2_sql.c | 49 +++++++++++++++++++++++++++++++++++++---- + 1 file changed, 45 insertions(+), 4 deletions(-) + +diff --git a/contrib/mod_wrap2_sql.c b/contrib/mod_wrap2_sql.c +index eaf6ea7dc5..d4fd56e699 100644 +--- a/contrib/mod_wrap2_sql.c ++++ b/contrib/mod_wrap2_sql.c +@@ -1,7 +1,7 @@ + /* + * ProFTPD: mod_wrap2_sql -- a mod_wrap2 sub-module for supplying IP-based + * access control data via SQL tables +- * Copyright (c) 2002-2016 TJ Saunders ++ * Copyright (c) 2002-2026 TJ Saunders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by +@@ -25,7 +25,7 @@ + #include "mod_wrap2.h" + #include "mod_sql.h" + +-#define MOD_WRAP2_SQL_VERSION "mod_wrap2_sql/1.0" ++#define MOD_WRAP2_SQL_VERSION "mod_wrap2_sql/1.1" + + #define WRAP2_SQL_NSLOTS 2 + #define WRAP2_SQL_CLIENT_QUERY_IDX 0 +@@ -62,6 +62,41 @@ static int sqltab_close_cb(wrap2_table_t *sqltab) { + return 0; + } + ++static char *sqltab_get_escaped_text(pool *p, wrap2_table_t *sqltab, ++ const char *text) { ++ pool *tmp_pool = NULL; ++ cmdtable *sql_cmdtab = NULL; ++ cmd_rec *sql_cmd = NULL; ++ modret_t *sql_res = NULL; ++ ++ /* Find the cmdtable for the sql_escapestr command, as the provided ++ * name needs to be properly escaped for SQL syntax; see Issue #2057. ++ */ ++ sql_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, "sql_escapestr", NULL, NULL, ++ NULL); ++ if (sql_cmdtab == NULL) { ++ wrap2_log("error: unable to find SQL hook symbol 'sql_escapestr': " ++ "perhaps your proftpd.conf needs 'LoadModule mod_sql.c'?"); ++ return NULL; ++ } ++ ++ sql_cmd = sql_cmd_create(tmp_pool, 1, text); ++ sql_res = pr_module_call(sql_cmdtab->m, sql_cmdtab->handler, sql_cmd); ++ if (sql_res == NULL) { ++ wrap2_log("sql_escapestr '%s' returned no data; " ++ "see the mod_sql.c SQLLogFile for more details", text); ++ return NULL; ++ } ++ ++ if (MODRET_ISERROR(sql_res)) { ++ wrap2_log("error processing sql_escapestr '%s': " ++ "check the mod_sql.c SQLLogFile for more details", text); ++ return NULL; ++ } ++ ++ return sql_res->data; ++} ++ + static array_header *sqltab_fetch_clients_cb(wrap2_table_t *sqltab, + const char *name) { + register unsigned int i; +@@ -70,12 +105,18 @@ static array_header *sqltab_fetch_clients_cb(wrap2_table_t *sqltab, + cmd_rec *sql_cmd = NULL; + modret_t *sql_res = NULL; + array_header *sql_data = NULL; +- char *query = NULL, **vals = NULL; ++ char *escaped_name = NULL, *query = NULL, **vals = NULL; + array_header *clients_list = NULL; + + /* Allocate a temporary pool for the duration of this read. */ + tmp_pool = make_sub_pool(sqltab->tab_pool); + ++ escaped_name = sqltab_get_escaped_text(tmp_pool, sqltab, name); ++ if (escaped_name == NULL) { ++ destroy_pool(tmp_pool); ++ return NULL; ++ } ++ + query = ((char **) sqltab->tab_data)[WRAP2_SQL_CLIENT_QUERY_IDX]; + + /* Find the cmdtable for the sql_lookup command. */ +@@ -89,7 +130,7 @@ static array_header *sqltab_fetch_clients_cb(wrap2_table_t *sqltab, + } + + /* Prepare the SELECT query. */ +- sql_cmd = sql_cmd_create(tmp_pool, 3, "sql_lookup", query, name); ++ sql_cmd = sql_cmd_create(tmp_pool, 3, "sql_lookup", query, escaped_name); + + /* Call the handler. */ + sql_res = pr_module_call(sql_cmdtab->m, sql_cmdtab->handler, sql_cmd); diff -Nru proftpd-dfsg-1.3.8.c+dfsg/debian/patches/226c85b97aa75cef4bdd74e7012b313a801796b5.diff proftpd-dfsg-1.3.8.c+dfsg/debian/patches/226c85b97aa75cef4bdd74e7012b313a801796b5.diff --- proftpd-dfsg-1.3.8.c+dfsg/debian/patches/226c85b97aa75cef4bdd74e7012b313a801796b5.diff 1970-01-01 00:00:00.000000000 +0000 +++ proftpd-dfsg-1.3.8.c+dfsg/debian/patches/226c85b97aa75cef4bdd74e7012b313a801796b5.diff 2026-07-22 20:56:40.000000000 +0000 @@ -0,0 +1,45 @@ +From 226c85b97aa75cef4bdd74e7012b313a801796b5 Mon Sep 17 00:00:00 2001 +From: TJ Saunders +Date: Sun, 31 May 2026 11:36:01 -0700 +Subject: [PATCH] Issue #2115: Move our check for excessive SFTP request length + earlier, to avoid another vector to underflow. + +Bankde Eakasit rightly pointed out this avenue. +--- + contrib/mod_sftp/fxp.c | 16 +++++++--------- + 1 file changed, 7 insertions(+), 9 deletions(-) + +diff --git a/contrib/mod_sftp/fxp.c b/contrib/mod_sftp/fxp.c +index dec6f37e9..ef9a1c391 100644 +--- a/contrib/mod_sftp/fxp.c ++++ b/contrib/mod_sftp/fxp.c +@@ -3422,6 +3422,13 @@ static struct fxp_packet *fxp_packet_read(uint32_t channel_id, + SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL); + } + ++ if (fxp->packet_len > FXP_MAX_PACKET_LEN) { ++ (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, ++ "received excessive SFTP packet (len %lu > max %lu bytes), rejecting", ++ (unsigned long) fxp->packet_len, (unsigned long) FXP_MAX_PACKET_LEN); ++ SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL); ++ } ++ + if (buflen == 0) { + fxp_packet_set_packet(fxp); + fxp_packet_clear_cache_data(); +@@ -14125,15 +14132,6 @@ int sftp_fxp_handle_packet(pool *p, void *ssh2, uint32_t channel_id, + (unsigned long) channel_id); + } + +- if (fxp->packet_len > FXP_MAX_PACKET_LEN) { +- (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, +- "received excessive SFTP packet (len %lu > max %lu bytes), rejecting", +- (unsigned long) fxp->packet_len, (unsigned long) FXP_MAX_PACKET_LEN); +- destroy_pool(fxp->pool); +- errno = EPERM; +- return -1; +- } +- + fxp_session = fxp_get_session(channel_id); + if (fxp_session == NULL) { + (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, diff -Nru proftpd-dfsg-1.3.8.c+dfsg/debian/patches/8685930f5e2e448563ef31d8871553308b954785.diff proftpd-dfsg-1.3.8.c+dfsg/debian/patches/8685930f5e2e448563ef31d8871553308b954785.diff --- proftpd-dfsg-1.3.8.c+dfsg/debian/patches/8685930f5e2e448563ef31d8871553308b954785.diff 1970-01-01 00:00:00.000000000 +0000 +++ proftpd-dfsg-1.3.8.c+dfsg/debian/patches/8685930f5e2e448563ef31d8871553308b954785.diff 2026-07-22 20:56:40.000000000 +0000 @@ -0,0 +1,29 @@ +From 8685930f5e2e448563ef31d8871553308b954785 Mon Sep 17 00:00:00 2001 +From: TJ Saunders +Date: Sat, 30 May 2026 11:25:33 -0700 +Subject: [PATCH] Issue #2115: Ensure that the SFTP request payload length + computation does not underflow. + +--- + contrib/mod_sftp/fxp.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +--- proftpd.orig/contrib/mod_sftp/fxp.c ++++ proftpd/contrib/mod_sftp/fxp.c +@@ -3389,6 +3389,16 @@ + return NULL; + } + ++ /* We require 5 bytes of SFTP request data at a minimum: 1 byte for the ++ * request type, and 4 bytes for the payload length (Issue #2115). ++ */ ++ if (fxp->packet_len < 5) { ++ (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, ++ "illegal SFTP request length (%lu bytes, require at least 5 bytes), " ++ "rejecting", (unsigned long) fxp->packet_len); ++ SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL); ++ } ++ + } else { + pr_trace_msg(trace_channel, 19, + "already have SFTP request packet len %lu from previous buffer data", diff -Nru proftpd-dfsg-1.3.8.c+dfsg/debian/patches/8ccd66576da8c3e4ca678335268905e00bc71bdb.diff proftpd-dfsg-1.3.8.c+dfsg/debian/patches/8ccd66576da8c3e4ca678335268905e00bc71bdb.diff --- proftpd-dfsg-1.3.8.c+dfsg/debian/patches/8ccd66576da8c3e4ca678335268905e00bc71bdb.diff 1970-01-01 00:00:00.000000000 +0000 +++ proftpd-dfsg-1.3.8.c+dfsg/debian/patches/8ccd66576da8c3e4ca678335268905e00bc71bdb.diff 2026-07-22 20:56:40.000000000 +0000 @@ -0,0 +1,188 @@ +From 8ccd66576da8c3e4ca678335268905e00bc71bdb Mon Sep 17 00:00:00 2001 +From: TJ Saunders +Date: Sat, 30 May 2026 13:48:58 -0700 +Subject: [PATCH] Implement some sanity checks on the length of extended + attributes (xattrs) that can be requested via custom SFTP extensions. + +Bankde Eakasit posited that this could be another vector to triggering excessive memory allocations. +--- + contrib/mod_sftp/fxp.c | 85 ++++++++++++++++++++++++++++++++++++------ + 1 file changed, 74 insertions(+), 11 deletions(-) + +diff --git a/contrib/mod_sftp/fxp.c b/contrib/mod_sftp/fxp.c +index 9a31cc722..dec6f37e9 100644 +--- a/contrib/mod_sftp/fxp.c ++++ b/contrib/mod_sftp/fxp.c +@@ -281,12 +281,14 @@ static size_t fxp_packet_data_allocsz = 0; + #define FXP_PACKET_DATA_DEFAULT_SZ (1024 * 16) + #define FXP_RESPONSE_DATA_DEFAULT_SZ 512 + +-#ifdef PR_USE_XATTR ++#if defined(PR_USE_XATTR) ++/* Impose limits on the xattr value length we are willing to process. */ ++# define FXP_XATTR_VALUE_MAX_LEN (1024 * 64) + /* Allocate larger buffers for extended attributes */ + # define FXP_RESPONSE_NAME_DEFAULT_SZ (1024 * 4) + #endif /* PR_USE_XATTR */ + +-#ifndef FXP_RESPONSE_NAME_DEFAULT_SZ ++#if !defined(FXP_RESPONSE_NAME_DEFAULT_SZ) + # define FXP_RESPONSE_NAME_DEFAULT_SZ FXP_RESPONSE_DATA_DEFAULT_SZ + #endif + +@@ -1893,7 +1895,7 @@ static char *fxp_strattrs(pool *p, struct stat *st, uint32_t *attr_flags) { + + if (fxp_session->client_version >= 6) { + flags |= SSH2_FX_ATTR_LINK_COUNT; +-#ifdef PR_USE_XATTR ++#if defined(PR_USE_XATTR) + flags |= SSH2_FX_ATTR_EXTENDED; + #endif /* PR_USE_XATTR */ + } +@@ -2572,7 +2574,7 @@ static uint32_t fxp_xattrs_write(pool *p, struct fxp_buffer *fxb, + const char *path) { + uint32_t len = 0; + +-#ifdef PR_USE_XATTR ++#if defined(PR_USE_XATTR) + int res; + array_header *names = NULL; + +@@ -5964,7 +5966,7 @@ static int fxp_handle_ext_statvfs(struct fxp_packet *fxp, const char *path) { + } + #endif /* !HAVE_SYS_STATVFS_H */ + +-#ifdef PR_USE_XATTR ++#if defined(PR_USE_XATTR) + static int fxp_handle_ext_getxattr(struct fxp_packet *fxp, const char *path, + const char *name, uint32_t valsz) { + ssize_t res; +@@ -5974,6 +5976,36 @@ static int fxp_handle_ext_getxattr(struct fxp_packet *fxp, const char *path, + const char *reason; + struct fxp_packet *resp; + ++ buflen = bufsz = FXP_RESPONSE_DATA_DEFAULT_SZ; ++ buf = ptr = palloc(fxp->pool, bufsz); ++ ++ if (valsz > FXP_XATTR_VALUE_MAX_LEN) { ++ int xerrno = EINVAL; ++ ++ (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, ++ "getxattr(2) on '%s' for attribute '%s' (%lu bytes) exceeds maximum " ++ "value size (%lu bytes), denying", path, name, (unsigned long) valsz, ++ (unsigned long) FXP_XATTR_VALUE_MAX_LEN); ++ ++ status_code = fxp_errno2status(xerrno, &reason); ++ ++ pr_trace_msg(trace_channel, 8, "sending response: STATUS %lu '%s' " ++ "('%s' [%d])", (unsigned long) status_code, reason, strerror(xerrno), ++ xerrno); ++ ++ fxp_status_write(fxp->pool, &buf, &buflen, fxp->request_id, status_code, ++ reason, NULL); ++ ++ resp = fxp_packet_create(fxp->pool, fxp->channel_id); ++ resp->payload = ptr; ++ resp->payload_sz = (bufsz - buflen); ++ ++ return fxp_packet_write(resp); ++ } ++ ++ /* Now that the value size has been checked, we allocate a new, larger ++ * buffer for that size. ++ */ + val = pcalloc(fxp->pool, (size_t) valsz+1); + + buflen = bufsz = FXP_RESPONSE_DATA_DEFAULT_SZ + valsz; +@@ -6027,7 +6059,7 @@ static int fxp_handle_ext_fgetxattr(struct fxp_packet *fxp, const char *handle, + struct fxp_handle *fxh; + struct fxp_packet *resp; + +- buflen = bufsz = FXP_RESPONSE_DATA_DEFAULT_SZ + valsz; ++ buflen = bufsz = FXP_RESPONSE_DATA_DEFAULT_SZ; + buf = ptr = palloc(fxp->pool, bufsz); + + fxh = fxp_handle_get(handle); +@@ -6076,6 +6108,37 @@ static int fxp_handle_ext_fgetxattr(struct fxp_packet *fxp, const char *handle, + } + + path = fxh->fh->fh_path; ++ ++ if (valsz > FXP_XATTR_VALUE_MAX_LEN) { ++ int xerrno = EINVAL; ++ ++ (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, ++ "fgetxattr(2) on '%s' for attribute '%s' (%lu bytes) exceeds maximum " ++ "value size (%lu bytes), denying", path, name, (unsigned long) valsz, ++ (unsigned long) FXP_XATTR_VALUE_MAX_LEN); ++ ++ status_code = fxp_errno2status(xerrno, &reason); ++ ++ pr_trace_msg(trace_channel, 8, "sending response: STATUS %lu '%s' " ++ "('%s' [%d])", (unsigned long) status_code, reason, strerror(xerrno), ++ xerrno); ++ ++ fxp_status_write(fxp->pool, &buf, &buflen, fxp->request_id, status_code, ++ reason, NULL); ++ ++ resp = fxp_packet_create(fxp->pool, fxp->channel_id); ++ resp->payload = ptr; ++ resp->payload_sz = (bufsz - buflen); ++ ++ return fxp_packet_write(resp); ++ } ++ ++ /* Now that the value size has been checked, we allocate a new, larger ++ * buffer for that size. ++ */ ++ buflen = bufsz = FXP_RESPONSE_DATA_DEFAULT_SZ + valsz; ++ buf = ptr = palloc(fxp->pool, bufsz); ++ + val = pcalloc(fxp->pool, (size_t) valsz+1); + + res = pr_fsio_fgetxattr(fxp->pool, fxh->fh, name, val, (size_t) valsz); +@@ -8064,7 +8127,7 @@ static int fxp_handle_fstat(struct fxp_packet *fxp) { + pr_trace_msg(trace_channel, 7, "received request: FSTAT %s", name); + attr_flags = SSH2_FX_ATTR_SIZE|SSH2_FX_ATTR_UIDGID|SSH2_FX_ATTR_PERMISSIONS| + SSH2_FX_ATTR_ACMODTIME; +-#ifdef PR_USE_XATTR ++#if defined(PR_USE_XATTR) + if (!(fxp_fsio_opts & PR_FSIO_OPT_IGNORE_XATTR)) { + attr_flags |= SSH2_FX_ATTR_EXTENDED; + } +@@ -8797,7 +8860,7 @@ static int fxp_handle_lstat(struct fxp_packet *fxp) { + pr_trace_msg(trace_channel, 7, "received request: LSTAT %s", path); + attr_flags = SSH2_FX_ATTR_SIZE|SSH2_FX_ATTR_UIDGID|SSH2_FX_ATTR_PERMISSIONS| + SSH2_FX_ATTR_ACMODTIME; +-#ifdef PR_USE_XATTR ++#if defined(PR_USE_XATTR) + if (!(fxp_fsio_opts & PR_FSIO_OPT_IGNORE_XATTR)) { + attr_flags |= SSH2_FX_ATTR_EXTENDED; + } +@@ -10854,7 +10917,7 @@ static int fxp_handle_readdir(struct fxp_packet *fxp) { + pr_signals_handle(); + + /* How much non-path data do we expect to be associated with this entry? */ +-#ifdef PR_USE_XATTR ++#if defined(PR_USE_XATTR) + /* Note that the "extra space" to allocate for extended attributes is + * currently a bit of a guess. Initially, this was 4K; that was causing + * slower directory listings due to the need for more READDIR requests, +@@ -11006,7 +11069,7 @@ static int fxp_handle_readdir(struct fxp_packet *fxp) { + * Thus we CHOOSE to only provide these extended attributes, if supported, + * to protocol version 6 clients. + */ +-#ifdef PR_USE_XATTR ++#if defined(PR_USE_XATTR) + if (!(fxp_fsio_opts & PR_FSIO_OPT_IGNORE_XATTR)) { + attr_flags |= SSH2_FX_ATTR_EXTENDED; + } +@@ -12883,7 +12946,7 @@ static int fxp_handle_stat(struct fxp_packet *fxp) { + pr_trace_msg(trace_channel, 7, "received request: STAT %s", path); + attr_flags = SSH2_FX_ATTR_SIZE|SSH2_FX_ATTR_UIDGID|SSH2_FX_ATTR_PERMISSIONS| + SSH2_FX_ATTR_ACMODTIME; +-#ifdef PR_USE_XATTR ++#if defined(PR_USE_XATTR) + if (!(fxp_fsio_opts & PR_FSIO_OPT_IGNORE_XATTR)) { + attr_flags |= SSH2_FX_ATTR_EXTENDED; + } diff -Nru proftpd-dfsg-1.3.8.c+dfsg/debian/patches/a237fa62341bf882c7edc4e5e8cc492cec851d0b.diff proftpd-dfsg-1.3.8.c+dfsg/debian/patches/a237fa62341bf882c7edc4e5e8cc492cec851d0b.diff --- proftpd-dfsg-1.3.8.c+dfsg/debian/patches/a237fa62341bf882c7edc4e5e8cc492cec851d0b.diff 1970-01-01 00:00:00.000000000 +0000 +++ proftpd-dfsg-1.3.8.c+dfsg/debian/patches/a237fa62341bf882c7edc4e5e8cc492cec851d0b.diff 2026-07-22 20:56:40.000000000 +0000 @@ -0,0 +1,43 @@ +From a237fa62341bf882c7edc4e5e8cc492cec851d0b Mon Sep 17 00:00:00 2001 +From: TJ Saunders +Date: Sat, 30 May 2026 13:12:51 -0700 +Subject: [PATCH] Issue #2115: The minimum packet length enforcement for SFTP + requests needs to occur slightly earlier, to prevent other means of + circumvention. + +--- + contrib/mod_sftp/fxp.c | 14 +++++++------- + 1 file changed, 7 insertions(+), 7 deletions(-) + +diff --git a/contrib/mod_sftp/fxp.c b/contrib/mod_sftp/fxp.c +index a4fe5bc25..9a31cc722 100644 +--- a/contrib/mod_sftp/fxp.c ++++ b/contrib/mod_sftp/fxp.c +@@ -3410,13 +3410,6 @@ static struct fxp_packet *fxp_packet_read(uint32_t channel_id, + "(%lu bytes remaining in buffer)", (unsigned long) fxp->packet_len, + (unsigned long) buflen); + +- if (buflen == 0) { +- fxp_packet_set_packet(fxp); +- fxp_packet_clear_cache_data(); +- +- return NULL; +- } +- + /* We require 5 bytes of SFTP request data at a minimum: 1 byte for the + * request type, and 4 bytes for the payload length (Issue #2115). + */ +@@ -3427,6 +3420,13 @@ static struct fxp_packet *fxp_packet_read(uint32_t channel_id, + SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL); + } + ++ if (buflen == 0) { ++ fxp_packet_set_packet(fxp); ++ fxp_packet_clear_cache_data(); ++ ++ return NULL; ++ } ++ + } else { + pr_trace_msg(trace_channel, 19, + "already have SFTP request packet len %lu from previous buffer data", diff -Nru proftpd-dfsg-1.3.8.c+dfsg/debian/patches/baf4b7929758c72cdb6cf16325fa25f435d23db6.diff proftpd-dfsg-1.3.8.c+dfsg/debian/patches/baf4b7929758c72cdb6cf16325fa25f435d23db6.diff --- proftpd-dfsg-1.3.8.c+dfsg/debian/patches/baf4b7929758c72cdb6cf16325fa25f435d23db6.diff 1970-01-01 00:00:00.000000000 +0000 +++ proftpd-dfsg-1.3.8.c+dfsg/debian/patches/baf4b7929758c72cdb6cf16325fa25f435d23db6.diff 2026-07-22 20:56:40.000000000 +0000 @@ -0,0 +1,49 @@ +From baf4b7929758c72cdb6cf16325fa25f435d23db6 Mon Sep 17 00:00:00 2001 +From: TJ Saunders +Date: Wed, 1 Jul 2026 09:14:38 -0700 +Subject: [PATCH] Exercise caution when reading the client-provided file size + for SCP uploads, as it could possibly overflow our size type. + +Thanks to Fabian Wahle of Hap Security for reporting this issue. +--- + contrib/mod_sftp/scp.c | 23 +++++++++++++++++------ + 1 file changed, 17 insertions(+), 6 deletions(-) + +diff --git a/contrib/mod_sftp/scp.c b/contrib/mod_sftp/scp.c +index 8c105587f..b1f3e89ae 100644 +--- a/contrib/mod_sftp/scp.c ++++ b/contrib/mod_sftp/scp.c +@@ -603,16 +603,27 @@ static int recv_perms(pool *p, uint32_t channel_id, char *mode_str, + + static int recv_filesz(pool *p, uint32_t channel_id, char *size_str, + off_t *filesz) { +- register unsigned int i; ++ char *endp = NULL; ++ unsigned long long sz; ++ *filesz = 0; + +- /* The file size field could be of arbitrary length. */ +- for (i = 0, *filesz = 0; PR_ISDIGIT(size_str[i]); i++) { +- pr_signals_handle(); ++#if defined(HAVE_STROULL) ++ sz = strtoull(size_str, &endp, 10); ++#else ++ sz = strtoul(size_str, &endp, 10); ++#endif /* HAVE_STROULL */ + +- *filesz = (*filesz * 10) + (size_str[i] - '0'); ++ *filesz = (off_t) sz; ++ ++ /* Watch for cases where the sent file size might overflow our size type. */ ++ if (*filesz < 0) { ++ pr_trace_msg(trace_channel, 2, "file size out of range"); ++ write_confirm(p, channel_id, 1, "file size out of range"); ++ return -1; + } + +- if (size_str[i] != ' ') { ++ if (endp == NULL || ++ *endp != ' ') { + pr_trace_msg(trace_channel, 2, "file size not followed by space delimiter"); + write_confirm(p, channel_id, 1, "file size not delimited"); + return -1; diff -Nru proftpd-dfsg-1.3.8.c+dfsg/debian/patches/ce13286900a7e25f1e3403620496868d73292f6b.diff proftpd-dfsg-1.3.8.c+dfsg/debian/patches/ce13286900a7e25f1e3403620496868d73292f6b.diff --- proftpd-dfsg-1.3.8.c+dfsg/debian/patches/ce13286900a7e25f1e3403620496868d73292f6b.diff 1970-01-01 00:00:00.000000000 +0000 +++ proftpd-dfsg-1.3.8.c+dfsg/debian/patches/ce13286900a7e25f1e3403620496868d73292f6b.diff 2026-07-22 20:56:40.000000000 +0000 @@ -0,0 +1,92 @@ +From ce13286900a7e25f1e3403620496868d73292f6b Mon Sep 17 00:00:00 2001 +From: TJ Saunders +Date: Fri, 26 Jun 2026 12:12:25 -0700 +Subject: [PATCH] Issue #2190: When assembling fragmented SFTP request chunks, + make sure we always allocate sufficient space for the assembled data. + +In addition, add a sanity check for cases where the assembled chunks far exceed our allowed SFTP request packet sizes. +--- + contrib/mod_sftp/fxp.c | 46 +++++++++++++++++++++++++----------------- + 1 file changed, 27 insertions(+), 19 deletions(-) + +diff --git a/contrib/mod_sftp/fxp.c b/contrib/mod_sftp/fxp.c +index 409159a05..6bcbf2995 100644 +--- a/contrib/mod_sftp/fxp.c ++++ b/contrib/mod_sftp/fxp.c +@@ -271,11 +271,11 @@ struct fxp_buffer { + #define FXP_PACKET_HAVE_PAYLOAD_SIZE 0x0008 + #define FXP_PACKET_HAVE_PAYLOAD 0x0010 + +-/* After 32K of allocation from the scratch SFTP payload pool, destroy the ++/* After 64K of allocation from the scratch SFTP payload pool, destroy the + * pool and create a new one. This will prevent unbounded allocation + * from the pool. + */ +-#define FXP_PACKET_DATA_ALLOC_MAX_SZ (1024 * 32) ++#define FXP_PACKET_DATA_ALLOC_MAX_SZ (1024 * 64) + static size_t fxp_packet_data_allocsz = 0; + + #define FXP_PACKET_DATA_DEFAULT_SZ (1024 * 16) +@@ -3311,37 +3311,45 @@ static void fxp_packet_add_cache_data(unsigned char *data, uint32_t datalen) { + + } else { + /* We need a larger buffer. Round up to the nearest 1K size. */ ++ pool *tmp_pool; ++ char *cached_data; ++ uint32_t cached_datalen; + size_t sz; + +- sz = sftp_crypto_get_size(curr_buflen + datalen + 1, 1024); ++ if (curr_buflen + datalen > FXP_MAX_PACKET_LEN) { ++ (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, ++ "received excessive SFTP data (len %lu > max %lu bytes), rejecting", ++ (unsigned long) curr_buflen + datalen, ++ (unsigned long) FXP_MAX_PACKET_LEN); ++ SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL); ++ } + +- if (fxp_packet_data_allocsz > FXP_PACKET_DATA_ALLOC_MAX_SZ) { +- pool *tmp_pool; +- char *tmp_data; +- uint32_t tmp_datalen; ++ /* Get the existing cached data before allocating a larger buffer. */ ++ tmp_pool = make_sub_pool(fxp_pool); + ++ cached_datalen = curr_buflen; ++ cached_data = palloc(tmp_pool, cached_datalen); ++ memcpy(cached_data, curr_buf, cached_datalen); ++ ++ if (fxp_packet_data_allocsz > FXP_PACKET_DATA_ALLOC_MAX_SZ) { + (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, + "renewing SFTP packet data pool"); + +- tmp_pool = make_sub_pool(fxp_pool); +- tmp_datalen = curr_buflen; +- tmp_data = palloc(tmp_pool, tmp_datalen); +- memcpy(tmp_data, curr_buf, tmp_datalen); +- + destroy_pool(curr_buf_pool); + + curr_buf_pool = make_sub_pool(fxp_pool); + pr_pool_tag(curr_buf_pool, "SFTP packet buffer pool"); ++ } + +- curr_bufsz = sz; +- curr_buf = palloc(curr_buf_pool, curr_bufsz); +- fxp_packet_data_allocsz += sz; ++ sz = sftp_crypto_get_size(curr_buflen + datalen + 1, 1024); ++ curr_bufsz = sz; ++ curr_buf = palloc(curr_buf_pool, curr_bufsz); ++ fxp_packet_data_allocsz += sz; + +- memcpy(curr_buf, tmp_data, tmp_datalen); +- curr_buflen = tmp_datalen; ++ memcpy(curr_buf, cached_data, cached_datalen); ++ curr_buflen = cached_datalen; + +- destroy_pool(tmp_pool); +- } ++ destroy_pool(tmp_pool); + } + + /* Append the SSH2 data to the current unconsumed buffer. diff -Nru proftpd-dfsg-1.3.8.c+dfsg/debian/patches/mod_sftp-1.3.8d-issue2115.diff proftpd-dfsg-1.3.8.c+dfsg/debian/patches/mod_sftp-1.3.8d-issue2115.diff --- proftpd-dfsg-1.3.8.c+dfsg/debian/patches/mod_sftp-1.3.8d-issue2115.diff 1970-01-01 00:00:00.000000000 +0000 +++ proftpd-dfsg-1.3.8.c+dfsg/debian/patches/mod_sftp-1.3.8d-issue2115.diff 2026-07-22 20:56:40.000000000 +0000 @@ -0,0 +1,44 @@ +diff --git a/contrib/mod_sftp/fxp.c b/contrib/mod_sftp/fxp.c +index d61e71046..8e867649c 100644 +--- a/contrib/mod_sftp/fxp.c ++++ b/contrib/mod_sftp/fxp.c +@@ -3381,6 +3381,23 @@ static struct fxp_packet *fxp_packet_read(uint32_t channel_id, + "(%lu bytes remaining in buffer)", (unsigned long) fxp->packet_len, + (unsigned long) buflen); + ++ /* We require 5 bytes of SFTP request data at a minimum: 1 byte for the ++ * request type, and 4 bytes for the payload length (Issue #2115). ++ */ ++ if (fxp->packet_len < 5) { ++ (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, ++ "illegal SFTP request length (%lu bytes, require at least 5 bytes), " ++ "rejecting", (unsigned long) fxp->packet_len); ++ SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL); ++ } ++ ++ if (fxp->packet_len > FXP_MAX_PACKET_LEN) { ++ (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, ++ "received excessive SFTP packet (len %lu > max %lu bytes), rejecting", ++ (unsigned long) fxp->packet_len, (unsigned long) FXP_MAX_PACKET_LEN); ++ SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL); ++ } ++ + if (buflen == 0) { + fxp_packet_set_packet(fxp); + fxp_packet_clear_cache(); +@@ -13674,15 +13691,6 @@ int sftp_fxp_handle_packet(pool *p, void *ssh2, uint32_t channel_id, + (unsigned long) channel_id); + } + +- if (fxp->packet_len > FXP_MAX_PACKET_LEN) { +- (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, +- "received excessive SFTP packet (len %lu > max %lu bytes), rejecting", +- (unsigned long) fxp->packet_len, (unsigned long) FXP_MAX_PACKET_LEN); +- destroy_pool(fxp->pool); +- errno = EPERM; +- return -1; +- } +- + fxp_session = fxp_get_session(channel_id); + if (fxp_session == NULL) { + (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, diff -Nru proftpd-dfsg-1.3.8.c+dfsg/debian/patches/mod_sftp-1.3.8d-issue2190.diff proftpd-dfsg-1.3.8.c+dfsg/debian/patches/mod_sftp-1.3.8d-issue2190.diff --- proftpd-dfsg-1.3.8.c+dfsg/debian/patches/mod_sftp-1.3.8d-issue2190.diff 1970-01-01 00:00:00.000000000 +0000 +++ proftpd-dfsg-1.3.8.c+dfsg/debian/patches/mod_sftp-1.3.8d-issue2190.diff 2026-07-22 20:56:40.000000000 +0000 @@ -0,0 +1,81 @@ +diff --git a/contrib/mod_sftp/fxp.c b/contrib/mod_sftp/fxp.c +index d61e71046..a7631eaa4 100644 +--- a/contrib/mod_sftp/fxp.c ++++ b/contrib/mod_sftp/fxp.c +@@ -264,11 +264,11 @@ struct fxp_buffer { + #define FXP_PACKET_HAVE_PAYLOAD_SIZE 0x0008 + #define FXP_PACKET_HAVE_PAYLOAD 0x0010 + +-/* After 32K of allocation from the scratch SFTP payload pool, destroy the ++/* After 64K of allocation from the scratch SFTP payload pool, destroy the + * pool and create a new one. This will prevent unbounded allocation + * from the pool. + */ +-#define FXP_PACKET_DATA_ALLOC_MAX_SZ (1024 * 32) ++#define FXP_PACKET_DATA_ALLOC_MAX_SZ (1024 * 64) + static size_t fxp_packet_data_allocsz = 0; + + #define FXP_PACKET_DATA_DEFAULT_SZ (1024 * 16) +@@ -3288,37 +3288,45 @@ static void fxp_packet_add_cache(unsigned char *data, uint32_t datalen) { + + } else { + /* We need a larger buffer. Round up to the nearest 1K size. */ ++ pool *tmp_pool; ++ char *cached_data; ++ uint32_t cached_datalen; + size_t sz; + +- sz = sftp_crypto_get_size(curr_buflen + datalen + 1, 1024); ++ if (curr_buflen + datalen > FXP_MAX_PACKET_LEN) { ++ (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, ++ "received excessive SFTP data (len %lu > max %lu bytes), rejecting", ++ (unsigned long) curr_buflen + datalen, ++ (unsigned long) FXP_MAX_PACKET_LEN); ++ SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL); ++ } + +- if (fxp_packet_data_allocsz > FXP_PACKET_DATA_ALLOC_MAX_SZ) { +- pool *tmp_pool; +- char *tmp_data; +- uint32_t tmp_datalen; ++ /* Get the existing cached data before allocating a larger buffer. */ ++ tmp_pool = make_sub_pool(fxp_pool); ++ ++ cached_datalen = curr_buflen; ++ cached_data = palloc(tmp_pool, cached_datalen); ++ memcpy(cached_data, curr_buf, cached_datalen); + ++ if (fxp_packet_data_allocsz > FXP_PACKET_DATA_ALLOC_MAX_SZ) { + (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, + "renewing SFTP packet data pool"); + +- tmp_pool = make_sub_pool(fxp_pool); +- tmp_datalen = curr_buflen; +- tmp_data = palloc(tmp_pool, tmp_datalen); +- memcpy(tmp_data, curr_buf, tmp_datalen); +- + destroy_pool(curr_buf_pool); + + curr_buf_pool = make_sub_pool(fxp_pool); + pr_pool_tag(curr_buf_pool, "SFTP packet buffer pool"); ++ } + +- curr_bufsz = sz; +- curr_buf = palloc(curr_buf_pool, curr_bufsz); +- fxp_packet_data_allocsz += sz; ++ sz = sftp_crypto_get_size(curr_buflen + datalen + 1, 1024); ++ curr_bufsz = sz; ++ curr_buf = palloc(curr_buf_pool, curr_bufsz); ++ fxp_packet_data_allocsz += sz; + +- memcpy(curr_buf, tmp_data, tmp_datalen); +- curr_buflen = tmp_datalen; ++ memcpy(curr_buf, cached_data, cached_datalen); ++ curr_buflen = cached_datalen; + +- destroy_pool(tmp_pool); +- } ++ destroy_pool(tmp_pool); + } + + /* Append the SSH2 data to the current unconsumed buffer. diff -Nru proftpd-dfsg-1.3.8.c+dfsg/debian/patches/series proftpd-dfsg-1.3.8.c+dfsg/debian/patches/series --- proftpd-dfsg-1.3.8.c+dfsg/debian/patches/series 2026-05-01 10:47:52.000000000 +0000 +++ proftpd-dfsg-1.3.8.c+dfsg/debian/patches/series 2026-07-22 20:56:40.000000000 +0000 @@ -21,3 +21,16 @@ 14c006b62c09d1efe302c57b2d183a489bcb22dc.diff 9b2b4a3e32d251798bf8fa841b124ab15ba58f11.diff 2052_pghmcfc.diff +# issue 2057, CVE-2026-44331 +07797aba88dca902da7eaf1dfe262c8896943de7.diff +# issue 2115, CVE-2026-53994 +mod_sftp-1.3.8d-issue2115.diff +#8685930f5e2e448563ef31d8871553308b954785.diff +#a237fa62341bf882c7edc4e5e8cc492cec851d0b.diff +#8ccd66576da8c3e4ca678335268905e00bc71bdb.diff +#226c85b97aa75cef4bdd74e7012b313a801796b5.diff +# issue 2201, CVE-2026-63091 +baf4b7929758c72cdb6cf16325fa25f435d23db6.diff +# issue 2190, CVE-2026-63090 +mod_sftp-1.3.8d-issue2190.diff +# ce13286900a7e25f1e3403620496868d73292f6b.diff