diff --git a/modules/security/malware-scanner.sh b/modules/security/malware-scanner.sh index 7fd66fe..4760dc8 100755 --- a/modules/security/malware-scanner.sh +++ b/modules/security/malware-scanner.sh @@ -1000,15 +1000,38 @@ for scanner in "${AVAILABLE_SCANNERS[@]}"; do done # Try to get count of malicious files from malicious list (if available) - # Run once with timeout (60s) - capture output and exit code together - IMUNIFY_INFECTED=$(timeout 60 imunify-antivirus malware malicious list 2>/dev/null | tail -n +2 | wc -l) + # FIXED Issue 4B: Defensive header detection + malicious_output=$(timeout 60 imunify-antivirus malware malicious list 2>/dev/null) IMUNIFY_MALICIOUS_EXIT=$? - # Validate the count - if [ "$IMUNIFY_MALICIOUS_EXIT" -ne 0 ] || ! [[ "$IMUNIFY_INFECTED" =~ ^[0-9]+$ ]]; then - IMUNIFY_INFECTED=0 - log_message "WARNING: Failed to get ImunifyAV malicious count (exit: $IMUNIFY_MALICIOUS_EXIT)" - fi + IMUNIFY_INFECTED=0 + # FIXED Issue 4A: Distinguish timeout from other errors + case "$IMUNIFY_MALICIOUS_EXIT" in + 0) + # Success - validate the output and count lines + if [ -n "$malicious_output" ]; then + # Check if first line looks like header (contains "Path", "ID", "Threat", etc.) + first_line=$(echo "$malicious_output" | head -1) + if [[ "$first_line" == *"Path"* ]] || [[ "$first_line" == *"ID"* ]] || [[ "$first_line" == *"Threat"* ]]; then + IMUNIFY_INFECTED=$(echo "$malicious_output" | tail -n +2 | wc -l) + else + IMUNIFY_INFECTED=$(echo "$malicious_output" | wc -l) + fi + # Ensure it's numeric + if ! [[ "$IMUNIFY_INFECTED" =~ ^[0-9]+$ ]]; then + IMUNIFY_INFECTED=0 + fi + fi + ;; + 124) + log_message "WARNING: ImunifyAV malicious list command timed out after 60s" + IMUNIFY_INFECTED=0 + ;; + *) + log_message "WARNING: Failed to get ImunifyAV malicious count (exit: $IMUNIFY_MALICIOUS_EXIT)" + IMUNIFY_INFECTED=0 + ;; + esac SCAN_END=$(date +%s) DURATION=$((SCAN_END - SCAN_START)) @@ -1048,10 +1071,14 @@ for scanner in "${AVAILABLE_SCANNERS[@]}"; do while kill -0 $CLAM_PID 2>/dev/null; do # Get current log size and file count from log if [ -f "$LOG_DIR/clamav.log" ]; then - current_size=$(stat -c%s "$LOG_DIR/clamav.log" 2>/dev/null || echo 0) + # FIXED Issue 5B: Improved error handling for stat + current_size=$(stat -c%s "$LOG_DIR/clamav.log" 2>/dev/null) + if [ -z "$current_size" ]; then + current_size=0 + fi - # Try to get current file being scanned (optimized - avoid UUOC) - current_file=$(sed -n 's/^.*\(\/.* \).*/\1/p' "$LOG_DIR/clamav.log" 2>/dev/null | tail -1) + # Try to get current file being scanned (FIXED Issue 5A: simpler, more robust pattern) + current_file=$(grep -oE '\./[^ ]+|/[^ ]+' "$LOG_DIR/clamav.log" 2>/dev/null | tail -1) if [ -n "$current_file" ]; then filename=$(basename "$current_file" 2>/dev/null || echo "...") @@ -1102,11 +1129,11 @@ for scanner in "${AVAILABLE_SCANNERS[@]}"; do continue fi - # Extract infected files + # Extract infected files (FIXED: acceptable current approach) grep "FOUND" "$LOG_DIR/clamav.log" 2>/dev/null | cut -d: -f1 >> "$INFECTED_LIST" 2>/dev/null || true - # Get scan stats from log (with validation) - FILES_SCANNED=$(grep "Scanned files:" "$LOG_DIR/clamav.log" 2>/dev/null | tail -1 | awk '{print $3}' || echo "0") + # Get scan stats from log (FIXED Issue 1B: robust number extraction independent of column position) + FILES_SCANNED=$(grep "Scanned files:" "$LOG_DIR/clamav.log" 2>/dev/null | tail -1 | grep -oE '[0-9]+' | head -1 || echo "0") CLAM_INFECTED=$(grep -c "FOUND" "$LOG_DIR/clamav.log" 2>/dev/null || echo 0) # Validate numbers (ensure they're numeric) @@ -1188,17 +1215,24 @@ for scanner in "${AVAILABLE_SCANNERS[@]}"; do # Extract scan results from event log (more reliable than parsing output) # Maldet logs to /usr/local/maldetect/logs/event_log - # Use dynamic path search for portability (different systems may have different paths) + # Use dynamic path search for portability (FIXED Issue 2B: more specific search order) local event_log="/usr/local/maldetect/logs/event_log" - [ -f "$event_log" ] || event_log=$(find /usr -name "event_log" -type f 2>/dev/null | head -1) + if [ ! -f "$event_log" ]; then + event_log=$(find /usr/local/maldetect -name "event_log" -type f 2>/dev/null | head -1) + fi + if [ ! -f "$event_log" ]; then + event_log=$(find /opt -name "*maldet*event_log" -type f 2>/dev/null | head -1) + fi FILES_SCANNED="0" MALDET_HITS="0" if [ -f "$event_log" ]; then # Use -E instead of -P for portability (BSD grep doesn't support -P) - FILES_SCANNED=$(grep "scan completed" "$event_log" 2>/dev/null | tail -1 | grep -oE 'files [0-9]+' | awk '{print $2}' || echo "0") - MALDET_HITS=$(grep "scan completed" "$event_log" 2>/dev/null | tail -1 | grep -oE 'malware hits [0-9]+' | awk '{print $3}' || echo "0") + # FIXED Issue 2A: Robust parsing independent of format variations + last_line=$(grep "scan completed" "$event_log" 2>/dev/null | tail -1) + FILES_SCANNED=$(echo "$last_line" | grep -oE '[0-9]+ files' 2>/dev/null | grep -oE '^[0-9]+' || echo "0") + MALDET_HITS=$(echo "$last_line" | grep -oE '[0-9]+ (malware hits|malicious)' 2>/dev/null | grep -oE '^[0-9]+' || echo "0") fi # Validate numbers @@ -1235,18 +1269,22 @@ for scanner in "${AVAILABLE_SCANNERS[@]}"; do # Run with timeout (30 minutes, RKHunter is usually fast) # Show test names as they run - timeout 1800 rkhunter --check --skip-keypress --report-warnings-only 2>&1 | tee -a "$LOG_DIR/rkhunter.log" | \ + # FIXED Issue 3A: Capture output to variable FIRST to avoid subshell exit code loss + output=$(timeout 1800 rkhunter --check --skip-keypress --report-warnings-only 2>&1) + RKH_EXIT=$? + + # Log output and display progress + echo "$output" | tee -a "$LOG_DIR/rkhunter.log" | \ while IFS= read -r line; do # Parse test names: "Checking for..." or "Testing..." if [[ "$line" =~ ^Checking\ for\ (.+)$ ]] || [[ "$line" =~ ^Testing\ (.+)$ ]]; then - test_name="${BASH_REMATCH[1]}" - printf "\r → %-60s" "${test_name:0:60}" + test_name="${BASH_REMATCH[1]:-}" + [ -n "$test_name" ] && printf "\r → %-60s" "${test_name:0:60}" elif [[ "$line" =~ ^Scanning\ (.+)$ ]]; then - scan_item="${BASH_REMATCH[1]}" - printf "\r → Scanning: %-50s" "${scan_item:0:50}" + scan_item="${BASH_REMATCH[1]:-}" + [ -n "$scan_item" ] && printf "\r → Scanning: %-50s" "${scan_item:0:50}" fi done - RKH_EXIT=$? echo "" # New line after test display if [ "$RKH_EXIT" -eq 124 ]; then @@ -1262,8 +1300,11 @@ for scanner in "${AVAILABLE_SCANNERS[@]}"; do echo " ⚠️ Scan completed with warnings (exit code: $RKH_EXIT)" fi - # Extract warnings + # Extract warnings (FIXED Issue 3B: add numeric validation) RKH_WARNINGS=$(grep -c "Warning:" "$LOG_DIR/rkhunter.log" 2>/dev/null || echo 0) + if ! [[ "$RKH_WARNINGS" =~ ^[0-9]+$ ]]; then + RKH_WARNINGS=0 + fi # Extract any rootkits found (FIXED: use -F flag for literal matching consistency) grep -F "Rootkit" "$LOG_DIR/rkhunter.log" 2>/dev/null | grep -iF "found" >> "$INFECTED_LIST" 2>/dev/null || true