Skip to content

Instantly share code, notes, and snippets.

@johnfedoruk
Last active April 29, 2024 11:43
Show Gist options
  • Star 23 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save johnfedoruk/19820540dc096380784c8cf0b7ef333b to your computer and use it in GitHub Desktop.
Save johnfedoruk/19820540dc096380784c8cf0b7ef333b to your computer and use it in GitHub Desktop.
Setting up ClamAV

ClamAV Setup Notes

Context

ClamAV can be used in a few different ways. Most importantly, it provides the ability to scan files in realtime (on-access) or to scan the file system periodically.

I tried configuring ClamAV to both perform on-access virus scanning and to perform nightly full filesystem scanning. Using the on-access option did not prove to be very useful, however a scheduled full system scan seems to be of value.

Here is my story.

Table of Contents

Installing ClamAV

This is pretty straight forward. I installed clamav, clamscan, and clamav-daemon

$ sudo apt-get install clamav clamscan clamav-daemon

On-Access Scanning

ClamAV comes with on-access scanning. I attempted to configure the ClamAV daemon to scan accessed files. My goal was to log and notify potential vulnerabilities. Additionally, ClamAV can be configured to interact with potential vulnerabilities directly - however I would not personally be comfortable with such an implementation on my systems, and have opted to skip this.

The ClamAV daemon does not start on-access scanning automatically. In order to facilitate this, I decided to create a system daemon to run the clamonacc script automatically.

I identified two large issues with on-access scanning:

  • Very verbose logging of access errors (tens of thousands of lines per hour)
  • Notifications are broken

On Access Setup

Update the clamd configuration file. This is located in /etc/clamav/clamd.conf

ExtendedDetectionInfo yes
FixStaleSocket yes
LocalSocket /var/run/clamav/clamd.ctl
LogFile /var/log/clamav/clamav.log
LogFileMaxSize 5M
LogRotate yes
LogTime yes
MaxDirectoryRecursion 15
MaxThreads 20
OnAccessExcludeUname clamav
OnAccessExcludeUname root
OnAccessIncludePath /home
OnAccessMountPath /home/johnfedoruk
OnAccessPrevention yes
User root
VirusEvent /etc/clamav/detected.sh

Add a notifitcation script to /etc/clamav/detected.sh and make sure it's executable with a chmod +x

#!/bin/bash

export LOG="/var/log/clamav/scan.log"
export TARGET="/"
export SUMMARY_FILE=`mktemp`

export SCAN_STATUS
export INFECTED_SUMMARY
export XUSERS

echo "------------ SCAN START ------------" >> "$LOG"
echo "Running scan on `date`" >> "$LOG"

sudo clamdscan --log "$LOG" --infected --multiscan --fdpass "$TARGET" > "$SUMMARY_FILE"

SCAN_STATUS="$?"
INFECTED_SUMMARY=`cat $SUMMARY_FILE | grep Infected`
rm "$SUMMARY_FILE"

if [[ "$SCAN_STATUS" -ne "0" ]] ; then

  # Send the alert to systemd logger if exist
  if [[ -n $(command -v systemd-cat) ]] ; then
    echo "Virus signature found - $INFECTED_SUMMARY" | /usr/bin/systemd-cat -t clamav -p emerg
  fi

  # Send an alert to all graphical users.
  XUSERS=($(who|awk '{print $1$NF}'|sort -u))
  for XUSER in $XUSERS; do
    NAME=(${XUSER/(/ })
    DISPLAY=${NAME[1]/)/}
    DBUS_ADDRESS=unix:path=/run/user/$(id -u ${NAME[0]})/bus
    echo "run $NAME - $DISPLAY - $DBUS_ADDRESS -" >> /tmp/testlog
    /usr/bin/sudo -u ${NAME[0]} DISPLAY=${DISPLAY} \
      DBUS_SESSION_BUS_ADDRESS=${DBUS_ADDRESS} \
      PATH=${PATH} \
      /usr/bin/notify-send -i security-low "Virus signature(s) found" "$INFECTED_SUMMARY"
  done

fi

Now restart the service

$ sudo systemctl restart clamav-daemon.service

And get the status

$ sudo systemctl status clamav-daemon.service
● clamav-daemon.service - Clam AntiVirus userspace daemon
   Loaded: loaded (/lib/systemd/system/clamav-daemon.service; enabled; vendor preset: enabled)
  Drop-In: /etc/systemd/system/clamav-daemon.service.d
           └─extend.conf
   Active: active (running) since Thu 2020-03-26 15:32:22 CDT; 40min ago
     Docs: man:clamd(8)
           man:clamd.conf(5)
           https://www.clamav.net/documents/
  Process: 32756 ExecStartPre=/bin/mkdir /run/clamav (code=exited, status=1/FAILURE)
  Process: 32759 ExecStartPre=/bin/chown clamav /run/clamav (code=exited, status=0/SUCCESS)
 Main PID: 32760 (clamd)
    Tasks: 5 (limit: 4915)
   Memory: 911.8M
   CGroup: /system.slice/clamav-daemon.service
           └─32760 /usr/sbin/clamd --foreground=true

Now add a Systemd configuration. Add it to a new file /etc/systemd/system/clamav-onacc.service.

[Unit]
Description=ClamAV On Access Scanner
Requires=clamav-daemon.service
After=clamav-daemon.service syslog.target network.target

[Service]
Type=simple
User=root
ExecStart=/usr/bin/clamonacc -F
Restart=on-failure
RestartSec=120s

[Install]
WantedBy=multi-user.target

And now enable the new service

$ sudo systemctl enable clamav-onacc.service

Start the new service

$ sudo systemctl start clamav-onacc.service

And get the status

$ sudo systemctl status clamav-onacc.service
● clamav-onacc.service - ClamAV On Access Scanner
   Loaded: loaded (/etc/systemd/system/clamav-onacc.service; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2020-03-26 15:33:25 CDT; 41min ago
 Main PID: 1789 (clamonacc)
    Tasks: 7 (limit: 4915)
   Memory: 11.6M
   CGroup: /system.slice/clamav-onacc.service
           └─1789 /usr/bin/clamonacc -F --log=/var/log/clamav/access.log

On Access Observations

On Access Logging

Logging was configured in /var/log/clamav/clamav.log. We can see that once started, it immediatly starts filling with garbage access errors.

Thu Mar 26 15:33:32 2020 -> /var/snap/microk8s/1247/certs/front-proxy-client.crt: Can't open file or directory ERROR
Thu Mar 26 15:33:32 2020 -> /var/snap/microk8s/1247/certs/front-proxy-client.crt: Can't open file or directory ERROR
Thu Mar 26 15:33:32 2020 -> /var/snap/microk8s/1247/certs/front-proxy-client.crt: Can't open file or directory ERROR
Thu Mar 26 15:33:32 2020 -> /var/snap/microk8s/1247/certs/front-proxy-client.crt: Can't open file or directory ERROR
Thu Mar 26 15:33:36 2020 -> /usr/bin/sudo: Can't open file or directory ERROR
Thu Mar 26 15:33:36 2020 -> /usr/bin/sudo: Can't open file or directory ERROR
Thu Mar 26 15:33:36 2020 -> /usr/share/icons/Pop/scalable/status/network-wireless-signal-good-symbolic.svg: Can't open file or directory ERROR
Thu Mar 26 15:33:36 2020 -> /usr/share/icons/Pop/scalable/status/network-wireless-signal-good-symbolic.svg: Can't open file or directory ERROR
Thu Mar 26 15:33:36 2020 -> /usr/share/icons/Pop/scalable/status/network-wireless-signal-good-symbolic.svg: Can't open file or directory ERROR
Thu Mar 26 15:33:36 2020 -> /usr/share/icons/Pop/scalable/status/network-wireless-signal-good-symbolic.svg: Can't open file or directory ERROR
Thu Mar 26 15:33:37 2020 -> /usr/bin/sudo: Canjohnfedorukstem76-power.conf: Can't open file or directory ERROR
Thu Mar 26 15:33:40 2020 -> /etc/fstab: Can't open file or directory ERROR
Thu Mar 26 15:33:40 2020 -> /etc/fstab: Can't open file or directory ERROR
Thu Mar 26 15:33:50 2020 -> /usr/bin/clamonacc: Can't open file or directory ERROR
Thu Mar 26 15:33:52 2020 -> /usr/bin/clamonacc: Can't open file or directory ERROR
Thu Mar 26 15:33:53 2020 -> /usr/bin/sudo: Can't open file or directory ERROR
Thu Mar 26 15:33:53 2020 -> /usr/bin/sudo: Can't open file or directory ERROR
Thu Mar 26 15:33:59 2020 -> WARNING: lstat() failed on: /home/johnfedoruk/.config/Microsoft/Microsoft Teams/Cache/index-dir/temp-index
Thu Mar 26 15:33:59 2020 -> WARNING: lstat() failed on: /home/johnfedoruk/.config/Microsoft/Microsoft Teams/Cache/index-dir/temp-index

If we cat an eicar test file, we can see that this is found by the anti-virus and logged. However, again, it is burried in garbage output.

Look for the line Thu Mar 26 15:36:50 2020 -> /home/johnfedoruk/Downloads/eicar.com: Win.Test.EICAR_HDB-1(44d88612fea8a8f36de82e1278abb02f:68) FOUND.

Thu Mar 26 15:36:49 2020 -> /usr/share/icons/Pop/scalable/actions/edit-find-symbolic.svg: Can't open file or directory ERROR
Thu Mar 26 15:36:49 2020 -> /usr/bin/cat: Can't open file or directory ERROR
Thu Mar 26 15:36:49 2020 -> /usr/bin/cat: Can't open file or directory ERROR
Thu Mar 26 15:36:49 2020 -> /usr/bin/cat: Can't open file or directory ERROR
Thu Mar 26 15:36:49 2020 -> /home/johnfedoruk/Downloads/eicar.com: Win.Test.EICAR_HDB-1(44d88612fea8a8f36de82e1278abb02f:68) FOUND
Thu Mar 26 15:36:50 2020 -> /usr/bin/cat: Can't open file or directory ERROR
Thu Mar 26 15:36:50 2020 -> /usr/bin/cat: Can't open file or directory ERROR
Thu Mar 26 15:36:50 2020 -> /home/johnfedoruk/Downloads/eicar.com: Win.Test.EICAR_HDB-1(44d88612fea8a8f36de82e1278abb02f:68) FOUND
Thu Mar 26 15:36:50 2020 -> /var/snap/microk8s/1247/args/kube-apiserver: Can't open file or directory ERROR
Thu Mar 26 15:36:58 2020 -> WARNING: lstat() failed on: /home/johnfedoruk/.config/Microsoft/Microsoft Teams/Cache/index-dir/temp-index

The log files quickly fill with thousands upon thousands of errors related to failed access of virtual and otherwise inaccessible files. Running SSDs in RAID 0 on a machine that never shuts down makes me uncomfortable with such verbose logging.

On Access Notifications

No matter what I did, I could not get the clamd.conf VirusEvent directive to work.

Reading the source, it becomes clear that the issue is with line 83 of the code as of version v0.100 (and removed completely in the latest stable versions)

/* TODO : FIXME? virusaction forks. This could be extraordinarily problematic, lead to deadlocks,
* or at the very least lead to extreme memory consumption. Leaving disabled for now.*/
//virusaction(fname, virname, tharg->opts);

While it would be possible to downgrade the ClamAV tool, or to reimplement and recompile the now missing onaccess_fan.c file into the tool - it seems unwise to overlook the developer's warning.

System Scan

ClamAV comes with clamdscan. I attempted to configure ClamAV daemon to scan all filesystem files and generate a report once a day. My goal was to log and notify potential vulnerabilities. Additionally, ClamAV can be configured to interact with potential vulnerabilities directly - however I would not personally be comfortable with such an implementation on my systems, and have opted to skip this.

The ClamAV daemon does not start a filesystem scanning automatically. In order to facilitate this, I decided to create a systemd timer to run the clamdscan script automatically.

System Scan Setup

Update the clamd configuration file. This is located in /etc/clamav/clamd.conf

ExcludePath ^/dev/
ExcludePath ^/proc/
ExcludePath ^/sys/
ExtendedDetectionInfo yes
FixStaleSocket yes
LocalSocket /var/run/clamav/clamd.ctl
LogTime yes
MaxDirectoryRecursion 0
MaxThreads 32
User root

Now restart the service

$ sudo systemctl restart clamav-daemon.service

And get the status

$ sudo systemctl status clamav-daemon.service
● clamav-daemon.service - Clam AntiVirus userspace daemon
   Loaded: loaded (/lib/systemd/system/clamav-daemon.service; enabled; vendor preset: enabled)
  Drop-In: /etc/systemd/system/clamav-daemon.service.d
           └─extend.conf
   Active: active (running) since Thu 2020-03-26 15:32:22 CDT; 40min ago
     Docs: man:clamd(8)
           man:clamd.conf(5)
           https://www.clamav.net/documents/
  Process: 32756 ExecStartPre=/bin/mkdir /run/clamav (code=exited, status=1/FAILURE)
  Process: 32759 ExecStartPre=/bin/chown clamav /run/clamav (code=exited, status=0/SUCCESS)
 Main PID: 32760 (clamd)
    Tasks: 5 (limit: 4915)
   Memory: 911.8M
   CGroup: /system.slice/clamav-daemon.service
           └─32760 /usr/sbin/clamd --foreground=true

Add a scan script to /etc/clamav/scan.sh and make sure it's executable with a chmod +x

#!/bin/bash

export LOG="/var/log/clamav/scan.log"
export TARGET="/"
export SUMMARY_FILE=`mktemp`
export FIFO_DIR=`mktemp -d`
export FIFO="$FIFO_DIR/log"

export SCAN_STATUS
export INFECTED_SUMMARY
export XUSERS

mkfifo "$FIFO"
tail -f "$FIFO" | tee -a "$LOG" "$SUMMARY_FILE" &

echo "------------ SCAN START ------------" > "$FIFO"
echo "Running scan on `date`" > "$FIFO"
echo "Scanning $TARGET" > "$FIFO"
clamdscan --infected --multiscan --fdpass --stdout "$TARGET" | grep -vE 'WARNING|ERROR|^$' > "$FIFO"
echo > "$FIFO"

SCAN_STATUS="${PIPESTATUS[0]}"
INFECTED_SUMMARY=`cat "$SUMMARY_FILE" | grep "Infected files"`

rm "$SUMMARY_FILE"
rm "$FIFO"
rmdir "$FIFO_DIR"

if [[ "$SCAN_STATUS" -ne "0" ]] ; then

  # Send the alert to systemd logger if exist
  if [[ -n $(command -v systemd-cat) ]] ; then
    echo "Virus signature found - $INFECTED_SUMMARY" | /usr/bin/systemd-cat -t clamav -p emerg
  fi

  # Send an alert to all graphical users.
  XUSERS=($(who|awk '{print $1$NF}'|sort -u))
  for XUSER in $XUSERS; do
    NAME=(${XUSER/(/ })
    DISPLAY=${NAME[1]/)/}
    DBUS_ADDRESS=unix:path=/run/user/$(id -u ${NAME[0]})/bus
    echo "run $NAME - $DISPLAY - $DBUS_ADDRESS -" >> /tmp/testlog
    /usr/bin/sudo -u ${NAME[0]} DISPLAY=${DISPLAY} \
      DBUS_SESSION_BUS_ADDRESS=${DBUS_ADDRESS} \
      PATH=${PATH} \
      /usr/bin/notify-send -i security-low "Virus signature(s) found" "$INFECTED_SUMMARY"
  done

fi

Now add a Systemd service and timer configuration.

Add the service to a new file /etc/systemd/system/clamav-scan.service

[Unit]
Description=ClamAV System Scanner
Requires=clamav-daemon.service

[Service]
Type=simple
User=root
ExecStart=/etc/clamav/scan.sh

[Install]
WantedBy=multi-user.target

Add the timer to /etc/systemd/system/clamav-scan.timer

[Unit]
Description=ClamAV System Scanner
Requires=clamav-daemon.service

[Timer]
OnCalendar=
OnCalendar=*-*-* 23:11:00
Persistent=false
Unit=clamav-scan.service

[Install]
WantedBy=timers.target

Enable the timer

$ sudo systemctl enable clamav-scan.timer

Start the timer

$ sudo systemctl start clamav-scan.timer

And verify the status of the timer

$ sudo systemctl status clamav-scan.timer
● clamav-scan.timer - ClamAV System Scanner
   Loaded: loaded (/etc/systemd/system/clamav-scan.timer; enabled; vendor preset: enabled)
   Active: active (elapsed) since Thu 2020-03-26 19:12:05 CDT; 7min ago
  Trigger: n/a

Mar 26 19:12:05 workstation systemd[1]: Started ClamAV System Scanner.

System Scan Observations

System Scan Logging

Viewing the logs for a full system scan, if configured correctly, is very easy. By using systemd timers, we have the benefit of viewing the status of the last scan using systemctl. For example

$ sudo systemctl status clamav-scan.service
● clamav-scan.service - ClamAV System Scanner
   Loaded: loaded (/etc/systemd/system/clamav-scan.service; disabled; vendor preset: enabled)
   Active: inactive (dead) since Thu 2020-03-26 22:17:45 CDT; 9min ago
  Process: 24222 ExecStart=/etc/clamav/scan.sh (code=exited, status=0/SUCCESS)
 Main PID: 24222 (code=exited, status=0/SUCCESS)

Mar 26 23:11:00 workstation scan.sh[24222]: ------------ SCAN START ------------
Mar 26 23:11:00 workstation scan.sh[24222]: Running scan on Thu 26 Mar 2020 10:01:00 PM CDT
Mar 26 23:11:00 workstation scan.sh[24222]: Scanning /
Mar 26 22:17:45 workstation scan.sh[24222]: /home/johnfedoruk/Downloads/eicar.com.txt: Win.Test.EICAR_HDB-1 FOUND
Mar 26 22:17:45 workstation scan.sh[24222]: ----------- SCAN SUMMARY -----------
Mar 26 22:17:45 workstation scan.sh[24222]: Infected files: 15
Mar 26 22:17:45 workstation scan.sh[24222]: Time: 345.011 sec (5 m 45 s)
Mar 26 22:17:45 workstation systemd[1]: clamav-scan.service: Succeeded.

We also have the ability to view the history of the logs stored in /var/log/clamav/scan.log, as configured in our script /etc/clamav/scan.sh

------------ SCAN START ------------
Running scan on Thu 26 Mar 2020 09:45:00 PM CDT
Scanning /
----------- SCAN SUMMARY -----------
Infected files: 0
Time: 330.231 sec (5 m 30 s)

------------ SCAN START ------------
Running scan on Thu 26 Mar 2020 10:01:00 PM CDT
Scanning /
/home/johnfedoruk/Downloads/eicar.com.txt: Win.Test.EICAR_HDB-1 FOUND
----------- SCAN SUMMARY -----------
Infected files: 1
Time: 345.011 sec (5 m 45 s)

Both of these log formats clearly showing any potentially infected files and a concise summary.

System Scan Notifications

When viruses are found, a notification is registered with the GNOME notification center if I am logged into my system. This works exactly as I would expect.

@KoenDG
Copy link

KoenDG commented Nov 28, 2023

Interesting.

Ran the script through shellcheck, here is the result:

#!/bin/bash

export LOG="/var/log/clamav/scan.log"
export TARGET="/"

SUMMARY_FILE=$(mktemp)
export SUMMARY_FILE

export SCAN_STATUS
export INFECTED_SUMMARY
export XUSERS

echo "------------ SCAN START ------------" >> "$LOG"
echo "Running scan on $(date)" >> "$LOG"

sudo clamdscan --log "$LOG" --infected --multiscan --fdpass "$TARGET" | sudo tee "$SUMMARY_FILE"

SCAN_STATUS="$?"
INFECTED_SUMMARY=$(grep Infected "${SUMMARY_FILE}")
rm "$SUMMARY_FILE"

if [[ "$SCAN_STATUS" -ne "0" ]] ; then

  # Send the alert to systemd logger if exist
  if [[ -n $(command -v systemd-cat) ]] ; then
    echo "Virus signature found - $INFECTED_SUMMARY" | /usr/bin/systemd-cat -t clamav -p emerg
  fi

  # Send an alert to all graphical users.
  read -r -a XUSERS <<< "$(who | awk '{print $1$NF}' | sort -u)"
  for XUSER in "${XUSERS[@]}"; do
    NAME=("${XUSER/(/ }")
    DISPLAY=${NAME[1]/)/}
    DBUS_ADDRESS=unix:path=/run/user/$(id -u "${NAME[0]}")/bus
    echo "run ${NAME[0]} - ${DISPLAY} - ${DBUS_ADDRESS} -" >> /tmp/testlog
    /usr/bin/sudo -u "${NAME[0]}" DISPLAY="${DISPLAY}" \
      DBUS_SESSION_BUS_ADDRESS="${DBUS_ADDRESS}" \
      PATH="${PATH}" \
      /usr/bin/notify-send -i security-low "Virus signature(s) found" "${INFECTED_SUMMARY}"
  done

fi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment