github rfxn/linux-malware-detect v2.0.1
LMD v2.0.1

5 hours ago

v2.0.1 is the largest release in the history of Linux Malware Detect. It is a foundational rewrite of the scan, monitor, alert, and packaging stacks, retains complete backward compatibility for every public CLI flag and config variable, and closes 50+ user-reported issues.

Headline changes

  • Native scan engine, no Perl dependency. HEX + CSIG merged into a single batch-scanned worker pass with parallel grep workers and micro-chunked processing (scan_hex_chunk_size). 43x faster on HEX-heavy workloads. Perl runtime requirement removed.
  • SHA-256 with hardware acceleration. New scan_hashtype=auto|sha256|md5|both selector; auto-detects SHA-NI on x86 / SHA2 on ARM and prefers SHA-256 transparently. Ships sha256v2.dat, custom.sha256.dat, and ClamAV .hsb integration (gated on ClamAV ≥ 0.97).
  • Compound signatures (csig). Multi-pattern boolean matching with AND / OR / threshold subsigs, case-insensitive (i:), wide UTF-16LE (w:), and bounded-gap wildcards ({N-M}). New csig.dat and custom.csig.dat; scan_csig config gate.
  • Scan lifecycle management. Foreground and background scans now expose --kill, --pause/--unpause, --stop/--continue, and a redesigned -L / --list-active with running, paused, stopped, and historical scans in one columnar view. Checkpoint resume is eval-free (printf -v + -co allowlist). scan.meta records per-scan state; --maintenance rotates history, compresses sessions, and archives by month.
  • Inotify monitor redesign. Supervisor model replaces the legacy double-fork: graceful shutdown, crash recovery, session rotation, event filter, PID guard, and ClamAV cache integration. 12 long-standing monitor defects resolved at once. monitor_paths_extra, digest_interval, and digest_escalate_hits added. Closes #447, #454, #459.
  • TSV canonical session format. 11-field structured hit records (#LMD_INDEX:v2, 14 fields total) replace plaintext session files. session_legacy_compat=auto keeps old reports readable. Powers JSON output and lifecycle list at O(1) cost per session.
  • JSON report output (schema 1.2). --json-report [SCANID|list|newest] renders TSV sessions as JSON with a uniform {schema_version, scanner, host, reports[]} shape; the same envelope is used for per-scan and list output. started_epoch / stopped_epoch / completed_epoch integers for consumer-side sorting; legacy plaintext sessions emit the same shape with null fields. Closes #466.
  • Email alerts reimagined. Dual HTML + text format with a {{TOKEN}} template engine, on-demand HTML rendering (no persisted .html artifacts), full SMTP relay support (smtp_relay, smtp_from, smtp_user, smtp_pass), and Outlook-compatible responsive layout. Closes #198, #265.
  • Slack, Telegram, Discord. Slack migrated off the deprecated files.upload API to getUploadURLExternal/completeUploadExternal (Block Kit templates). Telegram uses MarkdownV2 with proper /bot prefix. Discord webhooks added for the first time (discord_alert, discord_webhook_url). Closes #387, #458, #461, #426.
  • Native YARA stage. YARA runs as an independent scan stage when scan_yara=1 (or auto), supporting yara (4.x) and yr (YARA-X). Custom rules via custom.yara and custom.yara.d/ drop-ins; compiled rules via yarac; --scan-list batch scanning. Closes #239, #277, #392.
  • RPM and DEB packaging in-tree. Full RPM (el7/el8/el9) and DEB packaging under pkg/ with FHS-compliant layout, backward-compatible symlink farm (pkg/symlink-manifest), and a dh_fixperms override that preserves project modes. pkg-postinst.sh shared across install methods. Closes #267.
  • Sub-library decomposition. files/internals/functions split into 14 cohesive lmd_*.sh modules (config, init, clamav, sigs, engine, yara, quarantine, session, lifecycle, scan, hook, monitor, update, alert) sourced by a lmd.lib.sh hub with _LMD_<STEM>_LOADED source guards. Vendored shared libraries: tlog_lib, elog_lib (structured event logging with audit trail), alert_lib, pkg_lib.
  • Hookscan API. Real-time scanning callable from ModSecurity, ProFTPD, Pure-FTPd, Exim, and a generic mode. Per-mode dispatch, --list/--stdin batch input, rate limiting, sig masking, on-hit escalation, deduped digests, and --report hooks with time/mode filters.
  • post_scan_hook. Configurable script execution after every scan completion. args/file/json output tiers, sync/async, SIGTERM/SIGKILL timeout, min_hits threshold, scan-type filter, scan_start epoch in JSON. Closes #477.
  • Position-independent CLI. Modifier flags (-x, -i, -hscan, -qd, -co, --format, --mailto) work in any order. -co uses an in-memory parser. The -co allowlist (79 vars) and _safe_source_conf close the long-standing remote-config injection class.
  • CI on every push/PR. GitHub Actions smoke-test workflow, 9 OS matrix (CentOS 6/7, Rocky 8/9/10, Ubuntu 20.04/24.04, Debian 12, FreeBSD partial), 1080 BATS tests + 100-scenario UAT.

Incremental new features

  • scan_clamscan and scan_yara accept auto for runtime binary detection.
  • --test-alert {scan|digest} {email|slack|telegram|discord}: send a synthetic alert through the real rendering pipeline, root-only.
  • -v / --version flag. Closes #451.
  • Independent 6-hourly signature update job via /etc/cron.d/maldet-sigup, configurable via sigup_interval (set to 0 to disable). Plus cron.watchdog weekly fallback.
  • cron.daily cPanel addon/subdomain detection via /etc/userdatadomains and Bitrix panel detection. Closes #268, #381.
  • ClamAV signature validation gate (clamscan -d) before deployment; sigup() SIGUSR2 reload only on validation success. Closes #467.
  • monitor_scan_owner_filters toggle. Default 0 restores v1.6.6 monitor semantics so root-owned malware drops are scanned regardless of scan_ignore_root. Closes #485.
  • Two-file ignore_inotify model: LMD-managed ignore_inotify.defaults (systemd-private tmpdirs, modern MariaDB, PostgreSQL, Redis, ClamAV runtime) plus user-owned ignore_inotify; literal: per-entry escape prefix for paths with regex metacharacters. Closes #480, #484.
  • Logrotate config (/etc/logrotate.d/maldet, weekly, 12 rotations) when logrotate is available.
  • Symlink-farm enforcement at startup (pkg_fhs_verify_farm), FHS fallback sourcing (/usr/lib/maldet) when legacy symlinks are broken, and portable source-tree execution so maldet runs directly from git clone with no install.sh (LMD_BASEDIR env override).
  • Audit log with 7 event types (purge, update, alert_failed, hookscan source/rate metadata).
  • Importconf extracted from install.sh into a standalone helper for shared use.
  • Package install tests verifying FHS layout, symlink farm, and explicit permissions modes.

Changes & polish

  • Vendored libs synced to canonical: tlog_lib 2.0.6, alert_lib 1.0.7, elog_lib 1.0.6, pkg_lib 1.0.10.
  • Alert templates: summary consolidated into headers, TOTAL prefix dropped, quarantine metrics added, aligned column spacing.
  • Hook scans write to a rolling hook.hits.log instead of creating per-scan session files; genalert() suppressed for hook scans.
  • Scan engine: HEX+CSIG merged into a single worker pass; scan stage reorder (strlen runs last); bulk awk HEX classifier; gensigs() awk compiler.
  • _scan_progress(): plain newline output in non-TTY contexts (no ANSI), logarithmic backoff on non-native engines, watchdog semantics for engine health.
  • FHS log path: /var/log/maldet/ is authoritative for all install methods; $inspath/logs is a backward-compatible symlink; install.sh migrates existing logs preserving timestamps.
  • --maintenance: session compression age 1h → 30d, archive age 30d → 90d, scan_meta_cleanup_age 24h → 48h; new maint_compress_age and maint_archive_age (0 disables).
  • cron.daily: runs --maintenance after sigup/versionup; hardcoded log paths replaced with $maldet_log.
  • scan_workers default 0auto (legacy 0 still accepted).
  • scan_hexdepth default 512 KB → 256 KB; covers 98.9% of HEX patterns with YARA backstop.
  • Update verification prefers SHA-256 over MD5; graceful MD5 fallback when sha256sum is absent.
  • maldet.service: EnvironmentFile moved to /etc/sysconfig/maldet or /etc/default/maldet; init script uses -b.
  • -E/--dump-report suppressed from help; case handler retained for backward compat.
  • inotify_verbose and telegram_file_caption config variables removed (dead code).
  • compat.conf now maps deprecated variable names; import_custsigs_*_urlsig_import_*_url.
  • Lifecycle JSON: scanidscan_id (alias retained for one cycle, removed in v2.1.0); workers normalized to int. Closes #482.

Bug fixes (themed)

Monitor mode

  • ERE semantics restored for ignore_inotify entries; literal: escape prefix added; ignore_paths uses grep -E -vf to match scan-mode semantics. Closes #484, #104, #431.
  • Ownership filters no longer drop root-owned malware silently; gated behind monitor_scan_owner_filters (default off). Closes #485.
  • ignore_inotify union-loaded from defaults + user overrides. Closes #480.
  • Filtered-cycle event-count logging added when all events drop on tier-2 scan filter.

JSON / reporting

  • --json-report list: path field parity, unified JSON escaping, dedup+escape rewrite eliminates O(N²) hang at ~20K sessions (82s → 1.7s); active[] gains lifecycle schema. Closes #482.
  • reports[] globally sorted by started_epoch newest-first across TSV index + legacy passes; pass-2 glob skips legacy session.*.html artifacts (12s+ hangs on upgrades). Closes #483.
  • _json_escape_string: bash parameter expansion replaces sed pipeline (FreeBSD + multi-newline fix).
  • view_report(): on-demand text rendering from TSV; newest/empty SCANID resolve to most recent session. Closes #336.

Scan lifecycle

  • Background scans: BASHPID is recorded instead of parent shell $$; scan.meta stores ns_pid separately for correct signal delivery.
  • Scan progress: TTY line cleared before stage transitions to prevent garbled output.
  • -L/--list-active: live elapsed for running scans (was 0h 00m).
  • Checkpoint resume: eval removed; printf -v + -co allowlist rejects PATH, IFS, LD_PRELOAD from tampered checkpoint files.
  • --pause suppresses console progress and elapsed timer; elapsed excludes paused duration.
  • CSIG compiler rejects invalid && separator and universal subsigs in OR groups.
  • scan() exits 1 when all provided paths are missing.
  • Hash scanning handles md5sum/sha256sum backslash-escaped filenames.

Quarantine & cleanup

  • Correct scan ID in report warning, TOCTOU mv exit-code check, inode naming replaces $RANDOM, batch signature name, colon truncation, -q batch summary now writes to stdout. Closes #399.
  • purge() dotfile glob: find -delete catches dotfiles in tmp/quarantine/sess.
  • Restore: orphan .info removed after success; chmod quotes $file_mode.
  • Temp-file leaks: clean.$$, suspend.users.$$, .alert_html.*, .tmpf_get.* cleaned up.

ClamAV

  • Sig deployment perms 644, empty sig cleanup, linksigs ordering, version-gated .hsb, MD5 merge format corrected for custom user signatures, _count_signatures() reads on-disk files. Closes #476.
  • clamselector() logs warning on clamd test failure instead of silent switch; no longer overwrites scan_max_filesize. Closes #410, #452.
  • clamav_linksigs mktemp guard prevents writing sig files into filesystem root when staging fails.
  • rfxn.hdb leading-backslash bug fixed (md5sum filenames containing backslash). Closes #468.

Alerting

  • Slack files.uploadfiles.getUploadURLExternal + completeUploadExternal; Telegram /bot prefix; token security via curl -K; genalert() state isolation. Closes #387, #458, #461, #426.
  • HTML email rendered on-demand at send time; smtp-relay guards html_file before cat on text-format emails.
  • Slack alerts now fire from monitor mode. Closes #387.
  • --test-alert digest cursor pre-seed prevents real-hit leakage into test email and re-send.

Security

  • _safe_source_conf() allowlist (79 vars), conf.maldet 0640, dirs 750.
  • Remote config RCE prevention; semver validation prevents directory traversal; hookscan validates ModSecurity filenames; --user validates against path traversal.
  • JSON injection prevention; email header injection prevention.
  • 19 bare exit statements given explicit codes; -c, -x, -i, -q, -n, -s, --web-proxy reject missing/empty arguments. Closes #366.
  • Hook validation: world-writable detection bug (broken glob → bash substring) fixed.

Install / packaging

  • DEB override_dh_fixperms restores 640/750 modes (72 files including conf.maldet.hookscan.default, /etc/default/maldet, sub-library shells) after debhelper normalizes to 755/644. RPM uses %attr() and is unaffected.
  • RPM/DEB run signature update on fresh install (was missing); migrate core sig files from tarball installs; link ClamAV sigs and signal clamd reload; shared pkg-postinst.sh for install.sh post-install parity.
  • RPM/DEB %pre: defensive detection for dir-vs-symlink cpio conflict prevents rename failed - Is a directory on upgrades from prior install.sh or partial installs.
  • Package manifests: lmd_lifecycle.sh added to RPM spec, DEB rules, symlink-manifest.
  • install.sh: detects supervisor and legacy monitor; graceful stop+restart on upgrade; man page compression on fresh install; backup robustness; no longer kills inotify monitoring; Slackware init rc.NAME convention; preserves hookscan config and pub/ state. Closes #414.
  • Tar fallback excludes pkg/ directory (was pkg/build only).

FreeBSD & portability

  • Detection via uname -s; id -u (POSIX); restore() touch -t mtime; portable command cp/mv/rm throughout; scan_strlen guard (wc -L is GNU-only).
  • mktemp replaces $RANDOM/$$; command -v replaces which; grep -E replaces egrep; POSIX : chown separator; hardcoded binary paths replaced. Closes #71, #72, #270, #177.

Find/ignore filters

  • ignore_file_ext, --exclude-regex, --include-regex, ignore_paths converted to bash arrays for correct argument passing; scan_ignore_user/group handles non-existent entries. Closes #72, #335, #438, #440, #446, #450.

Misc

  • tlog cursor unit mismatch in purge() and monitor_cycle() fixed; ed dependency replaced. Closes #227, #308.
  • cron.daily flock lock leak to backgrounded scans; lockfile prevents overlapping runs; config errors propagate to cron. Closes #373.
  • sigignore() moved to runtime copies in gensigs(); signatures no longer irrecoverably lost between updates.
  • sigup: writes downloaded version to maldet.sigs.ver to prevent CDN desync repeated downloads.
  • scan engine isolates worker temp files using parent PID namespace to prevent concurrent scan conflicts.
  • Quarantine restore quotes $file_mode against corrupt .info files.
  • Alert rendering: hit-type regex handles {MD5}/{CSIG}/{SHA256} prefixes; empty quarpath field shift; empty SUMMARY_* tokens in single-hit messaging.
  • --report recognizes latest as alias for newest.
  • -e list strips timezone offset from TSV session timestamps for consistent alignment.
  • restore()/restore_hitlist() return exit 1 on all error paths.
  • YARA rules no longer counted as active when no engine is available.

Issues closed in this release

#28 #71 #72 #104 #198 #227 #239 #265 #267 #268 #277 #308 #335 #336 #366 #373 #381 #387 #392 #399 #410 #414 #426 #431 #438 #440 #446 #447 #450 #451 #452 #454 #458 #459 #461 #466 #467 #468 #476 #477 #480 #482 #483 #484 #485

Coverage

  • Anvil distro matrix on the rc4 HEAD: 9 OS targets, 1080 BATS tests each, 0 failures.
  • UAT: 100/100 on Debian 12.
  • CI smoke-test workflow GREEN on master.

Backward compatibility

The CLI is frozen. Every existing argument continues to behave as it did in v1.6.6.1. New options are additive only. compat.conf maps every renamed configuration variable, and old configs auto-migrate on load.

Credits

Thanks to @Gazoo for the detailed reproductions on #482, #483, #484, #485, and to everyone who reported the long-standing issues that made it into this release.

The tarball below is the canonical install artifact (git archive honoring .gitattributes; excludes tests/, pkg/, docs/, working files).

Don't miss a new linux-malware-detect release

NewReleases is sending notifications on new releases.