File gvfs-CVE-2026-28296.patch of Package gvfs.43109

commit 21dda19047b86c3e92fae668eb9dc80e33ca71fd
Author: Ondrej Holy <oholy@redhat.com>
Date:   Thu Feb 19 11:24:09 2026 +0100

    ftp: Reject paths containing CR/LF characters
    
    Currently, an FTP backend doesn't verify paths. Path with CR/LF can
    inject extra commands to the server. Let's validate the paths and fail
    with "Filename contains invalid characters." if that happens.
    
    Co-Authored-By: Cursor <cursoragent@cursor.com>
    
    Fixes: https://gitlab.gnome.org/GNOME/gvfs/-/issues/833
    Part-of: <https://gitlab.gnome.org/GNOME/gvfs/-/merge_requests/298>

--- a/daemon/gvfsbackendftp.c
+++ b/daemon/gvfsbackendftp.c
@@ -867,9 +867,14 @@ do_open_for_read (GVfsBackend *backend,
                                                          error_550_permission_or_not_found, 
                                                          NULL };
 
-  g_vfs_ftp_task_setup_data_connection (&task);
-  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename);
+  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename, &task.error);
+  if (file == NULL)
+    {
+      g_vfs_ftp_task_done (&task);
+      return;
+    }
 
+  g_vfs_ftp_task_setup_data_connection (&task);
   g_vfs_ftp_task_send_and_check (&task,
                                  G_VFS_FTP_PASS_100 | G_VFS_FTP_FAIL_200,
                                  open_read_handlers,
@@ -988,7 +993,13 @@ do_create (GVfsBackend *backend,
   GFileInfo *info;
   GVfsFtpFile *file;
 
-  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename);
+  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename, &task.error);
+  if (file == NULL)
+    {
+      g_vfs_ftp_task_done (&task);
+      return;
+    }
+
   info = g_vfs_ftp_dir_cache_lookup_file (ftp->dir_cache, &task, file, FALSE);
   if (info)
     {
@@ -1018,7 +1029,13 @@ do_append (GVfsBackend *backend,
   GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job));
   GVfsFtpFile *file;
 
-  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename);
+  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename, &task.error);
+  if (file == NULL)
+    {
+      g_vfs_ftp_task_done (&task);
+      return;
+    }
+
   do_start_write (&task, flags, "APPE %s", g_vfs_ftp_file_get_ftp_path (file));
   g_vfs_ftp_dir_cache_purge_file (ftp->dir_cache, file);
   g_vfs_ftp_file_free (file);
@@ -1040,14 +1057,25 @@ do_replace (GVfsBackend *backend,
   static const GVfsFtpErrorFunc rnfr_handlers[] = { error_550_permission_or_not_found,
                                                     NULL };
 
-  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename);
+  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename, &task.error);
+  if (file == NULL)
+    {
+      g_vfs_ftp_task_done (&task);
+      return;
+    }
 
   if (make_backup)
     {
       GFileInfo *info;
       char *backup_path = g_strconcat (filename, "~", NULL);
-      backupfile = g_vfs_ftp_file_new_from_gvfs (ftp, backup_path);
+      backupfile = g_vfs_ftp_file_new_from_gvfs (ftp, backup_path, &task.error);
       g_free (backup_path);
+      if (backupfile == NULL)
+        {
+          g_vfs_ftp_file_free (file);
+          g_vfs_ftp_task_done (&task);
+          return;
+        }
 
       info = g_vfs_ftp_dir_cache_lookup_file (ftp->dir_cache, &task, file, FALSE);
 
@@ -1117,7 +1145,7 @@ do_close_write (GVfsBackend *backend,
 
   stream = g_vfs_ftp_connection_get_data_stream (conn);
   filename = g_object_get_data (G_OBJECT (stream), "g-vfs-backend-ftp-filename");
-  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename);
+  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename, NULL);
 
   g_vfs_ftp_task_give_connection (&task, handle);
   g_vfs_ftp_task_close_data_connection (&task);
@@ -1170,7 +1198,13 @@ do_query_info (GVfsBackend *backend,
   GVfsFtpFile *file;
   GFileInfo *real;
  
-  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename);
+  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename, &task.error);
+  if (file == NULL)
+    {
+      g_vfs_ftp_task_done (&task);
+      return;
+    }
+
   real = g_vfs_ftp_dir_cache_lookup_file (ftp->dir_cache,
                                           &task,
                                           file,
@@ -1238,7 +1272,12 @@ do_set_attribute (GVfsBackend *backend,
   GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job));
   GVfsFtpFile *file;
 
-  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename);
+  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename, &task.error);
+  if (file == NULL)
+    {
+      g_vfs_ftp_task_done (&task);
+      return;
+    }
 
   if (strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_MODE) == 0)
     {
@@ -1294,7 +1333,13 @@ do_enumerate (GVfsBackend *backend,
   GVfsFtpFile *dir;
   GList *list, *walk;
 
-  dir = g_vfs_ftp_file_new_from_gvfs (ftp, dirname);
+  dir = g_vfs_ftp_file_new_from_gvfs (ftp, dirname, &task.error);
+  if (dir == NULL)
+    {
+      g_vfs_ftp_task_done (&task);
+      return;
+    }
+
   list = g_vfs_ftp_dir_cache_lookup_dir (ftp->dir_cache,
                                          &task,
                                          dir,
@@ -1336,9 +1381,23 @@ do_set_display_name (GVfsBackend *backen
   GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job));
   GVfsFtpFile *original, *dir, *now;
 
-  original = g_vfs_ftp_file_new_from_gvfs (ftp, filename);
+  original = g_vfs_ftp_file_new_from_gvfs (ftp, filename, &task.error);
+  if (original == NULL)
+    {
+      g_vfs_ftp_task_done (&task);
+      return;
+    }
+
   dir = g_vfs_ftp_file_new_parent (original);
   now = g_vfs_ftp_file_new_child (dir, display_name, &task.error);
+  if (now == NULL)
+    {
+      g_vfs_ftp_file_free (original);
+      g_vfs_ftp_file_free (dir);
+      g_vfs_ftp_task_done (&task);
+      return;
+    }
+
   g_vfs_ftp_task_send (&task,
                        G_VFS_FTP_PASS_300 | G_VFS_FTP_FAIL_200,
                        "RNFR %s", g_vfs_ftp_file_get_ftp_path (original));
@@ -1368,7 +1427,13 @@ do_delete (GVfsBackend *backend,
 
   /* We try file deletion first. If that fails, we try directory deletion.
    * The file-first-then-directory order has been decided by coin-toss. */
-  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename);
+  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename, &task.error);
+  if (file == NULL)
+    {
+      g_vfs_ftp_task_done (&task);
+      return;
+    }
+
   response = g_vfs_ftp_task_send (&task,
                 		  G_VFS_FTP_PASS_500,
                 		  "DELE %s", g_vfs_ftp_file_get_ftp_path (file));
@@ -1416,7 +1481,13 @@ do_make_directory (GVfsBackend *backend,
   GVfsFtpFile *file;
   static const GVfsFtpErrorFunc make_directory_handlers[] = { error_550_exists, error_550_parent_not_found, NULL };
 
-  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename);
+  file = g_vfs_ftp_file_new_from_gvfs (ftp, filename, &task.error);
+  if (file == NULL)
+    {
+      g_vfs_ftp_task_done (&task);
+      return;
+    }
+
   g_vfs_ftp_task_send_and_check (&task,
                                  0,
                                  make_directory_handlers,
@@ -1447,6 +1518,21 @@ do_move (GVfsBackend *backend,
   static const GVfsFtpErrorFunc rnfr_handlers[] = { error_550_permission_or_not_found,
                                                     NULL };
 
+  srcfile = g_vfs_ftp_file_new_from_gvfs (ftp, source, &task.error);
+  if (srcfile == NULL)
+    {
+      g_vfs_ftp_task_done (&task);
+      return;
+    }
+
+  destfile = g_vfs_ftp_file_new_from_gvfs (ftp, destination, &task.error);
+  if (destfile == NULL)
+    {
+      g_vfs_ftp_file_free (srcfile);
+      g_vfs_ftp_task_done (&task);
+      return;
+    }
+
   /* FIXME: what about G_FILE_COPY_NOFOLLOW_SYMLINKS and G_FILE_COPY_ALL_METADATA? */
 
   if (flags & G_FILE_COPY_BACKUP)
@@ -1474,8 +1560,6 @@ do_move (GVfsBackend *backend,
       return;
     }
 
-  srcfile = g_vfs_ftp_file_new_from_gvfs (ftp, source);
-  destfile = g_vfs_ftp_file_new_from_gvfs (ftp, destination);
   if (g_vfs_ftp_task_try_cd (&task, destfile))
     {
       char *basename = g_path_get_basename (source);
@@ -1613,7 +1697,13 @@ do_pull (GVfsBackend *         backend,
   GOutputStream *output;
   goffset total_size = 0;
   
-  src = g_vfs_ftp_file_new_from_gvfs (ftp, source);
+  src = g_vfs_ftp_file_new_from_gvfs (ftp, source, &task.error);
+  if (src == NULL)
+    {
+      g_vfs_ftp_task_done (&task);
+      return;
+    }
+
   dest = g_file_new_for_path (local_path);
 
   if (remove_source && (flags & G_FILE_COPY_NO_FALLBACK_FOR_MOVE))

--- a/daemon/gvfsftpfile.c
+++ b/daemon/gvfsftpfile.c
@@ -68,19 +68,30 @@ g_vfs_ftp_file_compute_gvfs_path (const
  * g_vfs_ftp_file_new_from_gvfs:
  * @ftp: the ftp backend this file is to be used on
  * @gvfs_path: gvfs path to create the file from
+ * @error: location to take an eventual error or %NULL
  *
- * Constructs a new #GVfsFtpFile representing the given gvfs path.
+ * Constructs a new #GVfsFtpFile representing the given gvfs path. If the
+ * display name is invalid, @error is set and %NULL is returned.
  *
- * Returns: a new file
+ * Returns: a new file or %NULL on error
+
  **/
 GVfsFtpFile *
-g_vfs_ftp_file_new_from_gvfs (GVfsBackendFtp *ftp, const char *gvfs_path)
+g_vfs_ftp_file_new_from_gvfs (GVfsBackendFtp *ftp, const char *gvfs_path, GError **error)
 {
   GVfsFtpFile *file;
 
   g_return_val_if_fail (G_VFS_IS_BACKEND_FTP (ftp), NULL);
   g_return_val_if_fail (gvfs_path != NULL, NULL);
 
+  if (strpbrk (gvfs_path, "\r\n") != NULL)
+    {
+      g_set_error_literal (error,
+                           G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME,
+                           _("Filename contains invalid characters."));
+      return NULL;
+    }
+
   file = g_slice_new (GVfsFtpFile);
   file->backend = g_object_ref (ftp);
   file->gvfs_path = g_strdup (gvfs_path);
@@ -136,7 +147,7 @@ g_vfs_ftp_file_new_parent (const GVfsFtp
     return g_vfs_ftp_file_copy (file);
 
   dirname = g_path_get_dirname (file->gvfs_path);
-  dir = g_vfs_ftp_file_new_from_gvfs (file->backend, dirname);
+  dir = g_vfs_ftp_file_new_from_gvfs (file->backend, dirname, NULL);
   g_free (dirname);
 
   return dir;
@@ -163,7 +174,7 @@ g_vfs_ftp_file_new_child (const GVfsFtpF
   g_return_val_if_fail (parent != NULL, NULL);
   g_return_val_if_fail (display_name != NULL, NULL);
 
-  if (strpbrk (display_name, "/\r\n"))
+  if (strchr (display_name, '/') != NULL)
     {
       g_set_error_literal (error,
                            G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME,
@@ -172,7 +183,7 @@ g_vfs_ftp_file_new_child (const GVfsFtpF
     }
 
   new_path = g_strconcat (parent->gvfs_path, parent->gvfs_path[1] == 0 ? "" : "/", display_name, NULL);
-  child = g_vfs_ftp_file_new_from_gvfs (parent->backend, new_path);
+  child = g_vfs_ftp_file_new_from_gvfs (parent->backend, new_path, error);
   g_free (new_path);
   return child;
 }

--- a/daemon/gvfsftpfile.h
+++ b/daemon/gvfsftpfile.h
@@ -31,7 +31,8 @@ G_BEGIN_DECLS
 typedef struct _GVfsFtpFile GVfsFtpFile;
 
 GVfsFtpFile *     g_vfs_ftp_file_new_from_gvfs          (GVfsBackendFtp *       ftp,
-                                                         const char *           gvfs_path);
+                                                         const char *           gvfs_path,
+                                                         GError **              error);
 GVfsFtpFile *     g_vfs_ftp_file_new_from_ftp           (GVfsBackendFtp *       ftp,
                                                          const char *           ftp_path);
 GVfsFtpFile *     g_vfs_ftp_file_new_parent             (const GVfsFtpFile *    file);
openSUSE Build Service is sponsored by