Add TRUSTED_CONFIG_PREFIX_FILE option
authorDavid Woodhouse <David.Woodhouse@intel.com>
Sun, 12 Dec 2010 02:41:37 +0000 (02:41 +0000)
committerDavid Woodhouse <David.Woodhouse@intel.com>
Sun, 12 Dec 2010 02:52:02 +0000 (02:52 +0000)
(Bug 1044, CVE-2010-4345)

doc/doc-docbook/spec.xfpt
doc/doc-src/FAQ.src
doc/doc-txt/ChangeLog
src/src/EDITME
src/src/config.h.defaults
src/src/exim.c

index 578485d..bbc3949 100644 (file)
@@ -3334,14 +3334,17 @@ proceeding any further along the list, and an error is generated.
 When this option is used by a caller other than root, and the list is different
 from the compiled-in list, Exim gives up its root privilege immediately, and
 runs with the real and effective uid and gid set to those of the caller.
-
-This behaviour precludes the possibility of testing a configuration using
-&%-C%& right through message reception and delivery, even if the caller is
-root. The reception works, but by that time, Exim is running as the Exim user,
-so when it re-executes to regain privilege for the delivery, the use of &%-C%&
-causes privilege to be lost. However, root can test reception and delivery
-using two separate commands (one to put a message on the queue, using &%-odq%&,
-and another to do the delivery, using &%-M%&).
+However, if a TRUSTED_CONFIG_PREFIX_LIST file is defined in &_Local/Makefile_&,
+root privilege is retained for any configuration file which matches a prefix
+listed in that file.
+
+Leaving TRUSTED_CONFIG_PREFIX_LIST unset precludes the possibility of testing
+a configuration using &%-C%& right through message reception and delivery,
+even if the caller is root. The reception works, but by that time, Exim is
+running as the Exim user, so when it re-executes to regain privilege for the
+delivery, the use of &%-C%& causes privilege to be lost. However, root can
+test reception and delivery using two separate commands (one to put a message
+on the queue, using &%-odq%&, and another to do the delivery, using &%-M%&).
 
 If ALT_CONFIG_PREFIX is defined &_in Local/Makefile_&, it specifies a
 prefix string with which any file named in a &%-C%& command line option
@@ -4525,19 +4528,21 @@ A one-off alternate configuration can be specified by the &%-C%& command line
 option, which may specify a single file or a list of files. However, when
 &%-C%& is used, Exim gives up its root privilege, unless called by root (or
 unless the argument for &%-C%& is identical to the built-in value from
-CONFIGURE_FILE). &%-C%& is useful mainly for checking the syntax of
-configuration files before installing them. No owner or group checks are done
-on a configuration file specified by &%-C%&.
-
-The Exim user is not trusted to specify an arbitrary configuration file with
-the &%-C%& option to be executed with root privileges. This locks out the
-possibility of testing a configuration using &%-C%& right through message
-reception and delivery, even if the caller is root. The reception works, but
-by that time, Exim is running as the Exim user, so when it re-execs to regain
-privilege for the delivery, the use of &%-C%& causes privilege to be lost.
-However, root can test reception and delivery using two separate commands
-(one to put a message on the queue, using &%-odq%&, and another to do the
-delivery, using &%-M%&).
+CONFIGURE_FILE) or matches a prefix listed in the TRUSTED_CONFIG_PREFIX_LIST
+file. &%-C%& is useful mainly for checking the syntax of configuration files
+before installing them. No owner or group checks are done on a configuration
+file specified by &%-C%&, if root privilege has been dropped.
+
+Even the Exim user is not trusted to specify an arbitrary configuration file
+with the &%-C%& option to be used with root privileges, unless that file is
+listed in the TRUSTED_CONFIG_PREFIX_LIST file. This locks out the possibility
+of testing a configuration using &%-C%& right through message reception and
+delivery, even if the caller is root. The reception works, but by that time,
+Exim is running as the Exim user, so when it re-execs to regain privilege for
+the delivery, the use of &%-C%& causes privilege to be lost. However, root
+can test reception and delivery using two separate commands (one to put a
+message on the queue, using &%-odq%&, and another to do the delivery, using
+&%-M%&).
 
 If ALT_CONFIG_PREFIX is defined &_in Local/Makefile_&, it specifies a
 prefix string with which any file named in a &%-C%& command line option must
@@ -33797,7 +33802,9 @@ which only root has access, this guards against someone who has broken
 into the Exim account from running a privileged Exim with an arbitrary
 configuration file, and using it to break into other accounts.
 .next
-If a non-default configuration file is specified with &%-C%&, or macros are
+If a non-trusted configuration file (i.e. the default configuration file or
+one which is trusted by virtue of matching a prefix listed in the
+TRUSTED_CONFIG_PREFIX_LIST file) is specified with &%-C%&, or if macros are
 given with &%-D%&, then root privilege is retained only if the caller of Exim
 is root. This locks out the possibility of testing a configuration using &%-C%&
 right through message reception and delivery, even if the caller is root. The
index b53070e..461b1a6 100644 (file)
@@ -851,7 +851,9 @@ A0044: Exim has been unable to create a file in its spool area in which to
 
        If you are running Exim with an alternate configuration file using a
        command such as \"exim -C altconfig..."\, remember that the use of -C
-       takes away Exim's root privilege.
+       takes away Exim's root privilege, unless \\TRUSTED_CONFIG_PREFIX_FILE\\
+       is set in \(Local/Makefile)\ and the corresponding file contains a
+       prefix which matches the alternative configuration file being used.
 
        Check that you have defined the spool directory correctly by running
 
@@ -1147,25 +1149,17 @@ Q0065: When (as \/root/\) I use -C to run Exim with an alternate configuration
        trying to run an \%autoreply%\ transport. Why is this?
 
 A0065: When Exim is called with -C, it passes on -C to any instances of itself
-       that it calls (so that the whole sequence uses the same config file). If
-       it's running as \/exim/\ when it does this, all is well. However, if it
-       happens as a consequence of a non-privileged user running \%autoreply%\,
-       the called Exim gives up its root privilege. Then it can't write to the
-       spool.
-
-       This means that you can't use -C (even as \/root/\) to run an instance of
-       Exim that is going to try to run \%autoreply%\ from a process that is
-       neither \/root/\ nor \/exim/\. Because of the architecture of Exim (using
-       re-execs to regain privilege), there isn't any way round this
-       restriction. Therefore, the only way you can make this scenario work is
-       to run the \%autoreply%\ transport as \/exim/\ (that is, the user that
-       owns the Exim spool files). This may be satisfactory for autoreplies
-       that are essentially system-generated, but of course is no good for
-       autoreplies from unprivileged users, where you want the \%autoreply%\
-       transport to be run as the user. To get that to work with an alternate
-       configuration, you'll have to use two Exim binaries, with different
-       configuration file names in each. See S001 for a script that patches
-       the configuration name in an Exim binary.
+       that it calls (so that the whole sequence uses the same config file).
+       However, Exim gives up its root privilege if any user except \/root\/
+       passes a -C option to use a non-default configuration file, and that
+       includes the case where Exim re-execs itself to regain root privilege.
+       Thus it can't write to the spool.
+
+       The fix for this is to use the \\TRUSTED_CONFIG_PREFIX_LIST\\ build-time
+       option. This defines a file containing a list of 'trusted' prefixes for
+       configuration files. Any configuration file specified with -C, if it
+       matches a prefix listed in that file, will be used without dropping root
+       privileges (as long as it is not writeable by a non-root user).
 
 
 Q0066: What does the message \*unable to set gid=xxx or uid=xxx*\ mean?
index afc854e..cf30701 100644 (file)
@@ -86,6 +86,9 @@ DW/23 Bugzilla 1044: CVE-2010-4345 - part two: extend checks for writeability
 DW/24 Bugzilla 1044: CVE-2010-4345 - part three: remove ALT_CONFIG_ROOT_ONLY
       option (effectively making it always true).
 
+DW/25 Add TRUSTED_CONFIG_PREFIX_FILE option to allow alternative configuration
+      files to be used while preserving root privileges.
+
 
 Exim version 4.72
 -----------------
index f8ba7cf..1ae1399 100644 (file)
@@ -473,6 +473,19 @@ FIXED_NEVER_USERS=root
 
 
 #------------------------------------------------------------------------------
+# When a user other than root uses the -C option to override the configuration
+# file (including the Exim user when re-executing Exim to regain root
+# privileges for local message delivery), this will normally cause Exim to
+# drop root privileges. The TRUSTED_CONFIG_PREFIX_LIST option, specifies
+# a file which contains a list of trusted configuration prefixes (like the
+# ALT_CONFIG_PREFIX above), one per line. If the -C option is used to specify
+# a configuration file which matches a trusted prefix, root privileges are not
+# dropped by Exim.
+
+# TRUSTED_CONFIG_PREFIX_LIST=/usr/exim/trusted_configs
+
+
+#------------------------------------------------------------------------------
 # Uncommenting this option disables the use of the -D command line option,
 # which changes the values of macros in the runtime configuration file.
 # This is another protection against somebody breaking into the Exim account.
index 9e4556e..d3d7d65 100644 (file)
@@ -13,6 +13,7 @@ in config.h unless some value is defined in Local/Makefile. If there is data,
 it's a default value. */
 
 #define ALT_CONFIG_PREFIX
+#define TRUSTED_CONFIG_PREFIX_LIST
 
 #define APPENDFILE_MODE            0600
 #define APPENDFILE_DIRECTORY_MODE  0700
index 0d9378d..9db61e2 100644 (file)
@@ -1845,10 +1845,103 @@ for (i = 1; i < argc; i++)
           }
         }
       #endif
+      if (real_uid != root_uid)
+        {
+        #ifdef TRUSTED_CONFIG_PREFIX_LIST
+
+       if (Ustrstr(argrest, "/../"))
+          trusted_config = FALSE;
+        else
+          {
+          FILE *trust_list = Ufopen(TRUSTED_CONFIG_PREFIX_LIST, "rb");
+          if (trust_list)
+            {
+            struct stat statbuf;
+
+            if (fstat(fileno(trust_list), &statbuf) != 0 ||
+                (statbuf.st_uid != root_uid        /* owner not root */
+                 #ifdef CONFIGURE_OWNER
+                 && statbuf.st_uid != config_uid   /* owner not the special one */
+                 #endif
+                   ) ||                            /* or */
+                (statbuf.st_gid != root_gid        /* group not root */
+                 #ifdef CONFIGURE_GROUP
+                 && statbuf.st_gid != config_gid   /* group not the special one */
+                 #endif
+                 && (statbuf.st_mode & 020) != 0   /* group writeable */
+                   ) ||                            /* or */
+                (statbuf.st_mode & 2) != 0)        /* world writeable */
+              {
+              trusted_config = FALSE;
+              fclose(trust_list);
+              }
+           else
+              {
+              /* Well, the trust list at least is up to scratch... */
+              void *reset_point = store_get(0);
+              uschar *trusted_prefixes[32];
+              int nr_prefixes = 0;
+              int i = 0;
+
+              while (Ufgets(big_buffer, big_buffer_size, trust_list))
+                {
+                uschar *start = big_buffer, *nl;
+                while (*start && isspace(*start))
+                start++;
+                if (*start == '#')
+                  continue;
+                nl = Ustrchr(start, '\n');
+                if (nl)
+                  *nl = 0;
+                trusted_prefixes[nr_prefixes++] = string_copy(start);
+                if (nr_prefixes == 32)
+                  break;
+                }
+              fclose(trust_list);
+
+              if (nr_prefixes)
+                {
+                int sep = 0;
+                uschar *list = argrest;
+                uschar *filename;
+                while (trusted_config && (filename = string_nextinlist(&list,
+                        &sep, big_buffer, big_buffer_size)) != NULL)
+                  {
+                  for (i=0; i < nr_prefixes; i++)
+                    {
+                    int len = Ustrlen(trusted_prefixes[i]);
+                    if (Ustrlen(filename) >= len &&
+                        Ustrncmp(filename, trusted_prefixes[i], len) == 0)
+                      break;
+                    }
+                  if (i == nr_prefixes)
+                    {
+                    trusted_config = FALSE;
+                    break;
+                    }
+                  }
+                }
+              else
+                {
+                /* No valid prefixes found in trust_list file. */
+                trusted_config = FALSE;
+                }
+              }
+           }
+          else
+            {
+            /* Could not open trust_list file. */
+            trusted_config = FALSE;
+            }
+          }
+      #else
+        /* Not root; don't trust config */
+        trusted_config = FALSE;
+      #endif
+        }
 
       config_main_filelist = argrest;
       config_changed = TRUE;
-      trusted_config = FALSE;
       }
     break;
 
@@ -3241,7 +3334,7 @@ else
 There is a problem if we were running as the Exim user. The sysadmin may
 expect this case to retain privilege because "the binary was called by the
 Exim user", but it hasn't, because either the -D option set macros, or the
--C option set a non-default configuration file. There are two possibilities:
+-C option set a non-trusted configuration file. There are two possibilities:
 
   (1) If deliver_drop_privilege is set, Exim is not going to re-exec in order
       to do message deliveries. Thus, the fact that it is running as a
@@ -3253,8 +3346,8 @@ Exim user", but it hasn't, because either the -D option set macros, or the
 
   (2) If deliver_drop_privilege is not set, the configuration won't work as
       apparently intended, and so we log a panic message. In order to retain
-      root for -C or -D, the caller must be root (when deliver_drop_privilege
-      is false). */
+      root for -C or -D, the caller must either be root or be invoking a
+      trusted configuration file (when deliver_drop_privilege is false). */
 
 if (removed_privilege && (!trusted_config || macros != NULL) &&
     real_uid == exim_uid)