]> granicus.if.org Git - linux-pam/commitdiff
Introduce pam_modutil_sanitize_helper_fds
authorDmitry V. Levin <ldv@altlinux.org>
Fri, 24 Jan 2014 15:32:08 +0000 (15:32 +0000)
committerDmitry V. Levin <ldv@altlinux.org>
Mon, 27 Jan 2014 15:42:11 +0000 (15:42 +0000)
This change introduces pam_modutil_sanitize_helper_fds - a new function
that redirects standard descriptors and closes all other descriptors.

pam_modutil_sanitize_helper_fds supports three types of input and output
redirection:
- PAM_MODUTIL_IGNORE_FD: do not redirect at all.
- PAM_MODUTIL_PIPE_FD: redirect to a pipe.  For stdin, it is implemented
  by creating a pipe, closing its write end, and redirecting stdin to
  its read end.  Likewise, for stdout/stderr it is implemented by
  creating a pipe, closing its read end, and redirecting to its write
  end.  Unlike stdin redirection, stdout/stderr redirection to a pipe
  has a side effect that a process writing to such descriptor should be
  prepared to handle SIGPIPE appropriately.
- PAM_MODUTIL_NULL_FD: redirect to /dev/null.  For stdin, it is
  implemented via PAM_MODUTIL_PIPE_FD because there is no functional
  difference.  For stdout/stderr, it is classic redirection to
  /dev/null.

PAM_MODUTIL_PIPE_FD is usually more suitable due to linux kernel
security restrictions, but when the helper process might be writing to
the corresponding descriptor and termination of the helper process by
SIGPIPE is not desirable, one should choose PAM_MODUTIL_NULL_FD.

* libpam/pam_modutil_sanitize.c: New file.
* libpam/Makefile.am (libpam_la_SOURCES): Add it.
* libpam/include/security/pam_modutil.h (pam_modutil_redirect_fd,
pam_modutil_sanitize_helper_fds): New declarations.
* libpam/libpam.map (LIBPAM_MODUTIL_1.1.9): New interface.
* modules/pam_exec/pam_exec.c (call_exec): Use
pam_modutil_sanitize_helper_fds.
* modules/pam_mkhomedir/pam_mkhomedir.c (create_homedir): Likewise.
* modules/pam_unix/pam_unix_acct.c (_unix_run_verify_binary): Likewise.
* modules/pam_unix/pam_unix_passwd.c (_unix_run_update_binary):
Likewise.
* modules/pam_unix/support.c (_unix_run_helper_binary): Likewise.
* modules/pam_xauth/pam_xauth.c (run_coprocess): Likewise.
* modules/pam_unix/support.h (MAX_FD_NO): Remove.

libpam/Makefile.am
libpam/include/security/pam_modutil.h
libpam/libpam.map
libpam/pam_modutil_sanitize.c [new file with mode: 0644]
modules/pam_exec/pam_exec.c
modules/pam_mkhomedir/pam_mkhomedir.c
modules/pam_unix/pam_unix_acct.c
modules/pam_unix/pam_unix_passwd.c
modules/pam_unix/support.c
modules/pam_unix/support.h
modules/pam_xauth/pam_xauth.c

index 417ca779621710dafac8c516180fd97204e647e4..685a797d557800e1ac8f774ea96ce9e1c66503d9 100644 (file)
@@ -43,4 +43,4 @@ libpam_la_SOURCES = pam_account.c pam_auth.c pam_data.c pam_delay.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_priv.c
+       pam_modutil_priv.c pam_modutil_sanitize.c
index 8087ba158e7b8e86539884057df2d1ed8da18f37..4ce8c5522f5bc3256cedfff5312d1bded4ad10d7 100644 (file)
@@ -129,6 +129,19 @@ extern int PAM_NONNULL((1,2))
 pam_modutil_regain_priv(pam_handle_t *pamh,
                      struct pam_modutil_privs *p);
 
+enum pam_modutil_redirect_fd {
+       PAM_MODUTIL_IGNORE_FD,  /* do not redirect */
+       PAM_MODUTIL_PIPE_FD,    /* redirect to a pipe */
+       PAM_MODUTIL_NULL_FD,    /* redirect to /dev/null */
+};
+
+/* redirect standard descriptors, close all other descriptors. */
+extern int PAM_NONNULL((1))
+pam_modutil_sanitize_helper_fds(pam_handle_t *pamh,
+                               enum pam_modutil_redirect_fd redirect_stdin,
+                               enum pam_modutil_redirect_fd redirect_stdout,
+                               enum pam_modutil_redirect_fd redirect_stderr);
+
 #ifdef __cplusplus
 }
 #endif
index b0885d656177cf1053fb34fefa078a7465f54fbb..d6835b4751b112bfb3fb8e4e0d526b15c0dfd127 100644 (file)
@@ -67,3 +67,8 @@ LIBPAM_MODUTIL_1.1.3 {
     pam_modutil_drop_priv;
     pam_modutil_regain_priv;
 } LIBPAM_MODUTIL_1.1;
+
+LIBPAM_MODUTIL_1.1.9 {
+  global:
+    pam_modutil_sanitize_helper_fds;
+} LIBPAM_MODUTIL_1.1.3;
diff --git a/libpam/pam_modutil_sanitize.c b/libpam/pam_modutil_sanitize.c
new file mode 100644 (file)
index 0000000..65f85d0
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * This file implements the following functions:
+ *   pam_modutil_sanitize_helper_fds:
+ *     redirects standard descriptors, closes all other descriptors.
+ */
+
+#include "pam_modutil_private.h"
+#include <security/pam_ext.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/resource.h>
+
+/*
+ * Creates a pipe, closes its write end, redirects fd to its read end.
+ * Returns fd on success, -1 otherwise.
+ */
+static int
+redirect_in_pipe(pam_handle_t *pamh, int fd, const char *name)
+{
+       int in[2];
+
+       if (pipe(in) < 0) {
+               pam_syslog(pamh, LOG_ERR, "Could not create pipe: %m");
+               return -1;
+       }
+
+       close(in[1]);
+
+       if (in[0] == fd)
+               return fd;
+
+       if (dup2(in[0], fd) != fd) {
+               pam_syslog(pamh, LOG_ERR, "dup2 of %s failed: %m", name);
+               fd = -1;
+       }
+
+       close(in[0]);
+       return fd;
+}
+
+/*
+ * Creates a pipe, closes its read end, redirects fd to its write end.
+ * Returns fd on success, -1 otherwise.
+ */
+static int
+redirect_out_pipe(pam_handle_t *pamh, int fd, const char *name)
+{
+       int out[2];
+
+       if (pipe(out) < 0) {
+               pam_syslog(pamh, LOG_ERR, "Could not create pipe: %m");
+               return -1;
+       }
+
+       close(out[0]);
+
+       if (out[1] == fd)
+               return fd;
+
+       if (dup2(out[1], fd) != fd) {
+               pam_syslog(pamh, LOG_ERR, "dup2 of %s failed: %m", name);
+               fd = -1;
+       }
+
+       close(out[1]);
+       return fd;
+}
+
+/*
+ * Opens /dev/null for writing, redirects fd there.
+ * Returns fd on success, -1 otherwise.
+ */
+static int
+redirect_out_null(pam_handle_t *pamh, int fd, const char *name)
+{
+       int null = open("/dev/null", O_WRONLY);
+
+       if (null < 0) {
+               pam_syslog(pamh, LOG_ERR, "open of %s failed: %m", "/dev/null");
+               return -1;
+       }
+
+       if (null == fd)
+               return fd;
+
+       if (dup2(null, fd) != fd) {
+               pam_syslog(pamh, LOG_ERR, "dup2 of %s failed: %m", name);
+               fd = -1;
+       }
+
+       close(null);
+       return fd;
+}
+
+static int
+redirect_out(pam_handle_t *pamh, enum pam_modutil_redirect_fd mode,
+            int fd, const char *name)
+{
+       switch (mode) {
+               case PAM_MODUTIL_PIPE_FD:
+                       if (redirect_out_pipe(pamh, fd, name) < 0)
+                               return -1;
+                       break;
+               case PAM_MODUTIL_NULL_FD:
+                       if (redirect_out_null(pamh, fd, name) < 0)
+                               return -1;
+                       break;
+               case PAM_MODUTIL_IGNORE_FD:
+                       break;
+       }
+       return fd;
+}
+
+/* Closes all descriptors after stderr. */
+static void
+close_fds(void)
+{
+       /*
+        * An arbitrary upper limit for the maximum file descriptor number
+        * returned by RLIMIT_NOFILE.
+        */
+       const int MAX_FD_NO = 65535;
+
+       /* The lower limit is the same as for _POSIX_OPEN_MAX. */
+       const int MIN_FD_NO = 20;
+
+       int fd;
+       struct rlimit rlim;
+
+       if (getrlimit(RLIMIT_NOFILE, &rlim) || rlim.rlim_max > MAX_FD_NO)
+               fd = MAX_FD_NO;
+       else if (rlim.rlim_max < MIN_FD_NO)
+               fd = MIN_FD_NO;
+       else
+               fd = rlim.rlim_max - 1;
+
+       for (; fd > STDERR_FILENO; --fd)
+               close(fd);
+}
+
+int
+pam_modutil_sanitize_helper_fds(pam_handle_t *pamh,
+                               enum pam_modutil_redirect_fd stdin_mode,
+                               enum pam_modutil_redirect_fd stdout_mode,
+                               enum pam_modutil_redirect_fd stderr_mode)
+{
+       if (stdin_mode != PAM_MODUTIL_IGNORE_FD &&
+           redirect_in_pipe(pamh, STDIN_FILENO, "stdin") < 0) {
+               return -1;
+       }
+
+       if (redirect_out(pamh, stdout_mode, STDOUT_FILENO, "stdout") < 0)
+               return -1;
+
+       /*
+        * If stderr should not be ignored and
+        * redirect mode for stdout and stderr are the same,
+        * optimize by redirecting stderr to stdout.
+        */
+       if (stderr_mode != PAM_MODUTIL_IGNORE_FD &&
+           stdout_mode == stderr_mode) {
+               if (dup2(STDOUT_FILENO, STDERR_FILENO) != STDERR_FILENO) {
+                       pam_syslog(pamh, LOG_ERR,
+                                  "dup2 of %s failed: %m", "stderr");
+                       return -1;
+               }
+       } else {
+               if (redirect_out(pamh, stderr_mode, STDERR_FILENO, "stderr") < 0)
+                       return -1;
+       }
+
+       close_fds();
+       return 0;
+}
index b56e4b26a3676a7737835e81519d5302124a78f9..12c44444c1ab145396ed2e5c3d21fcdc3e59a548 100644 (file)
@@ -302,6 +302,10 @@ call_exec (const char *pam_type, pam_handle_t *pamh,
       char **envlist, **tmp;
       int envlen, nitems;
       char *envstr;
+      enum pam_modutil_redirect_fd redirect_stdin =
+             expose_authtok ? PAM_MODUTIL_IGNORE_FD : PAM_MODUTIL_PIPE_FD;
+      enum pam_modutil_redirect_fd redirect_stdout =
+             (use_stdout || logfile) ? PAM_MODUTIL_IGNORE_FD : PAM_MODUTIL_NULL_FD;
 
       /* First, move all the pipes off of stdin, stdout, and stderr, to ensure
        * that calls to dup2 won't close them. */
@@ -330,18 +334,6 @@ call_exec (const char *pam_type, pam_handle_t *pamh,
              _exit (err);
            }
        }
-      else
-       {
-         close (STDIN_FILENO);
-
-         /* New stdin.  */
-         if ((i = open ("/dev/null", O_RDWR)) < 0)
-           {
-             int err = errno;
-             pam_syslog (pamh, LOG_ERR, "open of /dev/null failed: %m");
-             _exit (err);
-           }
-       }
 
       /* Set up stdout. */
 
@@ -374,26 +366,18 @@ call_exec (const char *pam_type, pam_handle_t *pamh,
              free (buffer);
            }
        }
-      else
-       {
-         close (STDOUT_FILENO);
-         if ((i = open ("/dev/null", O_RDWR)) < 0)
-           {
-             int err = errno;
-             pam_syslog (pamh, LOG_ERR, "open of /dev/null failed: %m");
-             _exit (err);
-           }
-       }
 
-      if (dup2 (STDOUT_FILENO, STDERR_FILENO) == -1)
+      if ((use_stdout || logfile) &&
+         dup2 (STDOUT_FILENO, STDERR_FILENO) == -1)
        {
          int err = errno;
          pam_syslog (pamh, LOG_ERR, "dup2 failed: %m");
          _exit (err);
        }
 
-      for (i = 3; i < sysconf (_SC_OPEN_MAX); i++)
-       close (i);
+      if (pam_modutil_sanitize_helper_fds(pamh, redirect_stdin,
+                                         redirect_stdout, redirect_stdout) < 0)
+       _exit(1);
 
       if (call_setuid)
        if (setuid (geteuid ()) == -1)
index a867a738447f01a03ac8cd3655b5293c8bd0bd5a..c9220897508e821e83fba5ab4c0e1792131b7841 100644 (file)
@@ -58,8 +58,6 @@
 #include <security/pam_modutil.h>
 #include <security/pam_ext.h>
 
-#define MAX_FD_NO 10000
-
 /* argument parsing */
 #define MKHOMEDIR_DEBUG      020       /* be verbose about things */
 #define MKHOMEDIR_QUIET      040       /* keep quiet about things */
@@ -131,18 +129,13 @@ create_homedir (pam_handle_t *pamh, options_t *opt,
    /* fork */
    child = fork();
    if (child == 0) {
-        int i;
-        struct rlimit rlim;
        static char *envp[] = { NULL };
        const char *args[] = { NULL, NULL, NULL, NULL, NULL };
 
-       if (getrlimit(RLIMIT_NOFILE, &rlim)==0) {
-          if (rlim.rlim_max >= MAX_FD_NO)
-                rlim.rlim_max = MAX_FD_NO;
-         for (i=0; i < (int)rlim.rlim_max; i++) {
-               close(i);
-         }
-       }
+       if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_PIPE_FD,
+                                           PAM_MODUTIL_PIPE_FD,
+                                           PAM_MODUTIL_PIPE_FD) < 0)
+               _exit(PAM_SYSTEM_ERR);
 
        /* exec the mkhomedir helper */
        args[0] = MKHOMEDIR_HELPER;
index dc505e738e647df376c58bdefad9fa679d667e59..279984514d82441bf29a06d8ec30cd7cc6600e55 100644 (file)
@@ -98,24 +98,21 @@ int _unix_run_verify_binary(pam_handle_t *pamh, unsigned int ctrl,
   /* fork */
   child = fork();
   if (child == 0) {
-    int i=0;
-    struct rlimit rlim;
     static char *envp[] = { NULL };
     const char *args[] = { NULL, NULL, NULL, NULL };
 
-    /* reopen stdout as pipe */
-    dup2(fds[1], STDOUT_FILENO);
-
     /* XXX - should really tidy up PAM here too */
 
-    if (getrlimit(RLIMIT_NOFILE,&rlim)==0) {
-      if (rlim.rlim_max >= MAX_FD_NO)
-        rlim.rlim_max = MAX_FD_NO;
-      for (i=0; i < (int)rlim.rlim_max; i++) {
-       if (i != STDOUT_FILENO) {
-         close(i);
-       }
-      }
+    /* reopen stdout as pipe */
+    if (dup2(fds[1], STDOUT_FILENO) != STDOUT_FILENO) {
+      pam_syslog(pamh, LOG_ERR, "dup2 of %s failed: %m", "stdout");
+      _exit(PAM_AUTHINFO_UNAVAIL);
+    }
+
+    if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_PIPE_FD,
+                                       PAM_MODUTIL_IGNORE_FD,
+                                       PAM_MODUTIL_PIPE_FD) < 0) {
+      _exit(PAM_AUTHINFO_UNAVAIL);
     }
 
     if (geteuid() == 0) {
index 5f3a3db395fa37a56565c7edf72b428b887a380d..606071eaf9590299f6bbe29be548361aafe38d50 100644 (file)
@@ -201,8 +201,6 @@ static int _unix_run_update_binary(pam_handle_t *pamh, unsigned int ctrl, const
     /* fork */
     child = fork();
     if (child == 0) {
-        int i=0;
-        struct rlimit rlim;
        static char *envp[] = { NULL };
        const char *args[] = { NULL, NULL, NULL, NULL, NULL, NULL };
         char buffer[16];
@@ -210,15 +208,15 @@ static int _unix_run_update_binary(pam_handle_t *pamh, unsigned int ctrl, const
        /* XXX - should really tidy up PAM here too */
 
        /* reopen stdin as pipe */
-       dup2(fds[0], STDIN_FILENO);
-
-       if (getrlimit(RLIMIT_NOFILE,&rlim)==0) {
-         if (rlim.rlim_max >= MAX_FD_NO)
-           rlim.rlim_max = MAX_FD_NO;
-         for (i=0; i < (int)rlim.rlim_max; i++) {
-           if (i != STDIN_FILENO)
-               close(i);
-         }
+       if (dup2(fds[0], STDIN_FILENO) != STDIN_FILENO) {
+               pam_syslog(pamh, LOG_ERR, "dup2 of %s failed: %m", "stdin");
+               _exit(PAM_AUTHINFO_UNAVAIL);
+       }
+
+       if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_IGNORE_FD,
+                                           PAM_MODUTIL_PIPE_FD,
+                                           PAM_MODUTIL_PIPE_FD) < 0) {
+               _exit(PAM_AUTHINFO_UNAVAIL);
        }
 
        /* exec binary helper */
index 3a849c8116263a32e40b61e13e24540dbc906ee9..fdb45c208286b5ebba08ccc44b64a91807819de4 100644 (file)
@@ -564,23 +564,21 @@ static int _unix_run_helper_binary(pam_handle_t *pamh, const char *passwd,
     /* fork */
     child = fork();
     if (child == 0) {
-        int i=0;
-        struct rlimit rlim;
        static char *envp[] = { NULL };
        const char *args[] = { NULL, NULL, NULL, NULL };
 
        /* XXX - should really tidy up PAM here too */
 
        /* reopen stdin as pipe */
-       dup2(fds[0], STDIN_FILENO);
-
-       if (getrlimit(RLIMIT_NOFILE,&rlim)==0) {
-          if (rlim.rlim_max >= MAX_FD_NO)
-                rlim.rlim_max = MAX_FD_NO;
-         for (i=0; i < (int)rlim.rlim_max; i++) {
-               if (i != STDIN_FILENO)
-                 close(i);
-         }
+       if (dup2(fds[0], STDIN_FILENO) != STDIN_FILENO) {
+               pam_syslog(pamh, LOG_ERR, "dup2 of %s failed: %m", "stdin");
+               _exit(PAM_AUTHINFO_UNAVAIL);
+       }
+
+       if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_IGNORE_FD,
+                                           PAM_MODUTIL_PIPE_FD,
+                                           PAM_MODUTIL_PIPE_FD) < 0) {
+               _exit(PAM_AUTHINFO_UNAVAIL);
        }
 
        if (geteuid() == 0) {
index 6f5b2eb628dd3f1812eb2b38c24cd7ebcdd26b9c..cd6ddb76a822cf408c00924035ab67d649d07489 100644 (file)
@@ -141,8 +141,6 @@ static const UNIX_Ctrls unix_args[UNIX_CTRLS_] =
 
 #define UNIX_DEFAULTS  (unix_args[UNIX__NONULL].flag)
 
-#define MAX_FD_NO 2000000
-
 /* use this to free strings. ESPECIALLY password strings */
 
 #define _pam_delete(xx)                \
index c7ce55ab4056ccb3d3ce7ef6de33b78410d063d0..2be4351344484d2ca5b858b58b6bc8ac0f589b6c 100644 (file)
@@ -128,7 +128,6 @@ run_coprocess(pam_handle_t *pamh, const char *input, char **output,
                /* We're the child. */
                size_t j;
                const char *args[10];
-               int maxopened;
                /* Drop privileges. */
                if (setgid(gid) == -1)
                  {
@@ -150,19 +149,26 @@ run_coprocess(pam_handle_t *pamh, const char *input, char **output,
                                (unsigned long) geteuid ());
                    _exit (err);
                  }
-               /* Initialize the argument list. */
-               memset(args, 0, sizeof(args));
                /* Set the pipe descriptors up as stdin and stdout, and close
                 * everything else, including the original values for the
                 * descriptors. */
-               dup2(ipipe[0], STDIN_FILENO);
-               dup2(opipe[1], STDOUT_FILENO);
-               maxopened = (int)sysconf(_SC_OPEN_MAX);
-               for (i = 0; i < maxopened; i++) {
-                       if ((i != STDIN_FILENO) && (i != STDOUT_FILENO)) {
-                               close(i);
-                       }
+               if (dup2(ipipe[0], STDIN_FILENO) != STDIN_FILENO) {
+                   int err = errno;
+                   pam_syslog(pamh, LOG_ERR, "dup2 of %s failed: %m", "stdin");
+                   _exit(err);
                }
+               if (dup2(opipe[1], STDOUT_FILENO) != STDOUT_FILENO) {
+                   int err = errno;
+                   pam_syslog(pamh, LOG_ERR, "dup2 of %s failed: %m", "stdout");
+                   _exit(err);
+               }
+               if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_IGNORE_FD,
+                                                   PAM_MODUTIL_IGNORE_FD,
+                                                   PAM_MODUTIL_NULL_FD) < 0) {
+                   _exit(1);
+               }
+               /* Initialize the argument list. */
+               memset(args, 0, sizeof(args));
                /* Convert the varargs list into a regular array of strings. */
                va_start(ap, command);
                args[0] = command;