]> granicus.if.org Git - linux-pam/commitdiff
Relevant BUGIDs:
authorDmitry V. Levin <ldv@altlinux.org>
Sun, 3 Oct 2010 21:00:53 +0000 (21:00 +0000)
committerDmitry V. Levin <ldv@altlinux.org>
Sun, 3 Oct 2010 21:00:53 +0000 (21:00 +0000)
Purpose of commit: bugfix

Commit summary:
---------------
2010-10-04  Dmitry V. Levin  <ldv@altlinux.org>

* libpam/pam_modutil_priv.c: New file.
* libpam/Makefile.am (libpam_la_SOURCES): Add it.
* libpam/include/security/pam_modutil.h (struct pam_modutil_privs,
PAM_MODUTIL_DEF_PRIVS, pam_modutil_drop_priv,
pam_modutil_regain_priv): New declarations.
* libpam/libpam.map (LIBPAM_MODUTIL_1.1.3): New interface.
* modules/pam_env/pam_env.c (handle_env): Use new pam_modutil interface.
* modules/pam_mail/pam_mail.c (_do_mail): Likewise.
* modules/pam_xauth/pam_xauth.c (check_acl, pam_sm_open_session,
pam_sm_close_session): Likewise.
(pam_sm_open_session): Remove redundant fchown call.
Fixes CVE-2010-3430, CVE-2010-3431.

ChangeLog
libpam/Makefile.am
libpam/include/security/pam_modutil.h
libpam/libpam.map
libpam/pam_modutil_priv.c [new file with mode: 0644]
modules/pam_env/pam_env.c
modules/pam_mail/pam_mail.c
modules/pam_xauth/pam_xauth.c

index 7473934bcebefbcce8a79ba425230506f91f3071..1b8e59995aedc7d4e5dab542ae3128f92a87aa7b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,18 @@
+2010-10-04  Dmitry V. Levin  <ldv@altlinux.org>
+
+       * libpam/pam_modutil_priv.c: New file.
+       * libpam/Makefile.am (libpam_la_SOURCES): Add it.
+       * libpam/include/security/pam_modutil.h (struct pam_modutil_privs,
+       PAM_MODUTIL_DEF_PRIVS, pam_modutil_drop_priv,
+       pam_modutil_regain_priv): New declarations.
+       * libpam/libpam.map (LIBPAM_MODUTIL_1.1.3): New interface.
+       * modules/pam_env/pam_env.c (handle_env): Use new pam_modutil interface.
+       * modules/pam_mail/pam_mail.c (_do_mail): Likewise.
+       * modules/pam_xauth/pam_xauth.c (check_acl, pam_sm_open_session,
+       pam_sm_close_session): Likewise.
+       (pam_sm_open_session): Remove redundant fchown call.
+       Fixes CVE-2010-3430, CVE-2010-3431.
+
 2010-10-01  Thorsten Kukuk  <kukuk@thkukuk.de>
 
        * configure.in: Extend cross compiling check.
index 57bd8109e774966a484f7998a8926a3fd5d71822..57080fcf64d6fc925bcb34180c6e40781b8e360c 100644 (file)
@@ -41,4 +41,5 @@ libpam_la_SOURCES = pam_account.c pam_auth.c pam_data.c pam_delay.c \
        pam_vprompt.c pam_syslog.c pam_dynamic.c pam_audit.c \
        pam_modutil_cleanup.c pam_modutil_getpwnam.c pam_modutil_ioloop.c \
        pam_modutil_getgrgid.c pam_modutil_getpwuid.c pam_modutil_getgrnam.c \
-       pam_modutil_getspnam.c pam_modutil_getlogin.c  pam_modutil_ingroup.c
+       pam_modutil_getspnam.c pam_modutil_getlogin.c pam_modutil_ingroup.c \
+       pam_modutil_priv.c
index ffdf5ad03dca592c9449828d841a7652cdff445d..317202deaa102cb217f51b0cfc3a7bcc49a54cff 100644 (file)
@@ -100,6 +100,30 @@ pam_modutil_write(int fd, const char *buffer, int count);
 extern int PAM_NONNULL((1,3))
 pam_modutil_audit_write(pam_handle_t *pamh, int type,
                        const char *message, int retval);
+
+struct pam_modutil_privs {
+       gid_t *grplist;
+       int number_of_groups;
+       int allocated;
+       gid_t old_gid;
+       uid_t old_uid;
+       int is_dropped;
+};
+
+#define PAM_MODUTIL_NGROUPS     64
+#define PAM_MODUTIL_DEF_PRIVS(n) \
+       gid_t n##_grplist[PAM_MODUTIL_NGROUPS]; \
+       struct pam_modutil_privs n = { n##_grplist, PAM_MODUTIL_NGROUPS, 0, -1, -1, 0 }
+
+extern int PAM_NONNULL((1,2,3))
+pam_modutil_drop_priv(pam_handle_t *pamh,
+                     struct pam_modutil_privs *p,
+                     const struct passwd *pw);
+
+extern int PAM_NONNULL((1,2))
+pam_modutil_regain_priv(pam_handle_t *pamh,
+                     struct pam_modutil_privs *p);
+
 #ifdef __cplusplus
 }
 #endif
index 9d55e84f3a1ed0c670d6fc3acce86b48ff8c405b..b0885d656177cf1053fb34fefa078a7465f54fbb 100644 (file)
@@ -61,3 +61,9 @@ LIBPAM_MODUTIL_1.1 {
   global:
     pam_modutil_audit_write;
 } LIBPAM_MODUTIL_1.0;
+
+LIBPAM_MODUTIL_1.1.3 {
+  global:
+    pam_modutil_drop_priv;
+    pam_modutil_regain_priv;
+} LIBPAM_MODUTIL_1.1;
diff --git a/libpam/pam_modutil_priv.c b/libpam/pam_modutil_priv.c
new file mode 100644 (file)
index 0000000..88094f6
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * $Id$
+ *
+ * This file provides two functions:
+ * pam_modutil_drop_priv:
+ *   temporarily lower process fs privileges by switching to another uid/gid,
+ * pam_modutil_regain_priv:
+ *   regain process fs privileges lowered by pam_modutil_drop_priv().
+ */
+
+#include "pam_modutil_private.h"
+#include <security/pam_ext.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/fsuid.h>
+
+/*
+ * Two setfsuid() calls in a row are necessary to check
+ * whether setfsuid() succeeded or not.
+ */
+static int change_uid(uid_t uid, uid_t *save)
+{
+       uid_t tmp = setfsuid(uid);
+       if (save)
+               *save = tmp;
+       return (uid_t) setfsuid(uid) == uid ? 0 : -1;
+}
+static int change_gid(gid_t gid, gid_t *save)
+{
+       gid_t tmp = setfsgid(gid);
+       if (save)
+               *save = tmp;
+       return (gid_t) setfsgid(gid) == gid ? 0 : -1;
+}
+
+static int cleanup(struct pam_modutil_privs *p)
+{
+       if (p->allocated) {
+               p->allocated = 0;
+               free(p->grplist);
+       }
+       p->grplist = NULL;
+       p->number_of_groups = 0;
+       return -1;
+}
+
+#define PRIV_MAGIC                     0x1004000a
+#define PRIV_MAGIC_DONOTHING           0xdead000a
+
+int pam_modutil_drop_priv(pam_handle_t *pamh,
+                         struct pam_modutil_privs *p,
+                         const struct passwd *pw)
+{
+       int res;
+
+       if (p->is_dropped) {
+               pam_syslog(pamh, LOG_CRIT,
+                          "pam_modutil_drop_priv: called with dropped privileges");
+               return -1;
+       }
+
+       /*
+        * If not root, we can do nothing.
+        * If switching to root, we have nothing to do.
+        * That is, in both cases, we do not care.
+        */
+       if (geteuid() != 0 || pw->pw_uid == 0) {
+               p->is_dropped = PRIV_MAGIC_DONOTHING;
+               return 0;
+       }
+
+       if (!p->grplist || p->number_of_groups <= 0) {
+               pam_syslog(pamh, LOG_CRIT,
+                          "pam_modutil_drop_priv: called without room for supplementary groups");
+               return -1;
+       }
+       res = getgroups(0, NULL);
+       if (res < 0) {
+               pam_syslog(pamh, LOG_ERR,
+                          "pam_modutil_drop_priv: getgroups failed: %m");
+               return -1;
+       }
+
+       p->allocated = 0;
+       if (res > p->number_of_groups) {
+               p->grplist = calloc(res, sizeof(gid_t));
+               if (!p->grplist) {
+                       pam_syslog(pamh, LOG_ERR, "out of memory");
+                       return cleanup(p);
+               }
+               p->allocated = 1;
+               p->number_of_groups = res;
+       }
+
+       res = getgroups(p->number_of_groups, p->grplist);
+       if (res < 0) {
+               pam_syslog(pamh, LOG_ERR,
+                          "pam_modutil_drop_priv: getgroups failed: %m");
+               return cleanup(p);
+       }
+
+       p->number_of_groups = res;
+
+       /*
+        * We should care to leave process credentials in consistent state.
+        * That is, e.g. if change_gid() succeeded but change_uid() failed,
+        * we should try to restore old gid.
+        */
+       if (setgroups(0, NULL)) {
+               pam_syslog(pamh, LOG_ERR,
+                          "pam_modutil_drop_priv: setgroups failed: %m");
+               return cleanup(p);
+       }
+       if (change_gid(pw->pw_gid, &p->old_gid)) {
+               pam_syslog(pamh, LOG_ERR,
+                          "pam_modutil_drop_priv: change_gid failed: %m");
+               (void) setgroups(p->number_of_groups, p->grplist);
+               return cleanup(p);
+       }
+       if (change_uid(pw->pw_uid, &p->old_uid)) {
+               pam_syslog(pamh, LOG_ERR,
+                          "pam_modutil_drop_priv: change_uid failed: %m");
+               (void) change_gid(p->old_gid, NULL);
+               (void) setgroups(p->number_of_groups, p->grplist);
+               return cleanup(p);
+       }
+
+       p->is_dropped = PRIV_MAGIC;
+       return 0;
+}
+
+int pam_modutil_regain_priv(pam_handle_t *pamh,
+                         struct pam_modutil_privs *p)
+{
+       switch (p->is_dropped) {
+               case PRIV_MAGIC_DONOTHING:
+                       p->is_dropped = 0;
+                       return 0;
+
+               case PRIV_MAGIC:
+                       break;
+
+               default:
+                       pam_syslog(pamh, LOG_CRIT,
+                                  "pam_modutil_regain_priv: called with invalid state");
+                       return -1;
+               }
+
+       if (change_uid(p->old_uid, NULL)) {
+               pam_syslog(pamh, LOG_ERR,
+                          "pam_modutil_regain_priv: change_uid failed: %m");
+               return cleanup(p);
+       }
+       if (change_gid(p->old_gid, NULL)) {
+               pam_syslog(pamh, LOG_ERR,
+                          "pam_modutil_regain_priv: change_gid failed: %m");
+               return cleanup(p);
+       }
+       if (setgroups(p->number_of_groups, p->grplist)) {
+               pam_syslog(pamh, LOG_ERR,
+                          "pam_modutil_regain_priv: setgroups failed: %m");
+               return cleanup(p);
+       }
+
+       p->is_dropped = 0;
+       cleanup(p);
+       return 0;
+}
index 3a9eebea228579181b12fe1f830e9f461f0f2966..8ac8ed332ebfbc2961a5ace6489738e89320826f 100644 (file)
@@ -23,7 +23,6 @@
 #include <string.h>
 #include <syslog.h>
 #include <sys/stat.h>
-#include <sys/fsuid.h>
 #include <sys/types.h>
 #include <unistd.h>
 
@@ -791,9 +790,15 @@ handle_env (pam_handle_t *pamh, int argc, const char **argv)
          return PAM_BUF_ERR;
        }
       if (stat(envpath, &statbuf) == 0) {
-       uid_t fsuid = setfsuid(user_entry->pw_uid);
-        retval = _parse_config_file(pamh, envpath);
-       setfsuid(fsuid);
+       PAM_MODUTIL_DEF_PRIVS(privs);
+
+       if (pam_modutil_drop_priv(pamh, &privs, user_entry)) {
+         retval = PAM_SESSION_ERR;
+       } else {
+         retval = _parse_config_file(pamh, envpath);
+         if (pam_modutil_regain_priv(pamh, &privs))
+           retval = PAM_SESSION_ERR;
+       }
         if (retval == PAM_IGNORE)
           retval = PAM_SUCCESS;
       }
index c19cbbe3d42b8ec80b53af7d5150537d73009939..f5ba173386154800f6abb3c39e488c50d0c60733 100644 (file)
@@ -17,7 +17,6 @@
 #include <syslog.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <sys/fsuid.h>
 #include <unistd.h>
 #include <dirent.h>
 #include <errno.h>
@@ -444,9 +443,18 @@ static int _do_mail(pam_handle_t *pamh, int flags, int argc,
 
     if ((est && !(ctrl & PAM_NO_LOGIN))
        || (!est && (ctrl & PAM_LOGOUT_TOO))) {
-       uid_t fsuid = setfsuid(pwd->pw_uid);
-       type = get_mail_status(pamh, ctrl, folder);
-       setfsuid(fsuid);
+       PAM_MODUTIL_DEF_PRIVS(privs);
+
+       if (pam_modutil_drop_priv(pamh, &privs, pwd)) {
+         retval = PAM_SESSION_ERR;
+         goto do_mail_cleanup;
+       } else {
+         type = get_mail_status(pamh, ctrl, folder);
+         if (pam_modutil_regain_priv(pamh, &privs)) {
+           retval = PAM_SESSION_ERR;
+           goto do_mail_cleanup;
+         }
+       }
 
        if (type != 0) {
            retval = report_mail(pamh, ctrl, type, folder);
index be2a2c7c9d0ddb34ce6954cc4666d8350f7be2ae..a64ae89f7ce48151104432df8217d52831e803b6 100644 (file)
@@ -35,7 +35,6 @@
 
 #include "config.h"
 #include <sys/types.h>
-#include <sys/fsuid.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <fcntl.h>
@@ -237,8 +236,9 @@ check_acl(pam_handle_t *pamh,
        struct passwd *pwd;
        FILE *fp = NULL;
        int i, fd = -1, save_errno;
-       uid_t fsuid;
        struct stat st;
+       PAM_MODUTIL_DEF_PRIVS(privs);
+
        /* Check this user's <sense> file. */
        pwd = pam_modutil_getpwnam(pamh, this_user);
        if (pwd == NULL) {
@@ -254,7 +254,8 @@ check_acl(pam_handle_t *pamh,
                           "name of user's home directory is too long");
                return PAM_SESSION_ERR;
        }
-       fsuid = setfsuid(pwd->pw_uid);
+       if (pam_modutil_drop_priv(pamh, &privs, pwd))
+               return PAM_SESSION_ERR;
        if (!stat(path, &st)) {
                if (!S_ISREG(st.st_mode))
                        errno = EINVAL;
@@ -262,7 +263,11 @@ check_acl(pam_handle_t *pamh,
                        fd = open(path, O_RDONLY | O_NOCTTY);
        }
        save_errno = errno;
-       setfsuid(fsuid);
+       if (pam_modutil_regain_priv(pamh, &privs)) {
+               if (fd >= 0)
+                       close(fd);
+               return PAM_SESSION_ERR;
+       }
        if (fd >= 0) {
                if (!fstat(fd, &st)) {
                        if (!S_ISREG(st.st_mode))
@@ -344,7 +349,7 @@ pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
        struct passwd *tpwd, *rpwd;
        int fd, i, debug = 0;
        int retval = PAM_SUCCESS;
-       uid_t systemuser = 499, targetuser = 0, fsuid;
+       uid_t systemuser = 499, targetuser = 0;
 
        /* Parse arguments.  We don't understand many, so no sense in breaking
         * this into a separate function. */
@@ -506,10 +511,11 @@ pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
                          getuid(), getgid(),
                          xauth, "-f", cookiefile, "nlist", display,
                          NULL) == 0) {
-               int save_errno;
 #ifdef WITH_SELINUX
                security_context_t context = NULL;
 #endif
+               PAM_MODUTIL_DEF_PRIVS(privs);
+
                /* Check that we got a cookie.  If not, we get creative. */
                if (((cookie == NULL) || (strlen(cookie) == 0)) &&
                    ((strncmp(display, "localhost:", 10) == 0) ||
@@ -592,8 +598,10 @@ pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
                }
 
                /* Generate a new file to hold the data. */
-               fsuid = setfsuid(tpwd->pw_uid);
-
+               if (pam_modutil_drop_priv(pamh, &privs, tpwd)) {
+                       retval = PAM_SESSION_ERR;
+                       goto cleanup;
+               }
 #ifdef WITH_SELINUX
                if (is_selinux_enabled() > 0) {
                        struct selabel_handle *ctx = selabel_open(SELABEL_CTX_FILE, NULL, 0);
@@ -611,33 +619,24 @@ pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
                                }
                        }
                }
+#endif /* WITH_SELINUX */
                fd = mkstemp(xauthority + sizeof(XAUTHENV));
-               save_errno = errno;
+               if (fd < 0)
+                       pam_syslog(pamh, LOG_ERR,
+                                  "error creating temporary file `%s': %m",
+                                  xauthority + sizeof(XAUTHENV));
+#ifdef WITH_SELINUX
                if (context != NULL) {
                        free(context);
                        setfscreatecon(NULL);
                }
-#else
-               fd = mkstemp(xauthority + sizeof(XAUTHENV));
-               save_errno = errno;
-#endif
-
-               setfsuid(fsuid);
-               if (fd == -1) {
-                       errno = save_errno;
-                       pam_syslog(pamh, LOG_ERR,
-                                  "error creating temporary file `%s': %m",
-                                  xauthority + sizeof(XAUTHENV));
+#endif /* WITH_SELINUX */
+               if (fd >= 0)
+                       close(fd);
+               if (pam_modutil_regain_priv(pamh, &privs) || fd < 0) {
                        retval = PAM_SESSION_ERR;
                        goto cleanup;
                }
-               /* Set permissions on the new file and dispose of the
-                * descriptor. */
-               setfsuid(tpwd->pw_uid);
-               if (fchown(fd, tpwd->pw_uid, tpwd->pw_gid) < 0)
-                 pam_syslog (pamh, LOG_ERR, "fchown: %m");
-               setfsuid(fsuid);
-               close(fd);
 
                /* Get a copy of the filename to save as a data item for
                 * removal at session-close time. */
@@ -736,7 +735,7 @@ pam_sm_close_session (pam_handle_t *pamh, int flags UNUSED,
        const void *data;
        const char *cookiefile;
        struct passwd *tpwd;
-       uid_t fsuid;
+       PAM_MODUTIL_DEF_PRIVS(privs);
 
        /* Try to retrieve the name of a file we created when
         * the session was opened. */
@@ -774,10 +773,12 @@ pam_sm_close_session (pam_handle_t *pamh, int flags UNUSED,
 
        if (debug)
                pam_syslog(pamh, LOG_DEBUG, "removing `%s'", cookiefile);
-       fsuid = setfsuid(tpwd->pw_uid);
+       if (pam_modutil_drop_priv(pamh, &privs, tpwd))
+               return PAM_SESSION_ERR;
        if (unlink(cookiefile) == -1 && errno != ENOENT)
          pam_syslog(pamh, LOG_WARNING, "Couldn't remove `%s': %m", cookiefile);
-       setfsuid(fsuid);
+       if (pam_modutil_regain_priv(pamh, &privs))
+               return PAM_SESSION_ERR;
 
        return PAM_SUCCESS;
 }