In the following post we will work on a rocky Linux server (RHEL).

Table of Contents

Install EPEL Repository

The Rocky Linux base repo does not contain packages for installing Clamv.

dnf install epel-release -y
# update your system if you can
dnf update -y

Install Clamav

dnf install clamav clamd clamav-update

Set SELinux for Clamav

setsebool -P antivirus_can_scan_system 1

freshclam: run Virus database signature update

freshclam

Enable local socket

If you are setting up a simple, local clamd instance, then enable the LocalSocket option in its scan configuration file.

sed -i 's/#LocalSocket \/run/LocalSocket \/run/g' /etc/clamd.d/scan.conf

Create a systemd freshclam service that run freshclam once a day (or more)

You call manually freshclam to update database signatures.

But freshclam can be run in a daemon mode (run in the background) thanks to the following option:

-d, --daemon
Run in a daemon mode. Defaults to 12 checks per day unless otherwise specified by --checks or freshclam.conf.

You can ask the freshclam daemon to update the signature database once a day thanks to:

-c #n, --checks=#n
              Check #n times per day for a new database. #n must be between 1 and 50.

Then you should just execute “freshclam -d -c 1” and you’re done with freshclam. But if you reboot your server the freshclam won’t start automatically. That’s the reason why you need to create a freshclam systemd service.

If you execute the locate freshclam command you’ll find that there is already systemd configuration file for freshclam:

[root@Eclipse ~]# locate freshclam
/etc/freshclam.conf
/usr/bin/freshclam
/usr/lib/systemd/system/clamav-freshclam.service
/usr/lib64/libfreshclam.so.2
/usr/lib64/libfreshclam.so.2.0.2
/usr/share/man/man1/freshclam.1.gz
/usr/share/man/man5/freshclam.conf.5.gz
[root@Eclipse ~]#

vim /usr/lib/systemd/system/clamav-freshclam.service

[Unit]
Description = ClamAV Scanner
After=network-online.target

[Service]
Type = forking
#if you want to update database automatically more than once a day change the number 1  
ExecStart = /usr/bin/freshclam -d -c 1
Restart = on-failure
PrivateTmp =true

[Install]
WantedBy=multi-user.target

Now save the file, and enable your service, and then start your service.

systemctl start clamav-freshclam
systemctl enable clamav-freshclam
systemctl status clamav-freshclam

Start and Enable Clamd Scanner service and test it

Clamd is the scanner service.

systemctl start clamd@scan
systemctl enable clamd@scan
systemctl status clamd@scan

Now you can test it on a file:

clamscan filename

Or on a directory:

clamscan -r directoryname

Important: control resources used by clamd service (CPU, memory and IO usage)

Systemd services use cgroups to limit memory usage of your services: https://www.kernel.org/doc/Documentation/cgroup-v2.txt

https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html

MemoryAccounting

When you set MemoryAccounting, it enables explicitly the memory cgroup controller.

But as specified https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html there is no need to enable explicitly a cgroup controller “for a given unit when this unit has configuration for a given controller. For example, when CPUWeight= is set, the cpu controller will be enabled”.

MemoryAccounting=true => when enabled explicitly or implicitly if you use for instance MemoryHigh configuration, you’ll be able to see statistics about the memory usage of your service (because memory usage is constantly analyzed, this costs some resources, but is negligible).

Usually it’s already enabled via a higher configuration option DefaultMemoryAccounting.

systemctl show clamd@scan -p MemoryCurrent

MemoryHigh

If the memory usage of your server is critical you may consider using memory.max, but in this case the OOM killer is invoked in the cgroup if MemoryMax is exceeded, the clamd will be killed.

In this article, we’ll set MemoryHigh: “Specify the throttling limit on memory usage of the executed processes in this unit. Memory usage may go above the limit if unavoidable, but the processes are heavily slowed down and memory is taken away aggressively in such cases. This is the main mechanism to control memory usage of a unit.”

MemoryHigh=1500M

CPUWeight

“These options control the “cpu.weight” control group attribute. The allowed range is 1 to 10000. Defaults to unset, but the kernel default is 100. The available CPU time is split up among all units within one slice relative to their CPU time weight.”

CPUWeight=50

CPUQuota

“Example: CPUQuota=20% ensures that the executed processes will never get more than 20% CPU time on one CPU.”

CPUQuota=20%

IOWeight

“Set the default overall block I/O weight for the executed processes, if the unified control group hierarchy is used on the system. Takes a single weight value (between 1 and 10000) to set the default block I/O weight. This controls the “io.weight” control group attribute, which defaults to 100.”

“A higher weight means more I/O bandwidth, a lower weight means less.”

We do not want to let clamav a normal IO priority, so we decrease its value:

IOWeight=50

Edit your systemd clamd service configuration

Now your clamd service configuration file looks like this /etc/systemd/system/multi-user.target.wants/clamd@scan.service :

[Unit]
Description = clamd scanner (%i) daemon
Documentation=man:clamd(8) man:clamd.conf(5) https://www.clamav.net/documents/
After = syslog.target nss-lookup.target network.target

[Service]
Type = forking
ExecStart = /usr/sbin/clamd -c /etc/clamd.d/%i.conf
# Reload the database
ExecReload=/bin/kill -USR2 $MAINPID
Restart = on-failure
TimeoutStartSec=420
### ! Here is your cgroup resource control ! ###
MemoryHigh=1500M
CPUWeight=50
CPUQuota=20%
IOWeight=50



[Install]
WantedBy = multi-user.target

On-Access Scanning: real time protection

Instead of manually scan a suspicious file you can enable the On-Access scanning protection in order each time a new file is copied or downloaded it is scanned by clamd.

You can choose to protect your whole filesystem, or only some directories. The last case can be interesting if you develop an application in which users can upload some files.

Activate on-access scanning

We will modify the clamd service options (by editing /etc/clamd.d/scan.conf), then first you need to stop clamd service:

systemctl stop clamd@scan

Now choose which kind of protection you want: https://docs.clamav.net/manual/OnAccess.html#configuration-and-recipes

Here the three use cases given by the official documentation:

  • User needs to watch the entire file system, but blocking malicious access attempts isn’t a concern (notify-only mode)
  • System Administrator needs to watch the home directory of multiple Users, but not all users. Blocking access attempts is un-needed. (notify-only mode)
  • The user needs to protect a single directory non-recursively and ensure all access attempts on malicious files are blocked.

In this blog post we will set the third use case’s configuration: active protection with malicious files blocked.

Edit the configuration file of clamd /etc/clamd.d/scan.conf, and ensure the following lines are present:

ScanOnAccess yes ## only for old versions <= 0.101.x , not needed after 0.101.x
OnAccessExcludeUname clamscan ## versions >= 0.102
OnAccessPrevention yes
OnAccessDisableDDD yes

The user for OnAccessExcludeUname is already specified and is clamscan user but it could be clamav depending on the clamav version you’ve installed. You shouldn’t change the user value, it’s correct by default. This user is the user that executes the clamd service

In /etc/clamd.d/scan.conf you can find the name of the user:

cat /etc/clamd.d/scan.conf | grep User
sed -i 's/#OnAccessExcludeUname clamscan/OnAccessExcludeUname clamscan/g' /etc/clamd.d/scan.conf
sed -i 's/#OnAccessExcludeUname clamav/OnAccessExcludeUname clamscan/g' /etc/clamd.d/scan.conf
sed -i 's/#OnAccessPrevention yes/OnAccessPrevention yes/g' /etc/clamd.d/scan.conf
sed -i 's/#OnAccessDisableDDD yes/OnAccessDisableDDD yes/g' /etc/clamd.d/scan.conf

Choose path you want to automatically scan:

OnAccessIncludePath /path/you/want/to/scan
OnAccessExcludePat /path/you/want/to/scan/safedir
OnAccessExcludePat /path/you/want/to/scan/safedir2

Start clamd service:

systemctl start clamd@scan

Execute clamonacc as a systemd service

If you want clamonacc to be executed after you reboot your linux server, then you configure clamonacc as a systemd service, and then enable the clamonacc service.

[root@Eclipse ~]# locate clamonacc
/usr/lib/systemd/system/clamav-clamonacc.service
/usr/lib/systemd/system/clamonacc.service
/usr/sbin/clamonacc
/usr/share/man/man8/clamonacc.8.gz
[root@Eclipse ~]# ll /usr/lib/systemd/system/clamav-clamonacc.service
-rw-r--r--. 1 root root 519  5 sept. 06:04 /usr/lib/systemd/system/clamav-clamonacc.service
[root@Eclipse ~]# ll /usr/lib/systemd/system/clamonacc.service
lrwxrwxrwx. 1 root root 24  5 sept. 06:07 /usr/lib/systemd/system/clamonacc.service -> clamav-clamonacc.service

Edit the file /usr/lib/systemd/system/clamonacc.service and copy-paste the following lines:

[Unit]
Description=ClamAV On-Access Scanner
Documentation=man:clamonacc(8) man:clamd.conf(5) https://docs.clamav.net/
Requires=clamd@scan.service
After=clamd@scan.service syslog.target network-online.target

[Service]

Type=simple
User=root
ExecStart=/usr/sbin/clamonacc --fdpass -F --config-file=/etc/clamd.d/scan.conf --log=/var/log/clamonacc --move=/tmp/clamav-quarantine
Restart=on-failure
RestartSec=7s

[Install]
WantedBy=multi-user.target

Inside ExecStart you can see the important option –fdpass, without this option you will have permission denied errors on the OnAccessIncludePath. If you more information on this option just do ‘man clamonacc’

The –move option defines where moving the infected files.

mkdir /tmp/clamav-quarantine

systemctl daemon-reload
systemctl start clamonacc
systemctl enable clamonacc
systemctl status clamonacc

Test clamonacc On-Access scanning

Go to the directories that you’ve define earlier in the OnAccessIncludePath configuration.

And then download the eicar.com.txt file, the famous file for safely Antivirus testing:

wget https://secure.eicar.org/eicar.com.txt
vim eicar.com.txt

When you try to edit the eicar.com.txt file with vim, you’ll get a message “eicar.com.txt [Permission denied]” => you are blocked from opening and reading the file. And at this moment the –move=/tmp/clamav-quarantine will be applied: the eicar.com.txt is automatically moved by clamonacc service to the directory /tmp/clamav-quarantine

You can verified the /var/log/clamonacc file:

If you are on a RHEL distro, you can also see the event inside /var/log/messages:

Evaluate ressources used by clamav solution

ps -aux | grep clam | grep -v grep
[root@Eclipse ~]# ps -aux | grep clam | grep -v grep
clamupd+   12126  0.0  0.0  26724  5760 ?        Ss   19:32   0:00 /usr/bin/freshclam -d -c 1
clamscan   22654  0.9  7.4 1583540 1355264 ?     Ssl  19:59   0:21 /usr/sbin/clamd -c /etc/clamd.d/scan.conf
root       29770  0.0  0.0 270324 10624 ?        Ssl  20:17   0:00 /usr/sbin/clamonacc --fdpass -F --config-file=/etc/clamd.d/scan.conf --log=/var/log/clamonacc --move=/tmp/clamav-quarantine

Then you have the process PID implied in clamav solution:

top -p 12126,22654,29770

If you look at the ‘VIRT’ column (The total amount of virtual memory used by the task. It includes all code, data and shared libraries plus pages that have been swapped out and pages that have been mapped but not used) you can see: 26,1 + 1546,4 + 264 = 1836,5 MBytes of RAM

Also don’t forget you have set some kind of boundaries for CPU,RAM and IO (have a look to “control resources used by clamd service (CPU and memory usage)”).

Send notification when an infected file is detected (send an email or a teams notification via Microsoft Workflow)

Clamd service allow us to trigger a shell command line when detecting an infected file. You have to set the VirusEvent inside clamd service configuration file. For instance, edit /etc/clamd.d/scan.conf and set this:

# Execute a command when virus is found.
# Use the following environment variables to identify the file and virus names:
# - $CLAM_VIRUSEVENT_FILENAME
# - $CLAM_VIRUSEVENT_VIRUSNAME
# In the command string, '%v' will also be replaced with the virus name.
# Note: The '%f' filename format character has been disabled and will no longer
# be replaced with the file name, due to command injection security concerns.
# Use the 'CLAM_VIRUSEVENT_FILENAME' environment variable instead.
# For the same reason, you should NOT use the environment variables in the
# command directly, but should use it carefully from your executed script.
# Default: no
#VirusEvent /opt/send_virus_alert_sms.sh
VirusEvent "/usr/local/src/sendMailNotifClamav.sh"

Now inside /usr/local/src directory create the following three files.

Firstly the sendMail.sh file, a generic file to send email with curl, you’ll be able to reuse for other processes if you want:

###########
#!/bin/bash
# Author: https://nicodevlog.com
# This script send an email to an existing smtp server

# Read of parameter
HOST=$1
MAIL_FROM=$2
MAIL_TO=$3
SUBJECT=$4
CONTENT=$5

CLEAN_MAIL_TO=$(echo $MAIL_TO | sed "s/'//g")       # Remove simple quote present in MAIL_TO
ESCAPED_CONTENT=$(echo "$CONTENT" | sed 's/"/\"/g') # Escape double quotes
IFS=';'
for email in $CLEAN_MAIL_TO
do
  curl smtp://${HOST} --mail-from "$MAIL_FROM" --mail-rcpt "$email" -T <(echo -e "From: ${MAIL_FROM}\nTo: ${email}\nContent-type: text/plain;charset=utf-8\nSubject: ${SUBJECT}\n\n${ESCAPED_CONTENT}")
done
IFS=$' \t\n'

Secondly create the sendMailNotifClamav.sh file that is called by VirusEvent:

###########
#!/bin/bash
# Author: https://nicodevlog.com
# This script send an email to an existing smtp server and give information about a virus threat detected
BASEDIR=$(dirname $0)
SUBJECT="Server $HOSTNAME is infected"
CONTENT="The server $HOSTNAME is infected by the file $CLAM_VIRUSEVENT_FILENAME, Virus name: $CLAM_VIRUSEVENT_VIRUSNAME"

# Read of parameter
source $BASEDIR/sendMailNotifClamav.conf

$BASEDIR/sendMail.sh "${SMTP_HOST}" "${MAIL_FROM}" "${MAIL_TO}" "${SUBJECT}" "${CONTENT}"

CLAM_VIRUSEVENT_FILENAME and CLAM_VIRUSEVENT_VIRUSNAME are environment variables

And finally create the sendMailNotifClamav.conf file:

SMTP_HOST='yoursmtpserver.nicodevlog.com'
MAIL_FROM='no-reply@nicodevlog.com'
MAIL_TO='youremail@nicodevlog.com'

This last file is created in order to separate these 3 variables SMTP_HOST, MAIL_FROM, MAIL_TO from the source code. And then it’s easier to automate its deployment with various values (for instance automate its deployment with Ansible).

SELinux: Authorize the curl function to be called by VirusEvent on the port 25. Otherwise you will have an error setroubleshoot visible inside /var/log/messages if you are on RHEL.

Then you create a SELinux policy rule for curl:

ausearch -c "curl" --raw | audit2allow -M my-curl
semodule -X 300 -i my-curl.pp

To send a notification with Teams, it’s the same idea, you just need to create the equivalent of sendEmail.sh for teams, and use Workflow URL (because Teams webhook will be deprecated by the end of the year 2024).

You can have a look to this link from somebody who wrote a script for sending Teams notifications via Microsoft workflow: https://gist.github.com/chusiang/895f6406fbf9285c58ad0a3ace13d025

How to troubleshoot

In case you face some errors, you can activate the –debug option:

vim /etc/systemd/system/multi-user.target.wants/clamd@scan.service and add –debug in ExecStart:

ExecStart = /usr/sbin/clamd --debug -c /etc/clamd.d/%i.conf

Also verify the content of the following log files:

/var/log/messages (under RHEL)

/var/log/clamonacc