How to Generate Diffs for Modified Files Identified by rpm -V Verification


8 views

When you run rpm -V PACKAGE_NAME, you get cryptic output like:

S.5....T.  /etc/myapp/config.conf
.M.......  /usr/bin/mytool

This tells you files were modified, but not what changed. For security audits or troubleshooting, you need the actual differences.

First, extract the original files from your RPM packages:

# Create extraction directory
mkdir -p /tmp/rpm-original

# Extract single file from RPM
rpm2cpio package.rpm | cpio -idmv "./path/to/file" 2>/dev/null

# Batch extract all verified files
for pkg in $(rpm -qa "MY_PACKAGES*"); do
  rpm -ql $pkg | while read file; do
    if rpm -V $pkg | grep -q "^..5...... $file$"; then
      rpm2cpio /path/to/$pkg.rpm | cpio -idmv "./$file" 2>/dev/null
      mv ./$file /tmp/rpm-original/$pkg-$file
    fi
  done
done

For comparing multiple files, this script generates unified diffs:

#!/bin/bash

while read -r line; do
  # Parse rpm -V output
  [[ $line =~ ^(..[^ ]+) (.+)$ ]] || continue
  flags=${BASH_REMATCH[1]}
  file=${BASH_REMATCH[2]}
  
  # Only check content changes (5 in flags)
  [[ $flags == *5* ]] || continue
  
  # Find which package owns the file
  pkg=$(rpm -qf "$file")
  
  # Generate diff
  echo "===== $file (Package: $pkg) ====="
  diff -u "/tmp/rpm-original/$pkg-$file" "$file"
done < <(rpm -V MY_PACKAGES)

For more reliability, verify against RPM checksums:

rpm -qlv MY_PACKAGE | awk '/^-/ {print $NF}' | while read file; do
  rpm_checksum=$(rpm -q --dump MY_PACKAGE | awk -v f="$file" '$1 == f {print $5}')
  current_checksum=$(sha256sum "$file" | awk '{print $1}')
  if [ "$rpm_checksum" != "$current_checksum" ]; then
    echo "Checksum mismatch for $file"
    # Generate diff here
  fi
done

Some files require special handling:

  • Config files: Use --nomtime to ignore timestamp changes
  • Binary files: Use hexdump -C or xxd before diffing
  • Compressed files: Decompress before comparison

For regular monitoring, consider this cron job setup:

0 * * * * root /usr/bin/rpm -Va > /var/log/rpm-verify.log && \
  /path/to/generate-diffs.sh > /var/log/rpm-diffs-$(date +\%Y\%m\%d-\%H\%M).log

When dealing with potentially compromised systems, rpm -V is your first line of defense. The verification output shows several types of changes:

S.5....T.  /etc/ssh/sshd_config
.M.......  /usr/bin/systemctl
missing    /var/log/important.log

The characters represent different verification failures (size, mode, checksum, etc.). For text file comparison, we're primarily interested in the '5' flag indicating checksum mismatches (content changes).

First, let's parse the rpm -V output to get clean file paths:

rpm -V package1 package2 | \
awk '/^..5/ {print $2}' > changed_files.txt

For each changed file, we need to:

  1. Extract the original from the RPM
  2. Compare with the installed version

Here's a complete bash script to automate this:

#!/bin/bash

# Create output directory
mkdir -p rpm_diffs

while read -r file; do
    # Find owning package
    pkg=$(rpm -qf "$file")
    
    # Skip files not owned by any package
    if [[ $pkg == *"not owned"* ]]; then
        echo "Skipping unowned file: $file"
        continue
    fi
    
    # Create temp dir for original file
    tmpdir=$(mktemp -d)
    
    # Extract original file from RPM
    rpm2cpio /path/to/rpms/${pkg}.rpm | \
    (cd "$tmpdir" && cpio -idm "./$file" 2>/dev/null)
    
    # Generate diff if extraction succeeded
    if [ -f "$tmpdir/$file" ]; then
        diff -u "$tmpdir/$file" "$file" > "rpm_diffs/${file//\//_}.diff"
    else
        echo "Failed to extract original version of $file"
    fi
    
    # Clean up
    rm -rf "$tmpdir"
done < changed_files.txt

For processing multiple packages, we can optimize by:

# First verify all packages and collect changes
rpm -Va > all_changes.txt

# Then process per-package
for pkg in $(rpm -qa | grep 'MY_PACKAGES'); do
    rpm -V "$pkg" | awk '/^..5/ {print $2}' | \
    xargs -I{} ./generate_diff.sh "$pkg" "{}"
done

For better readability, consider these diff options:

diff -u --color=always --label="Original" --label="Modified" original_file modified_file

Or for side-by-side comparison:

diff -y --suppress-common-lines original_file modified_file

To create an audit trail:

{
    echo "=== Diff Report $(date) ==="
    echo "System: $(hostname)"
    echo "=== Package Versions ==="
    rpm -q MY_PACKAGES
    echo "=== File Diffs ==="
    cat rpm_diffs/*.diff
} > security_audit_$(date +%Y%m%d).txt