mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-08 10:26:16 +01:00
Merge branch 'wip/smcv/but-what-if-capabilities' into 'master'
Make tests pass if we are euid != 0 with capabilities Closes #2029, #2028, and #2027 See merge request GNOME/glib!1363
This commit is contained in:
commit
4a153abebe
@ -22,6 +22,7 @@
|
||||
|
||||
#include <glib/glib.h>
|
||||
#include <gio/gio.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user