CentOS, SELinux and php mail

Note: This procedure was made up on CentOS 4.4 but the instructions should be similar for Redhat's RHEL 4 and (to a certain extent) Fedora (probably 3+4, maybe later versions).

Symptoms and Diagnosis

I ran into this issue when using Exim 4.50 on CentOS 4.4 together with an old osCommerce-Shop (osC). osC does use PHP's mail() function directly. SMTP mail sending is only possible on Windows (PHP 4.3).

First, I noticed that osC was not able to send mail. Nothing appeared in Exim's log (/var/log/exim/main.log). In /var/log/messages there were several messages like this:

kernel: audit(1167667247.529:28): avc:  denied  { setgid } for  pid=3803 comm="sendmail" capability=6
scontext=root:system_r:httpd_sys_script_t tcontext=root:system_r:httpd_sys_script_t tclass=capability

Fortunately, others had the same problem. In a thread on a CentOS mailing list, pointers were given to the RHEL SElinux Guide which was very helpful in the process.

Solutions

The ideal solution would be to a) compare the policies for sendmail and Exim to see if the deactivation of mail sending for Apache was a wanted security feature (at least this seems to be very handy as insecure CGI scripts are used by crackers to send their SPAM) and b) to fix osC so it uses SMTP mail sending (possible by just using PHPMailer). I'm no SElinux expert so option a) was out of my reach. And I did not choose b) because osC is one of the most bloated and ugly programs I have seen - ever!

Therefore it became clear that a minor policy modification would be enough to build a workaround for the issue. This would lower the system security but this was only my local development system and disabling only a small part of SElinux seemed to be better than just disabled SElinux completely. The goal was to allow setgid() for processes in the httpd_sys_script_t contexts.

Testcase

In order to test the wanted configuration, the following test script was very helpful:

<?php
$to = 'user@domain.invalid';
$from = $to;
$subject = 'PHP testmail';
$message = 'This is a mail sent by a PHP script through /usr/sbin/sendmail';
if (mail($to, $subject, $message, "From: $from"))
echo "Mail sent successfully.";
else
echo "Mail NOT sent! SElinux still not configured correctly";
?>

Building the rule

The rule was built automatically by the very useful tool audit2allow:

$ audit2allow < /var/log/messages
allow httpd_sys_script_t self:capability setgid;
...

Customizing the configuration

To customize a SElinux policy, the sources must be installed. Most people will probably use the "targeted" policy whose source can be installed by

yum install selinux-policy-targeted-sources

The sources are located in "/etc/selinux/targeted/src/policy/" afterwards. Rather than modifying "domains/program/mta.te" directly, the SElinux guide http://www.centos.org/docs/4/html/rhel-selg-en-4/selg-section-0120.html suggested to use domains/misc/local.te because this file won't be overridden after an SElinux policy update.

All following commands assume that your working directory is the policy source directory.

cd /etc/selinux/targeted/src/policy/

First Cycle

So I wrote the needed rule (see above) to domains/misc/local.te:

echo "allow httpd_sys_script_t self:capability setgid;" >> domains/misc/local.te

After that I did:

audit2allow -d -l -o domains/misc/local.te
make load

Then I called my test script again - but no success yet.

Second Cycle

Looking a /var/log/messages brought many new error messages. So the measures above basically worked, but more modifications were necessary.

$ audit2allow < /var/log/messages
allow httpd_sys_script_t console_device_t:chr_file write;
allow httpd_sys_script_t self:capability { dac_override dac_read_search setgid setuid };
allow httpd_sys_script_t self:unix_dgram_socket create;
allow httpd_sys_script_t initrc_var_run_t:file read;
allow httpd_sys_script_t var_log_t:dir search;
...

I decided to fix these first, ignoring others that may be present in /var/log/messages from earlier calls. The procedure was the same as for the first rule.

Called the test script. Again, no success. :-(

Third Cycle

Again some new error messages which seemed to be related with writing Exim's spool files. For now, I will only show the new rules in the output:

$ audit2allow < /var/log/messages
...
allow httpd_sys_script_t self:unix_dgram_socket { connect create };
allow httpd_sys_script_t var_log_t:dir { search write };
allow httpd_sys_script_t var_log_t:file append;
allow httpd_sys_script_t var_spool_t:dir write;
...

After added the new rules, my domains/misc/local.te contained these lines:

allow httpd_sys_script_t console_device_t:chr_file write;
allow httpd_sys_script_t self:capability { dac_override dac_read_search setgid setuid };
allow httpd_sys_script_t self:unix_dgram_socket { connect create };
allow httpd_sys_script_t initrc_var_run_t:file read;
allow httpd_sys_script_t var_log_t:dir { search write };
allow httpd_sys_script_t var_log_t:file append;
allow httpd_sys_script_t var_spool_t:dir write;

Test script, tried again. Again, no success. :-(((

Further Cycles

This circle continued several times. Try to send mail - build additional rules - compile policy - test again. Finally I came up with this configuration:

allow httpd_sys_script_t console_device_t:chr_file write;
allow httpd_sys_script_t devlog_t:sock_file write;
allow httpd_sys_script_t self:capability { dac_override dac_read_search setgid setuid };
allow httpd_sys_script_t self:unix_dgram_socket { connect create };
allow httpd_sys_script_t initrc_var_run_t:file read;
allow httpd_sys_script_t syslogd_t:unix_dgram_socket sendto;
allow httpd_sys_script_t var_log_t:dir { add_name search write };
allow httpd_sys_script_t var_log_t:file { append create getattr };
allow httpd_sys_script_t var_spool_t:dir { add_name read remove_name write };
allow httpd_sys_script_t var_spool_t:file { create getattr lock read rename setattr unlink write };
allow httpd_sys_script_t var_spool_t:dir read;

Conclusion

  1. SElinux adds several constraints on the system and may cause some additional problems.
  2. Minor changes to an existing SElinux policy are not that hard.
  3. Do not use PHP's mail function but a SMTP injection mechanism. This increases the overall system security since the mail sending should use SMTP Auth and the mail administrator can disable a certain login so that a spammy user is stopped. Furthermore this is much more flexible (e.g. no mail server installed on a web server).