ClamAV導入における問題対応

カバー

はじめに

私の所属するチームでは多くのLinux仮想サーバーを運用しており、それらのウイルス対策ソフトとして、OSSのClamAVを使用することにしました。
ClamAVの機能やインストール方法について書かれた記事は世の中に多く存在するため、この記事では、導入時に直面した問題とその対応方法を紹介します。

導入時に直面した問題

頻繁に公式リポジトリにアクセスするとブロックされる

当初、ウイルス定義ファイルを更新する方法として、ClamAVをインストールしたLinux仮想サーバーが定期的に直接ClamAV公式リポジトリにアクセスして、ウイルス定義ファイルを取得する運用を想定していました。
しかし、動作検証中に以下のメッセージが出力され、ClamAV公式リポジトリにアクセスできなくなる事象が発生しました。

WARNING: FreshClam received error code 429 from the ClamAV Content Delivery Network (CDN).
This means that you have been rate limited by the CDN.

調査したところ、ClamAV公式リポジトリは、同一IPから時間間隔を空けずに複数回のアクセスがあると、アクセスをブロックするようになっていることがわかりました。
429 この問題の対策として、社内にプライベートミラーを構築することにしました。
プライベートミラーは、1日1回cronでClamAV公式リポジトリからウイルス定義ファイルをダウンロードします。
社内のLinux仮想サーバーは、このプライベートミラーからウイルス定義ファイルをダウンロードします。
このようにすることで、社内からClamAV公式リポジトリへのアクセスを減らし、問題を解決することができました。
private-mirror

ウイルス定義ファイル更新時の社内ネットワークへの負荷

ウイルス定義ファイルの更新で問題となったのが、ファイルサイズです。
ウイルス定義ファイルは以下の3種類から構成されますが、main.cvdとdaily.cvdを合わせると200MB以上になります。

  • main.cvd:約160MB
  • daily.cvd:約60MB
  • bytecode.cvd:約0.3MB

当初はcronを使用して、毎日深夜時間帯に全Linux仮想サーバーが一斉にウイルス定義ファイルの更新を実施する予定でしたが、そうすると社内ネットワークへの負荷が大きくなります。
調査したところ、ファイルサイズが大きいのは初回更新時のみで、2回目以降は数KB~十数KBの差分ファイルが配布される仕組みであることがわかりました。
そこで、初回のウイルス定義ファイル更新のみ、ClamAVインストール直後に手動で実行する運用とすることで、ウイルス定義ファイル更新時にかかる負荷を抑えることができました。

設定ファイルのパラメータ(CrossFilesystems)が正常に動作しない

clamdデーモンは以下の図のように、clamdscanコマンドからのスキャン要求を受け、libclamavライブラリを使用してスキャンを実施します。

clamd

その際、clamdscanコマンドに--fdpassというオプションを指定すると、設定ファイル(clamd.conf)のCrossFilesystemsというパラメータが期待どおりに動作しないことがわかりました(v1.0.0で確認)。

--fdpassは、clamdscanコマンドの実行ユーザーとclamdデーモンの実行ユーザーが異なる場合に必要なオプションです。
また、CrossFilesystemsは、ファイルシステムを跨いでスキャンする機能を有効にするかどうかを指定するパラメータで、デフォルトでは有効になっています。
例えば以下のように、複数の異なるファイルシステムがあるディレクトリ構成で / 配下のスキャンを実施する場合を考えます。

  / (xfs) 
  |─volume_dir1 (xfs)
  └─volume_dir2 (ext4)

CrossFilesystemsが有効になっている場合は、volume_dir1,volume_dir2のどちらもスキャンされますが、CrossFilesystemsが無効になっている場合は、volume_dir2のファイルシステムが / のファイルシステムであるxfsと異なるため、スキャンされません。
しかし、--fdpassをclamdscanコマンドのオプションに指定した状態でCrossFilesystemsを無効にしてスキャンを実施すると、異なるファイルシステムもスキャンされることを確認しました。

調査したところ、--fdpassを指定した場合としていない場合でソースコードに違いがあることがわかりました。
--fdpassを指定しない場合、clamdデーモン側のパスチェック関数(clamav/clamd/scanner.c:scan_pathchk関数)が呼び出されてExcludePathによる除外処理とCrossFilesystemsの設定の確認が行われています。

int scan_pathchk(const char *path, struct cli_ftw_cbdata *data)
{
    struct scan_cb_data *scandata = data->data;
    const struct optstruct *opt;
    STATBUF statbuf;

    if ((opt = optget(scandata->opts, "ExcludePath"))->enabled) {
        while (opt) {
            if (match_regex(path, opt->strarg) == 1) {
                if (scandata->type != TYPE_MULTISCAN)
                    conn_reply_single(scandata->conn, path, "Excluded");
                return 1;
            }
            opt = (const struct optstruct *)opt->nextarg;
        }
    }

    if (!optget(scandata->opts, "CrossFilesystems")->enabled) {
        if (CLAMSTAT(path, &statbuf) == 0) {
            if (statbuf.st_dev != scandata->dev) {
                if (scandata->type != TYPE_MULTISCAN)
                    conn_reply_single(scandata->conn, path, "Excluded (another filesystem)");
                return 1;
            }
        }
    }

    return 0;
}

しかし、--fdpassを指定した場合は、clamdscanコマンド側のパスチェック関数(clamav/common/clamdcom.c:chkpath関数)が呼び出されます。こちらの関数では、ExcludePathの設定しか確認していないため、CrossFilesystemsを無効化することができません。

int chkpath(const char *path, struct optstruct *clamdopts)
{
    int status = 0;
    const struct optstruct *opt;
    char *real_path = NULL;

    if (!path) {
        status = 1;
        goto done;
    }

    if ((opt = optget(clamdopts, "ExcludePath"))->enabled) {
        while (opt) {
            if (match_regex(path, opt->strarg) == 1) {
                logg(LOGG_DEBUG, "%s: Excluded\n", path);
                status = 1;
                goto done;
            }
            opt = opt->nextarg;
        }
    }

done:
    if (NULL != real_path) {
        free(real_path);
    }
    return status;
}

clamdデーモンを使用する場合は、この動作の違いを考慮する必要があります。

デーモンの有無によるリソース消費の比較

スキャンを実施する際、デーモンを使用する方法と、使用しない方法があります。
どちらを採用するか決めるため、以下の観点で調査を行いました。

①メモリをどのタイミングでどれくらい使用するか

Linux仮想サーバーは業務で使用しており、様々なアプリケーションが動作しています。メモリを使用するタイミングや使用する量によっては業務に影響が出る可能性があるため、デーモンあり/デーモンなし、それぞれについてメモリ使用量の推移を測定しました。

  • デーモンありの場合
    デーモン起動中は常に1.5GB程度のメモリを使用しています。しかし、スキャン実行時はメモリ使用量は増加しませんでした。
  • デーモンなしの場合
    スキャン実行時のみメモリを1.5GB程度使用しています。

②リソース制限が可能か

ClamAVは、システム要件としてCPU1コア以上、メモリ3GiB以上を推奨しています。しかし、Linux仮想サーバーの中には、この推奨値より低いリソースの環境もあります。これら低リソースの環境でも、リソース制限を行うことでスキャンを実行できるかどうか、デーモンあり/デーモンなし、それぞれについて調査しました。

  • CPU使用率
    デーモンあり/デーモンなしの両方で、CPU使用率を制限することが可能です。
    デーモンありの場合はsystemdの設定ファイル(clamd.service)に以下を記載します。
[Service]
CPUQuota=25%

この設定はCPUを1コアあたり25%のみ使用するという意味です。仮に4コアのLinux仮想サーバーで動作させるとすると、clamdデーモンがCPUを合計で1コア分までしか使用しないように制限することが可能です。
デーモンなしの場合は、cgconfigというサービスを使用することで、CPU使用率を制限することが可能です。
なお、使用にはlibcgrouplibcgroup-toolsのパッケージが必要です。

上記を検証したところ、CPU使用率を25%に設定したLinux仮想サーバーでは、スキャンの実行に4倍程度の時間がかかりました。制限をかけた分だけスキャンにかかる時間が増えるため、スキャンにどれくらいの時間をかけられるかによって設定値を変えることになります。

  • メモリ使用量
    デーモンあり/デーモンなしの両方で、スキャン時には必ずメモリを1.5GB必要とします。実際、メモリを1GBしか積んでいないLinux仮想サーバーではスキャンを実行することができませんでした。
    以下は、デーモンありでスキャンを実行できなかった際、シスログに出力されたメッセージです。
LibClamAV Error: mpool_malloc(): Can't allocate memory (262144 bytes).

そのため、メモリの使用量が1.5GBを下回るような制限はできません。
メモリが不足する場合には、スワップを使用することが可能です。検証したところ、メモリが1GBのLinux仮想サーバーにスワップを1.5GB作成すると、スキャンが実行できました。

これらの検証結果を踏まえて、以下の理由により、デーモンなし・リソース制限なしで運用することに決めました。

  • 毎日深夜時間帯にフルスキャンを実行する運用を想定しているが、リソース制限を行うとスキャン時間が長くなり、朝までにスキャンが完了しない可能性がある
  • デーモンを使用すると常にメモリを1.5GB程度使用することになり、Linux仮想サーバーで動作している他のアプリケーションに影響が出る可能性がある

除外設定の指定方法による動作の違い

スキャン対象から除外したいディレクトリ・ファイルは、デーモンを使用しない場合は、clamscanコマンドの--exclude-dir--excludeのオプションで指定します。
ディレクトリ・ファイルは正規表現で指定するのですが、指定方法の詳細は公式ドキュメントに記載されていないため、指定方法による細かな動作の違いが明確ではありません。そのため、いくつかのパターンで検証した結果を以下に記載します。

No.ディレクトリ構成指定方法スキャン結果
1/
 |─bin/
 |─sbin/
└─bin2/
除外ディレクトリ
bin
/配下の全てのディレクトリ・ファイルが除外されます。
2/
 |─test.txt
 |─my_test.txt
└─test.txt.bak
除外ファイル
test.txt
/配下の全てのディレクトリ・ファイルが除外されます。
3/
 |─bin/
 |─sbin/
└─bin2/
除外ディレクトリ
/bin/
/bin/のみ除外されます。
4/
 |─bin/
 |─sbin/
└─bin2/
除外ディレクトリ
/bin
/bin/と/bin2/が除外されます。
5/
 |─bin/
 |─sbin/
└─bin2/
除外ディレクトリ
bin/
/bin/と/sbin/が除外されます。
6/
└─aaa/
 └─bbb/
   |─bbb.txt
  └─ccc.txt
除外ファイル
bbb
bbb.txtとccc.txtが除外されます。

上記の検証結果を整理すると、以下の動作になります。

  • ディレクトリ・ファイルともに、部分一致で検索される
  • 除外ファイルに指定した文字列が、スキャン対象のファイルパスの一部に含まれる場合も除外される

ClamAVのバグ/ウイルス誤検知

動作検証中に遭遇した、ClamAVのバグやウイルス誤検知の事例を紹介します。

  • バグ
    ClamAV v1.0.1からv1.1.0にバージョンアップしたところ、ウイルス定義ファイルのダウンロード時・スキャン時に以下の警告が出力されるようになりました。

・ウイルス定義ファイルのダウンロード時

WARNING:  ******* RESULT 200, SIZE: 61339631 *******
WARNING:  ******* RESULT 200, SIZE: 170479789 *******
WARNING:  ******* RESULT 200, SIZE: 291965 *******

・スキャン時

LibClamAV Warning: Don't know how to create filter for: Win.Downloader.LNKAgent-10001628-0
LibClamAV Warning: cli_ac_addsig: cannot use filter for trie

ClamAVのメーリングリストを確認したところ、上記はバグを修正する際に誤って追加されたものであり、v1.1.1 または v1.2.0で削除するとのことです。

  • ウイルス誤検知
    動作検証中に以下のウイルスを検出しました。
/usr/lib/python2.7/site-packages/distlib/t32.exe: Win.Virus.Memery-10002766-0 FOUND
/usr/lib/python2.7/site-packages/virtualenv/seed/embed/wheels/pip-20.0.2-py2.py3-none-any.whl: Win.Virus.Memery-10002766-0 FOUND
/usr/lib/python2.7/site-packages/pip/_vendor/distlib/t32.exe: Win.Virus.Memery-10002766-0 FOUND

ウイルスを検出した場合、誤検出である可能性を考慮して、検出したファイルは削除せず、所定のディレクトリにコピーする設定にしています。
そのため、翌日の定期フルスキャンで再度検出される想定でしたが、なぜか検出されなくなりました。
ClamAVのメーリングリストを確認したところ、このシグネチャコードの追加は取り下げたとのことでした。

これらの事例からわかるとおり、ClamAVの運用においては、ClamAVのメーリングリストの情報が役に立ちますので、適宜参照することをお勧めします。

スキャンコマンドの出力先指定と出力量の調整

ClamAVでスキャンを実行すると、標準出力や標準エラー出力に以下の内容が出力されます。

  • スキャン結果OKのファイルパス
  • スキャンしたファイルに対する警告
  • スキャン結果のサマリ
/run/udev/data/b253:1: OK
/run/udev/data/b253:0: OK
/run/udev/data/c226:64: OK
・
・(途中省略)
・
LibClamAV Warning: file_bytes is not valid unicode: invalid utf-8 sequence of 1 bytes from index 82
LibClamAV Warning: PNG: Unexpected early end-of-file.
/usr/share/doc/python-markdown-2.4.1/py.png: OK
・
・(途中省略)
・
----------- SCAN SUMMARY -----------
Known viruses: 8650123
Engine version: 1.0.0
Scanned directories: 5882
Scanned files: 24019
Infected files: 31
Data scanned: 1550.69 MB
Data read: 2702.42 MB (ratio 0.57:1)
Time: 406.855 sec (6 m 46 s)
Start Date: 2023:01:24 11:37:49
End Date:   2023:01:24 11:44:36

スキャン結果OKのファイルパスは大量に出力されますが、スキャンコマンドのオプション--quietを使用することで、この出力を抑止できます。
しかし、--quietを使用すると、上記の7,8行目のような警告が出たときに、どのファイルに対して警告が出たのか特定できなくなります。
そのため、当初は--quietを使用せず、スキャンに用いるcronファイルを以下のように設定することで、スキャン時の標準出力と標準エラー出力をすべてログファイルに出力させる予定でした。

[root@server ~]# cat /etc/cron.d/clamav-scan
CRON_TZ=Asia/Tokyo
MAILTO=root
0 2 * * * root /root/clamav_script/scan.sh >> /var/log/clamscan_stdout.log 2>&1

しかし、サーバーによってはスキャンするファイル数が数十万に及び、ログファイルのサイズが数百MBを超える事象が発生しました。
このログファイルのサイズの肥大化を抑止するため、通常は--quietを使用し、警告が出たときには、--quietを外して再度手動でスキャンを実施し、原因を調査する運用に変更しました。

仮想ファイルシステムをスキャンしたときの警告

スキャン時に、/procや/sysなど、一部のパスで以下の警告が出力されました。

LibClamAV Warning: fmap_readpage: pread fail: asked for 4094 bytes @ offset 2, got 0

これは、仮想ファイルシステムをスキャンしようとして警告が出力されています。
そのため、スキャンを実行する際に、スキャン対象を以下のように指定し、仮想ファイルシステムをスキャン対象から除外することで警告を抑止しました。

/usr/local/bin/clamscan -r $(mount | egrep -v ' type +(sysfs|proc|configfs) ' | cut -d ' ' -f 3) ・・・(以下、必要なオプション)

おわりに

今回はClamAV導入時の問題対応について解説しました。この記事がClamAVの導入を検討される方や、実際に運用されている方のお役に立てば幸いです。


TOP
アルファロゴ 株式会社アルファシステムズは、ITサービス事業を展開しています。このブログでは、技術的な取り組みを紹介しています。X(旧Twitter)で更新通知をしています。