diff --git a/debian/control b/debian/control index 4a944d1b..1673ab33 100644 --- a/debian/control +++ b/debian/control @@ -30,11 +30,11 @@ Rules-Requires-Root: no ## > security-misc is to be removed Package: security-misc-shared Architecture: all +Pre-Depends: helper-scripts Depends: adduser, apparmor-profile-dist, build-essential, dmsetup, - helper-scripts, libcap2-bin, libdbus-1-dev, libglib2.0-bin, diff --git a/debian/security-misc-shared.preinst b/debian/security-misc-shared.preinst index 93d8b2f5..1c2856cc 100755 --- a/debian/security-misc-shared.preinst +++ b/debian/security-misc-shared.preinst @@ -3,15 +3,23 @@ ## Copyright (C) 2012 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. -if [ -f /usr/libexec/helper-scripts/pre.bsh ]; then - source /usr/libexec/helper-scripts/pre.bsh -fi - -set -e +set -o errexit +set -o nounset +set -o pipefail +set -o errtrace +shopt -s inherit_errexit +shopt -s shift_verbose +export LC_ALL=C + +## helper-scripts is a Pre-Depends, so it is fully configured and source-able +## here (accountctl.sh provides group_has_nonroot_member). +source /usr/libexec/helper-scripts/pre.bsh +source /usr/libexec/helper-scripts/has.bsh +source /usr/libexec/helper-scripts/accountctl.sh true " ##################################################################### -## INFO: BEGIN: $DPKG_MAINTSCRIPT_PACKAGE $DPKG_MAINTSCRIPT_NAME $@ +## INFO: BEGIN: ${DPKG_MAINTSCRIPT_PACKAGE:-} ${DPKG_MAINTSCRIPT_NAME:-} $* ##################################################################### " @@ -60,66 +68,45 @@ user_groups_modifications() { } output_skip_checks() { - echo "security-misc-shared '$0' INFO: Allow installation of security-misc-shared anyway." >&2 - echo "security-misc-shared '$0' INFO: (technical reason: $@)" >&2 - echo "security-misc-shared '$0' INFO: If this is a chroot this is probably OK." >&2 - echo "security-misc-shared '$0' INFO: Otherwise you might not be able to login." >&2 + printf '%s\n' "security-misc-shared '${0}' INFO: Allow installation of security-misc-shared anyway." >&2 + printf '%s\n' "security-misc-shared '${0}' INFO: (technical reason: $*)" >&2 + printf '%s\n' "security-misc-shared '${0}' INFO: If this is a chroot this is probably OK." >&2 + printf '%s\n' "security-misc-shared '${0}' INFO: Otherwise you might not be able to login." >&2 } sudo_users_check () { - if command -v "qubesdb-read" &>/dev/null; then + if has qubesdb-read; then ## Qubes users can use dom0 to get a root terminal emulator. ## For example: ## qvm-run -u root debian-10 xterm return 0 fi - local sudo_users user_with_sudo are_there_any_sudo_users OLD_IFS - - sudo_users="$(getent group sudo | cut -d: -f4)" - ## example sudo_users: - ## user,root - - OLD_IFS="$IFS" - IFS="," - export IFS - - for user_with_sudo in $sudo_users ; do - if [ "$user_with_sudo" = "root" ]; then - ## root login is also restricted. - ## Therefore user "root" being member of group "sudo" is - ## considered insufficient. - continue - fi - are_there_any_sudo_users=yes - break - done - - IFS="$OLD_IFS" - export IFS - - if [ "$are_there_any_sudo_users" = "yes" ]; then + ## Any non-root account with sudo access (supplementary member OR primary + ## GID) prevents a lockout, so it satisfies the check. + if group_has_nonroot_member sudo; then return 0 fi ## Prevent users from locking themselves out. ## https://forums.whonix.org/t/is-security-misc-suitable-for-hardening-bridges-and-relays/8299/4 - echo "$0: ERROR: No account is a member of group 'sudo'. Installation aborted." >&2 - echo "$0: ERROR: You probably want to run:" >&2 - echo "$0: NOTE: Replace account 'user' with your actual Linux user account name." >&2 - echo "" >&2 - echo "sudo adduser user sudo" >&2 - echo "sudo adduser user console" >&2 - echo "" >&2 - echo "$0: ERROR: See also installation instructions:" >&2 - echo "https://www.kicksecure.com/wiki/security-misc#install" >&2 - - if [ "$SECURITY_MISC_INSTALL" = "force" ]; then + printf '%s\n' "${0}: ERROR: No account is a member of group 'sudo'. Installation aborted." >&2 + printf '%s\n' "${0}: ERROR: You probably want to run:" >&2 + printf '%s\n' "${0}: NOTE: Replace account 'user' with your actual Linux user account name." >&2 + printf '%s\n' "" >&2 + printf '%s\n' "sudo adduser user sudo" >&2 + printf '%s\n' "sudo adduser user console" >&2 + printf '%s\n' "" >&2 + printf '%s\n' "${0}: ERROR: See also installation instructions:" >&2 + printf '%s\n' "https://www.kicksecure.com/wiki/security-misc#install" >&2 + + if [ "${SECURITY_MISC_INSTALL:-}" = "force" ]; then output_skip_checks "Environment variable SECURITY_MISC_INSTALL is set to 'force'." return 0 fi - if test -f "/var/lib/security-misc/skip_install_check" ; then - output_skip_checks "File '/var/lib/security-misc/skip_install_check' exists." + if test -f "/var/lib/security-misc/skip_install_check" \ + || [ "${dist_build_iso:-}" = "true" ] ; then + output_skip_checks "File '/var/lib/security-misc/skip_install_check' exists or this is an ISO/live build (dist_build_iso=true)." return 0 fi @@ -127,64 +114,43 @@ sudo_users_check () { } console_users_check() { - if [ "$SECURITY_MISC_INSTALL" = "force" ]; then + if [ "${SECURITY_MISC_INSTALL:-}" = "force" ]; then return 0 fi - if test -f "/var/lib/security-misc/skip_install_check" ; then + if test -f "/var/lib/security-misc/skip_install_check" \ + || [ "${dist_build_iso:-}" = "true" ] ; then return 0 fi - if command -v "qubesdb-read" &>/dev/null; then + if has qubesdb-read; then ## Qubes users can use dom0 to get a root terminal emulator. ## For example: ## qvm-run -u root debian-10 xterm return 0 fi - local console_users console_unrestricted_users user_with_console are_there_any_console_users OLD_IFS - - console_users="$(getent group console | cut -d: -f4)" - ## example console_users: - ## user - console_unrestricted_users="$(getent group console-unrestricted | cut -d: -f4)" - - OLD_IFS="$IFS" - IFS="," - export IFS - - for user_with_console in $console_users $console_unrestricted_users ; do - if [ "$user_with_console" = "root" ]; then - ## root login is also restricted. - ## Therefore account "root" being member of group "console" is - ## considered insufficient. - continue - fi - are_there_any_console_users=yes - break - done - - IFS="$OLD_IFS" - export IFS - - ## Prevent users from locking themselves out. + ## A non-root member (supplementary OR primary GID) of either console group + ## prevents a lockout. ## https://forums.whonix.org/t/is-security-misc-suitable-for-hardening-bridges-and-relays/8299/4 - if [ "$are_there_any_console_users" = "yes" ]; then + if group_has_nonroot_member console \ + || group_has_nonroot_member console-unrestricted; then return 0 fi - echo "$0: ERROR: No account is a member of group 'console'. Installation aborted." >&2 - echo "$0: ERROR: You probably want to run:" >&2 - echo "" >&2 - echo "sudo adduser user console" >&2 - echo "" >&2 - echo "$0: ERROR: See also installation instructions:" >&2 - echo "https://www.whonix.org/wiki/security-misc#install" >&2 + printf '%s\n' "${0}: ERROR: No account is a member of group 'console'. Installation aborted." >&2 + printf '%s\n' "${0}: ERROR: You probably want to run:" >&2 + printf '%s\n' "" >&2 + printf '%s\n' "sudo adduser user console" >&2 + printf '%s\n' "" >&2 + printf '%s\n' "${0}: ERROR: See also installation instructions:" >&2 + printf '%s\n' "https://www.whonix.org/wiki/security-misc#install" >&2 - if [ "$SECURITY_MISC_INSTALL" = "force" ]; then + if [ "${SECURITY_MISC_INSTALL:-}" = "force" ]; then output_skip_checks "Environment variable SECURITY_MISC_INSTALL is set to 'force'." return 0 fi - if test -f "/var/lib/security-misc/skip_install_check" ; then - output_skip_checks "File '/var/lib/security-misc/skip_install_check' exists." + if test -f "/var/lib/security-misc/skip_install_check" \ + || [ "${dist_build_iso:-}" = "true" ] ; then + output_skip_checks "File '/var/lib/security-misc/skip_install_check' exists or this is an ISO/live build (dist_build_iso=true)." return 0 fi @@ -192,7 +158,7 @@ console_users_check() { } legacy() { - if [ -f "/var/lib/legacy/do_once/${FUNCNAME}_version_1" ]; then + if [ -f "/var/lib/legacy/do_once/${FUNCNAME[0]}_version_1" ]; then return 0 fi @@ -205,11 +171,11 @@ legacy() { continue_yes=true fi - if [ ! "$continue_yes" = "true" ]; then + if [ ! "${continue_yes:-}" = "true" ]; then return 0 fi - if command -v "qubesdb-read" &>/dev/null; then + if has qubesdb-read; then ## Qubes users can use dom0 to get a root terminal emulator. ## For example: ## qvm-run -u root debian-10 xterm @@ -220,23 +186,23 @@ legacy() { user_to_be_created=user - if ! id "$user_to_be_created" &>/dev/null ; then - true "INFO: Account '$user_to_be_created' does not exist. Skipping adding account '$user_to_be_created' to group 'console' and also skipping 'pam-auth-update --enable console-lockdown-security-misc'." + if ! id "${user_to_be_created}" &>/dev/null ; then + true "INFO: Account '${user_to_be_created}' does not exist. Skipping adding account '${user_to_be_created}' to group 'console' and also skipping 'pam-auth-update --enable console-lockdown-security-misc'." return 0 fi - adduser "$user_to_be_created" console + adduser "${user_to_be_created}" console pam-auth-update --enable console-lockdown-security-misc mkdir --parents "/var/lib/legacy/do_once" - touch "/var/lib/legacy/do_once/${FUNCNAME}_version_1" + touch "/var/lib/legacy/do_once/${FUNCNAME[0]}_version_1" } user_groups_modifications legacy -if [ "$1" = "install" ] || [ "$1" = "upgrade" ]; then +if [ "${1:-}" = "install" ] || [ "${1:-}" = "upgrade" ]; then sudo_users_check console_users_check fi @@ -249,7 +215,7 @@ true "INFO: Done with debhelper." true " ##################################################################### -## INFO: END : $DPKG_MAINTSCRIPT_PACKAGE $DPKG_MAINTSCRIPT_NAME $@ +## INFO: END : ${DPKG_MAINTSCRIPT_PACKAGE:-} ${DPKG_MAINTSCRIPT_NAME:-} $* ##################################################################### " diff --git a/usr/bin/permission-hardener#security-misc-shared b/usr/bin/permission-hardener#security-misc-shared index ec71c610..cc7de229 100755 --- a/usr/bin/permission-hardener#security-misc-shared +++ b/usr/bin/permission-hardener#security-misc-shared @@ -431,7 +431,8 @@ load_state() { ## to mean that all whitelisting should be ignored. local config_file line field_list policy_nosuid_file_item policy_file_item - local one_field field_count fso_name meta_list field_index hit_barrier + local one_field field_count fso_name meta_list + local split_index candidate_index fallback_index ## Load configuration, deferring whitelist handling until later for config_file in \ @@ -471,16 +472,12 @@ load_state() { processed_config_line="${line}" - ## We can handle filenames with spaces. This is because the first item - ## after a filename is either an octal file mode or a keyword, and we - ## only support user/group names, not UIDs and GIDs, as owner - ## identifiers. This means that we can scan from right to left for the - ## furthest-right octal integer or keyword field and use it to separate - ## the filename from the rest of the options. (If usernames could - ## themselves be octal integers, this would break, but adduser.conf's - ## default NAME_REGEX forbids this. This is definitely a hack, but the - ## alternative is to change the format of the policy files, which would - ## break custom config for users. + ## We can handle filenames with spaces. The options after a filename are a + ## fixed-width tail: either a lone whitelist keyword, or an octal mode + + ## owner + group + optional capability. The filename is whatever precedes + ## that tail, recovered by anchoring the tail from the right (see the field + ## split below). An owner that itself looks octal is disambiguated by which + ## recovered filename exists on disk. ## ## 'read' will collapse consecutive spaces when splitting on spaces. ## Convert spaces into newlines first, then split on newlines with a @@ -491,21 +488,48 @@ load_state() { done < <(printf '%s\n' "${line}" | tr ' ' '\n') field_count="${#field_list[@]}" - hit_barrier='false' + ## The option tail is fixed-width: a lone trailing whitelist keyword, or a + ## mode/owner/group[/capability] tail whose FIRST field is the octal mode. + ## Anchor the tail from the RIGHT. Two readings can be lexically valid when + ## a filename component OR a numeric owner looks octal (mode at field + ## count-3 vs count-4), so disambiguate by which recovered filename exists + ## on disk -- the tool skips nonexistent targets anyway. Fall back to the + ## rightmost octal candidate when neither recovered name exists. fso_name="" meta_list=() - for (( field_index = field_count - 1; field_index >= 0; \ - field_index-- )); do - if [ "${hit_barrier}" = 'false' ]; then - if [[ "${field_list[field_index]}" =~ ^[0-7]{3,4}$ ]] \ - || [[ "${field_list[field_index]}" =~ ^(nosuid|exactwhitelist|matchwhitelist|disablewhitelist)$ ]]; then - hit_barrier='true' + split_index='' + case "${field_list[field_count-1]}" in + nosuid|exactwhitelist|matchwhitelist|disablewhitelist) + split_index=$(( field_count - 1 )) + ;; + *) + fallback_index='' + for candidate_index in "$(( field_count - 3 ))" "$(( field_count - 4 ))"; do + (( candidate_index >= 1 )) || continue + [[ "${field_list[candidate_index]}" =~ ^[0-7]{3,4}$ ]] || continue + if [ -z "${fallback_index}" ]; then + fallback_index="${candidate_index}" fi - meta_list=( "${field_list[field_index]}" "${meta_list[@]}" ) - else - fso_name="${field_list[field_index]}${fso_name:+" ${fso_name}"}" - fi - done + fso_name="$(IFS=' '; printf '%s' "${field_list[*]:0:candidate_index}")" + if (( candidate_index == 1 )) || [ -e "${fso_name}" ]; then + split_index="${candidate_index}" + break + fi + done + if [ -z "${split_index}" ]; then + split_index="${fallback_index}" + fi + ;; + esac + if [ -n "${split_index}" ]; then + fso_name="$(IFS=' '; printf '%s' "${field_list[*]:0:split_index}")" + meta_list=( "${field_list[@]:split_index}" ) + else + ## No recoverable mode/keyword tail: hand every field to the field-count + ## check below, which rejects the line loudly (exit 200). + fso_name="" + meta_list=( "${field_list[@]}" ) + fi case "${#meta_list[@]}" in 1|3|4)