Exim - Taint mismatch, string_nextinlist

sparek-3

Well-Known Member
Aug 10, 2002
2,007
222
368
cPanel Access Level
Root Administrator
Was everything vetted properly from Exim 4.93 to Exim 4.94?

I'm seeing a lot of:

Taint mismatch, string_nextinlist: acl_check_condition 3675

in my logs - this SEEMS to happen when I try to send an email to [email protected] - but only on a few of our servers.

I've traced the issue (or at least it would seem) to the section of code in exim.conf:

warn
condition = ${perl{spamd_is_available}}
condition = ${if eq {${acl_m0}}{1}{1}{0}}
spam = ${acl_m1}/defer_ok
# Always make sure cPanel support mail can get through
!hosts = : +trustedmailhosts : +cpanel_mail_netblocks
log_message = "SpamAssassin as ${acl_m1} detected message as spam ($spam_score)"
add_header = X-Spam-Subject: $rh_subject
add_header = X-Spam-Status: Yes, score=$spam_score
add_header = X-Spam-Score: $spam_score_int
add_header = X-Spam-Bar: $spam_bar
add_header = X-Spam-Report: ${sg{$spam_report}{\N\n \n\N}{\n}}
add_header = X-Spam-Flag: YES
set acl_m2 = 1


Specifically the

spam = ${acl_m1}/defer_ok

code - because if I comment this out, the message goes through without issue.

Sending servers are getting a

421 Unexpected failure, please try later

response when trying to send the message.


To see if your server is experiencing, see if these log messages exist in your exim_mainlog

cat /var/log/exim_mainlog | grep "Taint mismatch, string_nextinlist:"


Somewhat related to this ... How does one completely reset the exim.conf to cPanel defaults? The Reset options in the Exim Configuration in the WHM don't do it. I have additional code in various /usr/local/cpanel/etc/exim/acls and the Reset options in the WHM don't clear these out. There should be an option to completely replace /usr/local/cpanel/etc/exim/acls and all Exim customizations back to the cPanel defaults. That way I could reset the Exim configuration to cPanel defaults and see if this Taint mismatch exists with cPanel defaults.

I know a lot of variable declarations and uses for Exim changed from Exim 4.93 to Exim 4.94
 

cPRex

Jurassic Moderator
Staff member
Oct 19, 2014
4,836
597
273
cPanel Access Level
Root Administrator
Hey hey! We've got case CPANEL-36175 open for this issue, but the root of the problem is actually the way that Exim handles string expansions. That code you mention in the additional ACLs area is likely related to the issue.

Here's some thoughts directly from Exim on these changes:


and details about the way Exim handle those strings here:

 

sparek-3

Well-Known Member
Aug 10, 2002
2,007
222
368
cPanel Access Level
Root Administrator
As long as you're aware of it - I guess that rules out that it's some of my exim customizations I have in place (at least until CPANEL-36175 is resolved and I'm still having issues).

The string expansions and tainted data seemed to throw a lot of people between Exim 4.93 and Exim 4.94 - that's why I wonder if cPanel vetted all of their Exim Configuration ACL configurations when the update from Exim 4.93 and Exim 4.94 happened - it would seem that at least some of these were missed.
 

sparek-3

Well-Known Member
Aug 10, 2002
2,007
222
368
cPanel Access Level
Root Administrator
I do see where there's some talk of similar issues on the Exim list from around June 2020 - apparently they pushed out a fix around that time.

Whether or not the cPanel Exim is using the Exim with that fix, I can't tell. Although it looks like Exim was updated to Exim 4.94 in December 2020.

There's also:


which references another fix for a "similar" error dated December 23, 2020.

Perhaps the cPanel Exim needs to be rebuilt against the latest Exim source?
 

sparek-3

Well-Known Member
Aug 10, 2002
2,007
222
368
cPanel Access Level
Root Administrator
It seems cPanel already had a fix, just not deployed fully.

It looks like the line:

set acl_m1 = ${if eq{$domain}{$primary_hostname}{${sg{$local_part}{\N[/+].*\N}{}}}{${lookup{$domain}lsearch{/etc/userdomains}}}}

Just needs to be changed to:

set acl_m1 = ${if eq{$domain}{$primary_hostname}{${perl{untaint}{${sg{$local_part}{\N[/+].*\N}{}}}}}{${lookup{$domain}lsearch{/etc/userdomains}}}}

When trying to think of possible resolutions to this - I thought about making a perl subroutine that just returns the argument that it was passed, hopefully this would satisfy the taintedness of the variable. Then when I was looking at this, I found that cPanel already had an untaint subroutine written (or at least one exists on my servers in /etc/exim.pl.local).

So then just passing this variable through the untaint() subroutine - seems to fix the issue for me.

This raises a couple of questions for me.

1) Is this safe to use? Why isn't cPanel configuring Exim to use this line? Or is that just an oversight on cPanel's part?

2) Where else is tainted $local_part being used in Exim that might create a simliar issue?
 

sparek-3

Well-Known Member
Aug 10, 2002
2,007
222
368
cPanel Access Level
Root Administrator
I suppose if you REALLY want to be proper about it - you should actually do a check in the perl subroutine and insure that the user being passed is actually a user.

Create a new perl subroutine - /usr/local/cpanel/etc/exim/perl/untaint_user_lookup - with:


## Do a getpwnam lookup to insure that the user is a user
sub untaint_user_lookup {
my($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwnam($_[0]);
return ($name ? $name : "nobody");
}


Then rebuild the Exim configuration

/scripts/buildeximconf

Then edit the /etc/exim.conf (probably a more proper way to do this ... you have to edit it after /scripts/buildeximconf because direct changes to /etc/exim.conf have been lost) ... and change

set acl_m1 = ${if eq{$domain}{$primary_hostname}{${sg{$local_part}{\N[/+].*\N}{}}}{${lookup{$domain}lsearch{/etc/userdomains}}}}

to

set acl_m1 = ${if eq{$domain}{$primary_hostname}{${perl{untaint_user_lookup}{${sg{$local_part}{\N[/+].*\N}{}}}}}{${lookup{$domain}lsearch{/etc/userdomains}}}}

This will insure that a correct username gets returned... although, I can't think of a scenario where a correct username would not get returned. Because this variable only gets set if /home/$localpart/.spamassassinenable - exists. But if... for whatever reason... /home/$localpart/.spamassassinenable exists but $localpart is NOT a valid user, then this subroutine will return nobody and will run SpamAssassin as the user nobody for this check.

I suppose this could also get hit if /etc/global_spamassassin_enable exists on the server. The condition statement:

condition = ${if exists{/etc/global_spamassassin_enable}{1}{${if exists{${extract{5}{::}{${lookup passwd{${if eq{$domain}{$primary_hostname}{${sg{$local_part}{\N[/+].*\N}{}}}{${lookup{$domain}lsearch{/etc/userdomains}}}}}}}}/.spamassassinenable}}}}

isn't exactly clear for me. Looks to me like there should be an OR in there some where, or maybe it defaults to an OR if one isn't specified. I guess this line is saying "If /etc/global_spamassassin_enable exists OR (lookup the homedir of $local_part if $domain is $primary_hostname else lookup the homedir of the user attached to $domain from /etc/userdomains, if that homedir/.spamassassinenable exists) - then this condition is true.

(Exim if statements are a PAIN!)

Not sure if this subroutine really gains anything. It just feels empty to pass an argument to a perl subroutine and have it return that argument without any checks. This way the untaint_user_lookup() subroutine is actually insuring that the argument passed is a real user on the server.



The

set acl_m1 = ${if eq{$domain}{$primary_hostname}{${sg{$local_part}{\N[/+].*\N}{}}}{${lookup{$domain}lsearch{/etc/userdomains}}}}

part is failing for [email protected] because no lookup is being done on the $local_part to insure that it's a clean variable. The ${lookup{$domain}lsearch{/etc/userdomains}} passes because you're returning a value based on a lookup. This variable cleanliness requirement was introduced in Exim 4.94.
 

sparek-3

Well-Known Member
Aug 10, 2002
2,007
222
368
cPanel Access Level
Root Administrator
Last one, I think - although I can't promise

You can make this change persistent by replacing default_spam_scan_check with a custom_begin_spam_scan_check with the updated set acl_m1 statement.

All steps:

1) Create the untaint_user_lookup perl subroutine that we'll use to untaint the $local_part:

cat >/usr/local/cpanel/etc/exim/perl/untaint_user_lookup <<EOF

## Do a getpwnam lookup to insure that the user is a user
sub untaint_user_lookup {
my($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwnam($_[0]);
return ($name ? $name : "nobody");
}
EOF


2) Create a new custom_begin_spam_scan_check that essentially copies everything from the default_spam_scan_check block, except with the updated acl_m1. Note, if you happen to already have stuff in a custom_begin_spam_scan_check, then doing this step will overwrite that code.

cat >/usr/local/cpanel/etc/exim/acls/ACL_SPAM_SCAN_CHECK_BLOCK/custom_begin_spam_scan_check <<EOF

# The only problem with this setup is that if the message is for multiple users on the same server
# and they are on different unix accounts, the settings for the first recipient which has spamassassin enabled will be used.
# This shouldn't be a problem 99.9% of the time, however its a very small price to pay for a massive speed increase.

warn
domains = +local_domains
condition = ${if <= {$message_size}{[% ACL_MAX_SPAM_SCAN_SIZE %]K}}
condition = ${if !eq{${acl_m0}}{1}}
condition = ${if exists{/etc/global_spamassassin_enable}{1}{${if exists{${extract{5}{::}{${lookup{${if eq{$domain}{$primary_hostname}{${sg{$local_part}{\N[/+].*\N}{}}}{${lookup{$domain}lsearch{/etc/userdomains}}}}}lsearch{/etc/passwd}}}}/.spamassassinenable}}}}
set acl_m0 = 1
# set acl_m1 = ${if eq{$domain}{$primary_hostname}{${sg{$local_part}{\N[/+].*\N}{}}}{${lookup{$domain}lsearch{/etc/userdomains}}}}
set acl_m1 = ${if eq{$domain}{$primary_hostname}{${perl{untaint_user_lookup}{${sg{$local_part}{\N[/+].*\N}{}}}}}{${lookup{$domain}lsearch{/etc/userdomains}}}}
EOF


3) Disable the default_spam_scan_check block:

sed -i "s/default_spam_scan_check=1/default_spam_scan_check=0/g" /etc/exim.conf.localopts

4) And enable this new custom_begin_spam_scan_check block:

sed -i "s/custom_begin_spam_scan_check=0/custom_begin_spam_scan_check=1/g" /etc/exim.conf.localopts

5) Rebuild the Exim Configuration:

/scripts/buildeximconf

6) And restart Exim:

/scripts/restartsrv_exim
 

cPRex

Jurassic Moderator
Staff member
Oct 19, 2014
4,836
597
273
cPanel Access Level
Root Administrator
I heard back from the team and the devs are working on a similar case, CPANEL-36663, which will also fix this issue as part of their work. I don't have any type of timeline, but it's definitely on their radar.

The support page to follow along for this case is listed here: cPanel
 

sparek-3

Well-Known Member
Aug 10, 2002
2,007
222
368
cPanel Access Level
Root Administrator
So, again, no fix for cPanel 94? Then what's the point of having an TLS release?

I just went ahead and applied my own fix to this on all of our servers. It was quicker than waiting for a cPanel resolution and now seemingly the only resolution unless I want to upgrade to cPanel 96.

This is also why I'm reluctant to come forward with issues I face. It's easier for me to just write my own workaround, rather than explain the issues to cPanel and wait for them to push out a fix and then even longer for that fix to reach the branch of cPanel I'm using.

Like I said in the other thread - cPanel really needs to look at their version/branching system.
 

cPRex

Jurassic Moderator
Staff member
Oct 19, 2014
4,836
597
273
cPanel Access Level
Root Administrator
@sparek-3 - you're a main contributor and I always look forward to hearing from you. Your replies are detailed, well thought out, and usually point me right to the issue I need to check. It's very valuable to get feedback like this.

Just because it hasn't moved to LTS yet doesn't mean it never will, and even simple fixes go through much more testing than I think most realize, so they often aren't quick. I've asked the team if this is something that will get moved to 94, and I'll update once I hear back.
 

sparek-3

Well-Known Member
Aug 10, 2002
2,007
222
368
cPanel Access Level
Root Administrator
I guess that's fair.

I guess I just wonder why nobody already in cPanel 96 never noticed the issue.

I still think there are issues with getting pre-STABLE/LTS releases properly vetted before those releases get sent to STABLE/LTS. But maybe there's more going on behind the scenes than I'm not aware of.