Submitted By: Douglas R. Reno Date: 2019-06-29 Initial Package Version: 1.40.1 Upstream Status: Applied Origin: Upstream Description: Fixes four security vulnerabilities in GVFS, two rated as High and two as Critical. The IDs are CVE-2019-12795, CVE-2019-12447, CVE-2019-12448, and CVE-2019-12449. In addition, fix the AFP backend for anyone who uses it since Apple iOS devices beyond 2017 use a UUID longer than 40 characters. diff -Naurp gvfs-1.40.1.orig/common/gmountsource.c gvfs-1.40.1/common/gmountsource.c --- gvfs-1.40.1.orig/common/gmountsource.c 2019-04-09 01:36:07.000000000 -0500 +++ gvfs-1.40.1/common/gmountsource.c 2019-06-29 13:37:36.962027478 -0500 @@ -146,8 +146,8 @@ typedef struct AskSyncData AskSyncData; struct AskSyncData { /* For sync calls */ - GMutex mutex; - GCond cond; + GMainContext *context; + GMainLoop *loop; /* results: */ GAsyncResult *result; @@ -375,10 +375,7 @@ ask_reply_sync (GObject *source_object, data->result = g_object_ref (res); - /* Wake up sync call thread */ - g_mutex_lock (&data->mutex); - g_cond_signal (&data->cond); - g_mutex_unlock (&data->mutex); + g_main_loop_quit (data->loop); } gboolean @@ -397,11 +394,10 @@ g_mount_source_ask_password (GMountSourc gboolean handled; AskSyncData data; - memset (&data, 0, sizeof (data)); - g_mutex_init (&data.mutex); - g_cond_init (&data.cond); - g_mutex_lock (&data.mutex); + data.context = g_main_context_new (); + data.loop = g_main_loop_new (data.context, FALSE); + g_main_context_push_thread_default (data.context); g_mount_source_ask_password_async (source, message_string, @@ -411,13 +407,7 @@ g_mount_source_ask_password (GMountSourc ask_reply_sync, &data); - while (data.result == NULL) - g_cond_wait (&data.cond, &data.mutex); - g_mutex_unlock (&data.mutex); - - g_cond_clear (&data.cond); - g_mutex_clear (&data.mutex); - + g_main_loop_run (data.loop); handled = g_mount_source_ask_password_finish (source, data.result, @@ -427,6 +417,10 @@ g_mount_source_ask_password (GMountSourc domain_out, anonymous_out, password_save_out); + + g_main_context_pop_thread_default (data.context); + g_main_context_unref (data.context); + g_main_loop_unref (data.loop); g_object_unref (data.result); return handled; @@ -563,10 +557,10 @@ g_mount_source_ask_question (GMountSourc gboolean handled, aborted; AskSyncData data; - memset (&data, 0, sizeof (data)); - g_mutex_init (&data.mutex); - g_cond_init (&data.cond); - g_mutex_lock (&data.mutex); + data.context = g_main_context_new (); + data.loop = g_main_loop_new (data.context, FALSE); + + g_main_context_push_thread_default (data.context); g_mount_source_ask_question_async (source, message, @@ -574,18 +568,16 @@ g_mount_source_ask_question (GMountSourc ask_reply_sync, &data); - while (data.result == NULL) - g_cond_wait (&data.cond, &data.mutex); - g_mutex_unlock (&data.mutex); - - g_cond_clear (&data.cond); - g_mutex_clear (&data.mutex); + g_main_loop_run (data.loop); handled = g_mount_source_ask_question_finish (source, data.result, &aborted, &choice); + g_main_context_pop_thread_default (data.context); + g_main_context_unref (data.context); + g_main_loop_unref (data.loop); g_object_unref (data.result); if (aborted_out) @@ -833,10 +825,10 @@ g_mount_source_show_processes (GMountSou gboolean handled, aborted; AskSyncData data; - memset (&data, 0, sizeof (data)); - g_mutex_init (&data.mutex); - g_cond_init (&data.cond); - g_mutex_lock (&data.mutex); + data.context = g_main_context_new (); + data.loop = g_main_loop_new (data.context, FALSE); + + g_main_context_push_thread_default (data.context); g_mount_source_show_processes_async (source, message, @@ -845,18 +837,16 @@ g_mount_source_show_processes (GMountSou ask_reply_sync, &data); - while (data.result == NULL) - g_cond_wait (&data.cond, &data.mutex); - g_mutex_unlock (&data.mutex); - - g_cond_clear (&data.cond); - g_mutex_clear (&data.mutex); + g_main_loop_run (data.loop); handled = g_mount_source_show_processes_finish (source, data.result, &aborted, &choice); + g_main_context_pop_thread_default (data.context); + g_main_context_unref (data.context); + g_main_loop_unref (data.loop); g_object_unref (data.result); if (aborted_out) diff -Naurp gvfs-1.40.1.orig/daemon/gvfsafpconnection.c gvfs-1.40.1/daemon/gvfsafpconnection.c --- gvfs-1.40.1.orig/daemon/gvfsafpconnection.c 2019-04-09 01:36:07.000000000 -0500 +++ gvfs-1.40.1/daemon/gvfsafpconnection.c 2019-06-29 13:28:17.228547773 -0500 @@ -515,6 +515,12 @@ g_vfs_afp_command_put_pascal (GVfsAfpCom { size_t len; + if (str == NULL) + { + g_vfs_afp_command_put_byte (comm, 0); + return; + } + len = MIN (strlen (str), 256); g_vfs_afp_command_put_byte (comm, len); diff -Naurp gvfs-1.40.1.orig/daemon/gvfsbackendadmin.c gvfs-1.40.1/daemon/gvfsbackendadmin.c --- gvfs-1.40.1.orig/daemon/gvfsbackendadmin.c 2019-04-09 01:36:07.000000000 -0500 +++ gvfs-1.40.1/daemon/gvfsbackendadmin.c 2019-06-29 14:15:39.601315566 -0500 @@ -42,6 +42,8 @@ #include "gvfsjobopenforwrite.h" #include "gvfsjobqueryattributes.h" #include "gvfsjobqueryinfo.h" +#include "gvfsjobqueryinforead.h" +#include "gvfsjobqueryinfowrite.h" #include "gvfsjobread.h" #include "gvfsjobseekread.h" #include "gvfsjobseekwrite.h" @@ -180,19 +182,6 @@ do_query_info (GVfsBackend *backend, if (error != NULL) goto out; - /* Override read/write flags, since the above call will use access() - * to determine permissions, which does not honor our privileged - * capabilities. - */ - g_file_info_set_attribute_boolean (real_info, - G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE); - g_file_info_set_attribute_boolean (real_info, - G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, TRUE); - g_file_info_set_attribute_boolean (real_info, - G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, TRUE); - g_file_info_set_attribute_boolean (real_info, - G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, TRUE); - g_file_info_copy_into (real_info, info); g_object_unref (real_info); @@ -201,6 +190,55 @@ do_query_info (GVfsBackend *backend, } static void +do_query_info_on_read (GVfsBackend *backend, + GVfsJobQueryInfoRead *query_info_job, + GVfsBackendHandle handle, + GFileInfo *info, + GFileAttributeMatcher *matcher) +{ + GVfsJob *job = G_VFS_JOB (query_info_job); + GFileInputStream *stream = handle; + GError *error = NULL; + GFileInfo *real_info; + + real_info = g_file_input_stream_query_info (stream, query_info_job->attributes, + job->cancellable, &error); + + if (error != NULL) + goto out; + + g_file_info_copy_into (real_info, info); + g_object_unref (real_info); + +out: + complete_job (job, error); +} +static void +do_query_info_on_write (GVfsBackend *backend, + GVfsJobQueryInfoWrite *query_info_job, + GVfsBackendHandle handle, + GFileInfo *info, + GFileAttributeMatcher *matcher) +{ + GVfsJob *job = G_VFS_JOB (query_info_job); + GFileOutputStream *stream = handle; + GError *error = NULL; + GFileInfo *real_info; + + real_info = g_file_output_stream_query_info (stream, query_info_job->attributes, + job->cancellable, &error); + + if (error != NULL) + goto out; + + g_file_info_copy_into (real_info, info); + g_object_unref (real_info); + +out: + complete_job (job, error); +} + +static void do_close_write (GVfsBackend *backend, GVfsJobCloseWrite *close_write_job, GVfsBackendHandle handle) @@ -771,6 +809,51 @@ do_move (GVfsBackend *backend, } static void +do_pull (GVfsBackend *backend, + GVfsJobPull *pull_job, + const char *source, + const char *local_path, + GFileCopyFlags flags, + gboolean remove_source, + GFileProgressCallback progress_callback, + gpointer progress_callback_data) +{ + GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend); + GVfsJob *job = G_VFS_JOB (pull_job); + GError *error = NULL; + GFile *src_file, *dst_file; + + /* Pull method is necessary when user/group needs to be restored, return + * G_IO_ERROR_NOT_SUPPORTED in other cases to proceed with the fallback code. + */ + if (!(flags & G_FILE_COPY_ALL_METADATA)) + { + g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + return; + } + + if (!check_permission (self, job)) + return; + + src_file = g_file_new_for_path (source); + dst_file = g_file_new_for_path (local_path); + + if (remove_source) + g_file_move (src_file, dst_file, flags, job->cancellable, + progress_callback, progress_callback_data, &error); + else + g_file_copy (src_file, dst_file, flags, job->cancellable, + progress_callback, progress_callback_data, &error); + + g_object_unref (src_file); + g_object_unref (dst_file); + + complete_job (job, error); +} + +static void do_query_settable_attributes (GVfsBackend *backend, GVfsJobQueryAttributes *query_job, const char *filename) @@ -868,6 +951,8 @@ g_vfs_backend_admin_class_init (GVfsBack backend_class->mount = do_mount; backend_class->open_for_read = do_open_for_read; backend_class->query_info = do_query_info; + backend_class->query_info_on_read = do_query_info_on_read; + backend_class->query_info_on_write = do_query_info_on_write; backend_class->read = do_read; backend_class->create = do_create; backend_class->append_to = do_append_to; @@ -888,6 +973,7 @@ g_vfs_backend_admin_class_init (GVfsBack backend_class->set_attribute = do_set_attribute; backend_class->delete = do_delete; backend_class->move = do_move; + backend_class->pull = do_pull; backend_class->query_settable_attributes = do_query_settable_attributes; backend_class->query_writable_namespaces = do_query_writable_namespaces; } @@ -913,7 +999,8 @@ g_vfs_backend_admin_init (GVfsBackendAdm #define REQUIRED_CAPS (CAP_TO_MASK(CAP_FOWNER) | \ CAP_TO_MASK(CAP_DAC_OVERRIDE) | \ - CAP_TO_MASK(CAP_DAC_READ_SEARCH)) + CAP_TO_MASK(CAP_DAC_READ_SEARCH) | \ + CAP_TO_MASK(CAP_CHOWN)) static void acquire_caps (uid_t uid) @@ -921,13 +1008,14 @@ acquire_caps (uid_t uid) struct __user_cap_header_struct hdr; struct __user_cap_data_struct data; - /* Tell kernel not clear capabilities when dropping root */ - if (prctl (PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) - g_error ("prctl(PR_SET_KEEPCAPS) failed"); - - /* Drop root uid, but retain the required permitted caps */ - if (setuid (uid) < 0) - g_error ("unable to drop privs"); + /* Set EUID to user to make dbus work */ + if (seteuid (uid) < 0) + g_error ("Unable to drop privileges"); + + /* Set fsuid to still behave like root when working with files */ + setfsuid (0); + if (setfsuid (-1) != 0) + g_error ("setfsuid failed"); memset (&hdr, 0, sizeof(hdr)); hdr.version = _LINUX_CAPABILITY_VERSION; diff -Naurp gvfs-1.40.1.orig/daemon/gvfsbackendafc.c gvfs-1.40.1/daemon/gvfsbackendafc.c --- gvfs-1.40.1.orig/daemon/gvfsbackendafc.c 2019-04-09 01:36:07.000000000 -0500 +++ gvfs-1.40.1/daemon/gvfsbackendafc.c 2019-06-29 13:40:12.774668154 -0500 @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -73,7 +72,7 @@ typedef struct { struct _GVfsBackendAfc { GVfsBackend backend; - char uuid[41]; + char *uuid; char *service; char *model; gboolean connected; @@ -472,16 +471,11 @@ g_vfs_backend_afc_mount (GVfsBackend *ba { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - _("Invalid mount spec")); - return; - } - if (G_UNLIKELY(sscanf(str, "%40s", (char *) &self->uuid) < 1)) - { - g_vfs_job_failed (G_VFS_JOB(job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid AFC location: must be in the form of " "afc://uuid:port-number")); return; } + self->uuid = g_strdup (str); str = g_mount_spec_get (spec, "port"); if (str == NULL) @@ -519,9 +513,7 @@ g_vfs_backend_afc_mount (GVfsBackend *ba display_name = NULL; real_spec = g_mount_spec_new ("afc"); - tmp = g_strdup_printf ("%40s", (char *) &self->uuid); - g_mount_spec_set (real_spec, "host", tmp); - g_free (tmp); + g_mount_spec_set (real_spec, "host", self->uuid); /* INFO: Don't ever set the DefaultPort again or everything goes crazy */ if (virtual_port != VIRTUAL_PORT_AFC) @@ -2752,6 +2744,8 @@ g_vfs_backend_afc_finalize (GObject *obj self->force_umount_id = 0; } + g_free (self->uuid); + if (G_OBJECT_CLASS(g_vfs_backend_afc_parent_class)->finalize) (*G_OBJECT_CLASS(g_vfs_backend_afc_parent_class)->finalize) (obj); } diff -Naurp gvfs-1.40.1.orig/daemon/gvfsdaemon.c gvfs-1.40.1/daemon/gvfsdaemon.c --- gvfs-1.40.1.orig/daemon/gvfsdaemon.c 2019-04-09 01:36:07.000000000 -0500 +++ gvfs-1.40.1/daemon/gvfsdaemon.c 2019-06-29 14:13:55.078149180 -0500 @@ -79,6 +79,7 @@ struct _GVfsDaemon gint mount_counter; + GDBusAuthObserver *auth_observer; GDBusConnection *conn; GVfsDBusDaemon *daemon_skeleton; GVfsDBusMountable *mountable_skeleton; @@ -171,7 +172,9 @@ g_vfs_daemon_finalize (GObject *object) } if (daemon->conn != NULL) g_object_unref (daemon->conn); - + if (daemon->auth_observer != NULL) + g_object_unref (daemon->auth_observer); + g_hash_table_destroy (daemon->registered_paths); g_hash_table_destroy (daemon->client_connections); g_mutex_clear (&daemon->lock); @@ -236,6 +239,51 @@ name_vanished_handler (GDBusConnection * daemon->lost_main_daemon = TRUE; } +/* + * Authentication observer signal handler that rejects all authentication + * mechanisms except for EXTERNAL (credentials-passing), which is the + * recommended authentication mechanism for all AF_UNIX sockets. + */ +static gboolean +allow_mechanism_cb (GDBusAuthObserver *observer, + const gchar *mechanism, + G_GNUC_UNUSED gpointer user_data) +{ + if (g_strcmp0 (mechanism, "EXTERNAL") == 0) + return TRUE; + + return FALSE; +} + +/* + * Authentication observer signal handler that authorizes connections + * from the same uid as this process. This matches the behavior of a + * libdbus DBusServer/DBusConnection when no DBusAllowUnixUserFunction + * has been set, but is not the default in GDBus. + */ +static gboolean +authorize_authenticated_peer_cb (GDBusAuthObserver *observer, + G_GNUC_UNUSED GIOStream *stream, + GCredentials *credentials, + G_GNUC_UNUSED gpointer user_data) +{ + gboolean authorized = FALSE; + + if (credentials != NULL) + { + GCredentials *own_credentials; + + own_credentials = g_credentials_new () ; + + if (g_credentials_is_same_user (credentials, own_credentials, NULL)) + authorized = TRUE; + + g_object_unref (own_credentials); + } + + return authorized; +} + static void g_vfs_daemon_init (GVfsDaemon *daemon) { @@ -265,6 +313,9 @@ g_vfs_daemon_init (GVfsDaemon *daemon) daemon->conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); g_assert (daemon->conn != NULL); + daemon->auth_observer = g_dbus_auth_observer_new (); + g_signal_connect (daemon->auth_observer, "allow-mechanism", G_CALLBACK (allow_mechanism_cb), NULL); + g_signal_connect (daemon->auth_observer, "authorized-authenticated-peer", G_CALLBACK (authorize_authenticated_peer_cb), NULL); daemon->daemon_skeleton = gvfs_dbus_daemon_skeleton_new (); g_signal_connect (daemon->daemon_skeleton, "handle-get-connection", G_CALLBACK (handle_get_connection), daemon); @@ -876,7 +927,7 @@ handle_get_connection (GVfsDBusDaemon *o server = g_dbus_server_new_sync (address1, G_DBUS_SERVER_FLAGS_NONE, guid, - NULL, /* GDBusAuthObserver */ + daemon->auth_observer, NULL, /* GCancellable */ &error); g_free (guid);