mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-15 05:30:51 +01:00
Follow-up to9085a4e5, which recorded services_units.sh at 84.51% because its remaining branches sit behind [[ -f /etc/systemd/system/*.service ]] -- absolute root-owned paths that exist on this machine, so the else-branch never runs and no PATH shim can redirect it. All 87 such paths across the seven check libs now go through $SYSROOT, read from SERVICES_ROOT. The prefix is deliberately NOT defaulted. Three commands in hosts_fix_resolved run outside `run` and so are not stopped by --dry-run (chattr -i on resolved.conf.d, find -delete over its *.conf, and an append to resolved.conf); with an empty default, a test that forgot to set SERVICES_ROOT would really delete this machine's resolved.conf drop-ins. Unset is therefore a hard error and only an explicitly empty value means the real filesystem. The harness now owns PATH outright: $FAKE_BIN for shims and staged fake apps, $REAL_BIN for a curated set of symlinks to genuine coreutils. The machine's /usr/bin is deliberately absent -- a really-installed discord and chromium otherwise make the "no target apps present" branches untestable, which is exactly what happened until these tests caught it. Coverage, measured against run_all.sh because two test files jointly cover services_common.sh after the 250-cap split: common 55/55, report 27/27, units 72/72 (was 84.51%), wrappers 100/100, apps 66/66, browser 79/79 -- seven libs at 100%. services_hosts.sh reads 86.05% and services_hosts_fix.sh 97.30%, and those 14 lines are a kcov attribution artifact rather than a gap. Falsified rather than assumed: the suite prints "51 passed, 0 failed" when run under kcov, so no group is skipped, and every supposedly-uncovered line is an issues+=("...") whose string can only reach stdout through the print loop below it -- with a passing assertion matching that exact string. Two falsification checks, both re-run after the rewrite: `--status --dry-run` as root stays byte-identical to rev6691cb26modulo the Date: line, and the sha256sums of /etc/hosts, /etc/nsswitch.conf and /etc/systemd/resolved.conf plus the contents of resolved.conf.d are unchanged across a full suite run. The second one is what makes a missed prefix site detectable instead of a matter of inspection: the repair paths really do write. 220 assertions across 8 test files. Also fixes a bug in the harness's own lsattr shim, where printf with a leading-dash format string parsed the dashes as options and silently produced nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
177 lines
5.9 KiB
Bash
Executable File
177 lines
5.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# lib/services_hosts.sh — the /etc/hosts blocking stack check: the hosts file
|
|
# itself, its immutable attribute, the three guard-lib file-guard instances
|
|
# (hosts/nsswitch/resolved), the pacman unlock/relock hooks, and the
|
|
# systemd-resolved settings that can silently bypass /etc/hosts.
|
|
#
|
|
# Sourced by check_and_enable_services.sh. The repair half lives in
|
|
# services_hosts_fix.sh so both files stay under the 250-line cap.
|
|
|
|
# Absolute paths the checks below probe are prefixed with $SYSROOT, which is
|
|
# empty in production and a fixture tree under test. It is deliberately NOT
|
|
# defaulted here: several repairs in this family write outside `run` (chattr,
|
|
# find -delete, an append to resolved.conf), so a test that forgot to set it
|
|
# would edit the real /etc. Unset is a hard error; empty is the real filesystem.
|
|
SYSROOT="${SERVICES_ROOT?SERVICES_ROOT must be set (empty = the real filesystem)}"
|
|
|
|
check_hosts() {
|
|
header "Hosts File and Guards"
|
|
|
|
local status="ok"
|
|
local issues=()
|
|
|
|
# Check /etc/hosts exists and has content
|
|
if [[ -f "${SYSROOT}/etc/hosts" ]]; then
|
|
local line_count
|
|
line_count=$(wc -l <"${SYSROOT}/etc/hosts")
|
|
if [[ $line_count -gt 100 ]]; then
|
|
msg "/etc/hosts exists with $line_count lines (StevenBlack list likely installed)"
|
|
else
|
|
issues+=("/etc/hosts has only $line_count lines (StevenBlack list may not be installed)")
|
|
status="warning"
|
|
fi
|
|
else
|
|
issues+=("/etc/hosts does not exist")
|
|
status="error"
|
|
fi
|
|
|
|
# Check if hosts file is immutable
|
|
local attrs
|
|
attrs=$(lsattr "${SYSROOT}/etc/hosts" 2>/dev/null | cut -d' ' -f1 || echo "")
|
|
if [[ $attrs == *"i"* ]]; then
|
|
msg "/etc/hosts has immutable attribute set"
|
|
else
|
|
issues+=("/etc/hosts is not immutable")
|
|
status="warning"
|
|
fi
|
|
|
|
# Check cached hosts file
|
|
if [[ -f "${SYSROOT}/etc/hosts.stevenblack" ]]; then
|
|
msg "StevenBlack cache exists at ${SYSROOT}/etc/hosts.stevenblack"
|
|
else
|
|
issues+=("StevenBlack cache not found")
|
|
status="warning"
|
|
fi
|
|
|
|
# Check the guard-lib "hosts" file-guard instance (path unit active +
|
|
# immutable attribute on the target). Replaces the legacy hosts-guard.path
|
|
# / hosts-bind-mount.service / enforce-hosts.sh checks now that this
|
|
# machine's migration to guard-lib is complete — see
|
|
# migrate_hosts_guard_to_guard_lib.sh.
|
|
if guard_lib_instance_healthy hosts; then
|
|
msg "guard-lib 'hosts' instance is active and enforced"
|
|
else
|
|
issues+=("guard-lib 'hosts' instance is missing or unhealthy")
|
|
status="error"
|
|
fi
|
|
|
|
# Check pacman hooks
|
|
if hosts_pacman_hooks_installed; then
|
|
msg "Pacman hooks installed"
|
|
else
|
|
issues+=("Pacman hooks not installed")
|
|
status="warning"
|
|
fi
|
|
|
|
# Check nsswitch.conf has 'files' in hosts line
|
|
if [[ -f "${SYSROOT}/etc/nsswitch.conf" ]]; then
|
|
local nsswitch_hosts
|
|
nsswitch_hosts=$(grep '^hosts:' "${SYSROOT}/etc/nsswitch.conf" 2>/dev/null || echo "")
|
|
if echo "$nsswitch_hosts" | grep -qw 'files'; then
|
|
msg "nsswitch.conf hosts line includes 'files'"
|
|
else
|
|
issues+=("nsswitch.conf hosts line missing 'files' — ${SYSROOT}/etc/hosts is bypassed!")
|
|
status="error"
|
|
fi
|
|
else
|
|
issues+=("/etc/nsswitch.conf does not exist")
|
|
status="error"
|
|
fi
|
|
|
|
# Check the guard-lib "nsswitch" file-guard instance
|
|
if guard_lib_instance_healthy nsswitch; then
|
|
msg "guard-lib 'nsswitch' instance is active and enforced"
|
|
else
|
|
issues+=("guard-lib 'nsswitch' instance is missing or unhealthy")
|
|
status="error"
|
|
fi
|
|
|
|
# Check resolved.conf has ReadEtcHosts=yes
|
|
if [[ -f "${SYSROOT}/etc/systemd/resolved.conf" ]]; then
|
|
local read_etc_hosts
|
|
read_etc_hosts=$(grep -E '^\s*ReadEtcHosts\s*=' "${SYSROOT}/etc/systemd/resolved.conf" 2>/dev/null |
|
|
tail -1 | sed 's/.*=\s*//' | tr -d '[:space:]')
|
|
if [[ "$read_etc_hosts" == "yes" ]]; then
|
|
msg "resolved.conf ReadEtcHosts=yes"
|
|
else
|
|
issues+=("resolved.conf ReadEtcHosts='$read_etc_hosts' — ${SYSROOT}/etc/hosts is bypassed by systemd-resolved!")
|
|
status="error"
|
|
fi
|
|
|
|
# Check DNSOverTLS is not enabled
|
|
local dns_over_tls
|
|
dns_over_tls=$(grep -E '^\s*DNSOverTLS\s*=' "${SYSROOT}/etc/systemd/resolved.conf" 2>/dev/null |
|
|
tail -1 | sed 's/.*=\s*//' | tr -d '[:space:]') || true
|
|
if [[ -z "$dns_over_tls" || "$dns_over_tls" == "no" ]]; then
|
|
msg "resolved.conf DNSOverTLS is disabled"
|
|
else
|
|
issues+=("resolved.conf DNSOverTLS='$dns_over_tls' — can bypass ${SYSROOT}/etc/hosts!")
|
|
status="error"
|
|
fi
|
|
|
|
# Check for drop-in overrides
|
|
if [[ -d "${SYSROOT}/etc/systemd/resolved.conf.d" ]]; then
|
|
local dropin_count
|
|
dropin_count=$(find "${SYSROOT}/etc/systemd/resolved.conf.d" -name '*.conf' -type f 2>/dev/null | wc -l)
|
|
if [[ "$dropin_count" -gt 0 ]]; then
|
|
issues+=("Found $dropin_count resolved.conf drop-in override(s) — potential bypass!")
|
|
status="error"
|
|
fi
|
|
fi
|
|
|
|
# Check immutable attribute
|
|
if command -v lsattr &>/dev/null; then
|
|
if lsattr "${SYSROOT}/etc/systemd/resolved.conf" 2>/dev/null | grep -q '.*i.*e.*'; then
|
|
msg "resolved.conf has immutable attribute"
|
|
else
|
|
issues+=("resolved.conf missing immutable attribute")
|
|
[[ "$status" == "ok" ]] && status="warning"
|
|
fi
|
|
fi
|
|
else
|
|
issues+=("/etc/systemd/resolved.conf does not exist")
|
|
[[ "$status" == "ok" ]] && status="warning"
|
|
fi
|
|
|
|
# Check the guard-lib "resolved" file-guard instance
|
|
if guard_lib_instance_healthy resolved; then
|
|
msg "guard-lib 'resolved' instance is active and enforced"
|
|
else
|
|
issues+=("guard-lib 'resolved' instance is missing or unhealthy")
|
|
status="error"
|
|
fi
|
|
|
|
# Report issues
|
|
if [[ $status != "ok" ]]; then
|
|
for issue in "${issues[@]}"; do
|
|
if [[ $status == "error" ]]; then
|
|
err "$issue"
|
|
else
|
|
warn "$issue"
|
|
fi
|
|
done
|
|
((ISSUES_FOUND++)) || true
|
|
|
|
if [[ $STATUS_ONLY -eq 0 ]]; then
|
|
# The repair half lives in services_hosts_fix.sh; it returns 0 only
|
|
# when the post-repair re-verify passes, so a promotion to "ok" here
|
|
# always reflects a re-checked machine rather than a fix attempt.
|
|
if hosts_repair_all; then
|
|
status="ok"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
set_service_status "hosts" "$status"
|
|
}
|