From 242108fcb939363ac4b298f1332ab1cae21eb686 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 21 Jan 2025 20:17:40 +0000 Subject: [PATCH 1/5] nixos/clamav: add the scanner -> daemon dependency as assertion --- nixos/modules/services/security/clamav.nix | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix index 3749dd114893..18a896e356ea 100644 --- a/nixos/modules/services/security/clamav.nix +++ b/nixos/modules/services/security/clamav.nix @@ -172,6 +172,13 @@ in }; config = lib.mkIf (cfg.updater.enable || cfg.daemon.enable) { + assertions = [ + { + assertion = cfg.scanner.enable -> cfg.daemon.enable; + message = "ClamAV scanner requires ClamAV daemon to operate"; + } + ]; + environment.systemPackages = [ cfg.package ]; users.users.${clamavUser} = { @@ -189,7 +196,7 @@ in DatabaseDirectory = stateDir; LocalSocket = "/run/clamav/clamd.ctl"; PidFile = "/run/clamav/clamd.pid"; - User = "clamav"; + User = clamavUser; Foreground = true; }; From c44e74ba26d982699cf344207266526133fcaef0 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 21 Jan 2025 20:20:09 +0000 Subject: [PATCH 2/5] nixos/clamav: add clamav-daemon.socket This is also included upstream. --- nixos/modules/services/security/clamav.nix | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix index 18a896e356ea..37f7db11e43e 100644 --- a/nixos/modules/services/security/clamav.nix +++ b/nixos/modules/services/security/clamav.nix @@ -223,11 +223,24 @@ in description = "ClamAV Antivirus Slice"; }; + systemd.sockets.clamav-daemon = lib.mkIf cfg.daemon.enable { + description = "Socket for ClamAV daemon (clamd)"; + wantedBy = [ "sockets.target" ]; + listenStreams = [ + cfg.daemon.settings.LocalSocket + ]; + socketConfig = { + SocketUser = clamavUser; + SocketGroup = clamavGroup; + }; + }; + systemd.services.clamav-daemon = lib.mkIf cfg.daemon.enable { description = "ClamAV daemon (clamd)"; documentation = [ "man:clamd(8)" ]; after = lib.optionals cfg.updater.enable [ "clamav-freshclam.service" ]; wants = lib.optionals cfg.updater.enable [ "clamav-freshclam.service" ]; + requires = [ "clamav-daemon.socket" ]; wantedBy = [ "multi-user.target" ]; restartTriggers = [ clamdConfigFile ]; From 25a78835858fd35239ea6e1c8db53eb22d2ba001 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 21 Jan 2025 20:21:09 +0000 Subject: [PATCH 3/5] nixos/clamav: add ClamAV on-access scanner support --- nixos/modules/services/security/clamav.nix | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix index 37f7db11e43e..009ac8267014 100644 --- a/nixos/modules/services/security/clamav.nix +++ b/nixos/modules/services/security/clamav.nix @@ -65,6 +65,34 @@ in ''; }; }; + + clamonacc = { + enable = lib.mkOption { + default = false; + example = true; + description = '' + Whether to enable ClamAV on-access scanner. + + The settings for ClamAV's on-access scanner is configured in `clamd.conf` via `services.clamav.daemon.settings`. + Refer to on how to configure it. + + Example to scan `/home/foo/Downloads` (and block access until scanning is completed) would be: + ``` + services.clamav = { + daemon.enable = true; + clamonacc.enable = true; + + daemon.settings = { + OnAccessPrevention = true; + OnAccessIncludePath = "/home/foo/Downloads"; + }; + }; + ``` + ''; + type = lib.types.bool; + }; + }; + updater = { enable = lib.mkEnableOption "ClamAV freshclam updater"; @@ -177,6 +205,10 @@ in assertion = cfg.scanner.enable -> cfg.daemon.enable; message = "ClamAV scanner requires ClamAV daemon to operate"; } + { + assertion = cfg.clamonacc.enable -> cfg.daemon.enable; + message = "ClamAV on-access scanner requires ClamAV daemon to operate"; + } ]; environment.systemPackages = [ cfg.package ]; @@ -198,6 +230,8 @@ in PidFile = "/run/clamav/clamd.pid"; User = clamavUser; Foreground = true; + # Prevent infinite recursion in scanning + OnAccessExcludeUname = clamavUser; }; services.clamav.updater.settings = { @@ -258,6 +292,20 @@ in }; }; + systemd.services.clamav-clamonacc = lib.mkIf cfg.clamonacc.enable { + description = "ClamAV on-access scanner (clamonacc)"; + after = [ "clamav-daemon.socket" ]; + requires = [ "clamav-daemon.socket" ]; + wantedBy = [ "multi-user.target" ]; + restartTriggers = [ clamdConfigFile ]; + + # This unit must start as root to be able to use fanotify. + serviceConfig = { + ExecStart = "${cfg.package}/bin/clamonacc -F --fdpass"; + Slice = "system-clamav.slice"; + }; + }; + systemd.timers.clamav-freshclam = lib.mkIf cfg.updater.enable { description = "Timer for ClamAV virus database updater (freshclam)"; wantedBy = [ "timers.target" ]; From ab9fb88c9df7ff41dd09c9c0b8f7eff46aea97eb Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Oct 2025 13:58:49 +0100 Subject: [PATCH 4/5] nixos/clamav: add a test --- nixos/tests/all-tests.nix | 1 + nixos/tests/clamav.nix | 45 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 nixos/tests/clamav.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 0216ea9d9bd5..d6f08cbe5bce 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -345,6 +345,7 @@ in cinnamon = runTest ./cinnamon.nix; cinnamon-wayland = runTest ./cinnamon-wayland.nix; cjdns = runTest ./cjdns.nix; + clamav = runTest ./clamav.nix; clatd = runTest ./clatd.nix; clickhouse = import ./clickhouse { inherit runTest; diff --git a/nixos/tests/clamav.nix b/nixos/tests/clamav.nix new file mode 100644 index 000000000000..b84a195747e7 --- /dev/null +++ b/nixos/tests/clamav.nix @@ -0,0 +1,45 @@ +# Test ClamAV. + +{ lib, pkgs, ... }: +{ + name = "clamav"; + nodes = { + machine = { + services.clamav = { + daemon.enable = true; + clamonacc.enable = true; + + daemon.settings = { + OnAccessPrevention = true; + OnAccessIncludePath = "/opt"; + }; + }; + + # Add the definition for our test file. + # We cannot download definitions from Internet using freshclam in sandboxed test. + systemd.tmpfiles.settings."10-eicar"."/var/lib/clamav/test.hdb".L.argument = "${pkgs.runCommand + "test.hdb" + { } + '' + echo CLAMAVTEST > testfile + ${lib.getExe' pkgs.clamav "sigtool"} --sha256 testfile > $out + '' + }"; + + # Test using /opt as the ClamAV on-access scanner-protected directory. + systemd.tmpfiles.settings."10-testdir"."/opt".d = { }; + }; + }; + + testScript = '' + start_all() + + machine.wait_for_unit("default.target") + + # Write test file into the test directory. + # This won't trigger ClamAV as it scans on file open. + machine.succeed("echo CLAMAVTEST > /opt/testfile") + + machine.fail("cat /opt/testfile") + ''; +} From 1c64432009dba7b7b8b4f2a554489f695b9d94b5 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 7 Oct 2025 15:51:36 +0100 Subject: [PATCH 5/5] nixos/clamav: use `LocalSocketMode` to set systemd socket mode --- nixos/modules/services/security/clamav.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix index 009ac8267014..a844c4e78db3 100644 --- a/nixos/modules/services/security/clamav.nix +++ b/nixos/modules/services/security/clamav.nix @@ -266,6 +266,8 @@ in socketConfig = { SocketUser = clamavUser; SocketGroup = clamavGroup; + # LocalSocketMode setting in clamd.conf is not prefixed with octal 0, add it here. + SocketMode = "0${cfg.daemon.settings.LocalSocketMode or "666"}"; }; };