Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package vsftpd for openSUSE:Factory checked 
in at 2026-11-10 16:29:44
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/vsftpd (Old)
 and      /work/SRC/openSUSE:Factory/.vsftpd.new.3759290 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "vsftpd"

Tue Nov 10 16:29:44 2026 rev:96 rq:1396499 version:3.0.5

Changes:
--------
--- /work/SRC/openSUSE:Factory/vsftpd/vsftpd.changes    2026-11-08 
19:21:45.592320276 +0100
+++ /work/SRC/openSUSE:Factory/.vsftpd.new.3759290/vsftpd.changes       
2026-11-10 16:29:53.549468034 +0100
@@ -1,0 +2,31 @@
+Tue Nov 10 11:43:32 UTC 2026 - Peter Simons <[email protected]>
+
+- Add vsftpd-CVE-2026-119567.patch: make cmds_allowed and cmds_denied
+  apply to the RFC 775 aliases XMKD, XRMD, XPWD, XCWD and XCUP as
+  well. Previously authenticated users could run denied commands by
+  issuing their X-prefixed alias, e.g. XRMD instead of RMD.
+  [CVE-2026-119567, bsc#1288995]
+
+-------------------------------------------------------------------
+Tue Nov 10 11:33:17 UTC 2026 - Peter Simons <[email protected]>
+
+- Add vsftpd-no-stderr-on-control-connection.patch: session processes
+  no longer have the client connection on stderr. Diagnostics printed
+  by library code, such as "yp_bind_client_create_v3: RPC: Unable to
+  send" from NIS lookups, were sent to FTP clients verbatim. They now
+  go to the listener's stderr (the journal under systemd) or
+  /dev/null instead. [bsc#1184651]
+
+- [email protected]: set StandardError=journal so that such messages are
+  logged when vsftpd is socket-activated.
+
+-------------------------------------------------------------------
+Mon Nov  9 15:39:43 UTC 2026 - Peter Simons <[email protected]>
+
+- Add vsftpd-CVE-2026-119568.patch: bind TLS sessions resumed on
+  the data channel to the session of the corresponding control
+  connection. Previously require_ssl_reuse accepted any resumed
+  session ticket, allowing an attacker sharing the victim's source
+  IP to hijack FTPS data connections. [CVE-2026-119568, bsc#1288996]
+
+-------------------------------------------------------------------

New:
----
  vsftpd-CVE-2026-119567.patch
  vsftpd-CVE-2026-119568.patch
  vsftpd-no-stderr-on-control-connection.patch

----------(New B)----------
  New:
- Add vsftpd-CVE-2026-119567.patch: make cmds_allowed and cmds_denied
  apply to the RFC 775 aliases XMKD, XRMD, XPWD, XCWD and XCUP as
  New:
- Add vsftpd-CVE-2026-119568.patch: bind TLS sessions resumed on
  the data channel to the session of the corresponding control
  New:
- Add vsftpd-no-stderr-on-control-connection.patch: session processes
  no longer have the client connection on stderr. Diagnostics printed
----------(New E)----------

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ vsftpd.spec ++++++
--- /var/tmp/diff_new_pack.p8BBoE/_old  2026-11-10 16:29:55.059531197 +0100
+++ /var/tmp/diff_new_pack.p8BBoE/_new  2026-11-10 16:29:55.061531280 +0100
@@ -102,6 +102,12 @@
 #PATCH-FIX-OPENSUSE bsc#1211301 Enable crypto-policies support
 Patch47:        vsftpd-use-system-wide-crypto-policy.patch
 Patch48:        terminate-peers-on-quit.patch
+#PATCH-FIX-OPENSUSE bsc#1288996 CVE-2026-119568 bind data channel TLS session 
to control channel
+Patch49:        vsftpd-CVE-2026-119568.patch
+#PATCH-FIX-OPENSUSE do not leak library diagnostics on stderr to FTP clients
+Patch50:        vsftpd-no-stderr-on-control-connection.patch
+#PATCH-FIX-OPENSUSE bsc#1288995 CVE-2026-119567 apply cmds_denied to RFC 775 
aliases
+Patch51:        vsftpd-CVE-2026-119567.patch
 BuildRequires:  libcap-devel
 %if 0%{?suse_version} == 1315
 BuildRequires:  libopenssl-1_1-devel >= 1.1.1
@@ -191,6 +197,9 @@
 %patch -P 46 -p1
 %patch -P 47 -p1
 %patch -P 48 -p1
+%patch -P 49 -p1
+%patch -P 50 -p1
+%patch -P 51 -p1
 
 %build
 %define seccomp_opts -D_GNU_SOURCE -DUSE_SECCOMP


++++++ vsftpd-CVE-2026-119567.patch ++++++
From: Peter Simons <[email protected]>
Subject: Apply cmds_allowed / cmds_denied to RFC 775 command aliases

process_post_login() compared the literal command token against the
cmds_allowed and cmds_denied lists, but dispatched the RFC 775 aliases
XMKD, XRMD, XPWD, XCWD and XCUP to the same handlers as MKD, RMD, PWD,
CWD and CDUP. An authenticated user could therefore bypass e.g.
cmds_denied=RMD by sending XRMD instead.

Both the received command and the configured list entries are now
mapped to their canonical name before comparison. List entries are also
upper-cased, because commands received from the client already are, so
lower-case entries previously never matched anything.

CVE-2026-119567, bsc#1288995

--- a/postlogin.c
+++ b/postlogin.c
@@ -80,6 +80,36 @@ static void get_unique_filename(struct m
 static int data_transfer_checks_ok(struct vsf_session* p_sess);
 static void resolve_tilde(struct mystr* p_str, struct vsf_session* p_sess);
 
+static void
+canonicalize_cmd(struct mystr* p_cmd_str)
+{
+  /* Map the RFC 775 X-prefixed aliases to the commands they are equivalent
+   * to, so that cmds_allowed / cmds_denied cannot be bypassed by using the
+   * alias of a denied command.
+   */
+  str_upper(p_cmd_str);
+  if (str_equal_text(p_cmd_str, "XMKD"))
+  {
+    str_alloc_text(p_cmd_str, "MKD");
+  }
+  else if (str_equal_text(p_cmd_str, "XRMD"))
+  {
+    str_alloc_text(p_cmd_str, "RMD");
+  }
+  else if (str_equal_text(p_cmd_str, "XPWD"))
+  {
+    str_alloc_text(p_cmd_str, "PWD");
+  }
+  else if (str_equal_text(p_cmd_str, "XCWD"))
+  {
+    str_alloc_text(p_cmd_str, "CWD");
+  }
+  else if (str_equal_text(p_cmd_str, "XCUP"))
+  {
+    str_alloc_text(p_cmd_str, "CDUP");
+  }
+}
+
 void
 process_post_login(struct vsf_session* p_sess)
 {
@@ -115,6 +145,7 @@ process_post_login(struct vsf_session* p
 
   while(1)
   {
+    static struct mystr s_canon_cmd_str;
     int cmd_ok = 1;
     if (tunable_setproctitle_enable)
     {
@@ -138,6 +169,11 @@ process_post_login(struct vsf_session* p
       str_free(&proctitle_str);
     }
     /* Test command against the allowed lists.. */
+    if (tunable_cmds_allowed || tunable_cmds_denied)
+    {
+      str_copy(&s_canon_cmd_str, &p_sess->ftp_cmd_str);
+      canonicalize_cmd(&s_canon_cmd_str);
+    }
     if (tunable_cmds_allowed)
     {
       static struct mystr s_src_str;
@@ -151,7 +187,8 @@ process_post_login(struct vsf_session* p
           cmd_ok = 0;
           break;
         }
-        else if (str_equal(&s_src_str, &p_sess->ftp_cmd_str))
+        canonicalize_cmd(&s_src_str);
+        if (str_equal(&s_src_str, &s_canon_cmd_str))
         {
           break;
         }
@@ -170,7 +207,8 @@ process_post_login(struct vsf_session* p
         {
           break;
         }
-        else if (str_equal(&s_src_str, &p_sess->ftp_cmd_str))
+        canonicalize_cmd(&s_src_str);
+        if (str_equal(&s_src_str, &s_canon_cmd_str))
         {
           cmd_ok = 0;
           break;
--- a/vsftpd.conf.5
+++ b/vsftpd.conf.5
@@ -864,6 +864,11 @@ login. USER, PASS, QUIT and others are a
 appears on both this and
 .BR cmds_allowed
 then the denial takes precedence. (Added in v2.1.0).
+The RFC 775 aliases XCUP, XCWD, XMKD, XPWD and XRMD are treated like CDUP,
+CWD, MKD, PWD and RMD respectively by both
+.BR cmds_allowed
+and
+.BR cmds_denied .
 
 Default: (none)
 .TP

++++++ vsftpd-CVE-2026-119568.patch ++++++
From: Peter Simons <[email protected]>
Subject: Bind data channel TLS sessions to the control channel session

require_ssl_reuse only checked SSL_session_reused() on the data channel.
In standalone mode all session processes inherit the same SSL_CTX and
therefore the same session ticket key, so a ticket an attacker obtained
via AUTH TLS on their own control connection was accepted on the PASV
data port of another user's session (from the same source IP), allowing
download theft or upload injection.

Each session process now generates a random token before its control
handshake and stores it as ticket appdata in all sessions issued on the
control channel. Foreign tickets are ignored on decryption, and data
channel sessions are only accepted if they carry this process' token.

CVE-2026-119568, bsc#1288996

--- a/ssl.c
+++ b/ssl.c
@@ -55,9 +55,30 @@ static int ssl_read_common(struct vsf_se
                            unsigned int len,
                            int (*p_ssl_func)(SSL*, void*, int));
 
+static int ssl_ticket_gen_callback(SSL* p_ssl, void* p_arg);
+static SSL_TICKET_RETURN ssl_ticket_dec_callback(SSL* p_ssl,
+                                                 SSL_SESSION* p_ssl_sess,
+                                                 const unsigned char* 
p_keyname,
+                                                 size_t keyname_len,
+                                                 SSL_TICKET_STATUS status,
+                                                 void* p_arg);
+static int ssl_session_is_bound(const SSL_SESSION* p_ssl_sess);
+
 static int ssl_inited;
 static struct mystr debug_str;
 
+/* SECURITY: random token generated by each session process right before its
+ * control channel TLS handshake. It is stored as ticket appdata in every TLS
+ * session issued on that control channel. Data channel sessions are only
+ * accepted if they resume a session carrying this token. In standalone mode
+ * all session processes share one SSL_CTX (and thus one ticket key), so
+ * merely checking SSL_session_reused() would accept a ticket obtained by an
+ * attacker on a completely different control connection (CVE-2026-119568).
+ */
+#define VSF_SSL_BIND_TOKEN_LEN 32
+static unsigned char s_bind_token[VSF_SSL_BIND_TOKEN_LEN];
+static int s_bind_token_set;
+
 void
 ssl_init(struct vsf_session* p_sess)
 {
@@ -184,6 +205,12 @@ ssl_init(struct vsf_session* p_sess)
       /* Ensure cached session doesn't expire */
       SSL_CTX_set_timeout(p_ctx, INT_MAX);
     }
+    /* Bind session tickets to the control connection they were issued on. */
+    if (!SSL_CTX_set_session_ticket_cb(p_ctx, ssl_ticket_gen_callback,
+                                       ssl_ticket_dec_callback, NULL))
+    {
+      die("SSL: could not set session ticket callbacks");
+    }
     /* Set up ALPN to check for FTP protocol intention of client. */
     SSL_CTX_set_alpn_select_cb(p_ctx, ssl_alpn_callback, p_sess);
     /* Set up SNI callback for an optional hostname check. */
@@ -499,6 +526,15 @@ ssl_accept(struct vsf_session* p_sess, i
     ssl_data_close(p_sess);
     return 0;
   }
+  if (tunable_require_ssl_reuse &&
+      !ssl_session_is_bound(SSL_get0_session(p_ssl)))
+  {
+    str_alloc_text(&debug_str,
+                   "SSL session on data channel not from control channel.");
+    vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
+    ssl_data_close(p_sess);
+    return 0;
+  }
   if (str_getlen(&p_sess->control_cert_digest) > 0)
   {
     static struct mystr data_cert_digest;
@@ -637,11 +673,32 @@ get_ssl(struct vsf_session* p_sess, int
 static int
 ssl_session_init(struct vsf_session* p_sess)
 {
-  SSL* p_ssl = get_ssl(p_sess, VSFTP_COMMAND_FD);
+  SSL* p_ssl;
+  /* Must happen in the per-session process, before the control handshake
+   * issues any session tickets.
+   */
+  if (!s_bind_token_set)
+  {
+    if (RAND_bytes(s_bind_token, sizeof(s_bind_token)) != 1)
+    {
+      die("SSL: RAND_bytes failed");
+    }
+    s_bind_token_set = 1;
+  }
+  p_ssl = get_ssl(p_sess, VSFTP_COMMAND_FD);
   if (p_ssl == NULL)
   {
     return 0;
   }
+  /* Covers session ID based resumption via the server side session cache,
+   * where no ticket (and thus no ticket callback) is involved. The cached
+   * session object is the same as the one attached to p_ssl.
+   */
+  if (!SSL_SESSION_set1_ticket_appdata(SSL_get_session(p_ssl), s_bind_token,
+                                       sizeof(s_bind_token)))
+  {
+    die("SSL: could not bind control session");
+  }
   p_sess->p_control_ssl = p_ssl;
   (void) ssl_cert_digest(p_ssl, p_sess, &p_sess->control_cert_digest);
   setup_bio_callbacks(p_ssl);
@@ -649,6 +706,77 @@ ssl_session_init(struct vsf_session* p_s
 }
 
 static int
+ssl_session_is_bound(const SSL_SESSION* p_ssl_sess)
+{
+  void* p_data = NULL;
+  size_t len = 0;
+  if (p_ssl_sess == NULL || !s_bind_token_set)
+  {
+    return 0;
+  }
+  if (!SSL_SESSION_get0_ticket_appdata((SSL_SESSION*) p_ssl_sess, &p_data,
+                                       &len))
+  {
+    return 0;
+  }
+  if (p_data == NULL || len != sizeof(s_bind_token))
+  {
+    return 0;
+  }
+  return CRYPTO_memcmp(p_data, s_bind_token, sizeof(s_bind_token)) == 0;
+}
+
+static int
+ssl_ticket_gen_callback(SSL* p_ssl, void* p_arg)
+{
+  (void) p_arg;
+  /* Tickets issued on the control channel get this process' token. Tickets
+   * issued on a data channel keep whatever the (possibly resumed) session
+   * already carries, so a fresh data channel session never gets bound.
+   */
+  if (SSL_get_fd(p_ssl) == VSFTP_COMMAND_FD && s_bind_token_set)
+  {
+    return SSL_SESSION_set1_ticket_appdata(SSL_get_session(p_ssl),
+                                           s_bind_token, sizeof(s_bind_token));
+  }
+  return 1;
+}
+
+static SSL_TICKET_RETURN
+ssl_ticket_dec_callback(SSL* p_ssl,
+                        SSL_SESSION* p_ssl_sess,
+                        const unsigned char* p_keyname,
+                        size_t keyname_len,
+                        SSL_TICKET_STATUS status,
+                        void* p_arg)
+{
+  (void) p_ssl;
+  (void) p_keyname;
+  (void) keyname_len;
+  (void) p_arg;
+  switch (status)
+  {
+    case SSL_TICKET_SUCCESS:
+    case SSL_TICKET_SUCCESS_RENEW:
+      /* Refuse to resume tickets minted by other session processes; this
+       * forces a full handshake, which require_ssl_reuse then rejects on
+       * the data channel.
+       */
+      if (!ssl_session_is_bound(p_ssl_sess))
+      {
+        return SSL_TICKET_RETURN_IGNORE_RENEW;
+      }
+      return (status == SSL_TICKET_SUCCESS) ? SSL_TICKET_RETURN_USE
+                                            : SSL_TICKET_RETURN_USE_RENEW;
+    case SSL_TICKET_FATAL_ERR_MALLOC:
+    case SSL_TICKET_FATAL_ERR_OTHER:
+      return SSL_TICKET_RETURN_ABORT;
+    default:
+      return SSL_TICKET_RETURN_IGNORE_RENEW;
+  }
+}
+
+static int
 ssl_cert_digest(SSL* p_ssl, struct vsf_session* p_sess, struct mystr* p_str)
 {
   X509* p_cert = SSL_get_peer_certificate(p_ssl);

++++++ vsftpd-no-stderr-on-control-connection.patch ++++++
From: Peter Simons <[email protected]>
Subject: Never send stderr output to the FTP client

In standalone mode the session process got the client socket on fds 0,
1 and 2, and in inetd mode the super server usually does the same.
vsftpd itself only talks to the client through fd 0, but library code
it calls into prints diagnostics to stderr. For example, libnsl prints
"yp_bind_client_create_v3: RPC: Unable to send" when ypbind is not
reachable during a getpwnam() lookup. That text was injected into the
control connection, which confuses clients and leaks server internals.

Standalone mode no longer puts the client socket on fd 2, so session
processes inherit the listener's stderr instead (the journal when running
in the foreground under systemd, /dev/null in background mode). In
addition, if fd 1 or 2 refers to the client socket (inetd mode) or is
closed, it is pointed at /dev/null before any NSS, PAM or libwrap code
runs.

--- a/main.c
+++ b/main.c
@@ -156,6 +156,11 @@ main(int argc, const char* argv[])
     the_session.num_this_ip = ret.num_this_ip;
   }
 
+  /* Make sure stdout / stderr do not point to the client connection before
+   * we call into any library code that might print to them.
+   */
+  vsf_sysutil_detach_stdio_from_socket(VSFTP_COMMAND_FD);
+
   die_init(&the_session);
 
   if (tunable_tcp_wrappers)
--- a/standalone.c
+++ b/standalone.c
@@ -201,10 +201,13 @@ vsf_standalone_main(void)
 static void
 prepare_child(int new_client_sock)
 {
-  /* We must satisfy the contract: command socket on fd 0, 1, 2 */
+  /* We must satisfy the contract: command socket on fd 0, 1. Do not put it
+   * on fd 2: stderr is inherited from the listener (/dev/null in background
+   * mode, otherwise e.g. the journal), so that diagnostics printed by library
+   * code end up there instead of being sent to the FTP client.
+   */
   vsf_sysutil_dupfd2(new_client_sock, 0);
   vsf_sysutil_dupfd2(new_client_sock, 1);
-  vsf_sysutil_dupfd2(new_client_sock, 2);
   if (new_client_sock > 2)
   {
     vsf_sysutil_close(new_client_sock);
--- a/sysutil.c
+++ b/sysutil.c
@@ -2555,6 +2555,62 @@ error:
 }
 
 void
+vsf_sysutil_detach_stdio_from_socket(int sock_fd)
+{
+  /* stdout and stderr must never refer to the client connection. Library
+   * code we call into (NSS modules such as NIS, PAM modules, libwrap, ...)
+   * may print diagnostics there, which would then end up being injected into
+   * the FTP control connection. This happens in inetd mode, where the super
+   * server typically passes the connected socket on fds 0, 1 and 2.
+   */
+  struct stat sock_stat;
+  int fd;
+  int null_fd = -1;
+  if (fstat(sock_fd, &sock_stat) != 0)
+  {
+    /* Leave reporting this to the regular sanity checks. */
+    return;
+  }
+  for (fd = STDOUT_FILENO; fd <= STDERR_FILENO; ++fd)
+  {
+    struct stat fd_stat;
+    if (fd == sock_fd)
+    {
+      continue;
+    }
+    if (fstat(fd, &fd_stat) == 0)
+    {
+      if (fd_stat.st_dev != sock_stat.st_dev ||
+          fd_stat.st_ino != sock_stat.st_ino)
+      {
+        /* Something else, e.g. the journal or a log file - keep it. */
+        continue;
+      }
+    }
+    else if (errno != EBADF)
+    {
+      continue;
+    }
+    /* Either the client socket, or closed. A closed fd would be handed out
+     * by the next open()/socket() call, so plug it with /dev/null as well.
+     */
+    if (null_fd < 0)
+    {
+      null_fd = open("/dev/null", O_WRONLY, 0);
+      if (null_fd < 0)
+      {
+        die("open /dev/null");
+      }
+    }
+    vsf_sysutil_dupfd2(null_fd, fd);
+  }
+  if (null_fd > STDERR_FILENO)
+  {
+    vsf_sysutil_close(null_fd);
+  }
+}
+
+void
 vsf_sysutil_tzset(void)
 {
   int retval;
--- a/sysutil.h
+++ b/sysutil.h
@@ -303,6 +303,7 @@ unsigned int vsf_sysutil_get_umask(void)
 void vsf_sysutil_set_umask(unsigned int umask);
 void vsf_sysutil_make_session_leader(void);
 void vsf_sysutil_reopen_standard_fds(void);
+void vsf_sysutil_detach_stdio_from_socket(int sock_fd);
 void vsf_sysutil_tzset(void);
 const char* vsf_sysutil_get_current_date(void);
 void vsf_sysutil_qsort(void* p_base, unsigned int num_elem,


++++++ [email protected] ++++++
--- /var/tmp/diff_new_pack.p8BBoE/_old  2026-11-10 16:29:55.469548347 +0100
+++ /var/tmp/diff_new_pack.p8BBoE/_new  2026-11-10 16:29:55.473548514 +0100
@@ -5,5 +5,6 @@
 Type=simple
 ExecStart=/usr/sbin/vsftpd /etc/vsftpd.conf -obackground=NO -olisten=NO 
-olisten_ipv6=NO
 StandardInput=socket
+StandardError=journal
 SuccessExitStatus=2
 

Reply via email to