diff options
author | Matthias Clasen <mclasen@redhat.com> | 2010-03-30 12:36:04 -0400 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2010-03-30 12:36:04 -0400 |
commit | 69b526a6cd4c078732068de2ba393cf9242a404b (patch) | |
tree | d56257876d50517d55fbb1202db45fb30a80fc2b | |
parent | a1d944672acb3c350abacf461f13f020aff87057 (diff) |
Be more careful when copying the icon file
Don't read the file in the root process, instead fork, become the
calling user, then read the file and pipe it back to the parent process.
This protects against callers passing e.g. "/etc/shadow" as filename.
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | src/user.c | 94 | ||||
-rw-r--r-- | src/util.c | 31 | ||||
-rw-r--r-- | src/util.h | 2 |
4 files changed, 119 insertions, 10 deletions
diff --git a/configure.ac b/configure.ac index ffc82aa..6110c2b 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ AM_GLIB_GNU_GETTEXT IT_PROG_INTLTOOL([0.40.0]) PKG_CHECK_MODULES(GLIB, glib-2.0) -PKG_CHECK_MODULES(GIO, gio-2.0) +PKG_CHECK_MODULES(GIO, gio-2.0 gio-unix-2.0) PKG_CHECK_MODULES(DBUS_GLIB, dbus-glib-1) PKG_CHECK_MODULES(POLKIT, polkit-gobject-1) @@ -23,6 +23,7 @@ #include "config.h" +#include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> @@ -35,6 +36,7 @@ #include <glib-object.h> #include <glib/gstdio.h> #include <gio/gio.h> +#include <gio/gunixinputstream.h> #include <dbus/dbus-glib.h> #include <dbus/dbus-glib-lowlevel.h> @@ -1222,6 +1224,19 @@ user_set_shell (User *user, } static void +become_user (gpointer data) +{ + struct passwd *pw = data; + + if (pw == NULL || + initgroups (pw->pw_name, pw->pw_gid) != 0 || + setgid (pw->pw_gid) != 0 || + setuid (pw->pw_uid) != 0) { + exit (1); + } +} + +static void user_change_icon_file_authorized_cb (Daemon *daemon, User *user, DBusGMethodInvocation *context, @@ -1232,43 +1247,104 @@ user_change_icon_file_authorized_cb (Daemon *daemon, GFile *file; GFileInfo *info; guint32 mode; + guint64 size; filename = g_strdup (data); file = g_file_new_for_path (filename); - info = g_file_query_info (file, G_FILE_ATTRIBUTE_UNIX_MODE, 0, NULL, NULL); + info = g_file_query_info (file, G_FILE_ATTRIBUTE_UNIX_MODE "," + G_FILE_ATTRIBUTE_STANDARD_SIZE, + 0, NULL, NULL); mode = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE); + size = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE); + g_object_unref (info); + g_object_unref (file); + + if (size > 1048576) { + g_warning ("file too large\n"); + /* 1MB ought to be enough for everybody */ + throw_error (context, ERROR_FAILED, "file '%s' is too large to be used as an icon", filename); + g_free (filename); + return; + } if ((mode & S_IROTH) == 0 || (!g_str_has_prefix (filename, DATADIR) && !g_str_has_prefix (filename, ICONDIR))) { gchar *dest_path; GFile *dest; + gchar *argv[3]; + gint std_out; GError *error; + GInputStream *input; + GOutputStream *output; + gint uid; + gssize bytes; + struct passwd *pw; + + if (!get_caller_uid (context, &uid)) { + throw_error (context, ERROR_FAILED, "failed to copy file, could not determine caller UID"); + g_free (filename); + return; + } dest_path = g_build_filename (ICONDIR, user->user_name, NULL); dest = g_file_new_for_path (dest_path); error = NULL; - if (!g_file_copy (file, dest, - G_FILE_COPY_OVERWRITE|G_FILE_COPY_TARGET_DEFAULT_PERMS, - NULL, NULL, NULL, &error)) { - throw_error (context, ERROR_FAILED, "copying file from '%s' to '%s' failed: %s", filename, dest_path, error->message); + output = G_OUTPUT_STREAM (g_file_replace (dest, NULL, FALSE, 0, NULL, &error)); + if (!output) { + throw_error (context, ERROR_FAILED, "creating file '%s' failed: %s", dest_path, error->message); g_error_free (error); + g_free (filename); g_free (dest_path); g_object_unref (dest); - g_object_unref (file); + return; + } + + argv[0] = "/bin/cat"; + argv[1] = filename; + argv[2] = NULL; + pw = getpwuid (uid); + + error = NULL; + if (!g_spawn_async_with_pipes (NULL, argv, NULL, 0, become_user, pw, NULL, NULL, &std_out, NULL, &error)) { + throw_error (context, ERROR_FAILED, "reading file '%s' failed: %s", filename, error->message); + g_error_free (error); + g_free (filename); + g_free (dest_path); + g_object_unref (dest); return; } - filename = dest_path; + input = g_unix_input_stream_new (std_out, FALSE); + + error = NULL; + bytes = g_output_stream_splice (output, input, G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error); + if (bytes < 0 || bytes != size) { + throw_error (context, ERROR_FAILED, "copying file '%s' to '%s' failed: %s", filename, dest_path, error ? error->message : "unknown reason"); + if (error) + g_error_free (error); + + g_file_delete (dest, NULL, NULL); + + g_free (filename); + g_free (dest_path); + g_object_unref (dest); + g_object_unref (input); + g_object_unref (output); + return; + } g_object_unref (dest); - } + g_object_unref (input); + g_object_unref (output); - g_object_unref (file); + g_free (filename); + filename = dest_path; + } if (g_strcmp0 (user->icon_file, filename) != 0) { g_free (user->icon_file); @@ -251,3 +251,34 @@ get_user_groups (const gchar *user, return res; } + + +gboolean +get_caller_uid (DBusGMethodInvocation *context, gint *uid) +{ + PolkitSubject *subject; + PolkitSubject *process; + GError *error; + + subject = polkit_system_bus_name_new (dbus_g_method_get_sender (context)); + process = polkit_system_bus_name_get_process_sync (POLKIT_SYSTEM_BUS_NAME (subject), NULL, NULL); + if (!process) { + g_object_unref (subject); + return FALSE; + } + + error = NULL; + *uid = polkit_unix_process_get_owner (POLKIT_UNIX_PROCESS (process), &error); + if (error) { + g_error_free (error); + g_object_unref (subject); + g_object_unref (process); + + return FALSE; + } + + g_object_unref (subject); + g_object_unref (process); + + return TRUE; +} @@ -31,6 +31,8 @@ void sys_log (DBusGMethodInvocation *context, const gchar *format, ...); +gboolean get_caller_uid (DBusGMethodInvocation *context, gint *uid); + gboolean spawn_with_login_uid (DBusGMethodInvocation *context, gchar *argv[], GError **error); |