#!/bin/bash # SPDX-Version: 3.0 # SPDX-CreationInfo: 2026-06-11; WEIDNER, Marc S.; # SPDX-ExternalRef: GIT https://git.coresecret.dev/msw/CISS.debian.live.builder.git # SPDX-FileContributor: WEIDNER, Marc S.; Centurion Intelligence Consulting Agency # SPDX-FileCopyrightText: 2024-2026; WEIDNER, Marc S.; # SPDX-FileType: SOURCE # SPDX-License-Identifier: LicenseRef-CNCL-1.1 OR LicenseRef-CCLA-1.1 # SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework. # SPDX-PackageName: CISS.debian.live.builder # SPDX-Security-Contact: security@coresecret.eu # shellcheck disable=SC2154 # Module overview: # This module centralizes build-directory safety checks for path validation, builder-ownership markers, and destructive cleanup # helpers. It keeps cleanup operations limited to canonical, explicitly validated build-directory paths. # # Function behavior: # build_dir_safety_error(): writes a scoped build-directory safety error message to stderr. # reject_broad_build_dir_path(): rejects the filesystem root and common top-level system directories as build targets. # validate_build_dir_argument(): validates a non-empty absolute build-directory argument before the path is created. # validate_existing_build_dir(): validates the argument and confirms that it resolves to an existing directory. # require_builder_owned_build_dir(): requires a validated directory with a safe root-owned builder marker. # ensure_builder_owned_build_dir(): creates the marker for a safe empty build directory or verifies an existing marker. # require_builder_owned_subpath(): confirms that a target exists strictly below a verified builder-owned directory. # safe_clean_build_dir_contents(): removes direct build-directory contents while preserving the builder marker. # safe_remove_builder_subpath(): removes one verified subpath below a builder-owned build directory. guard_sourcing || return "${ERR_GUARD_SRCE}" ####################################### # Print a cleanup/path safety error. # Globals: # None # Arguments: # 1: Error detail. # Returns: # 0: on success ####################################### build_dir_safety_error() { declare detail="${1}" printf "\e[91m❌ build directory safety: %s \e[0m\n" "${detail}" >&2 return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f build_dir_safety_error ####################################### # Reject broad parent directories as build-directory targets. # Globals: # ERR_INVLD_CHAR # Arguments: # 1: Canonical path. # Returns: # 0: on success # ERR_INVLD_CHAR: on failure ####################################### reject_broad_build_dir_path() { declare canonical_path="${1}" case "${canonical_path}" in "" | "/" | "/bin" | "/boot" | "/dev" | "/etc" | "/home" | "/lib" | "/lib64" | "/opt" | "/proc" | "/root" | "/run" | "/sbin" | "/sys" | "/tmp" | "/usr" | "/var") build_dir_safety_error "refusing broad path." return "${ERR_INVLD_CHAR}" ;; *) ;; esac return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f reject_broad_build_dir_path ####################################### # Validate a build-directory argument before it is created. # Globals: # ERR_INVLD_CHAR # Arguments: # 1: Build directory path. # Returns: # 0: on success # ERR_INVLD_CHAR: on failure ####################################### validate_build_dir_argument() { declare build_dir="${1}" canonical_path="" if [[ -z "${build_dir}" ]]; then build_dir_safety_error "path MUST NOT be empty." return "${ERR_INVLD_CHAR}" fi if [[ "${build_dir}" != /* ]]; then build_dir_safety_error "path MUST be absolute." return "${ERR_INVLD_CHAR}" fi if [[ -L "${build_dir}" ]]; then build_dir_safety_error "path MUST NOT be a symlink." return "${ERR_INVLD_CHAR}" fi canonical_path="$(realpath -m -- "${build_dir}")" reject_broad_build_dir_path "${canonical_path}" || return "${?}" return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f validate_build_dir_argument ####################################### # Canonicalize and validate an existing build directory. # Globals: # ERR_INVLD_CHAR # Arguments: # 1: Build directory path. # Returns: # 0: on success # ERR_INVLD_CHAR: on failure ####################################### validate_existing_build_dir() { declare build_dir="${1}" canonical_path="" validate_build_dir_argument "${build_dir}" || return "${?}" if [[ ! -d "${build_dir}" ]]; then build_dir_safety_error "path MUST be an existing directory." return "${ERR_INVLD_CHAR}" fi canonical_path="$(realpath -e -- "${build_dir}")" reject_broad_build_dir_path "${canonical_path}" || return "$?" return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f validate_existing_build_dir ####################################### # Validate the builder-owned marker in a build directory. # Globals: # CISS_BUILD_DIR_MARKER # ERR_INVLD_CHAR # Arguments: # 1: Build directory path. # Returns: # 0: on success # ERR_INVLD_CHAR: on failure ####################################### require_builder_owned_build_dir() { declare build_dir="${1}" canonical_path="" marker_path="" marker_owner="" marker_mode="" marker_mode_octal="" validate_existing_build_dir "${build_dir}" || return "$?" canonical_path="$(realpath -e -- "${build_dir}")" marker_path="${canonical_path}/${CISS_BUILD_DIR_MARKER}" if [[ -L "${marker_path}" || ! -f "${marker_path}" ]]; then build_dir_safety_error "builder-owned marker is missing or unsafe." return "${ERR_INVLD_CHAR}" fi marker_owner="$(stat -c '%u:%g' -- "${marker_path}")" if [[ "${marker_owner}" != "0:0" ]]; then build_dir_safety_error "builder-owned marker MUST be owned by root:root." return "${ERR_INVLD_CHAR}" fi marker_mode="$(stat -c '%a' -- "${marker_path}")" marker_mode_octal=$((8#${marker_mode})) if (( (marker_mode_octal & 022) != 0 )); then build_dir_safety_error "builder-owned marker MUST NOT be group- or world-writable." return "${ERR_INVLD_CHAR}" fi return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f require_builder_owned_build_dir ####################################### # Create or preserve the builder-owned marker. # Globals: # CISS_BUILD_DIR_MARKER # ERR_INVLD_CHAR # Arguments: # 1: Build directory path. # Returns: # 0: on success # ERR_INVLD_CHAR: on failure ####################################### ensure_builder_owned_build_dir() { declare build_dir="${1}" canonical_path="" marker_path="" validate_existing_build_dir "${build_dir}" || return "${?}" canonical_path="$(realpath -e -- "${build_dir}")" marker_path="${canonical_path}/${CISS_BUILD_DIR_MARKER}" if [[ -e "${marker_path}" || -L "${marker_path}" ]]; then require_builder_owned_build_dir "${canonical_path}" || return "${?}" return 0 fi if [[ -d "${canonical_path}/.build" ]]; then build_dir_safety_error "existing live-build state lacks the builder-owned marker." return "${ERR_INVLD_CHAR}" fi install -m 0600 -o root -g root /dev/null "${marker_path}" return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f ensure_builder_owned_build_dir ####################################### # Validate that a target path is strictly below a builder-owned build directory. # Globals: # ERR_INVLD_CHAR # Arguments: # 1: Build directory path. # 2: Target-path below the build directory. # Returns: # 0: on success # ERR_INVLD_CHAR: on failure ####################################### require_builder_owned_subpath() { declare build_dir="${1}" target_path="${2}" build_real="" target_real="" require_builder_owned_build_dir "${build_dir}" || return "$?" if [[ -z "${target_path}" || -L "${target_path}" || ! -e "${target_path}" ]]; then build_dir_safety_error "target subpath is empty, missing, or a symlink." return "${ERR_INVLD_CHAR}" fi build_real="$(realpath -e -- "${build_dir}")" target_real="$(realpath -e -- "${target_path}")" if [[ "${target_real}" == "${build_real}" ]]; then build_dir_safety_error "target subpath MUST NOT be the build directory itself." return "${ERR_INVLD_CHAR}" fi case "${target_real}" in "${build_real}"/*) ;; *) build_dir_safety_error "target subpath MUST stay below the build directory." return "${ERR_INVLD_CHAR}" ;; esac return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f require_builder_owned_subpath ####################################### # Remove all contents of the exact builder-owned build directory. # Globals: # CISS_BUILD_DIR_MARKER # Arguments: # 1: Build directory path. # Returns: # 0: on success # Non-zero: on failure ####################################### safe_clean_build_dir_contents() { declare build_dir="${1}" build_real="" require_builder_owned_build_dir "${build_dir}" || return "${?}" build_real="$(realpath -e -- "${build_dir}")" find "${build_real}" -mindepth 1 -maxdepth 1 -xdev ! -name "${CISS_BUILD_DIR_MARKER}" -exec rm -rf --one-file-system -- {} + return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f safe_clean_build_dir_contents ####################################### # Remove one exact builder-owned subpath. # Globals: # None # Arguments: # 1: Build-directory-path. # 2: Target-path below build-directory. # Returns: # 0: on success # Non-zero: on failure ####################################### safe_remove_builder_subpath() { declare build_dir="${1}" target_path="${2}" target_real="" require_builder_owned_subpath "${build_dir}" "${target_path}" || return "${?}" target_real="$(realpath -e -- "${target_path}")" rm -rf --one-file-system -- "${target_real}" return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f safe_remove_builder_subpath # vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh