| From b0ee8d82e470a6365bf7201cfd093f7b953ec899 Mon Sep 17 00:00:00 2001 |
| From: Vaibhav Rustagi <vaibhavrustagi@google.com> |
| Date: Tue, 21 Sep 2021 15:56:04 -0700 |
| Subject: [PATCH] Fixes for CVE-2021-28153. |
| |
| Cherry-pick from |
| https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1982/diffs?commit_id=317b3b587058a05dca95d56dac26568c5b098d33 |
| --- |
| gio/glocalfileoutputstream.c | 20 +++++-- |
| gio/tests/file.c | 112 ++++++++++++++++++++++++++++++++++- |
| 2 files changed, 124 insertions(+), 8 deletions(-) |
| |
| diff --git a/gio/glocalfileoutputstream.c b/gio/glocalfileoutputstream.c |
| index f34c3e439..f04180095 100644 |
| --- a/gio/glocalfileoutputstream.c |
| +++ b/gio/glocalfileoutputstream.c |
| @@ -63,6 +63,12 @@ |
| #define O_BINARY 0 |
| #endif |
| |
| +#ifndef O_CLOEXEC |
| +#define O_CLOEXEC 0 |
| +#else |
| +#define HAVE_O_CLOEXEC 1 |
| +#endif |
| + |
| struct _GLocalFileOutputStreamPrivate { |
| char *tmp_filename; |
| char *original_filename; |
| @@ -850,6 +856,7 @@ handle_overwrite_open (const char *filename, |
| int res; |
| int mode; |
| int errsv; |
| + gboolean replace_destination_set = (flags & G_FILE_CREATE_REPLACE_DESTINATION); |
| |
| mode = mode_from_flags_or_info (flags, reference_info); |
| |
| @@ -960,7 +967,7 @@ handle_overwrite_open (const char *filename, |
| * to a backup file and rewrite the contents of the file. |
| */ |
| |
| - if ((flags & G_FILE_CREATE_REPLACE_DESTINATION) || |
| + if (replace_destination_set || |
| (!(_g_stat_nlink (&original_stat) > 1) && !is_symlink)) |
| { |
| char *dirname, *tmp_filename; |
| @@ -979,7 +986,7 @@ handle_overwrite_open (const char *filename, |
| |
| /* try to keep permissions (unless replacing) */ |
| |
| - if ( ! (flags & G_FILE_CREATE_REPLACE_DESTINATION) && |
| + if ( !replace_destination_set && |
| ( |
| #ifdef HAVE_FCHOWN |
| fchown (tmpfd, _g_stat_uid (&original_stat), _g_stat_gid (&original_stat)) == -1 || |
| @@ -1120,7 +1127,7 @@ handle_overwrite_open (const char *filename, |
| } |
| } |
| |
| - if (flags & G_FILE_CREATE_REPLACE_DESTINATION) |
| + if (replace_destination_set) |
| { |
| g_close (fd, NULL); |
| |
| @@ -1205,7 +1212,7 @@ _g_local_file_output_stream_replace (const char *filename, |
| sync_on_close = FALSE; |
| |
| /* If the file doesn't exist, create it */ |
| - open_flags = O_CREAT | O_EXCL | O_BINARY; |
| + open_flags = O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC; |
| if (readable) |
| open_flags |= O_RDWR; |
| else |
| @@ -1235,7 +1242,10 @@ _g_local_file_output_stream_replace (const char *filename, |
| set_error_from_open_errno (filename, error); |
| return NULL; |
| } |
| - |
| +#if !defined(HAVE_O_CLOEXEC) && defined(F_SETFD) |
| + else |
| + fcntl (fd, F_SETFD, FD_CLOEXEC); |
| +#endif |
| |
| stream = g_object_new (G_TYPE_LOCAL_FILE_OUTPUT_STREAM, NULL); |
| stream->priv->fd = fd; |
| diff --git a/gio/tests/file.c b/gio/tests/file.c |
| index d8769656c..ddd1ffcba 100644 |
| --- a/gio/tests/file.c |
| +++ b/gio/tests/file.c |
| @@ -686,7 +686,7 @@ test_replace_cancel (void) |
| guint count; |
| GError *error = NULL; |
| |
| - g_test_bug ("629301"); |
| + g_test_bug ("https://bugzilla.gnome.org/629301"); |
| |
| path = g_dir_make_tmp ("g_file_replace_cancel_XXXXXX", &error); |
| g_assert_no_error (error); |
| @@ -805,6 +805,113 @@ test_replace_cancel (void) |
| g_object_unref (tmpdir); |
| } |
| |
| +static void |
| +test_replace_symlink (void) |
| +{ |
| +#ifdef G_OS_UNIX |
| + gchar *tmpdir_path = NULL; |
| + GFile *tmpdir = NULL, *source_file = NULL, *target_file = NULL; |
| + GFileOutputStream *stream = NULL; |
| + const gchar *new_contents = "this is a test message which should be written to source and not target"; |
| + gsize n_written; |
| + GFileEnumerator *enumerator = NULL; |
| + GFileInfo *info = NULL; |
| + gchar *contents = NULL; |
| + gsize length = 0; |
| + GError *local_error = NULL; |
| + |
| + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2325"); |
| + g_test_summary ("Test that G_FILE_CREATE_REPLACE_DESTINATION doesn’t follow symlinks"); |
| + |
| + /* Create a fresh, empty working directory. */ |
| + tmpdir_path = g_dir_make_tmp ("g_file_replace_symlink_XXXXXX", &local_error); |
| + g_assert_no_error (local_error); |
| + tmpdir = g_file_new_for_path (tmpdir_path); |
| + |
| + g_test_message ("Using temporary directory %s", tmpdir_path); |
| + g_free (tmpdir_path); |
| + |
| + /* Create symlink `source` which points to `target`. */ |
| + source_file = g_file_get_child (tmpdir, "source"); |
| + target_file = g_file_get_child (tmpdir, "target"); |
| + g_file_make_symbolic_link (source_file, "target", NULL, &local_error); |
| + g_assert_no_error (local_error); |
| + |
| + /* Ensure that `target` doesn’t exist */ |
| + g_assert_false (g_file_query_exists (target_file, NULL)); |
| + |
| + /* Replace the `source` symlink with a regular file using |
| + * %G_FILE_CREATE_REPLACE_DESTINATION, which should replace it *without* |
| + * following the symlink */ |
| + stream = g_file_replace (source_file, NULL, FALSE /* no backup */, |
| + G_FILE_CREATE_REPLACE_DESTINATION, NULL, &local_error); |
| + g_assert_no_error (local_error); |
| + |
| + g_output_stream_write_all (G_OUTPUT_STREAM (stream), new_contents, strlen (new_contents), |
| + &n_written, NULL, &local_error); |
| + g_assert_no_error (local_error); |
| + g_assert_cmpint (n_written, ==, strlen (new_contents)); |
| + |
| + g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, &local_error); |
| + g_assert_no_error (local_error); |
| + |
| + g_clear_object (&stream); |
| + |
| + /* At this point, there should still only be one file: `source`. It should |
| + * now be a regular file. `target` should not exist. */ |
| + enumerator = g_file_enumerate_children (tmpdir, |
| + G_FILE_ATTRIBUTE_STANDARD_NAME "," |
| + G_FILE_ATTRIBUTE_STANDARD_TYPE, |
| + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &local_error); |
| + g_assert_no_error (local_error); |
| + |
| + info = g_file_enumerator_next_file (enumerator, NULL, &local_error); |
| + g_assert_no_error (local_error); |
| + g_assert_nonnull (info); |
| + |
| + g_assert_cmpstr (g_file_info_get_name (info), ==, "source"); |
| + g_assert_cmpint (g_file_info_get_file_type (info), ==, G_FILE_TYPE_REGULAR); |
| + |
| + g_clear_object (&info); |
| + |
| + info = g_file_enumerator_next_file (enumerator, NULL, &local_error); |
| + g_assert_no_error (local_error); |
| + g_assert_null (info); |
| + |
| + g_file_enumerator_close (enumerator, NULL, &local_error); |
| + g_assert_no_error (local_error); |
| + g_clear_object (&enumerator); |
| + |
| + /* Double-check that `target` doesn’t exist */ |
| + g_assert_false (g_file_query_exists (target_file, NULL)); |
| + |
| + /* Check the content of `source`. */ |
| + g_file_load_contents (source_file, |
| + NULL, |
| + &contents, |
| + &length, |
| + NULL, |
| + &local_error); |
| + g_assert_no_error (local_error); |
| + g_assert_cmpstr (contents, ==, new_contents); |
| + g_assert_cmpuint (length, ==, strlen (new_contents)); |
| + g_free (contents); |
| + |
| + /* Tidy up. */ |
| + g_file_delete (source_file, NULL, &local_error); |
| + g_assert_no_error (local_error); |
| + |
| + g_file_delete (tmpdir, NULL, &local_error); |
| + g_assert_no_error (local_error); |
| + |
| + g_clear_object (&target_file); |
| + g_clear_object (&source_file); |
| + g_clear_object (&tmpdir); |
| +#else /* if !G_OS_UNIX */ |
| + g_test_skip ("Symlink replacement tests can only be run on Unix") |
| +#endif |
| +} |
| + |
| static void |
| on_file_deleted (GObject *object, |
| GAsyncResult *result, |
| @@ -1785,8 +1892,6 @@ main (int argc, char *argv[]) |
| { |
| g_test_init (&argc, &argv, NULL); |
| |
| - g_test_bug_base ("http://bugzilla.gnome.org/"); |
| - |
| g_test_add_func ("/file/basic", test_basic); |
| g_test_add_func ("/file/build-filename", test_build_filename); |
| g_test_add_func ("/file/parent", test_parent); |
| @@ -1800,6 +1905,7 @@ main (int argc, char *argv[]) |
| g_test_add_data_func ("/file/async-create-delete/4096", GINT_TO_POINTER (4096), test_create_delete); |
| g_test_add_func ("/file/replace-load", test_replace_load); |
| g_test_add_func ("/file/replace-cancel", test_replace_cancel); |
| + g_test_add_func ("/file/replace-symlink", test_replace_symlink); |
| g_test_add_func ("/file/async-delete", test_async_delete); |
| g_test_add_func ("/file/copy-preserve-mode", test_copy_preserve_mode); |
| g_test_add_func ("/file/measure", test_measure); |
| -- |
| 2.33.0.464.g1972c5931b-goog |
| |