Commit 412fd53fb for clamav.net

commit 412fd53fb18c8b9fade3101cb7afe1a6787e6e1f
Author: Val S. <valsnyde@cisco.com>
Date:   Fri Jul 24 18:08:00 2026 -0400

    ClamD: harden VirusEvent virus name expansion (#1765)

    VirusEvent executes the configured command through /bin/sh -c. It previously expanded %v by appending the detection name directly into that command string, matching the older behavior for virus-name substitution.

    Loaded signature names are treated as trusted database content rather than attacker-controlled scanned-file input, so this change is intended as hardening. Still, replacing the shell interpolation avoids a footgun for deployments that load custom signatures and use VirusEvent.

    Match the existing %f behavior by replacing %v with a disabled-feature message instead of the virus name. The virus name remains available to VirusEvent scripts through CLAM_VIRUSEVENT_VIRUSNAME.

    Update the clamd configuration help, sample config, manpage source, and VirusEvent unit test to cover the environment-variable path.

    Credit: Yazdan Soltani

    Credit: Nir Yehoshua

    CLAM-2992

diff --git a/clamd/clamd_others.c b/clamd/clamd_others.c
index 4bb2f20d2..86cb760df 100644
--- a/clamd/clamd_others.c
+++ b/clamd/clamd_others.c
@@ -102,6 +102,7 @@ void virusaction(const char *filename, const char *virname,
 #define VE_VIRUSNAME "CLAM_VIRUSEVENT_VIRUSNAME"

 #define FILENAME_DISABLED_MESSAGE "The filename format character has been disabled due to security concerns, use the 'CLAM_VIRUSEVENT_FILENAME' environment variable instead."
+#define VIRUSNAME_DISABLED_MESSAGE "The virus name format character has been disabled due to security concerns, use the 'CLAM_VIRUSEVENT_VIRUSNAME' environment variable instead."

 void virusaction(const char *filename, const char *virname,
                  const struct optstruct *opts)
@@ -147,7 +148,7 @@ void virusaction(const char *filename, const char *virname,
     }
     len = strlen(opt->strarg);
     buffer_cmd =
-        (char *)calloc(len + v * strlen(virname) + f * strlen(FILENAME_DISABLED_MESSAGE) + 1, sizeof(char));
+        (char *)calloc(len + v * strlen(VIRUSNAME_DISABLED_MESSAGE) + f * strlen(FILENAME_DISABLED_MESSAGE) + 1, sizeof(char));
     if (!buffer_cmd) {
         if (path)
             xfree(env[0]);
@@ -158,8 +159,8 @@ void virusaction(const char *filename, const char *virname,
     }
     for (i = 0, j = 0; i < len; i++) {
         if (i + 1 < len && opt->strarg[i] == '%' && opt->strarg[i + 1] == 'v') {
-            strcat(buffer_cmd, virname);
-            j += strlen(virname);
+            strcat(buffer_cmd, VIRUSNAME_DISABLED_MESSAGE);
+            j += strlen(VIRUSNAME_DISABLED_MESSAGE);
             i++;
         } else if (i + 1 < len && opt->strarg[i] == '%' && opt->strarg[i + 1] == 'f') {
             strcat(buffer_cmd, FILENAME_DISABLED_MESSAGE);
diff --git a/common/optparser.c b/common/optparser.c
index 66217f393..d391a7471 100644
--- a/common/optparser.c
+++ b/common/optparser.c
@@ -369,7 +369,7 @@ const struct clam_option __clam_options[] = {

     {"DisableCache", "disable-cache", 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "This option allows you to disable clamd's caching feature.", "no"},

-    {"VirusEvent", NULL, 0, CLOPT_TYPE_STRING, NULL, -1, NULL, 0, OPT_CLAMD, "Execute a command when virus is found.\nUse the following environment variables to identify the file and virus names:\n- $CLAM_VIRUSEVENT_FILENAME\n- $CLAM_VIRUSEVENT_VIRUSNAME\nIn the command string, '%v' will also be replaced with the virus name.\nNote: The '%f' filename format character has been disabled and will no longer\nbe replaced with the file name, due to command injection security concerns.\nUse the 'CLAM_VIRUSEVENT_FILENAME' environment variable instead.\nFor the same reason, you should NOT use the environment variables in the\ncommand directly, but should use it carefully from your executed script.", "/opt/send_virus_alert_sms.sh"},
+    {"VirusEvent", NULL, 0, CLOPT_TYPE_STRING, NULL, -1, NULL, 0, OPT_CLAMD, "Execute a command when virus is found.\nUse the following environment variables to identify the file and virus names:\n- $CLAM_VIRUSEVENT_FILENAME\n- $CLAM_VIRUSEVENT_VIRUSNAME\nNote: The '%v' virus name and '%f' filename format characters have been\ndisabled and will no longer be replaced with the virus or file name, due to\ncommand injection security concerns. Use the environment variables listed\nabove instead.\nFor the same reason, you should NOT use the environment variables directly\nin the command, but should use them carefully from your executed script.", "/opt/send_virus_alert_sms.sh"},

     {"ExitOnOOM", NULL, 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 0, NULL, 0, OPT_CLAMD, "Stop the daemon when libclamav reports an out of memory condition.", "yes"},

diff --git a/docs/man/clamd.conf.5.in b/docs/man/clamd.conf.5.in
index 8e28ff96d..8fc53f1dd 100644
--- a/docs/man/clamd.conf.5.in
+++ b/docs/man/clamd.conf.5.in
@@ -294,12 +294,12 @@ 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.
+Note: The '%v' virus name and '%f' filename format characters have been
+disabled and will no longer be replaced with the virus or file name, due to
+command injection security concerns. Use the environment variables listed
+above instead.
+For the same reason, you should NOT use the environment variables directly
+in the command, but should use them carefully from your executed script.
 \fR
 .br
 Default: disabled
diff --git a/etc/clamd.conf.sample b/etc/clamd.conf.sample
index 116822c4d..4545c5e24 100644
--- a/etc/clamd.conf.sample
+++ b/etc/clamd.conf.sample
@@ -260,12 +260,12 @@ Example
 # 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.
+# Note: The '%v' virus name and '%f' filename format characters have been
+# disabled and will no longer be replaced with the virus or file name, due to
+# command injection security concerns. Use the environment variables listed
+# above instead.
+# For the same reason, you should NOT use the environment variables directly
+# in the command, but should use them carefully from your executed script.
 # Default: no
 #VirusEvent /opt/send_virus_alert_sms.sh

diff --git a/unit_tests/clamd_test.py b/unit_tests/clamd_test.py
index fc06a468e..fa90afdc0 100644
--- a/unit_tests/clamd_test.py
+++ b/unit_tests/clamd_test.py
@@ -675,7 +675,7 @@ class TC(testcase.TestCase):
         self.log.info('verifying log output from virusaction-test.sh: {}'.format(str(TC.path_tmp / 'test-clamd.log')))
         self.verify_log(str(TC.path_tmp / 'test-clamd.log'),
             expected=['Virus found: ClamAV-Test-File.UNOFFICIAL'],
-            unexpected=['VirusEvent incorrect', 'VirusName incorrect'])
+            unexpected=['VirusEvent incorrect', 'VirusName incorrect', 'VirusName argument incorrect'])

     def test_clamd_09_clamdscan_ExcludePath(self):
         '''
diff --git a/unit_tests/input/virusaction-test.sh b/unit_tests/input/virusaction-test.sh
index ff69fbea9..eee96aa27 100755
--- a/unit_tests/input/virusaction-test.sh
+++ b/unit_tests/input/virusaction-test.sh
@@ -1,10 +1,14 @@
 #!/bin/sh
-if test ! `basename $CLAM_VIRUSEVENT_FILENAME` = "clam.exe"; then
-	echo "VirusEvent incorrect: $CLAM_VIRUSEVENT_FILENAME" >$1/test-clamd.log
+if test ! "$(basename "$CLAM_VIRUSEVENT_FILENAME")" = "clam.exe"; then
+	echo "VirusEvent incorrect: $CLAM_VIRUSEVENT_FILENAME" >"$1/test-clamd.log"
 	exit 1
 fi
 if test ! "x$CLAM_VIRUSEVENT_VIRUSNAME" = "xClamAV-Test-File.UNOFFICIAL"; then
-	echo "VirusName incorrect: $CLAM_VIRUSEVENT_VIRUSNAME" >$1/test-clamd.log
+	echo "VirusName incorrect: $CLAM_VIRUSEVENT_VIRUSNAME" >"$1/test-clamd.log"
 	exit 2
 fi
-echo $2 >$1/test-clamd.log
+if test ! "x$2" = "xVirus found: The virus name format character has been disabled due to security concerns, use the 'CLAM_VIRUSEVENT_VIRUSNAME' environment variable instead."; then
+	echo "VirusName argument incorrect: $2" >"$1/test-clamd.log"
+	exit 3
+fi
+echo "Virus found: $CLAM_VIRUSEVENT_VIRUSNAME" >"$1/test-clamd.log"