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
orxxd
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:
- Extract the original from the RPM
- 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