Why does it access /etc/shadow?

why-does-it-access-etcshadow

Sven Vermeulen Tue 30 December 2014

While updating the SELinux policy for the Courier IMAP daemon, I noticed that it (well, the authdaemon that is part of Courier) wanted to access /etc/shadow, which is of course a big no-no. It doesn't take long to know that this is through the PAM support (more specifically, pam_unix.so). But why? After all, pam_unix.so should try to execute unix_chkpwd to verify a password and not read in the shadow file directly (which would require all PAM-aware applications to be granted access to the shadow file).

So I dived into the PAM-Linux sources (yay free software).

In pam_unix_passwd.c, the _unix_run_verify_binary() method is called but only if the get_account_info() method returns PAM_UNIX_RUN_HELPER.

static int _unix_verify_shadow(pam_handle_t *pamh, const char *user, unsigned int ctrl)
{
...
        retval = get_account_info(pamh, user, &pwent, &spent);
...
        if (retval == PAM_UNIX_RUN_HELPER) {
                retval = _unix_run_verify_binary(pamh, ctrl, user, &daysleft);
                if (retval == PAM_AUTH_ERR || retval == PAM_USER_UNKNOWN)
                        return retval;
        }

In passverify.c this method will check the password entry file and, if the entry is a shadow file, will return PAM_UNIX_RUN_HELPER if the current user id is not root, or if SELinux is enabled:

PAMH_ARG_DECL(int get_account_info,
        const char *name, struct passwd **pwd, struct spwd **spwdent)
{
        /* UNIX passwords area */
        *pwd = pam_modutil_getpwnam(pamh, name);        /* Get password file entry... */
        *spwdent = NULL;

        if (*pwd != NULL) {
...
                } else if (is_pwd_shadowed(*pwd)) {
                        /*
                         * ...and shadow password file entry for this user,
                         * if shadowing is enabled
                         */
#ifndef HELPER_COMPILE
                        if (geteuid() || SELINUX_ENABLED)
                                return PAM_UNIX_RUN_HELPER;
#endif

The SELINUX_ENABLED is a C macro defined in the same file:

#ifdef WITH_SELINUX
#include 
#define SELINUX_ENABLED is_selinux_enabled()>0
#else
#define SELINUX_ENABLED 0
#endif

And this is where my "aha" moment came forth: the Courier authdaemon runs as root, so its user id is 0. The geteuid() method will return 0, so the SELINUX_ENABLED macro must return non-zero for the proper path to be followed. A quick check in the audit logs, after disabling dontaudit lines, showed that the Courier IMAPd daemon wants to get the attribute(s) of the security_t file system (on which the SELinux information is exposed). As this was denied, the call to is_selinux_enabled() returns -1 (error) which, through the macro, becomes 0.

So granting selinux_getattr_fs(courier_authdaemon_t) was enough to get it to use the unix_chkpwd binary again.

To fix this properly, we need to grant this to all PAM using applications. There is an interface called auth_use_pam() in the policies, but that isn't used by the Courier policy. Until now, that is ;-)