glib2/CVE-2021-28153.patch

389 lines
13 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From 8fef6abe1131da0c8a7211c740a12ebe11cbcc51 Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@endlessos.org>
Date: Wed, 10 Mar 2021 16:05:55 +0000
Subject: [PATCH 1/3] glocalfileoutputstream: Factor out a flag check
This clarifies the code a little. It introduces no functional changes.
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
---
gio/glocalfileoutputstream.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/gio/glocalfileoutputstream.c b/gio/glocalfileoutputstream.c
index 57d2d5dfe..6a70b2a04 100644
--- a/gio/glocalfileoutputstream.c
+++ b/gio/glocalfileoutputstream.c
@@ -751,6 +751,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);
@@ -857,8 +858,8 @@ handle_overwrite_open (const char *filename,
* The second strategy consist simply in copying the old file
* to a backup file and rewrite the contents of the file.
*/
-
- if ((flags & G_FILE_CREATE_REPLACE_DESTINATION) ||
+
+ if (replace_destination_set ||
(!(original_stat.st_nlink > 1) && !is_symlink))
{
char *dirname, *tmp_filename;
@@ -877,7 +878,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, original_stat.st_uid, original_stat.st_gid) == -1 ||
@@ -1016,7 +1017,7 @@ handle_overwrite_open (const char *filename,
}
}
- if (flags & G_FILE_CREATE_REPLACE_DESTINATION)
+ if (replace_destination_set)
{
g_close (fd, NULL);
--
2.31.1
From 6c10e8ce6905e8fcc3466eb8af707b5d0d3bdb85 Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@endlessos.org>
Date: Wed, 24 Feb 2021 17:36:07 +0000
Subject: [PATCH 2/3] glocalfileoutputstream: Fix CREATE_REPLACE_DESTINATION
with symlinks
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The `G_FILE_CREATE_REPLACE_DESTINATION` flag is equivalent to unlinking
the destination file and re-creating it from scratch. That did
previously work, but in the process the code would call `open(O_CREAT)`
on the file. If the file was a dangling symlink, this would create the
destination file (empty). Thats not an intended side-effect, and has
security implications if the symlink is controlled by a lower-privileged
process.
Fix that by not opening the destination file if its a symlink, and
adjusting the rest of the code to cope with
- the fact that `fd == -1` is not an error iff `is_symlink` is true,
- and that `original_stat` will contain the `lstat()` results for the
symlink now, rather than the `stat()` results for its target (again,
iff `is_symlink` is true).
This means that the target of the dangling symlink is no longer created,
which was the bug. The symlink itself continues to be replaced (as
before) with the new file — this is the intended behaviour of
`g_file_replace()`.
The behaviour for non-symlink cases, or cases where the symlink was not
dangling, should be unchanged.
Includes a unit test.
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
Fixes: #2325
---
gio/glocalfileoutputstream.c | 63 ++++++++++++++-------
gio/tests/file.c | 107 ++++++++++++++++++++++++++++++++++-
2 files changed, 149 insertions(+), 21 deletions(-)
diff --git a/gio/glocalfileoutputstream.c b/gio/glocalfileoutputstream.c
index 6a70b2a04..4a7766f68 100644
--- a/gio/glocalfileoutputstream.c
+++ b/gio/glocalfileoutputstream.c
@@ -779,16 +779,22 @@ handle_overwrite_open (const char *filename,
/* Could be a symlink, or it could be a regular ELOOP error,
* but then the next open will fail too. */
is_symlink = TRUE;
- fd = g_open (filename, open_flags, mode);
+ if (!replace_destination_set)
+ fd = g_open (filename, open_flags, mode);
}
-#else
- fd = g_open (filename, open_flags, mode);
- errsv = errno;
+#else /* if !O_NOFOLLOW */
/* This is racy, but we do it as soon as possible to minimize the race */
is_symlink = g_file_test (filename, G_FILE_TEST_IS_SYMLINK);
+
+ if (!is_symlink || !replace_destination_set)
+ {
+ fd = g_open (filename, open_flags, mode);
+ errsv = errno;
+ }
#endif
- if (fd == -1)
+ if (fd == -1 &&
+ (!is_symlink || !replace_destination_set))
{
char *display_name = g_filename_display_name (filename);
g_set_error (error, G_IO_ERROR,
@@ -800,10 +806,17 @@ handle_overwrite_open (const char *filename,
}
#ifdef G_OS_WIN32
- res = GLIB_PRIVATE_CALL (g_win32_fstat) (fd, &original_stat);
-#else
- res = fstat (fd, &original_stat);
+#error This patch has not been ported to Windows, sorry
#endif
+
+ if (!is_symlink)
+ {
+ res = fstat (fd, &original_stat);
+ }
+ else
+ {
+ res = lstat (filename, &original_stat);
+ }
errsv = errno;
if (res != 0)
@@ -821,16 +834,27 @@ handle_overwrite_open (const char *filename,
if (!S_ISREG (original_stat.st_mode))
{
if (S_ISDIR (original_stat.st_mode))
- g_set_error_literal (error,
- G_IO_ERROR,
- G_IO_ERROR_IS_DIRECTORY,
- _("Target file is a directory"));
- else
- g_set_error_literal (error,
- G_IO_ERROR,
- G_IO_ERROR_NOT_REGULAR_FILE,
- _("Target file is not a regular file"));
- goto err_out;
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_IS_DIRECTORY,
+ _("Target file is a directory"));
+ goto err_out;
+ }
+ else if (!is_symlink ||
+#ifdef S_ISLNK
+ !S_ISLNK (original_stat.st_mode)
+#else
+ FALSE
+#endif
+ )
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_REGULAR_FILE,
+ _("Target file is not a regular file"));
+ goto err_out;
+ }
}
if (etag != NULL)
@@ -911,7 +935,8 @@ handle_overwrite_open (const char *filename,
}
}
- g_close (fd, NULL);
+ if (fd >= 0)
+ g_close (fd, NULL);
*temp_filename = tmp_filename;
return tmpfd;
}
diff --git a/gio/tests/file.c b/gio/tests/file.c
index 98eeb85d4..44db6e295 100644
--- a/gio/tests/file.c
+++ b/gio/tests/file.c
@@ -671,8 +671,6 @@ test_replace_cancel (void)
guint count;
GError *error = NULL;
- g_test_bug ("629301");
-
path = g_dir_make_tmp ("g_file_replace_cancel_XXXXXX", &error);
g_assert_no_error (error);
tmpdir = g_file_new_for_path (path);
@@ -779,6 +777,110 @@ 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;
+
+ /* 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` doesnt 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` doesnt 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,
@@ -1170,6 +1272,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);
#ifdef G_OS_UNIX
g_test_add_func ("/file/copy-preserve-mode", test_copy_preserve_mode);
--
2.31.1
From 7f0b0d7fd744ad2f51236444005db49c80a0293d Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@endlessos.org>
Date: Wed, 24 Feb 2021 17:42:24 +0000
Subject: [PATCH 3/3] glocalfileoutputstream: Add a missing O_CLOEXEC flag to
replace()
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
---
gio/glocalfileoutputstream.c | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/gio/glocalfileoutputstream.c b/gio/glocalfileoutputstream.c
index 4a7766f68..275770fa4 100644
--- a/gio/glocalfileoutputstream.c
+++ b/gio/glocalfileoutputstream.c
@@ -56,6 +56,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;
@@ -1127,7 +1133,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
@@ -1157,8 +1163,11 @@ _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;
stream->priv->sync_on_close = sync_on_close;
--
2.31.1