security-misc/usr/bin/remount-secure

389 lines
9.8 KiB
Plaintext
Raw Normal View History

#!/bin/bash
2024-02-22 15:07:53 -05:00
## Copyright (C) 2019 - 2024 ENCRYPTED SUPPORT LP <adrelanos@whonix.org>
## See the file COPYING for copying conditions.
2024-02-22 15:07:53 -05:00
## features:
## - nodev,nosuid where appropriate
## - optional noexec for most except /home
## - optional noexec for all including /home
## - idempotent (script can be safely re-run)
## - can be run from:
## - systemd
## - dracut
## - manually from command line
## - can safely handle non-existing folders
## - error handling
## - log output:
## - shows each and every command executed
## - shows old mount options prior running remount-secure
## - shows new mount options after running remount-secure
## noexec in /tmp and/or /home can break some malware but also legitimate
## applications.
2024-02-22 15:07:53 -05:00
## https://www.kicksecure.com/wiki/Noexec
2023-10-22 12:54:25 -04:00
## https://www.kicksecure.com/wiki/Dev/remount-secure
2019-12-20 06:35:02 -05:00
## https://forums.whonix.org/t/re-mount-home-and-other-with-noexec-and-nosuid-among-other-useful-mount-options-for-better-security/7707
2023-10-22 06:32:19 -04:00
#set -x
set -e
set -o pipefail
set -o nounset
2023-10-22 10:01:38 -04:00
init() {
if test -o xtrace ; then
output_command=true
else
output_command=echo
fi
2023-10-22 16:08:21 -04:00
$output_command "$0: INFO: START"
## dracut does not have id. Saving space in initial ramdisk.
if command -v id &>/dev/null ; then
if [ "$(id -u)" != "0" ]; then
$output_command "ERROR: must be run as root! sudo $0"
exit 1
fi
2023-10-22 10:01:38 -04:00
fi
2023-10-22 10:01:38 -04:00
mkdir --parents "/run/remount-secure"
exit_code=0
2023-10-22 10:16:43 -04:00
2023-10-22 12:54:25 -04:00
## dracut sets NEWROOT=/sysroot
[[ -v NEWROOT ]] || NEWROOT=""
if [ "$NEWROOT" = "" ]; then
2023-10-22 16:08:21 -04:00
$output_command "INFO: dracut detected: no"
2023-10-22 12:54:25 -04:00
else
$output_command "INFO: dracut detected: yes - NEWROOT: '$NEWROOT'"
fi
## Debugging.
#echo "ls -la /root/"
#ls -la / || true
#echo "ls -la /sysroot/"
#ls -la /sysroot/ || true
#echo "env"
#env || true
2023-10-22 10:01:38 -04:00
}
2019-12-21 05:07:10 -05:00
2023-10-22 09:36:03 -04:00
parse_options() {
## Thanks to:
## http://mywiki.wooledge.org/BashFAQ/035
while :
do
case ${1:-} in
2023-10-22 16:08:21 -04:00
0)
$output_command "WARNING: Not using remount-secure."
exit 0
shift
;;
1)
$output_command "INFO: level 1/3 (low)"
most_noexec_maybe=""
home_noexec_maybe=""
parsed=true
shift
;;
2)
$output_command "INFO: level 2/3 (medium)"
most_noexec_maybe=",noexec"
home_noexec_maybe=""
parsed=true
shift
;;
3)
$output_command "INFO: level 3/3 (high)"
most_noexec_maybe=",noexec"
home_noexec_maybe=",noexec"
parsed=true
2023-10-22 09:36:03 -04:00
shift
;;
--force)
$output_command "INFO: --force"
option_force=true
shift
;;
--)
shift
break
;;
-*)
2023-10-22 16:08:21 -04:00
echo "ERROR: unknown option: $1" >&2
2023-10-22 09:36:03 -04:00
exit 1
;;
*)
break
;;
esac
done
2023-10-22 10:11:31 -04:00
[[ -v option_force ]] || option_force=""
2023-10-22 16:08:21 -04:00
[[ -v parsed ]] || parsed=false
[[ -v home_noexec_maybe ]] || home_noexec_maybe=""
[[ -v most_noexec_maybe ]] || most_noexec_maybe=""
$output_command "INFO: using nosuid,nodev: yes"
if [ "$home_noexec_maybe" = "" ]; then
$output_command "INFO: using noexec for all: no"
else
$output_command "INFO: using noexec for all: yes"
return 0
fi
if [ "$most_noexec_maybe" = "" ]; then
$output_command "INFO: using noexec for most: no"
else
$output_command "INFO: using noexec for most (not all): yes"
return 0
fi
if [ "$parsed" = "true" ]; then
return 0
fi
$output_command "ERROR: syntax error. use either:
$0 0
$0 1
$0 2
$0 3"
exit 1
2023-10-22 09:36:03 -04:00
}
2023-10-22 19:16:12 -04:00
preparation() {
## Debugging.
2023-10-22 19:21:51 -04:00
#$output_command "INFO: 'findmnt --list' output at the START."
#$output_command "$(findmnt --list)"
#$output_command ""
2023-10-22 19:16:12 -04:00
true
}
2019-12-21 05:07:10 -05:00
remount_secure() {
2023-10-22 10:32:24 -04:00
$output_command ""
2019-12-21 05:07:10 -05:00
## ${FUNCNAME[1]} is the name of the calling function. I.e. the function
## which called this function.
status_file_name="${FUNCNAME[1]}"
## example status_file_name:
## _home
2023-10-22 09:44:17 -04:00
status_file_full_path="/run/remount-secure/${status_file_name}"
2019-12-21 05:18:34 -05:00
## example status_file_full_path:
2023-10-22 09:44:17 -04:00
## /run/remount-secure/_home
2019-12-21 05:07:10 -05:00
2023-10-22 10:25:57 -04:00
old_mount_options="$(findmnt --noheadings --output options -- "$mount_folder")" || true
## example old_mount_options:
## rw,nosuid,nodev,relatime,discard
2019-12-21 05:11:19 -05:00
2023-10-22 10:32:24 -04:00
$output_command "INFO: '$mount_folder' old_mount_options: '$old_mount_options'"
if echo "$old_mount_options" | grep --quiet "$intended_mount_options" ; then
2024-02-22 14:57:50 -05:00
$output_command "INFO: '$mount_folder' has already intended mount options. ('$intended_mount_options')"
return 0
fi
2019-12-21 05:07:10 -05:00
## When this package is upgraded, the systemd unit will run again.
## If the user meanwhile manually relaxed mount options, this should not be undone.
2023-10-22 11:06:34 -04:00
if [ ! "$option_force" == "true" ]; then
if [ -e "$status_file_full_path" ]; then
2023-10-22 10:32:24 -04:00
$output_command "INFO: '$mount_folder' already remounted earlier. Not remounting again. Use --force if this is what you want."
return 0
fi
2019-12-21 05:25:54 -05:00
fi
2023-10-22 12:54:25 -04:00
if ! test -d "$mount_folder" ; then
## For example /boot/efi does not always exist on all systems.
2023-10-22 12:54:25 -04:00
$output_command "INFO: '$mount_folder' folder exists: no"
return 0
fi
$output_command "INFO: '$mount_folder' folder exists: yes"
2023-10-22 11:11:10 -04:00
if findmnt --noheadings "$mount_folder" >/dev/null ; then
2023-10-22 10:49:53 -04:00
$output_command "INFO: '$mount_folder' already mounted, therefore using remount."
$output_command INFO: Executing: mount --make-private --options "remount,${intended_mount_options}" "$mount_folder"
mount --make-private --options "remount,${intended_mount_options}" "$mount_folder" || exit_code=100
2019-12-21 05:07:10 -05:00
else
2023-10-22 10:49:53 -04:00
$output_command "INFO: '$mount_folder' not yet mounted, therefore using mount bind."
$output_command INFO: Executing: mount --make-private --options "$intended_mount_options" --bind "$mount_folder" "$mount_folder"
mount --make-private --options "$intended_mount_options" --bind "$mount_folder" "$mount_folder" || exit_code=101
2019-12-21 05:07:10 -05:00
fi
2023-10-22 11:12:54 -04:00
new_mount_options="$(findmnt --noheadings --output options -- "$mount_folder")" || true
2024-02-22 09:14:52 -05:00
$output_command "INFO: '$mount_folder' new_mount_options: '$new_mount_options'"
2023-10-22 10:32:24 -04:00
2019-12-21 05:18:34 -05:00
touch "$status_file_full_path"
}
2023-10-22 12:54:25 -04:00
_boot() {
mount_folder="$NEWROOT/boot"
## https://lists.freedesktop.org/archives/systemd-devel/2015-February/028456.html
2023-10-22 15:36:16 -04:00
intended_mount_options="nosuid,nodev,noexec"
2023-10-22 15:44:30 -04:00
remount_secure
2023-10-22 12:54:25 -04:00
}
_boot_efi() {
## TODO: new, test
mount_folder="$NEWROOT/boot/efi"
intended_mount_options="nosuid,nodev,noexec"
remount_secure
}
2019-12-21 04:33:03 -05:00
_run() {
2023-10-22 14:29:02 -04:00
mount_folder="/run"
## https://lists.freedesktop.org/archives/systemd-devel/2015-February/028456.html
2023-10-22 16:08:21 -04:00
intended_mount_options="nosuid,nodev${most_noexec_maybe}"
2023-10-22 15:44:30 -04:00
remount_secure
}
2023-10-22 13:30:50 -04:00
_dev() {
2023-10-22 14:29:02 -04:00
mount_folder="/dev"
2023-10-22 15:35:03 -04:00
## /dev should be nosuid,noexec as per:
## https://bugs.launchpad.net/ubuntu/+source/systemd/+bug/1991975
2023-10-22 15:27:01 -04:00
intended_mount_options="nosuid,noexec"
2023-10-22 15:44:30 -04:00
remount_secure
2023-10-22 13:30:50 -04:00
}
2023-10-22 12:54:25 -04:00
2019-12-21 04:33:03 -05:00
_dev_shm() {
2023-10-22 14:29:02 -04:00
mount_folder="/dev/shm"
2023-10-22 16:08:21 -04:00
intended_mount_options="nosuid,nodev${most_noexec_maybe}"
2023-10-22 15:44:30 -04:00
remount_secure
}
_sys() {
## TODO: new, test
mount_folder="/sys"
intended_mount_options="nosuid,nodev,noexec"
remount_secure
}
2019-12-21 04:33:03 -05:00
_tmp() {
2023-10-22 15:05:33 -04:00
mount_folder="$NEWROOT/tmp"
2023-10-22 16:08:21 -04:00
intended_mount_options="nosuid,nodev${most_noexec_maybe}"
2023-10-22 15:44:30 -04:00
remount_secure
2023-10-22 12:54:25 -04:00
}
_var_tmp() {
2023-10-22 16:45:10 -04:00
mount_folder="$NEWROOT/var/tmp"
2023-10-22 16:08:21 -04:00
intended_mount_options="nosuid,nodev${most_noexec_maybe}"
2023-10-22 15:44:30 -04:00
remount_secure
}
2023-10-22 14:44:58 -04:00
_var_log() {
2023-10-22 15:05:33 -04:00
mount_folder="$NEWROOT/var/log"
2023-10-22 14:44:58 -04:00
intended_mount_options="nosuid,nodev,noexec"
2023-10-22 15:44:30 -04:00
remount_secure
2023-10-22 14:44:58 -04:00
}
_var() {
mount_folder="$NEWROOT/var"
## noexec: Not possible. Reason:
## Debian stores executable maintainer scripts in /var/lib/dpkg/info folder.
intended_mount_options="nosuid,nodev"
remount_secure
}
_usr() {
## TODO: new, test
mount_folder="$NEWROOT/usr"
intended_mount_options="nodev"
remount_secure
}
2023-10-22 10:37:02 -04:00
_home() {
2023-10-22 12:54:25 -04:00
mount_folder="$NEWROOT/home"
2023-10-22 16:08:21 -04:00
intended_mount_options="nosuid,nodev${home_noexec_maybe}"
2023-10-22 15:44:30 -04:00
remount_secure
2023-10-22 10:37:02 -04:00
}
_root() {
## TODO: new, test
mount_folder="$NEWROOT/root"
intended_mount_options="nosuid,nodev${home_noexec_maybe}"
remount_secure
}
_srv() {
## TODO: new, test
mount_folder="$NEWROOT/srv"
intended_mount_options="nosuid,nodev${most_noexec_maybe}"
remount_secure
}
_media() {
## TODO: new, test
mount_folder="$NEWROOT/media"
intended_mount_options="nosuid,nodev${most_noexec_maybe}"
remount_secure
}
_mnt() {
## TODO: new, test
mount_folder="$NEWROOT/mnt"
intended_mount_options="nosuid,nodev${most_noexec_maybe}"
remount_secure
}
_opt() {
## TODO: new, test
mount_folder="$NEWROOT/opt"
## Allow /opt exec as usually optional binaries are placed there such as Firefox
## when manually installed from tarball.
intended_mount_options="nosuid,nodev"
remount_secure
}
_etc() {
## TODO: new, test
## /etc cannot be noexec because various executables are there. To find, run:
## sudo find /etc -executable
mount_folder="$NEWROOT/etc"
intended_mount_options="nosuid,nodev"
remount_secure
}
end() {
2023-10-22 11:11:10 -04:00
## Debugging.
2023-10-22 19:21:51 -04:00
#$output_command "INFO: 'findmnt --list' output at the END."
#$output_command "$(findmnt --list)"
2024-02-22 09:49:48 -05:00
$output_command ""
2023-10-22 15:37:21 -04:00
$output_command "INFO: exit_code: $exit_code"
2023-10-22 13:58:55 -04:00
$output_command "$0: INFO: END"
exit $exit_code
}
main() {
2023-10-22 14:45:45 -04:00
init
2023-10-22 09:36:03 -04:00
parse_options "$@"
2023-10-22 19:16:12 -04:00
preparation
2023-10-22 16:08:21 -04:00
2023-10-22 14:45:45 -04:00
_boot
_boot_efi
2023-10-22 14:45:45 -04:00
_run
2023-10-22 15:27:01 -04:00
_dev
2023-10-22 14:45:45 -04:00
_dev_shm
_tmp
2023-10-22 15:33:11 -04:00
_var_tmp
_var_log
_var
_usr
2023-10-22 14:45:45 -04:00
_home
_root
_srv
_media
_mnt
_opt
_etc
2023-10-22 15:27:01 -04:00
2023-10-22 14:45:45 -04:00
end
}
## TODO: see also hidepid /usr/lib/systemd/system/proc-hidepid.service
#mount --options defaults,nosuid,nodev,noexec,remount,subset=pid /proc
main "$@"