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
--nomtimeto ignore timestamp changes - Binary files: Use
hexdump -Corxxdbefore 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