From 936e4c229a4ed205e9981fc4f31acea063701b69 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Wed, 17 Jul 2013 11:57:02 +0200 Subject: Don't load configs from user directory when setuid When running as setuid() or setgid() don't access the user's home directory, or use $HOME environment variables. https://bugzilla.redhat.com/show_bug.cgi?id=985014 --- common/compat.c | 48 +++++++++++++ common/compat.h | 12 ++++ common/path.c | 5 ++ common/test.c | 99 +++++++++++++++++++++++++++ common/test.h | 9 +++ common/tests/Makefile.am | 5 +- common/tests/frob-getauxval.c | 63 +++++++++++++++++ common/tests/test-compat.c | 30 ++++++++ configure.ac | 3 + doc/manual/p11-kit-config.xml | 3 + doc/manual/pkcs11.conf.xml | 3 + p11-kit/conf.c | 5 ++ p11-kit/tests/Makefile.am | 1 + p11-kit/tests/files/system-modules/one.module | 3 +- p11-kit/tests/files/user-modules/one.module | 3 +- p11-kit/tests/frob-setuid.c | 95 +++++++++++++++++++++++++ p11-kit/tests/test-conf.c | 39 +++++++++++ 17 files changed, 423 insertions(+), 3 deletions(-) create mode 100644 common/tests/frob-getauxval.c create mode 100644 p11-kit/tests/frob-setuid.c diff --git a/common/compat.c b/common/compat.c index 5efc932..3b1361c 100644 --- a/common/compat.c +++ b/common/compat.c @@ -759,3 +759,51 @@ mkdtemp (char *template) } #endif /* HAVE_MKDTEMP */ + +#ifndef HAVE_GETAUXVAL + +unsigned long +getauxval (unsigned long type) +{ + static unsigned long secure = 0UL; + static bool check_secure_initialized = false; + + /* + * This is the only one our stand-in impl supports and is + * also the only type we define in compat.h header + */ + assert (type == AT_SECURE); + + if (!check_secure_initialized) { +#if defined(HAVE___LIBC_ENABLE_SECURE) + extern int __libc_enable_secure; + secure = __libc_enable_secure; + +#elif defined(HAVE_ISSETUGID) + secure = issetugid (); + +#elif defined(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 */ + +#ifdef HAVE_GETRESUID + if (getresuid (&ruid, &euid, &suid) != 0 || + getresgid (&rgid, &egid, &sgid) != 0) +#endif /* HAVE_GETRESUID */ + { + suid = ruid = getuid (); + sgid = rgid = getgid (); + euid = geteuid (); + egid = getegid (); + } + + secure = (ruid != euid || ruid != suid || + rgid != egid || rgid != sgid); +#endif /* OS_UNIX */ + check_secure_initialized = true; + } + + return secure; +} + +#endif /* HAVE_GETAUXVAL */ diff --git a/common/compat.h b/common/compat.h index 20f9a81..1cedc35 100644 --- a/common/compat.h +++ b/common/compat.h @@ -298,4 +298,16 @@ time_t timegm (struct tm *tm); #endif /* HAVE_TIMEGM */ +#ifdef HAVE_GETAUXVAL + +#include + +#else /* !HAVE_GETAUXVAL */ + +unsigned long getauxval (unsigned long type); + +#define AT_SECURE 23 + +#endif /* !HAVE_GETAUXVAL */ + #endif /* __COMPAT_H__ */ diff --git a/common/path.c b/common/path.c index 0ff1431..d807301 100644 --- a/common/path.c +++ b/common/path.c @@ -99,6 +99,11 @@ expand_homedir (const char *remainder) if (remainder[0] == '\0') remainder = NULL; + if (getauxval (AT_SECURE)) { + errno = EPERM; + return NULL; + } + env = getenv ("HOME"); if (env && env[0]) { return p11_path_build (env, remainder, NULL); diff --git a/common/test.c b/common/test.c index 0f5c451..eb02645 100644 --- a/common/test.c +++ b/common/test.c @@ -48,6 +48,12 @@ #include #include +#ifdef OS_UNIX +#include +#include +#include +#endif + enum { FIXTURE, TEST, @@ -331,3 +337,96 @@ p11_test_directory (const char *prefix) free (templ); return directory; } + +#ifdef OS_UNIX + +static void +copy_file (const char *input, + int fd) +{ + p11_mmap *mmap; + const char *data; + ssize_t written; + size_t size; + + mmap = p11_mmap_open (input, (void **)&data, &size); + assert (mmap != NULL); + + while (size > 0) { + written = write (fd, data, size); + assert (written >= 0); + + data += written; + size -= written; + } + + p11_mmap_close (mmap); +} + +char * +p11_test_copy_setgid (const char *input) +{ + gid_t groups[128]; + char *path; + gid_t group = 0; + int ret; + int fd; + int i; + + ret = getgroups (128, groups); + for (i = 0; i < ret; ++i) { + if (groups[i] != getgid ()) { + group = groups[i]; + break; + } + } + if (i == ret) { + fprintf (stderr, "# no suitable group, skipping test"); + return NULL; + } + + path = strdup ("/tmp/test-setgid.XXXXXX"); + assert (path != NULL); + + fd = mkstemp (path); + assert (fd >= 0); + + copy_file (input, fd); + if (fchown (fd, getuid (), group) < 0) + assert_not_reached (); + if (fchmod (fd, 02750) < 0) + assert_not_reached (); + if (close (fd) < 0) + assert_not_reached (); + + return path; +} + +int +p11_test_run_child (const char **argv, + bool quiet_out) +{ + pid_t child; + int status; + + child = fork (); + assert (child >= 0); + + /* In the child process? */ + if (child == 0) { + if (quiet_out) + close (1); /* stdout */ + execve (argv[0], (char **)argv, NULL); + assert_not_reached (); + } + + if (waitpid (child, &status, 0) < 0) + assert_not_reached (); + + assert (!WIFSIGNALED (status)); + assert (WIFEXITED (status)); + + return WEXITSTATUS (status); +} + +#endif /* OS_UNIX */ diff --git a/common/test.h b/common/test.h index 7354595..c9f519a 100644 --- a/common/test.h +++ b/common/test.h @@ -130,4 +130,13 @@ int p11_test_run (int argc, char * p11_test_directory (const char *prefix); +#ifdef OS_UNIX + +char * p11_test_copy_setgid (const char *path); + +int p11_test_run_child (const char **argv, + bool quiet_out); + +#endif + #endif /* P11_TEST_H_ */ diff --git a/common/tests/Makefile.am b/common/tests/Makefile.am index 637399b..b1a42bd 100644 --- a/common/tests/Makefile.am +++ b/common/tests/Makefile.am @@ -7,7 +7,9 @@ AM_CPPFLAGS = \ -I$(top_srcdir) \ -I$(srcdir)/.. \ -I$(COMMON) \ - $(TEST_CFLAGS) + -DBUILDDIR=\"$(abs_builddir)\" \ + $(TEST_CFLAGS) \ + $(CUTEST_CFLAGS) LDADD = \ $(NULL) @@ -26,6 +28,7 @@ CHECK_PROGS = \ $(NULL) noinst_PROGRAMS = \ + frob-getauxval \ $(CHECK_PROGS) TESTS = $(CHECK_PROGS) diff --git a/common/tests/frob-getauxval.c b/common/tests/frob-getauxval.c new file mode 100644 index 0000000..54ebea0 --- /dev/null +++ b/common/tests/frob-getauxval.c @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2013 Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Author: Stef Walter + */ + +#include "config.h" +#include "compat.h" + +#include + +#include +#include +#include +#include + +int +main (int argc, + char *argv[]) +{ + unsigned long type = 0; + unsigned long ret; + + if (argc == 2) + type = atoi (argv[1]); + + if (type == 0) { + fprintf (stderr, "usage: frob-getauxval 23"); + abort (); + } + + ret = getauxval (type); + printf ("getauxval(%lu) == %lu\n", type, ret); + return (int)ret; +} diff --git a/common/tests/test-compat.c b/common/tests/test-compat.c index f1960ce..a541235 100644 --- a/common/tests/test-compat.c +++ b/common/tests/test-compat.c @@ -35,6 +35,7 @@ #include "config.h" #include "test.h" +#include #include #include #include @@ -56,10 +57,39 @@ test_strndup (void) free (res); } +#ifdef OS_UNIX + +static void +test_getauxval (void) +{ + /* 23 is AT_SECURE */ + const char *args[] = { BUILDDIR "/frob-getauxval", "23", NULL }; + char *path; + int ret; + + ret = p11_test_run_child (args, true); + assert_num_eq (ret, 0); + + path = p11_test_copy_setgid (args[0]); + if (path == NULL) + return; + + args[0] = path; + ret = p11_test_run_child (args, true); + assert_num_cmp (ret, !=, 0); + + if (unlink (path) < 0) + assert_fail ("unlink failed", strerror (errno)); + free (path); +} + +#endif /* OS_UNIX */ + int main (int argc, char *argv[]) { p11_test (test_strndup, "/test/strndup"); + p11_test (test_getauxval, "/test/getauxval"); return p11_test_run (argc, argv); } diff --git a/configure.ac b/configure.ac index b0bbbd5..2f92b8c 100644 --- a/configure.ac +++ b/configure.ac @@ -79,6 +79,7 @@ if test "$os_unix" = "yes"; then # These are thngs we can work around AC_CHECK_MEMBERS([struct dirent.d_type],,,[#include ]) AC_CHECK_FUNCS([getprogname getexecname basename mkstemp mkdtemp]) + AC_CHECK_FUNCS([getauxval issetugid getresuid]) AC_CHECK_FUNCS([strnstr memdup strndup]) AC_CHECK_FUNCS([asprintf vasprintf vsnprintf]) AC_CHECK_FUNCS([timegm]) @@ -100,6 +101,8 @@ if test "$os_unix" = "yes"; then AC_CHECK_DECLS([__progname]) AC_LINK_IFELSE([AC_LANG_SOURCE([extern char *__progname; void main() { }])], [AC_DEFINE(HAVE___PROGNAME, [1], [Whether __progname available])]) + AC_LINK_IFELSE([AC_LANG_SOURCE([extern int __libc_enable_secure; void main() { }])], + [AC_DEFINE(HAVE___LIBC_ENABLE_SECURE, [1], [Whether __libc_enable_secure available])]) fi AC_CHECK_LIB(intl, dgettext) diff --git a/doc/manual/p11-kit-config.xml b/doc/manual/p11-kit-config.xml index 6d069dd..1df55b1 100644 --- a/doc/manual/p11-kit-config.xml +++ b/doc/manual/p11-kit-config.xml @@ -87,5 +87,8 @@ critical: yes See the manual page for more details on the format and available options. + + Note that user configuration files are not loaded from the home + directory if running inside a setuid or setgid program. diff --git a/doc/manual/pkcs11.conf.xml b/doc/manual/pkcs11.conf.xml index 1ff2562..cda02ee 100644 --- a/doc/manual/pkcs11.conf.xml +++ b/doc/manual/pkcs11.conf.xml @@ -241,6 +241,9 @@ x-custom : text file per module. In addition the ~/.pkcs11/modules directory can be used for modules installed by the user. + Note that user configuration files are not loaded from the home + directory if running inside a setuid or setgid program. + The default system config file and module directory can be changed when building p11-kit. Always lookup these paths using diff --git a/p11-kit/conf.c b/p11-kit/conf.c index e699e66..d29d9ec 100644 --- a/p11-kit/conf.c +++ b/p11-kit/conf.c @@ -227,6 +227,11 @@ _p11_conf_load_globals (const char *system_conf, const char *user_conf, goto finished; } + if (mode != CONF_USER_NONE && getauxval (AT_SECURE)) { + p11_debug ("skipping user config in setuid or setgid program"); + mode = CONF_USER_NONE; + } + if (mode != CONF_USER_NONE) { path = p11_path_expand (user_conf); if (!path) { diff --git a/p11-kit/tests/Makefile.am b/p11-kit/tests/Makefile.am index 6963850..16ba280 100644 --- a/p11-kit/tests/Makefile.am +++ b/p11-kit/tests/Makefile.am @@ -40,6 +40,7 @@ endif noinst_PROGRAMS = \ print-messages \ + frob-setuid \ $(CHECK_PROGS) TESTS = $(CHECK_PROGS) diff --git a/p11-kit/tests/files/system-modules/one.module b/p11-kit/tests/files/system-modules/one.module index 15cb7f2..5f49a8f 100644 --- a/p11-kit/tests/files/system-modules/one.module +++ b/p11-kit/tests/files/system-modules/one.module @@ -1,4 +1,5 @@ module: mock-one.so setting: system1 -trust-policy: yes \ No newline at end of file +trust-policy: yes +number: 18 diff --git a/p11-kit/tests/files/user-modules/one.module b/p11-kit/tests/files/user-modules/one.module index 6f1a2e8..5197daf 100644 --- a/p11-kit/tests/files/user-modules/one.module +++ b/p11-kit/tests/files/user-modules/one.module @@ -1,3 +1,4 @@ setting: user1 -managed: yes \ No newline at end of file +managed: yes +number: 33 diff --git a/p11-kit/tests/frob-setuid.c b/p11-kit/tests/frob-setuid.c new file mode 100644 index 0000000..e546ece --- /dev/null +++ b/p11-kit/tests/frob-setuid.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2012 Red Hat Inc + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Author: Stef Walter + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "compat.h" +#include "p11-kit.h" + +int +main (void) +{ + CK_FUNCTION_LIST **modules; + CK_FUNCTION_LIST *module; + char *field; + char *name; + int ret; + int i; + + /* + * Use 'chmod ug+s frob-setuid' to change this program + * and test the output with/without setuid or setgid. + */ + + putenv ("P11_KIT_STRICT=1"); + + modules = p11_kit_modules_load_and_initialize (0); + assert (modules != NULL); + + /* This is a system configured module */ + module = p11_kit_module_for_name (modules, "one"); + assert (module != NULL); + + field = p11_kit_config_option (module, "setting"); + printf ("'setting' on module 'one': %s\n", field ? field : "(null)"); + + assert (field != NULL); + if (getauxval (AT_SECURE)) + assert (strcmp (field, "system1") == 0); + else + assert (strcmp (field, "user1") == 0); + + free (field); + + for (i = 0; modules[i] != NULL; i++) { + name = p11_kit_module_get_name (modules[i]); + printf ("%s\n", name); + free (name); + } + + field = p11_kit_config_option (module, "number"); + printf ("'number' on module 'one': %s\n", field ? field : "(null)"); + + ret = atoi (field ? field : "0"); + assert (ret != 0); + free (field); + + p11_kit_modules_finalize_and_release (modules); + return ret; +} diff --git a/p11-kit/tests/test-conf.c b/p11-kit/tests/test-conf.c index c214bac..3a94c12 100644 --- a/p11-kit/tests/test-conf.c +++ b/p11-kit/tests/test-conf.c @@ -46,6 +46,12 @@ #include "p11-kit.h" #include "private.h" +#ifdef OS_UNIX +#include +#include +#include +#endif + static void test_parse_conf_1 (void) { @@ -391,6 +397,36 @@ test_parse_boolean (void) assert_num_eq (true, _p11_conf_parse_boolean ("!!!", true)); } +#ifdef OS_UNIX + +static void +test_setuid (void) +{ + const char *args[] = { BUILDDIR "/frob-setuid", NULL, }; + char *path; + int ret; + + /* This is the 'number' setting set in one.module user configuration. */ + ret = p11_test_run_child (args, true); + assert_num_eq (ret, 33); + + path = p11_test_copy_setgid (args[0]); + if (path == NULL) + return; + + args[0] = path; + + /* This is the 'number' setting set in one.module system configuration. */ + ret = p11_test_run_child (args, true); + assert_num_eq (ret, 18); + + if (unlink (path) < 0) + assert_fail ("unlink failed", strerror (errno)); + free (path); +} + +#endif /* OS_UNIX */ + int main (int argc, char *argv[]) @@ -410,5 +446,8 @@ main (int argc, p11_test (test_load_modules_user_only, "/conf/test_load_modules_user_only"); p11_test (test_load_modules_user_none, "/conf/test_load_modules_user_none"); p11_test (test_parse_boolean, "/conf/test_parse_boolean"); +#ifdef OS_UNIX + p11_test (test_setuid, "/conf/setuid"); +#endif return p11_test_run (argc, argv); } -- cgit v1.1