reliant-system/tools/reliant-security

303 lines
9.2 KiB
Bash
Executable file

#!/usr/bin/sh
set -e
. /usr/local/share/scripts/reliant-common.sh
reliant_print_help() {
echo "usage: $0 [SYSTEM ROOT]"
echo
echo "Verifies the state of system security."
}
# Check if we're inside the initramfs
if [ -z "$RELIANT_INITRAMFS" ]; then
RELIANT_INITRAMFS=$RELIANT_FALSE
fi
# No more variable checks needed
set -u
# Require superuser permissions
if [ "$EUID" -ne 0 ]; then
reliant_fail "must be superuser"
fi
# Kernel command line check
reliant_security_check_cmdline() {
# Check if /proc/cmdline exists
if [ ! -f /proc/cmdline ]; then
reliant_error "reliant_security_check_cmdline: cannot read /proc/cmdline"
return $RELIANT_FAIL
fi
# Check it for the target string
if ! grep -q "systemd.volatile=overlay" /proc/cmdline; then
reliant_error "reliant_security_check_cmdline: systemd.volatile=overlay not found in kernel cmdline"
return $RELIANT_SECURITY_FAIL
fi
# Pass
return $RELIANT_OK
}
# No swap files must be active
reliant_security_check_swap() {
# Check if /proc/swaps exists
if [ ! -f /proc/swaps ]; then
reliant_error "reliant_security_check_swap: cannot read /proc/swaps"
return $RELIANT_FAIL
fi
# Check if any swapfiles are enabled
if [ "$(wc -l < /proc/swaps)" -gt 1 ]; then
reliant_error "reliant_security_check_swap: swap is enabled"
return $RELIANT_SECURITY_FAIL
fi
# Pass
return $RELIANT_OK
}
# No network interfaces must be active except for loopback
reliant_security_check_network() {
# Check if /sys/class/net exists
if [ ! -d /sys/class/net ]; then
reliant_error "reliant_security_check_network: cannot access /sys/class/net"
return $RELIANT_FAIL
fi
# Check every interface individually
for interface_path in /sys/class/net/*; do
if [ ! -d "$interface_path" ]; then
reliant_error "reliant_security_check_network: cannot access network interface $interface_path"
return $RELIANT_FAIL
fi
# Determine the interface's name
interface="${interface_path##*/}"
# Allow for the loopback interface to be UP
if [ "$interface" = "lo" ]; then continue; fi
# Check the interface's operational state
operstate_file="$interface_path/operstate"
if [ ! -f "$operstate_file" ]; then
reliant_error "reliant_security_check_network: operstate unavailable for network interface $interface"
return $RELIANT_FAIL
fi
# Forbid any other interface to be UP
operstate=$(cat "$interface_path/operstate")
if [ "$operstate" = "up" ]; then
reliant_error "reliant_security_check_network: network interface $interface is up"
return $RELIANT_SECURITY_FAIL
fi
done
# Pass
return $RELIANT_OK
}
# Persistent, real filesystems must be readonly
reliant_security_check_filesystems() {
# Check each mountpoint
mount | while read -r line; do
# Determine the filesystem type
filesystem_type=$(echo "$line" | awk '{ print $5 }' )
# Skip virtual filesystems
case "$filesystem_type" in
'rootfs'|'proc'|'sysfs'|'tmpfs'|'devtmpfs'|'securityfs'|'devpts') continue ;;
'cgroup'|'cgroup2'|'pstore'|'efivarfs'|'bpf'|'configfs'|'overlay') continue ;;
'autofs'|'mqueue'|'debugfs'|'tracefs'|'xenfs'|'fusectl') continue ;;
esac
# Determine the filesystem flags
filesystem_flags=$(echo "$line" | awk '{ print $6 }')
# Check for readonly flag
case "$filesystem_flags" in
"(ro"*) : ;;
*) reliant_error "reliant_security_check_filesystems: persistent non-readonly filesystem: $line"; return $RELIANT_SECURITY_FAIL ;;
esac
done
# Pass status from subshell
return $?
}
# Check if a filesystem is overlay and volatile
reliant_security_check_overlay() {
# Validate arguments
if [ "$#" -ne 1 ]; then
reliant_error "reliant_security_check_overlay: mountpoint not specified"
return $RELIANT_FAIL
fi
if [ -z "$1" ]; then
reliant_error "reliant_security_check_overlay: empty mountpoint specified"
return $RELIANT_FAIL
fi
# Verify that /run is a tmpfs
run_line=$(mount | grep "tmpfs on /run type tmpfs")
if [ -z "$run_line" ]; then
reliant_error "reliant_security_check_overlay: /run is not a temporary filesystem"
return $RELIANT_SECURITY_FAIL
fi
# Find the mountpoint
mount | {
found=$RELIANT_FALSE
while read -r line; do
# Filter by mountpoint
filesystem_mountpoint=$(echo "$line" | awk '{ print $3 }')
if [ "$filesystem_mountpoint" != "$1" ]; then continue; fi
# Filter by filesystem type
filesystem_type=$(echo "$line" | awk '{ print $5 }' )
if [ "$filesystem_type" != "overlay" ]; then continue; fi
# Determine the filesystem flags
filesystem_flags=$(echo "$line" | awk '{ print $6 }')
# Iterate the flags
upper_found=$RELIANT_FALSE
work_found=$RELIANT_FALSE
IFS=','
for flag in $filesystem_flags; do
case $flag in
'upperdir=/run/systemd/overlay-sysroot/upper') upper_found=$RELIANT_TRUE ;;
'workdir=/run/systemd/overlay-sysroot/work' ) work_found=$RELIANT_TRUE ;;
esac
done
# Check if they have been validated
if [ $work_found -ne $RELIANT_TRUE ]; then
reliant_error "reliant_security_check_overlay: workdir for $1 could not be validated"
exit $RELIANT_SECURITY_FAIL
fi
if [ $upper_found -ne $RELIANT_TRUE ]; then
reliant_error "reliant_security_check_overlay: upperdir for $1 could not be validated"
exit $RELIANT_SECURITY_FAIL
fi
# Indicate that the mountpoint has been found
found=$RELIANT_TRUE
break
done
# Check if the mountpoint has been found within the loop
if [ $found -ne $RELIANT_TRUE ]; then
reliant_error "reliant_security_check_overlay: mountpoint for overlay root on $1 not found"
exit $RELIANT_SECURITY_FAIL
else
exit $RELIANT_OK
fi
}
# Forward from subshell
return $?
}
# Checksum verification
reliant_security_check_devices() {
IFS=$'\n'
for device_path in $(lsblk -rnpo name); do
device="${device_path##*/}"
# Check if it is a valid block device
if [ ! -b "$device_path" ]; then return $RELIANT_BUG; fi
# Some devices must be skipped
if [ "$device_path" = "$RELIANT_SECURE_DEVICE" ]; then continue; fi
if [[ "$device_path" =~ ^/dev/mapper/.* ]]; then continue; fi
if [[ "$device" =~ ^loop[0-9]+ ]] || [[ "$device" =~ ^dm-.* ]]; then continue; fi
# We do not want ovelapping checksums, so only checksum partitions
if [[ "$device" =~ [a-zA-Z]$ ]]; then continue; fi
# User-specified devices will be excluded
should_skip=$RELIANT_FALSE
IFS=' '
for skip_device in $RELIANT_SKIP_CHECKSUM; do
if [ "$skip_device" = "$device_path" ]; then
should_skip=$RELIANT_TRUE
fi
done
if [ $should_skip -eq $RELIANT_TRUE ]; then continue; fi
# We're good to go, but first verify if the device has been set to read-only
if [ "$(blockdev --getro "$device_path")" -eq 0 ]; then
reliant_warn "reliant_security_check_devices: attempted to checksum a non-readonly device $device_path, which will likely lead to false positive hash mismatches"
fi
blockdev --setro "$device_path" 2>/dev/null
# Manual verification
hash=$(reliant-hash "$device_path")
# Inside initramfs, use Plymouth, otherwise, use read
prompt="$device_path matches $hash? [Y/N]"
if [ "$RELIANT_INITRAMFS" -eq $RELIANT_TRUE ]; then
REPLY=$(plymouth ask-question --prompt="$prompt")
else
read -r -p "$prompt: "
fi
# Check the user-provided reply
if [[ ! $REPLY =~ ^[Yy] ]]; then
reliant_error "reliant_security_checksum_devices: hash mismatch reported for $device_path"
return $RELIANT_SECURITY_FAIL
fi
done
# Forward from subshell
return $?
}
# Main function
main() {
# Validate arguments
if [ "$#" -ne 1 ]; then
reliant_error "root mountpoint not specified"
return $RELIANT_FAIL
fi
if [ -z "$1" ]; then
reliant_error "empty root mountpoint specified"
return $RELIANT_FAIL
fi
# Status array
status_cmdline=$RELIANT_OK
status_swap=$RELIANT_OK
status_network=$RELIANT_OK
status_filesystems=$RELIANT_OK
status_overlay=$RELIANT_OK
status_devices=$RELIANT_OK
# Run the checks
reliant_security_check_cmdline || status_cmdline=$?
reliant_security_check_swap || status_swap=$?
reliant_security_check_network || status_network=$?
reliant_security_check_filesystems || status_filesystems=$?
reliant_security_check_overlay "$1" || status_overlay=$?
reliant_security_check_devices || status_devices=$?
# Print the report
echo "[REPORT]"
echo "[ROOT]: $(reliant_err2str $status_overlay)"
echo "[SWAP]: $(reliant_err2str $status_swap)"
echo "[DEVICES]: $(reliant_err2str $status_devices)"
echo "[NETWORK]: $(reliant_err2str $status_network)"
echo "[CMDLINE]: $(reliant_err2str $status_cmdline)"
echo "[FILESYSTEMS]: $(reliant_err2str $status_filesystems)"
echo -n "[SUMMARY]: "
# Detemine the final security state
if [ $status_cmdline -eq 0 ] && [ $status_swap -eq 0 ] && [ $status_network -eq 0 ] && [ $status_filesystems -eq 0 ] && [ $status_overlay -eq 0 ] && [ $status_devices -eq 0 ]; then
echo "VERIFIED"
return 0
else
echo "FAILED"
return 1
fi
}
main "$@"