#1151962 proftpd-core: mod_tls spins at 100% CPU, then "double free", after TLS SNI vhost switch with resumed data connection

Package:
proftpd-core
Source:
proftpd-core
Description:
Versatile, virtual-hosting FTP daemon - binaries
Submitter:
Ákos Oravecz
Date:
2026-10-25 13:11:00 UTC
Severity:
normal
Tags:
#1151962#5
Date:
2026-10-25 09:43:01 UTC
From:
To:
Dear Maintainer,

ProFTPD in trixie is affected by upstream issues #1963 and #2056, which are
fixed upstream since 1.3.9a/1.3.9b (testing/unstable ship 1.3.9d and are not
affected). Please consider backporting the fix to trixie via a stable
update.

Symptom

With several <VirtualHost>s on one address/port selected by TLS SNI,
TLSv1.2 and "TLSSessionCache internal", a session whose TLS data connection
resumes the control session never exits when it ends (QUIT or a dropped
connection). The session process spins at 100% CPU in
SSL_CTX_flush_sessions_ex(), its heap grows by ~10-20 MB/s, and it finally
aborts with "double free or corruption (!prev)". Without a session
RLimitMemory,
a few such sessions exhaust the host's memory (for us: an OOM kill of the
whole
FTP container, dropping every user's connection). Any authenticated user can
trigger it, and FileZilla does it routinely, because it resumes the TLS
session
on every data connection.

Stack of the spinning process (from ProFTPD's own signal handler):

  pr_session_disconnect -> pr_netio_close -> mod_tls -> SSL_free
  -> SSL_CTX_free -> SSL_CTX_flush_sessions_ex
  -> mod_tls SNI session table delete callback -> BIO_new/BIO_free

Conditions (all required): the connection starts in one vhost and TLS SNI
switches it to another; at least one data connection resumes the control
session; then the session ends. Without the vhost switch, without
resumption, or without a transfer, the session exits normally.

How to reproduce

Two vhosts on the same port, each with its own certificate, for example:

  TLSSessionCache internal: 1800
  <VirtualHost 0.0.0.0>
    ServerName a.example
    Port 2121
    TLSEngine on
    TLSProtocol TLSv1.2
    TLSOptions NoSessionReuseRequired
    TLSRSACertificateFile /etc/proftpd/a.crt
    TLSRSACertificateKeyFile /etc/proftpd/a.key
  </VirtualHost>
  <VirtualHost 0.0.0.0>
    ServerName b.example
    (same as above, with b.crt/b.key)
  </VirtualHost>

The first vhost owns the port, so every connection starts there. A client
that requests the second vhost via SNI and resumes the session on the data
connection (Python):

  import ftplib, io, ssl

  class ResumingFTP(ftplib.FTP_TLS):
      def ntransfercmd(self, cmd, rest=None):
          conn, size = ftplib.FTP.ntransfercmd(self, cmd, rest)
          conn = self.context.wrap_socket(conn, server_hostname="b.example",
                                          session=self.sock.session)
          return conn, size

  ctx = ssl.create_default_context()
  ctx.check_hostname = False
  ctx.verify_mode = ssl.CERT_NONE
  ctx.maximum_version = ssl.TLSVersion.TLSv1_2
  ftp = ResumingFTP(context=ctx)
  ftp.connect("b.example", 2121)   # hostname -> SNI b.example
  ftp.auth()
  ftp.login("user-of-b", "password")
  ftp.prot_p()
  ftp.storbinary("STOR test.txt", io.BytesIO(b"x" * 700))
  ftp.quit()

After QUIT, the session process stays in state R and its RSS keeps growing.

Upstream fix

All three commits are needed: the third fixes a NULL pointer dereference
(signal 11) introduced by the first one.

  c4fbdcd6714e41505b2add1b1c4e423fbe5baf69  Issue #1963 (PR #1970)
  ed5c4a1207575af2c007a3e4c81c87bb9d3908bd  Issue #2056 (PR #2076)
  c1607a2a219d56443809de326e673b4e38b3bc58  Issue #2093 (PR #2094)

They apply to 1.3.8.c+dfsg-4+deb13u3 with "patch -p1 --fuzz=0" (offsets
only). The first commit also touches tests/t/lib/ProFTPD/Tests/Modules/
mod_tls.pm,
which applies cleanly as well.

Testing

amd64, trixie, libssl3t64 3.5.7-1~deb13u3, with the reproducer above (QUIT
and dropped connection):

  1.3.8.c+dfsg-4+deb13u3, unmodified        -> spins, "double free"
  1.3.8.c+dfsg-4+deb13u3 + the 3 commits    -> session exits cleanly
  1.3.9d~dfsg-1 rebuilt on trixie            -> session exits cleanly

With the patched package, data connections are still resumed ("resumed
session" in TLSLog).

Thank you!

Ákos Oravecz

#1151962#14
Date:
2026-10-25 13:09:44 UTC
From:
To:
On 10/25/26 10:43 AM, Ákos Oravecz wrote:

Hello,

I've put patched packages here:

https://people.debian.org/~hille42/1151962/

They should not differ from current stable that just the three patches.
Please test.

Hilmar