Blame SOURCES/0002-loader-work-around-lstat-EACCES-regression-in-_g_loc.patch

6497cf
From e5bdc6759195dbcfc4e7dcb02bf59190a3debe06 Mon Sep 17 00:00:00 2001
6497cf
From: Laszlo Ersek <lersek@redhat.com>
6497cf
Date: Wed, 9 Feb 2022 13:14:54 +0100
6497cf
Subject: [PATCH] loader: work around lstat()/EACCES regression in
6497cf
 _g_local_file_info_get()
6497cf
6497cf
In glib commit 71e7b5800a31 ("Handle MLS selinux policy better",
6497cf
2010-07-08), which was made for
6497cf
<https://bugzilla.gnome.org/show_bug.cgi?id=623692>, an lstat() failure
6497cf
with error code EACCES was *masked* in function _g_local_file_info_get().
6497cf
6497cf
Consequently, if osinfo_loader_find_files() calls g_file_query_info() on a
6497cf
file that is inaccessible due to (e.g.) a missing "x" (search) permission
6497cf
on a leading directory, then g_file_query_info() succeeds, our
6497cf
"skipMissing" branch is dead, g_file_info_get_attribute_uint32() is
6497cf
reached, and it returns G_FILE_TYPE_UNKNOWN.
6497cf
6497cf
As a consequence, the outer osinfo_loader_process_default_path() function
6497cf
can fail, even though it passes skipMissing=TRUE to
6497cf
osinfo_loader_process_list(). Example:
6497cf
6497cf
> $ HOME=/root \
6497cf
>   OSINFO_SYSTEM_DIR=/usr/share/osinfo \
6497cf
>   build/tools/osinfo-query os
6497cf
> Error loading OS data: Can't read path /root/.config/osinfo
6497cf
6497cf
Arguably, this situation should be handled by simply skipping the
6497cf
inaccessible path, as if all leading directories could be searched, and
6497cf
only the last pathname compontent (the filename entry) didn't exist in its
6497cf
direct parent directory.
6497cf
6497cf
The glib regression was reported in 2017:
6497cf
6497cf
  https://bugzilla.gnome.org/show_bug.cgi?id=777187
6497cf
6497cf
and then migrated to gitlab:
6497cf
6497cf
  https://gitlab.gnome.org/GNOME/glib/-/issues/1237
6497cf
6497cf
but it's still not solved today.
6497cf
6497cf
Work around the issue by honoring "skipMissing" on the G_FILE_TYPE_UNKNOWN
6497cf
branch. Demonstration:
6497cf
6497cf
> $ HOME=/root \
6497cf
>   OSINFO_SYSTEM_DIR=/usr/share/osinfo \
6497cf
>   build/tools/osinfo-query os
6497cf
>
6497cf
> ** (osinfo-query:9924): WARNING **: 13:23:12.776: Can't read path /root/.config/osinfo
6497cf
>  Short ID       | Name             | Version | ID
6497cf
> ----------------+------------------+---------+----------------------------------------
6497cf
>  alpinelinux3.5 | Alpine Linux 3.5 | 3.5     | http://alpinelinux.org/alpinelinux/3.5
6497cf
> ...
6497cf
6497cf
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2051559
6497cf
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
6497cf
---
6497cf
 osinfo/osinfo_loader.c |  15 ++++++
6497cf
 tests/test-loader.c    | 105 +++++++++++++++++++++++++++++++++++++++++
6497cf
 2 files changed, 120 insertions(+)
6497cf
6497cf
diff --git a/osinfo/osinfo_loader.c b/osinfo/osinfo_loader.c
6497cf
index 96ca6ee..e244b3f 100644
6497cf
--- a/osinfo/osinfo_loader.c
6497cf
+++ b/osinfo/osinfo_loader.c
6497cf
@@ -2377,6 +2377,21 @@ static void osinfo_loader_find_files(OsinfoLoader *loader,
6497cf
     } else if (type == G_FILE_TYPE_UNKNOWN) {
6497cf
         g_autofree gchar *path = g_file_get_path(file);
6497cf
         g_autofree gchar *msg = g_strdup_printf("Can't read path %s", path);
6497cf
+        if (skipMissing) {
6497cf
+            /* This is a work-around for
6497cf
+             * <https://gitlab.gnome.org/GNOME/glib/-/issues/1237>. If the
6497cf
+             * lstat() call underlying our g_file_query_info() call at the top
6497cf
+             * of this function fails for "path" with EACCES, then
6497cf
+             * g_file_query_info() should fail, and the "skipMissing" branch up
6497cf
+             * there should suppress the error and return cleanly.
6497cf
+             * Unfortunately, _g_local_file_info_get() masks the lstat()
6497cf
+             * failure, g_file_info_get_attribute_uint32() is reached above,
6497cf
+             * and returns G_FILE_TYPE_UNKNOWN for the file that could never be
6497cf
+             * accessed. So we need to consider "skipMissing" here too.
6497cf
+             */
6497cf
+            g_warning("%s", msg);
6497cf
+            return;
6497cf
+        }
6497cf
         OSINFO_LOADER_SET_ERROR(&error, msg);
6497cf
         g_propagate_error(err, error);
6497cf
     } else {
6497cf
diff --git a/tests/test-loader.c b/tests/test-loader.c
6497cf
index 6644943..bb86585 100644
6497cf
--- a/tests/test-loader.c
6497cf
+++ b/tests/test-loader.c
6497cf
@@ -16,6 +16,8 @@
6497cf
  */
6497cf
 
6497cf
 #include <osinfo/osinfo.h>
6497cf
+#include <glib/gstdio.h>
6497cf
+#include <unistd.h>
6497cf
 
6497cf
 static void
6497cf
 test_basic(void)
6497cf
@@ -31,6 +33,101 @@ test_basic(void)
6497cf
     g_object_unref(loader);
6497cf
 }
6497cf
 
6497cf
+typedef struct {
6497cf
+    gchar *tmp_parent;
6497cf
+    gchar *tmp_child;
6497cf
+    gchar *orig_userdir;
6497cf
+    gchar *expected_warning;
6497cf
+} TestEaccesFixture;
6497cf
+
6497cf
+static void
6497cf
+eacces_fixture_setup(TestEaccesFixture *fixture, gconstpointer user_data)
6497cf
+{
6497cf
+    gpointer rp;
6497cf
+    gint ri;
6497cf
+    gboolean rb;
6497cf
+
6497cf
+    /* create a temporary directory with permissions 0700 */
6497cf
+    fixture->tmp_parent = g_strdup_printf("%s/%s", g_get_tmp_dir(),
6497cf
+                                          "test_eacces.XXXXXX");
6497cf
+    rp = g_mkdtemp_full(fixture->tmp_parent, 0700);
6497cf
+    g_assert_nonnull(rp);
6497cf
+
6497cf
+    /* create a child directory called "osinfo" in it, with permissions 0700 */
6497cf
+    fixture->tmp_child = g_strdup_printf("%s/osinfo", fixture->tmp_parent);
6497cf
+    ri = g_mkdir(fixture->tmp_child, 0700);
6497cf
+    g_assert_cmpint(ri, ==, 0);
6497cf
+
6497cf
+    /* revoke the search permission (0100) from the parent */
6497cf
+    ri = g_chmod(fixture->tmp_parent, 0600);
6497cf
+    g_assert_cmpint(ri, ==, 0);
6497cf
+
6497cf
+    /* stash the current value of OSINFO_USER_DIR */
6497cf
+    fixture->orig_userdir = g_strdup(g_getenv("OSINFO_USER_DIR"));
6497cf
+
6497cf
+    /* point osinfo_loader_get_user_path() inside
6497cf
+     * osinfo_loader_process_default_path() to the child directory
6497cf
+     */
6497cf
+    rb = g_setenv("OSINFO_USER_DIR", fixture->tmp_child, TRUE);
6497cf
+    g_assert_true(rb);
6497cf
+
6497cf
+    /* format the pattern for the warning expected later on */
6497cf
+    fixture->expected_warning = g_strdup_printf("Can't read path %s",
6497cf
+                                                fixture->tmp_child);
6497cf
+}
6497cf
+
6497cf
+static void
6497cf
+eacces_fixture_teardown(TestEaccesFixture *fixture, gconstpointer user_data)
6497cf
+{
6497cf
+    gboolean rb;
6497cf
+    gint ri;
6497cf
+
6497cf
+    /* free the expected warning pattern */
6497cf
+    g_free(fixture->expected_warning);
6497cf
+
6497cf
+    /* restore the OSINFO_USER_DIR variable */
6497cf
+    if (fixture->orig_userdir) {
6497cf
+        rb = g_setenv("OSINFO_USER_DIR", fixture->orig_userdir, TRUE);
6497cf
+        g_assert_true(rb);
6497cf
+        g_free(fixture->orig_userdir);
6497cf
+    } else {
6497cf
+        g_unsetenv("OSINFO_USER_DIR");
6497cf
+    }
6497cf
+
6497cf
+    /* restore search permission on the parent */
6497cf
+    ri = g_chmod(fixture->tmp_parent, 0700);
6497cf
+    g_assert_cmpint(ri, ==, 0);
6497cf
+
6497cf
+    /* remove both directories */
6497cf
+    ri = g_rmdir(fixture->tmp_child);
6497cf
+    g_assert_cmpint(ri, ==, 0);
6497cf
+    g_free(fixture->tmp_child);
6497cf
+
6497cf
+    ri = g_rmdir(fixture->tmp_parent);
6497cf
+    g_assert_cmpint(ri, ==, 0);
6497cf
+    g_free(fixture->tmp_parent);
6497cf
+}
6497cf
+
6497cf
+static void
6497cf
+test_eacces(TestEaccesFixture *fixture, gconstpointer user_data)
6497cf
+{
6497cf
+    OsinfoLoader *loader = osinfo_loader_new();
6497cf
+    GError *error = NULL;
6497cf
+
6497cf
+    g_assert_true(OSINFO_IS_LOADER(loader));
6497cf
+
6497cf
+    /* this should trigger an EACCES in glib's lstat(), but not break db
6497cf
+     * loading; also we expect the warning here
6497cf
+     */
6497cf
+    g_test_expect_message(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
6497cf
+                          fixture->expected_warning);
6497cf
+    osinfo_loader_process_default_path(loader, &error);
6497cf
+    g_assert_no_error(error);
6497cf
+    g_test_assert_expected_messages();
6497cf
+
6497cf
+    g_object_unref(loader);
6497cf
+}
6497cf
+
6497cf
 int
6497cf
 main(int argc, char *argv[])
6497cf
 {
6497cf
@@ -38,6 +135,14 @@ main(int argc, char *argv[])
6497cf
 
6497cf
     g_test_add_func("/loader/basic", test_basic);
6497cf
 
6497cf
+    /* the following test depends on a directory with file mode bits 0600 being
6497cf
+     * unsearchable for the owner, so skip it if the test is running as root
6497cf
+     */
6497cf
+    if (geteuid() != 0) {
6497cf
+        g_test_add("/loader/eacces", TestEaccesFixture, NULL,
6497cf
+                   eacces_fixture_setup, test_eacces, eacces_fixture_teardown);
6497cf
+    }
6497cf
+
6497cf
     /* Upfront so we don't confuse valgrind */
6497cf
     osinfo_entity_get_type();
6497cf
     osinfo_db_get_type();
6497cf
-- 
6497cf
2.34.1
6497cf