From 6e28ce28600a27103676fd50b777abcdc61963d9 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Thu, 13 Feb 2020 12:54:46 +0000 Subject: [PATCH 1/2] tests: Don't assume that unprivileged uids are bound by RLIMIT_NPROC Some CI platforms invoke tests as euid != 0, but with capabilities that include CAP_SYS_RESOURCE and/or CAP_SYS_ADMIN. If we detect this, we can't test what happens if our RLIMIT_NPROC is too low to create a thread, because RLIMIT_NPROC is bypassed in these cases. Signed-off-by: Simon McVittie Fixes: https://gitlab.gnome.org/GNOME/glib/issues/2029 --- glib/tests/thread.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/glib/tests/thread.c b/glib/tests/thread.c index b9f87967b..579ce1fca 100644 --- a/glib/tests/thread.c +++ b/glib/tests/thread.c @@ -138,12 +138,6 @@ test_thread4 (void) GError *error; gint ret; - /* Linux CAP_SYS_RESOURCE overrides RLIMIT_NPROC, and probably similar - * things are true on other systems. - */ - if (getuid () == 0 || geteuid () == 0) - return; - getrlimit (RLIMIT_NPROC, &nl); nl.rlim_cur = 1; @@ -152,9 +146,26 @@ test_thread4 (void) error = NULL; thread = g_thread_try_new ("a", thread1_func, NULL, &error); - g_assert (thread == NULL); - g_assert_error (error, G_THREAD_ERROR, G_THREAD_ERROR_AGAIN); - g_error_free (error); + + if (thread != NULL) + { + gpointer result; + + /* Privileged processes might be able to create new threads even + * though the rlimit is too low. There isn't much we can do about + * this; we just can't test this failure mode in this situation. */ + g_test_skip ("Unable to test g_thread_try_new() failing with EAGAIN " + "while privileged (CAP_SYS_RESOURCE, CAP_SYS_ADMIN or " + "euid 0?)"); + result = g_thread_join (thread); + g_assert_cmpint (GPOINTER_TO_INT (result), ==, 1); + } + else + { + g_assert (thread == NULL); + g_assert_error (error, G_THREAD_ERROR, G_THREAD_ERROR_AGAIN); + g_error_free (error); + } if ((ret = prlimit (getpid (), RLIMIT_NPROC, &ol, NULL)) != 0) g_error ("resetting RLIMIT_NPROC failed: %s", g_strerror (errno)); From b9d04b37b05e66a014d1abd612d2cf2aa9db4568 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Thu, 13 Feb 2020 12:53:13 +0000 Subject: [PATCH 2/2] tests: Cope with having CAP_DAC_OVERRIDE, even if not euid 0 Some CI platforms invoke these tests with euid != 0 but with capabilities. Detect whether we have Linux CAP_DAC_OVERRIDE or other OSs' equivalents, and skip tests that rely on DAC permissions being denied if we do have that privilege. Signed-off-by: Simon McVittie Fixes: https://gitlab.gnome.org/GNOME/glib/issues/2027 Fixes: https://gitlab.gnome.org/GNOME/glib/issues/2028 --- gio/tests/live-g-file.c | 88 ++++++++++++++++++++++++++++++++-- glib/tests/fileutils.c | 104 ++++++++++++++++++++++++++++++++++------ 2 files changed, 172 insertions(+), 20 deletions(-) diff --git a/gio/tests/live-g-file.c b/gio/tests/live-g-file.c index 1eb0949eb..2ede210e3 100644 --- a/gio/tests/live-g-file.c +++ b/gio/tests/live-g-file.c @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -115,6 +116,65 @@ static gboolean write_test; static gboolean verbose; static gboolean posix_compat; +#ifdef G_OS_UNIX +/* + * check_cap_dac_override: + * @tmpdir: A temporary directory in which we can create and delete files + * + * Check whether the current process can bypass DAC permissions. + * + * Traditionally, "privileged" processes (those with effective uid 0) + * could do this (and bypass many other checks), and "unprivileged" + * processes could not. + * + * In Linux, the special powers of euid 0 are divided into many + * capabilities: see `capabilities(7)`. The one we are interested in + * here is `CAP_DAC_OVERRIDE`. + * + * We do this generically instead of actually looking at the capability + * bits, so that the right thing will happen on non-Linux Unix + * implementations, in particular if they have something equivalent to + * but not identical to Linux permissions. + * + * Returns: %TRUE if we have Linux `CAP_DAC_OVERRIDE` or equivalent + * privileges + */ +static gboolean +check_cap_dac_override (const char *tmpdir) +{ + gchar *dac_denies_write; + gchar *inside; + gboolean have_cap; + + dac_denies_write = g_build_filename (tmpdir, "dac-denies-write", NULL); + inside = g_build_filename (dac_denies_write, "inside", NULL); + + g_assert_cmpint (mkdir (dac_denies_write, S_IRWXU) == 0 ? 0 : errno, ==, 0); + g_assert_cmpint (chmod (dac_denies_write, 0) == 0 ? 0 : errno, ==, 0); + + if (mkdir (inside, S_IRWXU) == 0) + { + g_test_message ("Looks like we have CAP_DAC_OVERRIDE or equivalent"); + g_assert_cmpint (rmdir (inside) == 0 ? 0 : errno, ==, 0); + have_cap = TRUE; + } + else + { + int saved_errno = errno; + + g_test_message ("We do not have CAP_DAC_OVERRIDE or equivalent"); + g_assert_cmpint (saved_errno, ==, EACCES); + have_cap = FALSE; + } + + g_assert_cmpint (chmod (dac_denies_write, S_IRWXU) == 0 ? 0 : errno, ==, 0); + g_assert_cmpint (rmdir (dac_denies_write) == 0 ? 0 : errno, ==, 0); + g_free (dac_denies_write); + g_free (inside); + return have_cap; +} +#endif + #ifdef G_HAVE_ISO_VARARGS #define log(...) if (verbose) g_printerr (__VA_ARGS__) #elif defined(G_HAVE_GNUC_VARARGS) @@ -708,6 +768,9 @@ do_copy_move (GFile * root, struct StructureItem item, const char *target_dir, GFile *dst_dir, *src_file, *dst_file; gboolean res; GError *error; +#ifdef G_OS_UNIX + gboolean have_cap_dac_override = check_cap_dac_override (g_file_peek_path (root)); +#endif log (" do_copy_move: '%s' --> '%s'\n", item.filename, target_dir); @@ -768,12 +831,17 @@ do_copy_move (GFile * root, struct StructureItem item, const char *target_dir, (extra_flags == TEST_NO_ACCESS)) { /* This works for root, see bug #552912 */ - if (test_suite && getuid () == 0) +#ifdef G_OS_UNIX + if (have_cap_dac_override) { + g_test_message ("Unable to exercise g_file_copy() or g_file_move() " + "failing with EACCES: we probably have " + "CAP_DAC_OVERRIDE"); g_assert_true (res); g_assert_no_error (error); } else +#endif { g_assert_false (res); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED); @@ -1121,6 +1189,9 @@ test_make_directory_with_parents (gconstpointer test_data) GFile *root, *child, *grandchild, *greatgrandchild; gboolean res; GError *error = NULL; +#ifdef G_OS_UNIX + gboolean have_cap_dac_override = check_cap_dac_override (test_data); +#endif g_assert_nonnull (test_data); @@ -1171,9 +1242,16 @@ test_make_directory_with_parents (gconstpointer test_data) if (!posix_compat) goto out; -#ifndef G_PLATFORM_WIN32 - if (getuid() == 0) /* permissions are ignored for root */ - goto out; +#ifdef G_OS_UNIX + /* Permissions are ignored if we have CAP_DAC_OVERRIDE or equivalent, + * and in particular if we're root */ + if (have_cap_dac_override) + { + g_test_skip ("Unable to exercise g_file_make_directory_with_parents " + "failing with EACCES: we probably have " + "CAP_DAC_OVERRIDE"); + goto out; + } #endif g_file_make_directory (child, NULL, NULL); @@ -1343,7 +1421,7 @@ main (int argc, char *argv[]) } g_option_context_free (context); - + /* Write test - clean target directory first */ /* this can be also considered as a test - enumerate + delete */ if (write_test || only_create_struct) diff --git a/glib/tests/fileutils.c b/glib/tests/fileutils.c index 06b14bead..0c84e326d 100644 --- a/glib/tests/fileutils.c +++ b/glib/tests/fileutils.c @@ -522,6 +522,65 @@ test_mkdir_with_parents (void) g_assert_cmpint (errno, ==, EINVAL); } +#ifdef G_OS_UNIX +/* + * check_cap_dac_override: + * @tmpdir: A temporary directory in which we can create and delete files + * + * Check whether the current process can bypass DAC permissions. + * + * Traditionally, "privileged" processes (those with effective uid 0) + * could do this (and bypass many other checks), and "unprivileged" + * processes could not. + * + * In Linux, the special powers of euid 0 are divided into many + * capabilities: see `capabilities(7)`. The one we are interested in + * here is `CAP_DAC_OVERRIDE`. + * + * We do this generically instead of actually looking at the capability + * bits, so that the right thing will happen on non-Linux Unix + * implementations, in particular if they have something equivalent to + * but not identical to Linux permissions. + * + * Returns: %TRUE if we have Linux `CAP_DAC_OVERRIDE` or equivalent + * privileges + */ +static gboolean +check_cap_dac_override (const char *tmpdir) +{ + gchar *dac_denies_write; + gchar *inside; + gboolean have_cap; + + dac_denies_write = g_build_filename (tmpdir, "dac-denies-write", NULL); + inside = g_build_filename (dac_denies_write, "inside", NULL); + + g_assert_cmpint (mkdir (dac_denies_write, S_IRWXU) == 0 ? 0 : errno, ==, 0); + g_assert_cmpint (chmod (dac_denies_write, 0) == 0 ? 0 : errno, ==, 0); + + if (mkdir (inside, S_IRWXU) == 0) + { + g_test_message ("Looks like we have CAP_DAC_OVERRIDE or equivalent"); + g_assert_cmpint (rmdir (inside) == 0 ? 0 : errno, ==, 0); + have_cap = TRUE; + } + else + { + int saved_errno = errno; + + g_test_message ("We do not have CAP_DAC_OVERRIDE or equivalent"); + g_assert_cmpint (saved_errno, ==, EACCES); + have_cap = FALSE; + } + + g_assert_cmpint (chmod (dac_denies_write, S_IRWXU) == 0 ? 0 : errno, ==, 0); + g_assert_cmpint (rmdir (dac_denies_write) == 0 ? 0 : errno, ==, 0); + g_free (dac_denies_write); + g_free (inside); + return have_cap; +} +#endif + /* Reproducer for https://gitlab.gnome.org/GNOME/glib/issues/1852 */ static void test_mkdir_with_parents_permission (void) @@ -534,21 +593,23 @@ test_mkdir_with_parents_permission (void) GError *error = NULL; int result; int saved_errno; + gboolean have_cap_dac_override; tmpdir = g_dir_make_tmp ("test-fileutils.XXXXXX", &error); g_assert_no_error (error); g_assert_nonnull (tmpdir); + have_cap_dac_override = check_cap_dac_override (tmpdir); + subdir = g_build_filename (tmpdir, "sub", NULL); subdir2 = g_build_filename (subdir, "sub2", NULL); subdir3 = g_build_filename (subdir2, "sub3", NULL); g_assert_cmpint (g_mkdir (subdir, 0700) == 0 ? 0 : errno, ==, 0); g_assert_cmpint (g_chmod (subdir, 0) == 0 ? 0 : errno, ==, 0); - if (g_mkdir (subdir2, 0700) == 0) + if (have_cap_dac_override) { g_test_skip ("have CAP_DAC_OVERRIDE or equivalent, cannot test"); - g_remove (subdir2); } else { @@ -949,14 +1010,8 @@ test_stdio_wrappers (void) GError *error = NULL; GStatBuf path_statbuf, cwd_statbuf; time_t now; - - /* The permissions tests here don’t work when running as root. */ #ifdef G_OS_UNIX - if (getuid () == 0 || geteuid () == 0) - { - g_test_skip ("File permissions tests cannot be run as root"); - return; - } + gboolean have_cap_dac_override; #endif g_remove ("mkdir-test/test-create"); @@ -973,13 +1028,32 @@ test_stdio_wrappers (void) cwd = g_get_current_dir (); path = g_build_filename (cwd, "mkdir-test", NULL); - g_free (cwd); -#ifndef G_OS_WIN32 - /* 0666 on directories means nothing to Windows, it only obeys ACLs */ - ret = g_chdir (path); - g_assert_cmpint (errno, ==, EACCES); - g_assert_cmpint (ret, ==, -1); +#ifdef G_OS_UNIX + have_cap_dac_override = check_cap_dac_override (cwd); #endif + g_free (cwd); + + /* 0666 on directories means nothing to Windows, it only obeys ACLs. + * It doesn't necessarily mean anything on Unix either: if we have + * Linux CAP_DAC_OVERRIDE or equivalent (in particular if we're root), + * then we ignore filesystem permissions. */ +#ifdef G_OS_UNIX + if (have_cap_dac_override) + { + g_test_message ("Cannot test g_chdir() failing with EACCES: we " + "probably have CAP_DAC_OVERRIDE or equivalent"); + } + else + { + ret = g_chdir (path); + g_assert_cmpint (ret == 0 ? 0 : errno, ==, EACCES); + g_assert_cmpint (ret, ==, -1); + } +#else + g_test_message ("Cannot test g_chdir() failing with EACCES: " + "it's Unix-specific behaviour"); +#endif + ret = g_chmod (path, 0777); g_assert_cmpint (ret, ==, 0); ret = g_chdir (path);