Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package clamav for openSUSE:Factory checked in at 2025-07-06 17:07:59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/clamav (Old) and /work/SRC/openSUSE:Factory/.clamav.new.1903 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "clamav" Sun Jul 6 17:07:59 2025 rev:135 rq:1290236 version:1.4.3 Changes: -------- --- /work/SRC/openSUSE:Factory/clamav/clamav.changes 2025-06-20 16:54:27.856813241 +0200 +++ /work/SRC/openSUSE:Factory/.clamav.new.1903/clamav.changes 2025-07-06 17:10:46.934086719 +0200 @@ -1,0 +2,6 @@ +Mon Jun 30 16:13:30 UTC 2025 - Reinhard Max <m...@suse.com> + +- bsc#1240363, clamav-disable-administrative-commands.patch: clamd: + Add an option to toggle SHUTDOWN, RELOAD, STATS and VERSION. + +------------------------------------------------------------------- New: ---- clamav-disable-administrative-commands.patch ----------(New B)---------- New: - bsc#1240363, clamav-disable-administrative-commands.patch: clamd: Add an option to toggle SHUTDOWN, RELOAD, STATS and VERSION. ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ clamav.spec ++++++ --- /var/tmp/diff_new_pack.CWe8MA/_old 2025-07-06 17:10:48.634157065 +0200 +++ /var/tmp/diff_new_pack.CWe8MA/_new 2025-07-06 17:10:48.634157065 +0200 @@ -53,6 +53,7 @@ Source65: system-user-vscan.conf Patch1: clamav-conf.patch Patch2: clamav-freshclam_test.patch +Patch3: clamav-disable-administrative-commands.patch Patch5: clamav-obsolete-config.patch Patch12: clamav-fips.patch Patch14: clamav-document-maxsize.patch @@ -184,6 +185,7 @@ %setup -q %patch -P 1 %patch -P 2 +%patch -P 3 %patch -P 5 %patch -P 12 %patch -P 14 ++++++ clamav-disable-administrative-commands.patch ++++++ >From a3be0d2d452023e367f3a5425f88179c732a50cd Mon Sep 17 00:00:00 2001 From: ChaoticByte <j...@keemail.me> Date: Wed, 4 Jun 2025 16:47:57 +0200 Subject: [PATCH] clamd: Add options to toggle SHUTDOWN, RELOAD, STATS and VERSION (#1502) The `clamd` protocol lacks authentication or authorization controls needed to limit access to more administrative commands. Depending on your use case, disabling some commands like `SHUTDOWN` may improve the security of the scanning daemon. This commit adds options to enable/disable the `SHUTDOWN`, `RELOAD`, `STATS` and `VERSION` commands in `clamd.conf`. When a client sends one of the following commands but it is disabled, `clamd` will respond with "COMMAND UNAVAILABLE". The new `clamd.conf` options are: - `EnableShutdownCommand`: Enable the `SHUTDOWN` command. Setting this to no prevents a client to stop `clamd` via the protocol. Default: yes - `EnableReloadCommand` Enable the `RELOAD` command. Setting this to no prevents a client to reload the database. This disables Freshclam's `NotifyClamd` option. `clamd` monitors for database directory changes, so this should Default: yes - `EnableStatsCommand` Enable the `STATS` command. Setting this to no prevents a client from querying statistics. This disables the `clamdtop` program. Default: yes - `EnableVersionCommand` Enable the `VERSION` command. Setting this to no prevents a client from querying version information. This disables the `clamdtop` program and will cause `clamdscan` to display a warning when using the `--version` option. Default: yes Resolves: https://github.com/Cisco-Talos/clamav/issues/922 Resolves: https://github.com/Cisco-Talos/clamav/issues/1169 Related: https://github.com/Cisco-Talos/clamav/pull/347 --- clamd/session.c | 47 +++++++++++++++++++-------- clamdscan/client.c | 9 +++++ clamdtop/clamdtop.c | 32 ++++++++++++++++-- common/optparser.c | 8 +++++ docs/man/clamd.conf.5.in | 28 ++++++++++++++++ etc/clamd.conf.sample | 25 ++++++++++++++ freshclam/notify.c | 11 +++++++ win32/conf_examples/clamd.conf.sample | 25 ++++++++++++++ 8 files changed, 169 insertions(+), 16 deletions(-) --- clamd/session.c.orig +++ clamd/session.c @@ -551,17 +551,24 @@ int execute_or_dispatch_command(client_c switch (cmd) { case COMMAND_SHUTDOWN: - pthread_mutex_lock(&exit_mutex); - progexit = 1; - pthread_mutex_unlock(&exit_mutex); + if (optget(conn->opts, "EnableShutdownCommand")->enabled) { + pthread_mutex_lock(&exit_mutex); + progexit = 1; + pthread_mutex_unlock(&exit_mutex); + } else { + conn_reply_single(conn, NULL, "COMMAND UNAVAILABLE"); + } return 1; case COMMAND_RELOAD: - pthread_mutex_lock(&reload_mutex); - reload = 1; - pthread_mutex_unlock(&reload_mutex); - mdprintf(desc, "RELOADING%c", term); - /* we set reload flag, and we'll reload before closing the - * connection */ + if (optget(conn->opts, "EnableReloadCommand")->enabled) { + pthread_mutex_lock(&reload_mutex); + reload = 1; + pthread_mutex_unlock(&reload_mutex); + mdprintf(desc, "RELOADING%c", term); + /* we set reload flag, and we'll reload before closing the connection */ + } else { + conn_reply_single(conn, NULL, "COMMAND UNAVAILABLE"); + } return 1; case COMMAND_PING: if (conn->group) @@ -570,10 +577,15 @@ int execute_or_dispatch_command(client_c mdprintf(desc, "PONG%c", term); return conn->group ? 0 : 1; case COMMAND_VERSION: { - if (conn->group) - mdprintf(desc, "%u: ", conn->id); - print_ver(desc, conn->term, engine); - return conn->group ? 0 : 1; + if (optget(conn->opts, "EnableVersionCommand")->enabled) { + if (conn->group) + mdprintf(desc, "%u: ", conn->id); + print_ver(desc, conn->term, engine); + return conn->group ? 0 : 1; + } else { + conn_reply_single(conn, NULL, "COMMAND UNAVAILABLE"); + return 1; + } } case COMMAND_COMMANDS: { if (conn->group) @@ -598,9 +610,16 @@ int execute_or_dispatch_command(client_c conn->mode = MODE_STREAM; return 0; } + case COMMAND_STATS: { + if (optget(conn->opts, "EnableStatsCommand")->enabled) { + return dispatch_command(conn, cmd, argument); + } else { + conn_reply_single(conn, NULL, "COMMAND UNAVAILABLE"); + return 1; + } + } case COMMAND_MULTISCAN: case COMMAND_CONTSCAN: - case COMMAND_STATS: case COMMAND_FILDES: case COMMAND_SCAN: case COMMAND_INSTREAMSCAN: --- clamdscan/client.c.orig +++ clamdscan/client.c @@ -357,6 +357,15 @@ int get_clamd_version(const struct optst logg(LOGG_ERROR, "Error occurred while receiving version information.\n"); break; } + + /* Check if the response was "COMMAND UNAVAILABLE", which means that + clamd has the VERSION command disabled. */ + if (len >= 19 && memcmp(buff, "COMMAND UNAVAILABLE", 19) == 0) { + logg(LOGG_WARNING, "VERSION command disabled in clamd, printing the local version.\n"); + closesocket(sockd); + return 2; + } + printf("%s\n", buff); } --- clamdtop/clamdtop.c.orig +++ clamdtop/clamdtop.c @@ -110,6 +110,7 @@ static void cleanup(void); static int send_string_noreconn(conn_t *conn, const char *cmd); static void send_string(conn_t *conn, const char *cmd); static int read_version(conn_t *conn); +static int check_stats_available(conn_t *conn); char *get_ip(const char *ip); char *get_port(const char *ip); char *make_ip(const char *host, const char *port); @@ -790,6 +791,7 @@ done: static int make_connection(const char *soname, conn_t *conn) { int rc; + int rv; if (!soname) { return -1; @@ -801,8 +803,20 @@ static int make_connection(const char *s send_string(conn, "nIDSESSION\nnVERSION\n"); free(conn->version); conn->version = NULL; - if (!read_version(conn)) - return 0; + + rv = read_version(conn); + if (rv == -3) { + print_con_info(conn, "VERSION command unavailable, consider enabling it in the clamd configuration.\n"); + EXIT_PROGRAM(FAIL_INITIAL_CONN); + } else if (!rv) { + // check if STATS command is available + if (check_stats_available(conn)) { + return 0; + } else { + print_con_info(conn, "STATS command unavailable, consider enabling it in the clamd configuration.\n"); + EXIT_PROGRAM(FAIL_INITIAL_CONN); + } + } /* clamd < 0.95 */ if ((rc = make_connection_real(soname, conn))) @@ -1328,6 +1342,9 @@ static int read_version(conn_t *conn) return -1; if (!strcmp(buf, "UNKNOWN COMMAND\n")) return -2; + // check if VERSION command is available + if (!strcmp(strchr(buf, ':'), ": COMMAND UNAVAILABLE\n")) + return -3; conn->version = strdup(buf); OOM_CHECK(conn->version); @@ -1337,6 +1354,17 @@ static int read_version(conn_t *conn) return 0; } +static int check_stats_available(conn_t *conn) +{ + char buf[1024]; + send_string(conn, "nSTATS\n"); + if (!recv_line(conn, buf, sizeof(buf))) + return 0; + if (!strcmp(strchr(buf, ':'), ": COMMAND UNAVAILABLE\n")) + return 0; + return 1; +} + static void sigint(int a) { UNUSEDPARAM(a); --- common/optparser.c.orig +++ common/optparser.c @@ -296,6 +296,14 @@ const struct clam_option __clam_options[ {"TCPSocket", NULL, 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, -1, NULL, 0, OPT_CLAMD, "A TCP port number the daemon will listen on.", "3310"}, + {"EnableShutdownCommand", NULL, 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 1, NULL, 0, OPT_CLAMD, "Enables the SHUTDOWN command for clamd", "no"}, + + {"EnableReloadCommand", NULL, 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 1, NULL, 0, OPT_CLAMD, "Enables the RELOAD command for clamd", "no"}, + + {"EnableVersionCommand", NULL, 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 1, NULL, 0, OPT_CLAMD, "Enables the VERSION command for clamd", "yes"}, + + {"EnableStatsCommand", NULL, 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 1, NULL, 0, OPT_CLAMD, "Enables the STATS command for clamd", "yes"}, + /* FIXME: add a regex for IP addr */ {"TCPAddr", NULL, 0, CLOPT_TYPE_STRING, NULL, -1, NULL, FLAG_MULTIPLE, OPT_CLAMD, "By default clamd binds to INADDR_ANY.\nThis option allows you to restrict the TCP address and provide\nsome degree of protection from the outside world.", "localhost"}, --- docs/man/clamd.conf.5.in.orig +++ docs/man/clamd.conf.5.in @@ -139,6 +139,34 @@ This option allows you to restrict the T .br Default: disabled .TP +\fBEnableShutdownCommand BOOL\fR +Enables the SHUTDOWN command. Setting this to no prevents a client to stop clamd via the protocol. +.br +When disabled, clamd responds to this command with COMMAND UNAVAILABLE. +.br +Default: yes +.TP +\fBEnableReloadCommand BOOL\fR +Enables the RELOAD command. Setting this to no prevents a client to reload the database. +.br +When disabled, clamd responds to this command with COMMAND UNAVAILABLE. +.br +Default: yes +.TP +\fBEnableVersionCommand BOOL\fR +Enables the VERSION command. Setting this to no prevents a client from querying version information. +.br +When disabled, clamd responds to this command with COMMAND UNAVAILABLE. +.br +Default: yes +.TP +\fBEnableStatsCommand BOOL\fR +Enables the STATS command. Setting this to no prevents a client from querying statistics. +.br +When disabled, clamd responds to this command with COMMAND UNAVAILABLE. +.br +Default: yes +.TP \fBMaxConnectionQueueLength NUMBER\fR Maximum length the queue of pending connections may grow to. .br --- etc/clamd.conf.sample.orig +++ etc/clamd.conf.sample @@ -121,6 +121,31 @@ LocalSocket /run/clamav/clamd.sock # Default: no #TCPAddr localhost +# Enable or disable certain commands. +# Disabling some commands like SHUTDOWN may improve the security of the daemon. +# When a client sends one of the following commands but it is disabled, +# clamd responds with COMMAND UNAVAILABLE. +# +# Enable the SHUTDOWN command. +# Setting this to no prevents a client to stop clamd via the protocol. +# Default: yes +#EnableShutdownCommand no +# +# Enable the RELOAD command +# Setting this to no prevents a client to reload the database. +# Default: yes +#EnableReloadCommand no +# +# Enable the STATS command +# Setting this to no prevents a client from querying statistics. +# Default: yes +#EnableStatsCommand no +# +# Enable the VERSION command +# Setting this to no prevents a client from querying version information. +# Default: yes +#EnableVersionCommand no + # Maximum length the queue of pending connections may grow to. # Default: 200 #MaxConnectionQueueLength 30 --- freshclam/notify.c.orig +++ freshclam/notify.c @@ -62,6 +62,11 @@ int clamd_connect(const char *cfgfile, c return -11; } + if (!optget(opts, "EnableReloadCommand")->enabled) { + logg(LOGG_WARNING, "Clamd was NOT notified: The RELOAD command is disabled. Consider enabling it in the clamd configuration!\n"); + return -1; + } + #ifndef _WIN32 if ((opt = optget(opts, "LocalSocket"))->enabled) { memset(&server, 0x00, sizeof(server)); @@ -163,6 +168,12 @@ int notify(const char *cfgfile) memset(buff, 0, sizeof(buff)); if ((bread = recv(sockd, buff, sizeof(buff), 0)) > 0) { + if (strstr(buff, "COMMAND UNAVAILABLE")) { + // this will only happen when the running clamd instance has EnableReloadCommand set to no, + // but the config on disk differs (e.g. after a config change without clamd restart) + logg(LOGG_ERROR, "NotifyClamd: RELOAD command unavailable, consider enabling it in the clamd configuration and restarting clamd.\n"); + return -1; + } if (!strstr(buff, "RELOADING")) { logg(LOGG_ERROR, "NotifyClamd: Unknown answer from clamd: '%s'\n", buff); closesocket(sockd); --- win32/conf_examples/clamd.conf.sample.orig +++ win32/conf_examples/clamd.conf.sample @@ -97,6 +97,31 @@ TCPSocket 3310 # Default: no TCPAddr localhost +# Enable or disable certain commands. +# Disabling some commands like SHUTDOWN may improve the security of the daemon. +# When a client sends one of the following commands but it is disabled, +# clamd responds with COMMAND UNAVAILABLE. +# +# Enable the SHUTDOWN command. +# Setting this to no prevents a client to stop clamd via the protocol. +# Default: yes +#EnableShutdownCommand no +# +# Enable the RELOAD command +# Setting this to no prevents a client to reload the database. +# Default: yes +#EnableReloadCommand no +# +# Enable the STATS command +# Setting this to no prevents a client from querying statistics. +# Default: yes +#EnableStatsCommand no +# +# Enable the VERSION command +# Setting this to no prevents a client from querying version information. +# Default: yes +#EnableVersionCommand no + # Maximum length the queue of pending connections may grow to. # Default: 200 #MaxConnectionQueueLength 30