Qubes-VM-hardening/vm-boot-protect.sh

272 lines
8.3 KiB
Bash
Raw Normal View History

2017-04-17 14:23:59 -04:00
#!/bin/sh
2018-04-14 11:18:55 -04:00
## Protect startup of Qubes VMs from /rw content ##
## https://github.com/tasket/Qubes-VM-hardening ##
2019-07-13 05:39:42 -04:00
## Copyright 2017-2019 Christopher Laprise ##
## tasket@protonmail.com ##
2018-04-14 11:18:55 -04:00
# This file is part of Qubes-VM-hardening.
# Qubes-VM-hardening is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Qubes-VM-hardening is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
2018-04-14 11:18:55 -04:00
# along with Qubes-VM-hardening. If not, see <http://www.gnu.org/licenses/>.
2017-05-09 06:48:12 -04:00
# Source Qubes library.
. /usr/lib/qubes/init/functions
2017-05-12 05:47:53 -04:00
vmname=`qubesdb-read /name`
dev=/dev/xvdb
2017-05-12 05:47:53 -04:00
rw=/mnt/rwtmp
rwbak=$rw/vm-boot-protect
2018-04-02 10:55:55 -04:00
errlog=/var/run/vm-protect-error
2018-04-03 11:01:31 -04:00
defdir=/etc/default/vms
2019-07-19 12:48:40 -04:00
version="0.8.5"
2018-04-03 11:01:31 -04:00
# Define sh, bash, X and desktop init scripts in /home/user
# to be protected
chfiles=${chfiles:-".bashrc .bash_profile .bash_login .bash_logout .profile \
.xprofile .xinitrc .xserverrc .xsession"}
chfiles_add=""
chdirs=${chdirs:-"bin .local/bin .config/autostart .config/plasma-workspace/env \
.config/plasma-workspace/shutdown .config/autostart-scripts .config/systemd"}
chdirs_add=""
# Define dirs to apply quarrantine / whitelists
privdirs=${privdirs:-"/rw/config /rw/usrlocal /rw/bind-dirs"}
privdirs_add=""
2019-07-30 23:43:32 -04:00
# Placeholder function: Runs at end
vm_boot_finish() { return; }
2019-07-30 23:43:32 -04:00
# Run rc file commands if they exist
if [ -e $defdir/vms.all.rc ]; then
. $defdir/vms.all.rc
fi
if [ -e $defdir/$vmname.rc ]; then
. $defdir/$vmname.rc
fi
2017-04-17 14:23:59 -04:00
# Remount fs as read-write
remount_rw() {
# Begin write operations
if [ -e $dev ] && mount -o remount,rw $dev $rw ; then
echo Good rw remount.
else
abort_startup RELOCATE "Remount failed!"
fi
}
2017-05-13 15:00:13 -04:00
# Function: Make user scripts immutable.
2017-05-09 06:48:12 -04:00
make_immutable() {
remount_rw
2017-05-13 15:00:13 -04:00
#initialize_home $rw/home ifneeded
2017-05-09 06:48:12 -04:00
cd $rw/home/user
su user -c "mkdir -p $chdirs $chdirs_add; touch $chfiles $chfiles_add"
chattr -R -f +i $chfiles $chfiles_add $chdirs $chdirs_add
2017-05-13 15:00:13 -04:00
cd /root
2017-05-09 06:48:12 -04:00
}
2018-04-02 10:55:55 -04:00
# Start rescue shell then exit/fail
abort_startup() {
type="$1"
msg="$2"
echo "$msg" >>$errlog
2018-04-02 10:55:55 -04:00
cat $errlog
rc=1
if [ $type = "RELOCATE" ]; then
# quarantine private volume
umount $dev
mv -f $dev /dev/badxvdb
truncate --size=500M /root/dev-xvdb
loop=`losetup --find --show /root/dev-xvdb`
mv -f $loop $dev
elif [ $type = "OK" ]; then
# allow normal start with private vol
rc=0
fi
2018-04-02 10:55:55 -04:00
# insert status msg and run xterm
2018-04-02 10:55:55 -04:00
cat /etc/bashrc /etc/bash.bashrc >/etc/bashrc-insert
echo "echo '** VM-BOOT-PROTECT SERVICE SHELL'" >/etc/bashrc
if [ $type = "RELOCATE" ]; then
echo "echo '** Private volume is located at /dev/badxvdb'" >>/etc/bashrc
fi
2018-04-02 10:55:55 -04:00
echo "cat $errlog" >>/etc/bashrc
echo ". /etc/bashrc-insert" >>/etc/bashrc
ln -f /etc/bashrc /etc/bash.bashrc
echo '/usr/bin/nohup /usr/bin/xterm /bin/bash 0<&- &>/dev/null &' \
>/etc/X11/Xsession.d/98rescue
exit $rc
2018-04-02 10:55:55 -04:00
}
echo >$errlog # Clear
2018-03-29 02:57:06 -04:00
if qsvc vm-boot-protect-cli; then
abort_startup RELOCATE "CLI requested."
2018-04-02 10:55:55 -04:00
fi
2019-07-15 17:27:53 -04:00
if qsvc vm-boot-protect || qsvc vm-boot-protect-root; then
# Mount private volume in temp location
mkdir -p $rw
if [ -e $dev ] && mount -o ro $dev $rw ; then
echo "Good read-only mount."
else
2019-07-15 17:27:53 -04:00
echo "Mount failed."
# decide if this is initial boot or a bad volume
private_size_512=$(blockdev --getsz "$dev")
if head -c $(( private_size_512 * 512 )) /dev/zero | diff "$dev" - >/dev/null; then
touch /var/run/qubes/VM-BOOT-PROTECT-INITIALIZERW
abort_startup OK "FIRST BOOT INITIALIZATION: PLEASE RESTART VM!"
else
abort_startup RELOCATE "Mount failed; BAD private volume!"
fi
fi
2019-07-30 23:25:35 -04:00
# Don't bother with root protections in template or standalone
if ! is_rwonly_persistent; then
2019-07-30 23:43:32 -04:00
vm_boot_finish
2019-07-30 23:25:35 -04:00
make_immutable
exit 0
fi
2017-05-13 15:00:13 -04:00
fi
2017-04-17 14:23:59 -04:00
2017-05-09 06:48:12 -04:00
# Protection measures for /rw dirs:
2018-03-29 02:57:06 -04:00
# Activated by presence of vm-boot-protect-root Qubes service.
2017-05-12 05:47:53 -04:00
# * Hashes in vms/vms.all.SHA and vms/$vmname.SHA files will be checked.
2018-04-02 10:55:55 -04:00
# * Remove /rw root startup files (config, usrlocal, bind-dirs).
2017-05-12 05:47:53 -04:00
# * Contents of vms/vms.all and vms/$vmname folders will be copied.
2017-05-09 06:48:12 -04:00
2018-03-29 02:57:06 -04:00
if qsvc vm-boot-protect-root && is_rwonly_persistent; then
2017-05-09 06:48:12 -04:00
# Check hashes
checkcode=0
if [ -e $defdir/$vmname.SHA ]; then
# remove padding and add number field
sed 's/^ *//; s/ *$//; /^$/d; s/^/1 /' $defdir/$vmname.SHA \
>/tmp/vm-boot-protect-sha
fi
if [ -e $defdir/vms.all.SHA ]; then
sed 's/^ *//; s/ *$//; /^$/d; s/^/2 /' $defdir/vms.all.SHA \
>>/tmp/vm-boot-protect-sha
fi
if [ -e /tmp/vm-boot-protect-sha ]; then
echo "Checking file hashes." |tee $errlog
# Get unique paths, remove field and switch path to $rw before check;
# this allows hashes in $vmname.SHA to override ones in vms.all.SHA.
sort --unique --key=3 /tmp/vm-boot-protect-sha \
| sed -r 's|^[1-2] (.*[[:space:]]*)/rw|\1'$rw'|' \
| sha256sum --strict -c >>$errlog; checkcode=$?
fi
# Divert startup on hash mismatch:
2017-05-09 06:48:12 -04:00
if [ $checkcode != 0 ]; then
abort_startup RELOCATE "Hash check failed!"
2018-04-02 10:55:55 -04:00
fi
remount_rw
2017-05-09 06:48:12 -04:00
2017-05-09 19:02:54 -04:00
# Files mutable for del/copy operations
2017-05-09 06:48:12 -04:00
cd $rw/home/user
chattr -R -f -i $chfiles $chfiles_add $chdirs $chdirs_add $privdirs $privdirs_add
2017-05-13 15:00:13 -04:00
cd /root
2017-05-12 05:47:53 -04:00
2018-04-02 10:55:55 -04:00
2017-05-12 05:47:53 -04:00
# Deactivate private.img config dirs
mkdir -p $rwbak
for dir in $privdirs $privdirs_add; do # maybe use 'eval' for privdirs quotes/escaping
2018-04-02 10:55:55 -04:00
echo "Deactivate $dir"
subdir=`echo $dir |sed -r 's|^/rw/||'`
bakdir="$rwbak/BAK-$subdir"
origdir="$rwbak/ORIG-$subdir"
2019-07-30 23:25:35 -04:00
if [ -e "$bakdir" ] && [ ! -e "$origdir" ]; then
mv "$bakdir" "$origdir"
2018-01-25 07:46:33 -05:00
fi
2019-07-30 23:25:35 -04:00
if [ -e "$bakdir" ]; then
chattr -R -i "$bakdir"
rm -rf "$bakdir"
fi
mv "$rw/$subdir" "$bakdir"
mkdir -p "$rw/$subdir"
# Populate /home/user w skel files if it was in privdirs
case "$subdir" in
"home"|"home/"|"home/user"|"home/user/")
2019-07-30 23:25:35 -04:00
echo "Populating home dir"
rm -rf /home/user $rw/home/user
mount --bind $rw/home /home
2019-07-30 23:25:35 -04:00
mkhomedir_helper user
#mv /home/user $rw/home
umount /home
;;
esac
2017-05-09 19:02:54 -04:00
done
2017-05-12 05:47:53 -04:00
for vmset in vms.all $vmname; do
# Process whitelists...
2017-05-13 15:00:13 -04:00
cat $defdir/$vmset.whitelist \
| while read wlfile; do
2017-05-12 05:47:53 -04:00
# Must begin with '/rw/'
if echo $wlfile |grep -q "^\/rw\/"; then
srcfile="`echo $wlfile |sed -r \"s|^/rw/(.+)$|$rwbak/BAK-\1|\"`"
2017-05-13 15:00:13 -04:00
dstfile="`echo $wlfile |sed -r \"s|^/rw/(.+)$|$rw/\1|\"`"
dstdir="`dirname \"$dstfile\"`"
if [ ! -e "$srcfile" ]; then
2018-04-02 10:55:55 -04:00
echo "Whitelist entry not present in filesystem:"
echo "$srcfile"
2017-05-13 15:00:13 -04:00
continue
# For very large dirs: mv whole dir when entry ends with '/'
elif echo $wlfile |grep -q "\/$"; then
echo "Whitelist mv $srcfile"
echo "to $dstfile"
2017-05-13 15:00:13 -04:00
mkdir -p "$dstdir"
mv -T "$srcfile" "$dstfile"
2017-05-12 05:47:53 -04:00
else
2017-05-13 15:00:13 -04:00
echo "Whitelist cp $srcfile"
mkdir -p "$dstdir"
2017-05-13 15:00:13 -04:00
cp -a --link "$srcfile" "$dstdir"
2017-05-12 05:47:53 -04:00
fi
2017-05-13 15:00:13 -04:00
elif [ -n "$wlfile" ]; then
2018-04-02 10:55:55 -04:00
echo "Whitelist path must begin with /rw/. Skipped."
2017-05-12 05:47:53 -04:00
fi
2017-05-13 15:00:13 -04:00
done
2017-05-09 06:48:12 -04:00
2017-05-12 05:47:53 -04:00
# Copy default files...
if [ -d $defdir/$vmset/rw ]; then
2018-04-02 10:55:55 -04:00
echo "Copy files from $defdir/$vmset/rw"
2018-03-29 09:54:31 -04:00
cp -af $defdir/$vmset/rw/* $rw
fi
2017-05-09 06:48:12 -04:00
done
2019-07-15 17:27:53 -04:00
fi
2018-03-29 07:31:40 -04:00
# Keep configs invisible at runtime...
rm -rf "$defdir"
2019-07-15 17:27:53 -04:00
if qsvc vm-boot-protect || qsvc vm-boot-protect-root; then
2019-07-30 23:43:32 -04:00
vm_boot_finish
2019-07-15 17:27:53 -04:00
make_immutable
umount $rw
2017-05-09 06:48:12 -04:00
fi
exit 0