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);