| From 4c2928a54482913cf236bff0e66650a8f47e17ea Mon Sep 17 00:00:00 2001 |
| From: Colin Walters <walters@verbum.org> |
| Date: Wed, 22 Aug 2012 18:26:11 +0000 |
| Subject: CVE-2012-3524: Hardening for being run in a setuid environment |
| |
| Some programs attempt to use libglib (or even libgio) when setuid. |
| For a long time, GTK+ simply aborted if launched in this |
| configuration, but we never had a real policy for GLib. |
| |
| I'm not sure whether we should advertise such support. However, given |
| that there are real-world programs that do this currently, we can make |
| them safer with not too much effort. |
| |
| Better to fix a problem caused by an interaction between two |
| components in *both* places if possible. |
| |
| This patch adds a private function g_check_setuid() which is used to |
| first ensure we don't run an external dbus-launch binary if |
| DBUS_SESSION_BUS_ADDRESS isn't set. |
| |
| Second, we also ensure the local VFS is used in this case. The |
| gdaemonvfs extension point will end up talking to the session bus |
| which is typically undesirable in a setuid context. |
| |
| Implementing g_check_setuid() is interesting - whether or not we're |
| running in a privilege-escalated path is operating system specific. |
| Note that GTK+'s code to check euid versus uid worked historically on |
| Unix, more modern systems have filesystem capabilities and SELinux |
| domain transitions, neither of which are captured by the uid |
| comparison. |
| |
| On Linux/glibc, the way this works is that the kernel sets an |
| AT_SECURE flag in the ELF auxiliary vector, and glibc looks for it on |
| startup. If found, then glibc sets a public-but-undocumented |
| __libc_enable_secure variable which we can use. Unfortunately, while |
| it *previously* worked to check this variable, a combination of newer |
| binutils and RPM break it: |
| http://www.openwall.com/lists/owl-dev/2012/08/14/1 |
| |
| So for now on Linux/glibc, we fall back to the historical Unix version |
| until we get glibc fixed. |
| |
| On some BSD variants, there is a issetugid() function. On other Unix |
| variants, we fall back to what GTK+ has been doing. |
| |
| Reported-By: Sebastian Krahmer <krahmer@suse.de> |
| Signed-off-by: Colin Walters <walters@verbum.org> |
| --- |
| diff --git a/configure.ac b/configure.ac |
| index 584df1d..67ea1a9 100644 |
| --- a/configure.ac |
| +++ b/configure.ac |
| @@ -583,9 +583,20 @@ AC_TRY_COMPILE([#include <dirent.h>], [DIR *dir;], |
| # Checks for library functions. |
| AC_FUNC_VPRINTF |
| AC_FUNC_ALLOCA |
| -AC_CHECK_FUNCS(mmap posix_memalign memalign valloc fsync pipe2) |
| +AC_CHECK_FUNCS(mmap posix_memalign memalign valloc fsync pipe2 issetugid) |
| AC_CHECK_FUNCS(atexit on_exit timegm gmtime_r) |
| |
| +AC_CACHE_CHECK([for __libc_enable_secure], glib_cv_have_libc_enable_secure, |
| + [AC_TRY_LINK([#include <unistd.h> |
| + extern int __libc_enable_secure;], |
| + [return __libc_enable_secure;], |
| + glib_cv_have_libc_enable_secure=yes, |
| + glib_cv_have_libc_enable_secure=no)]) |
| +AS_IF([test x$glib_cv_have_libc_enable_secure = xyes], [ |
| + AC_DEFINE(HAVE_LIBC_ENABLE_SECURE, 1, |
| + [Define if you have the __libc_enable_secure variable (GNU libc, eglibc)]) |
| +]) |
| + |
| AC_CHECK_SIZEOF(char) |
| AC_CHECK_SIZEOF(short) |
| AC_CHECK_SIZEOF(long) |
| @@ -984,7 +995,7 @@ AC_MSG_RESULT(unsigned $glib_size_type) |
| |
| # Check for some functions |
| AC_CHECK_FUNCS(lstat strerror strsignal memmove vsnprintf stpcpy strcasecmp strncasecmp poll getcwd vasprintf setenv unsetenv getc_unlocked readlink symlink fdwalk memmem) |
| -AC_CHECK_FUNCS(chown lchmod lchown fchmod fchown link utimes getgrgid getpwuid) |
| +AC_CHECK_FUNCS(chown lchmod lchown fchmod fchown link utimes getgrgid getpwuid getresuid) |
| AC_CHECK_FUNCS(getmntent_r setmntent endmntent hasmntopt getfsstat getvfsstat) |
| # Check for high-resolution sleep functions |
| AC_CHECK_FUNCS(splice) |
| diff --git a/gio/gdbusaddress.c b/gio/gdbusaddress.c |
| index 4aa13b9..96b6343 100644 |
| --- a/gio/gdbusaddress.c |
| +++ b/gio/gdbusaddress.c |
| @@ -37,6 +37,7 @@ |
| #include "giostream.h" |
| #include "gasyncresult.h" |
| #include "gsimpleasyncresult.h" |
| +#include "glib-private.h" |
| #include "gdbusprivate.h" |
| #include "giomodule-priv.h" |
| #include "gdbusdaemon.h" |
| @@ -1023,6 +1024,14 @@ get_session_address_dbus_launch (GError **error) |
| restore_dbus_verbose = FALSE; |
| old_dbus_verbose = NULL; |
| |
| + /* Don't run binaries as root if we're setuid. */ |
| + if (GLIB_PRIVATE_CALL (g_check_setuid) ()) |
| + { |
| + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, |
| + _("Cannot spawn a message bus when setuid")); |
| + goto out; |
| + } |
| + |
| machine_id = _g_dbus_get_machine_id (error); |
| if (machine_id == NULL) |
| { |
| diff --git a/gio/gvfs.c b/gio/gvfs.c |
| index dda8afb..9afbcec 100644 |
| --- a/gio/gvfs.c |
| +++ b/gio/gvfs.c |
| @@ -23,6 +23,7 @@ |
| #include "config.h" |
| #include <string.h> |
| #include "gvfs.h" |
| +#include "glib-private.h" |
| #include "glocalvfs.h" |
| #include "gresourcefile.h" |
| #include "giomodule-priv.h" |
| @@ -191,6 +192,8 @@ g_vfs_parse_name (GVfs *vfs, |
| GVfs * |
| g_vfs_get_default (void) |
| { |
| + if (GLIB_PRIVATE_CALL (g_check_setuid) ()) |
| + return g_vfs_get_local (); |
| return _g_io_module_get_default (G_VFS_EXTENSION_POINT_NAME, |
| "GIO_USE_VFS", |
| (GIOModuleVerifyFunc)g_vfs_is_active); |
| diff --git a/glib/genviron.c b/glib/genviron.c |
| index 59a8bbe..9525cf0 100644 |
| --- a/glib/genviron.c |
| +++ b/glib/genviron.c |
| @@ -40,6 +40,7 @@ |
| #include <windows.h> |
| #endif |
| |
| +#include "glib-private.h" |
| #include "gmem.h" |
| #include "gmessages.h" |
| #include "gstrfuncs.h" |
| diff --git a/glib/glib-private.c b/glib/glib-private.c |
| index 3946e77..3506782 100644 |
| --- a/glib/glib-private.c |
| +++ b/glib/glib-private.c |
| @@ -38,7 +38,9 @@ glib__private__ (void) |
| g_wakeup_signal, |
| g_wakeup_acknowledge, |
| |
| - g_get_worker_context |
| + g_get_worker_context, |
| + |
| + g_check_setuid |
| }; |
| |
| return &table; |
| diff --git a/glib/glib-private.h b/glib/glib-private.h |
| index fde0be8..87da6f3 100644 |
| --- a/glib/glib-private.h |
| +++ b/glib/glib-private.h |
| @@ -25,6 +25,8 @@ |
| |
| G_GNUC_INTERNAL |
| GMainContext * g_get_worker_context (void); |
| +G_GNUC_INTERNAL |
| +gboolean g_check_setuid (void); |
| |
| #define GLIB_PRIVATE_CALL(symbol) (glib__private__()->symbol) |
| |
| @@ -40,6 +42,8 @@ typedef struct { |
| /* See gmain.c */ |
| GMainContext * (* g_get_worker_context) (void); |
| /* Add other private functions here, initialize them in glib-private.c */ |
| + |
| + gboolean (* g_check_setuid) (void); |
| } GLibPrivateVTable; |
| |
| GLibPrivateVTable *glib__private__ (void); |
| diff --git a/glib/gutils.c b/glib/gutils.c |
| index 38b5e44..f8a38d1 100644 |
| --- a/glib/gutils.c |
| +++ b/glib/gutils.c |
| @@ -2409,3 +2409,60 @@ g_get_tmp_dir (void) |
| } |
| |
| #endif |
| + |
| +/* Private API: |
| + * |
| + * Returns %TRUE if the current process was executed as setuid (or an |
| + * equivalent __libc_enable_secure is available). See: |
| + * http://osdir.com/ml/linux.lfs.hardened/2007-04/msg00032.html |
| + */ |
| +gboolean |
| +g_check_setuid (void) |
| +{ |
| + /* TODO: get __libc_enable_secure exported from glibc. |
| + * See http://www.openwall.com/lists/owl-dev/2012/08/14/1 |
| + */ |
| +#if 0 && defined(HAVE_LIBC_ENABLE_SECURE) |
| + { |
| + /* See glibc/include/unistd.h */ |
| + extern int __libc_enable_secure; |
| + return __libc_enable_secure; |
| + } |
| +#elif defined(HAVE_ISSETUGID) |
| + /* BSD: http://www.freebsd.org/cgi/man.cgi?query=issetugid&sektion=2 */ |
| + return issetugid (); |
| +#elif defined(G_OS_UNIX) |
| + uid_t ruid, euid, suid; /* Real, effective and saved user ID's */ |
| + gid_t rgid, egid, sgid; /* Real, effective and saved group ID's */ |
| + |
| + static gsize check_setuid_initialised; |
| + static gboolean is_setuid; |
| + |
| + if (g_once_init_enter (&check_setuid_initialised)) |
| + { |
| +#ifdef HAVE_GETRESUID |
| + /* These aren't in the header files, so we prototype them here. |
| + */ |
| + int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid); |
| + int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid); |
| + |
| + if (getresuid (&ruid, &euid, &suid) != 0 || |
| + getresgid (&rgid, &egid, &sgid) != 0) |
| +#endif /* HAVE_GETRESUID */ |
| + { |
| + suid = ruid = getuid (); |
| + sgid = rgid = getgid (); |
| + euid = geteuid (); |
| + egid = getegid (); |
| + } |
| + |
| + is_setuid = (ruid != euid || ruid != suid || |
| + rgid != egid || rgid != sgid); |
| + |
| + g_once_init_leave (&check_setuid_initialised, 1); |
| + } |
| + return is_setuid; |
| +#else |
| + return FALSE; |
| +#endif |
| +} |
| -- |
| cgit v0.9.0.2 |