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

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