summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog15
-rw-r--r--libpam/Makefile.am3
-rw-r--r--libpam/include/security/pam_modutil.h24
-rw-r--r--libpam/libpam.map6
-rw-r--r--libpam/pam_modutil_priv.c170
-rw-r--r--modules/pam_env/pam_env.c13
-rw-r--r--modules/pam_mail/pam_mail.c16
-rw-r--r--modules/pam_xauth/pam_xauth.c61
8 files changed, 269 insertions, 39 deletions
diff --git a/ChangeLog b/ChangeLog
index 7473934b..1b8e5999 100644
--- 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.
diff --git a/libpam/Makefile.am b/libpam/Makefile.am
index 57bd8109..57080fcf 100644
--- a/libpam/Makefile.am
+++ b/libpam/Makefile.am
@@ -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
diff --git a/libpam/include/security/pam_modutil.h b/libpam/include/security/pam_modutil.h
index ffdf5ad0..317202de 100644
--- a/libpam/include/security/pam_modutil.h
+++ b/libpam/include/security/pam_modutil.h
@@ -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
diff --git a/libpam/libpam.map b/libpam/libpam.map
index 9d55e84f..b0885d65 100644
--- a/libpam/libpam.map
+++ b/libpam/libpam.map
@@ -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
index 00000000..88094f63
--- /dev/null
+++ b/libpam/pam_modutil_priv.c
@@ -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;
+}
diff --git a/modules/pam_env/pam_env.c b/modules/pam_env/pam_env.c
index 3a9eebea..8ac8ed33 100644
--- a/modules/pam_env/pam_env.c
+++ b/modules/pam_env/pam_env.c
@@ -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;
}
diff --git a/modules/pam_mail/pam_mail.c b/modules/pam_mail/pam_mail.c
index c19cbbe3..f5ba1733 100644
--- a/modules/pam_mail/pam_mail.c
+++ b/modules/pam_mail/pam_mail.c
@@ -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);
diff --git a/modules/pam_xauth/pam_xauth.c b/modules/pam_xauth/pam_xauth.c
index be2a2c7c..a64ae89f 100644
--- a/modules/pam_xauth/pam_xauth.c
+++ b/modules/pam_xauth/pam_xauth.c
@@ -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;
}