Skip to content

SELinux: Dovecot IMAPSieve Spam Training Blocked (sa-learn execmem + labels)

On a Fedora/RHEL mail server with SELinux enforcing, moving a message into your Junk folder (to train spam) can silently fail — Bayes never learns the message, and an AVC-denial notice fires. The cause is that Dovecot's IMAPSieve learn pipeline runs sa-learn in the dovecot_t domain, which lacks several permissions by default.

Symptoms

  • You drag/move a message into Junk (or out of Junk to train ham) and Bayes token counts don't change (sa-learn --dump magic shows the same nspam/nham).
  • One or more selinux-avc-notify (or setroubleshoot) emails after a manual spam-learn session.
  • ausearch -m avc shows dovecot_t denials such as:
    avc: denied { execute } ... name="sa-learn-blocked-spam.sh"  tcontext=...:dovecot_etc_t   tclass=file
    avc: denied { execmem } ... comm="grep"                       scontext=...:dovecot_t       tclass=process
    avc: denied { open }    ... path="/var/log/dovecot-imapsieve-learn.log" tcontext=...:var_log_t tclass=file
    

Why It Happens

The training path is: Dovecot IMAPSieve pipe :copy → a shell wrapper (sa-learn-blocked-spam.sh / sa-learn-unblocked-ham.sh) → /usr/sbin/sa-learn. The wrapper scripts are labeled bin_t, and executing a bin_t file does not cause a domain transition — so sa-learn (and any helper like grep) runs as dovecot_t, the Dovecot service domain. That domain isn't granted everything a Perl-based learner needs:

Denial Needed by Correct fix
execute on the wrapper scripts Dovecot executing the pipe script Label the scripts bin_t (not dovecot_etc_t)
self:process execmem sa-learn / Perl (executable memory) Small local policy module (allow)
open/write on the learn log Wrapper appending its log Label the log dovecot_var_log_t

Two of the three are fixed by relabeling, not by policy allows — keep the policy delta as small as possible.

Fix

1. Label the pipe scripts bin_t

semanage fcontext -a -t bin_t '/usr/lib/dovecot/sieve-pipe(/.*)?\.sh'
restorecon -Rv /usr/lib/dovecot/sieve-pipe
ls -Z /usr/lib/dovecot/sieve-pipe/*.sh   # expect bin_t

This label drifts back to dovecot_etc_t after some updates/relabels

If it reverts, dovecot_t can no longer execute the scripts and training silently stops. Persist the semanage fcontext rule (above) so a restorecon restores bin_t rather than reverting it.

2. Label the learn log dovecot_var_log_t

The base Dovecot policy already lets dovecot_t write files of type dovecot_var_log_t, so relabeling avoids granting the overly-broad var_log_t:file open:

semanage fcontext -a -t dovecot_var_log_t '/var/log/dovecot-imapsieve-learn\.log'
restorecon -v /var/log/dovecot-imapsieve-learn.log

3. Grant execmem with a minimal local module

execmem cannot be fixed by relabeling — it's a process permission. Keep it scoped to exactly this:

cat > /etc/selinux/local/local_dovecot_salearn.te <<'EOF'
module local_dovecot_salearn 1.0;

require {
    type dovecot_t;
    class process execmem;
}

allow dovecot_t self:process execmem;
EOF

cd /etc/selinux/local
checkmodule -M -m -o local_dovecot_salearn.mod local_dovecot_salearn.te
semodule_package -o local_dovecot_salearn.pp -m local_dovecot_salearn.mod
semodule -i local_dovecot_salearn.pp

Why not just audit2allow -M the whole batch?

Piping all accumulated denials through audit2allow produces allow dovecot_t dovecot_etc_t:file execute; and allow dovecot_t var_log_t:file open; too — both far broader than needed. Relabel for those two and reserve the policy module for execmem only.

Verify

# Permissions now present:
sesearch -A -s dovecot_t -c process -p execmem | grep -w dovecot_t
sesearch -A -s dovecot_t -t dovecot_var_log_t -c file -p write

# End-to-end (the real test): move ONE spam message into Junk, then:
sa-learn --dump magic | grep -E 'nspam|nham'      # count should increment
tail -2 /var/log/dovecot-imapsieve-learn.log       # "SPAM learned score=..."
ausearch -m avc -ts recent | grep dovecot_t         # expect none

Don't verify with runcon

Trying to runcon -t dovecot_t -- ...the-script... from a root/unconfined shell triggers a process transition denial (and its own AVC email) — that's a limitation of forcing the domain from outside, not a real failure. Only a genuine client-side Junk move exercises the true path.

  • SELinux: Fixing Fail2ban grep execmem Denial — the same execmem class for a different daemon.
  • SELinux: Fixing Dovecot Mail Spool Context (/var/vmail) — the mailstore-labeling counterpart.