summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHeiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>2016-01-28 22:20:33 +0100
committerHeiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>2016-02-18 17:36:56 +0100
commitbc3c7bb7d4aba3e563434e5627fe1f2176aa18c0 (patch)
tree769b3afeefcc0c189967965f00576b756e9e7966 /src
parent382bf15bed5d85fe017dd3b7d117eeb28f219358 (diff)
Fix CVE-2016-1531exim-4_87_RC4
Add keep_environment, add_environment. Change the working directory to "/" during the early startup phase. (cherry picked from commit 2b92b67bfc33efe05e6ff2ea3852731ac2273832) (cherry picked from commit 14b82c8b736c8ed24eda144f57703cb9feac6323) (cherry picked from commit 9ca92d0c6e9c6f161bd8111366c6952d3a9315e2) (cherry picked from commit 0020c6d9ecfd98ed7b2b337ed4f898fdc409784b) (cherry picked from commit e8f96966360ea8867ad6a8b5affda6c37fa4958c) (cherry picked from commit ef6fb807c1e1a665f444f644c60c77269f7c5209)
Diffstat (limited to 'src')
-rw-r--r--src/OS/Makefile-Base2
-rwxr-xr-xsrc/scripts/MakeLinks2
-rw-r--r--src/src/environment.c54
-rw-r--r--src/src/exim.c20
-rw-r--r--src/src/functions.h2
-rw-r--r--src/src/globals.c3
-rw-r--r--src/src/globals.h2
-rw-r--r--src/src/readconf.c36
-rw-r--r--src/src/setenv.c3
-rw-r--r--src/src/string.c11
-rw-r--r--src/src/tls-openssl.c2
11 files changed, 131 insertions, 6 deletions
diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base
index 9bc819dd7..e0bdeecfd 100644
--- a/src/OS/Makefile-Base
+++ b/src/OS/Makefile-Base
@@ -335,6 +335,7 @@ OBJ_EXIM = acl.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \
rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o \
route.o search.o sieve.o smtp_in.o smtp_out.o spool_in.o spool_out.o \
std-crypto.o store.o string.o tls.o tod.o transport.o tree.o verify.o \
+ environment.o \
$(OBJ_LOOKUPS) \
local_scan.o $(EXIM_PERL) $(OBJ_WITH_CONTENT_SCAN) \
$(OBJ_WITH_OLD_DEMIME) $(OBJ_EXPERIMENTAL)
@@ -592,6 +593,7 @@ dns.o: $(HDRS) dns.c
enq.o: $(HDRS) enq.c
exim.o: $(HDRS) exim.c
expand.o: $(HDRS) expand.c
+environment.o: $(HDRS) environment.c
filter.o: $(HDRS) filter.c
filtertest.o: $(HDRS) filtertest.c
globals.o: $(HDRS) globals.c
diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks
index 886214030..9420b11c6 100755
--- a/src/scripts/MakeLinks
+++ b/src/scripts/MakeLinks
@@ -101,7 +101,7 @@ for f in dbfunctions.h dbstuff.h exim.h functions.h globals.h local_scan.h \
exim_dbutil.c exim_lock.c expand.c filter.c filtertest.c globals.c \
header.c host.c ip.c log.c lss.c match.c moan.c parse.c perl.c queue.c \
rda.c readconf.c receive.c retry.c rewrite.c rfc2047.c route.c search.c \
- setenv.c \
+ setenv.c environment.c \
sieve.c smtp_in.c smtp_out.c spool_in.c spool_out.c std-crypto.c store.c \
string.c tls.c tlscert-gnu.c tlscert-openssl.c tls-gnu.c tls-openssl.c \
tod.c transport.c tree.c verify.c version.c dkim.c dkim.h dmarc.c dmarc.h \
diff --git a/src/src/environment.c b/src/src/environment.c
new file mode 100644
index 000000000..aaa84f817
--- /dev/null
+++ b/src/src/environment.c
@@ -0,0 +1,54 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) Heiko Schlittermann 2016
+ * hs@schlittermann.de
+ * See the file NOTICE for conditions of use and distribution.
+ */
+
+#include "exim.h"
+
+/* The cleanup_environment() function is used during the startup phase
+of the Exim process, right after reading the configurations main
+part, before any expansions take place. It retains the environment
+variables we trust (via the keep_environment option) and allows to
+set additional variables (via add_environment).
+
+Returns: TRUE if successful
+ FALSE otherwise
+*/
+
+BOOL
+cleanup_environment()
+{
+if (!keep_environment || *keep_environment == '\0')
+ clearenv();
+else if (Ustrcmp(keep_environment, "*") != 0)
+ {
+ uschar **p;
+ if (environ) for (p = USS environ; *p; /* see below */)
+ {
+ uschar *name = string_copyn(*p, US Ustrchr(*p, '=') - *p);
+
+ if (OK != match_isinlist(name, CUSS &keep_environment,
+ 0, NULL, NULL, MCL_NOEXPAND, FALSE, NULL))
+ if (unsetenv(CS name) < 0) return FALSE;
+ else /* nothing */;
+ else
+ p++;
+
+ store_reset(name);
+ }
+ }
+if (add_environment)
+ {
+ uschar *p;
+ int sep = 0;
+ const uschar* envlist = add_environment;
+ while ((p = string_nextinlist(&envlist, &sep, NULL, 0)))
+ putenv(CS p);
+ }
+
+ return TRUE;
+}
diff --git a/src/src/exim.c b/src/src/exim.c
index 02f2a9a4d..d6f2d4aac 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -3743,8 +3743,19 @@ if (running_in_test_harness) smtputf8_advertise_hosts = NULL;
is a failure. It leaves the configuration file open so that the subsequent
configuration data for delivery can be read if needed. */
+/* To be safe: change the working directory to /. */
+if (Uchdir("/") < 0)
+ {
+ perror("exim: chdir `/': ");
+ exit(EXIT_FAILURE);
+ }
+
readconf_main();
+if (cleanup_environment() == FALSE)
+ log_write(0, LOG_PANIC_DIE, "Can't cleanup environment");
+
+
/* If an action on specific messages is requested, or if a daemon or queue
runner is being started, we need to know if Exim was called by an admin user.
This is the case if the real user is root or exim, or if the real group is
@@ -3907,7 +3918,7 @@ EXIM_TMPDIR by the build scripts.
#ifdef EXIM_TMPDIR
{
uschar **p;
- for (p = USS environ; *p != NULL; p++)
+ if (environ) for (p = USS environ; *p != NULL; p++)
{
if (Ustrncmp(*p, "TMPDIR=", 7) == 0 &&
Ustrcmp(*p+7, EXIM_TMPDIR) != 0)
@@ -3947,10 +3958,10 @@ else
uschar **new;
uschar **newp;
int count = 0;
- while (*p++ != NULL) count++;
+ if (environ) while (*p++ != NULL) count++;
if (envtz == NULL) count++;
newp = new = malloc(sizeof(uschar *) * (count + 1));
- for (p = USS environ; *p != NULL; p++)
+ if (environ) for (p = USS environ; *p != NULL; p++)
{
if (Ustrncmp(*p, "TZ=", 3) == 0) continue;
*newp++ = *p;
@@ -4540,7 +4551,8 @@ if (list_options)
(Ustrcmp(argv[i], "router") == 0 ||
Ustrcmp(argv[i], "transport") == 0 ||
Ustrcmp(argv[i], "authenticator") == 0 ||
- Ustrcmp(argv[i], "macro") == 0))
+ Ustrcmp(argv[i], "macro") == 0 ||
+ Ustrcmp(argv[i], "environment") == 0))
{
readconf_print(argv[i+1], argv[i], flag_n);
i++;
diff --git a/src/src/functions.h b/src/src/functions.h
index 97d95dd3d..a4c791f10 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -108,6 +108,7 @@ extern int check_host(void *, const uschar *, const uschar **, uschar **);
extern uschar **child_exec_exim(int, BOOL, int *, BOOL, int, ...);
extern pid_t child_open_uid(const uschar **, const uschar **, int,
uid_t *, gid_t *, int *, int *, uschar *, BOOL);
+extern BOOL cleanup_environment(void);
extern uschar *cutthrough_finaldot(void);
extern BOOL cutthrough_flush_send(void);
extern BOOL cutthrough_headers_send(void);
@@ -418,6 +419,7 @@ extern uschar *string_append_listele(uschar *, uschar, const uschar *);
extern uschar *string_append_listele_n(uschar *, uschar, const uschar *, unsigned);
extern uschar *string_base62(unsigned long int);
extern uschar *string_cat(uschar *, int *, int *, const uschar *, int);
+extern int string_compare_by_pointer(const uschar **, const uschar **);
extern uschar *string_copy_dnsdomain(uschar *);
extern uschar *string_copy_malloc(const uschar *);
extern uschar *string_copylc(const uschar *);
diff --git a/src/src/globals.c b/src/src/globals.c
index 69f217642..e5bbb8340 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -321,6 +321,7 @@ uschar *acl_wherecodes[] = { US"550", /* RCPT */
BOOL active_local_from_check = FALSE;
BOOL active_local_sender_retain = FALSE;
BOOL accept_8bitmime = TRUE; /* deliberately not RFC compliant */
+uschar *add_environment = NULL;
address_item *addr_duplicate = NULL;
address_item address_defaults = {
@@ -803,6 +804,8 @@ uschar *iterate_item = NULL;
int journal_fd = -1;
+uschar *keep_environment = NULL;
+
int keep_malformed = 4*24*60*60; /* 4 days */
uschar *eldap_dn = NULL;
diff --git a/src/src/globals.h b/src/src/globals.h
index 787de41f0..1919d8c1f 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -156,6 +156,7 @@ extern const uschar **address_expansions[ADDRESS_EXPANSIONS_COUNT];
/* General global variables */
extern BOOL accept_8bitmime; /* Allow *BITMIME incoming */
+extern uschar *add_environment; /* List of environment variables to add */
extern header_line *acl_added_headers; /* Headers added by an ACL */
extern tree_node *acl_anchor; /* Tree of named ACLs */
extern uschar *acl_arg[9]; /* Argument to ACL call */
@@ -516,6 +517,7 @@ extern uschar *iterate_item; /* Item from iterate list */
extern int journal_fd; /* Fd for journal file */
+extern uschar *keep_environment; /* Whitelist for environment variables */
extern int keep_malformed; /* Time to keep malformed messages */
extern uschar *eldap_dn; /* Where LDAP DNs are left */
diff --git a/src/src/readconf.c b/src/src/readconf.c
index 9de064d95..cf5f069e9 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -178,6 +178,7 @@ static optionlist optionlist_config[] = {
{ "acl_smtp_starttls", opt_stringptr, &acl_smtp_starttls },
#endif
{ "acl_smtp_vrfy", opt_stringptr, &acl_smtp_vrfy },
+ { "add_environment", opt_stringptr, &add_environment },
{ "admin_groups", opt_gidlist, &admin_groups },
{ "allow_domain_literals", opt_bool, &allow_domain_literals },
{ "allow_mx_to_ip", opt_bool, &allow_mx_to_ip },
@@ -296,6 +297,7 @@ static optionlist optionlist_config[] = {
{ "ignore_bounce_errors_after", opt_time, &ignore_bounce_errors_after },
{ "ignore_fromline_hosts", opt_stringptr, &ignore_fromline_hosts },
{ "ignore_fromline_local", opt_bool, &ignore_fromline_local },
+ { "keep_environment", opt_stringptr, &keep_environment },
{ "keep_malformed", opt_time, &keep_malformed },
#ifdef LOOKUP_LDAP
{ "ldap_ca_cert_dir", opt_stringptr, &eldap_ca_cert_dir },
@@ -2549,6 +2551,7 @@ second argument is NULL. There are some special values:
+name print a named list item
local_scan print the local_scan options
config print the configuration as it is parsed
+ environment print the used execution environment
If the second argument is not NULL, it must be one of "router", "transport",
"authenticator" or "macro" in which case the first argument identifies the
@@ -2697,6 +2700,25 @@ if (type == NULL)
names_only = TRUE;
}
+ else if (Ustrcmp(name, "environment") == 0)
+ {
+ if (environ)
+ {
+ uschar **p;
+ size_t n;
+ for (p = USS environ; *p; p++) ;
+ n = p - USS environ;
+ qsort(environ, p - USS environ, sizeof(*p), (__compar_fn_t) string_compare_by_pointer);
+
+ for (p = USS environ; *p; p++)
+ {
+ if (no_labels) *(Ustrchr(*p, '=')) = '\0';
+ puts(*p);
+ }
+ }
+ return;
+ }
+
else
{
print_ol(find_option(name, optionlist_config, optionlist_config_size),
@@ -3022,6 +3044,15 @@ const uschar *list = config_main_filelist;
while((filename = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
!= NULL)
{
+
+ /* To avoid confusion: Exim changes to / at the very beginning and
+ * and to $spool_directory later. */
+ if (filename[0] != '/')
+ {
+ fprintf(stderr, "-C %s: only absolute names are allowed\n", filename);
+ exit(EXIT_FAILURE);
+ }
+
/* Cut out all the fancy processing unless specifically wanted */
#if defined(CONFIGURE_FILE_USE_NODE) || defined(CONFIGURE_FILE_USE_EUID)
@@ -3467,6 +3498,11 @@ if (gnutls_require_kx || gnutls_require_mac || gnutls_require_proto)
" gnutls_require_kx, gnutls_require_mac and gnutls_require_protocols"
" are obsolete\n");
#endif /*SUPPORT_TLS*/
+
+if ((!add_environment || *add_environment == '\0') && !keep_environment)
+ log_write(0, LOG_MAIN,
+ "WARNING: purging the environment.\n"
+ " Suggested action: use keep_environment and add_environment.\n");
}
diff --git a/src/src/setenv.c b/src/src/setenv.c
index 6da56d58d..eefbec09b 100644
--- a/src/src/setenv.c
+++ b/src/src/setenv.c
@@ -34,6 +34,9 @@ if (!name)
return -1;
}
+if (!environ)
+ return 0;
+
for (end = name; *end != '=' && *end; ) end++;
len = end - name;
diff --git a/src/src/string.c b/src/src/string.c
index c0dfd04b6..1c4ba121c 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -1707,6 +1707,17 @@ return yield;
#endif /* COMPILE_UTILITY */
+#ifndef COMPILE_UTILITY
+/* qsort(3), currently used to sort the environment variables
+for -bP environment output, needs a function to compare two pointers to string
+pointers. Here it is. */
+
+int
+string_compare_by_pointer(const uschar **a, const uschar **b)
+{
+return Ustrcmp(CUS *a, CUS *b);
+}
+#endif /* COMPILE_UTILITY */
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index 4f02d078f..83594c973 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -897,7 +897,7 @@ bad:
{
extern char ** environ;
uschar ** p;
- for (p = USS environ; *p != NULL; p++)
+ if (environ) for (p = USS environ; *p != NULL; p++)
if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
{
DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n");