Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 71 additions & 6 deletions autorun/wipe-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,74 @@ unfreeze_drive() {
return 0
}

# --- Overwrite pass count (opt-in multi-pass for random-overwrite methods) ---
# NIST SP 800-88 Rev. 1 treats a single random overwrite pass as sufficient, so
# the default is 1. Operators can raise it for stricter organizational policies
# (e.g. legacy DoD 5220.22-M expectations). Erase/crypto methods are unaffected.
# Resolution order (highest precedence first):
# 1. WIPE_OVERWRITE_ROUNDS environment variable
# 2. ssr.overwrite-rounds=N on the kernel cmdline
# 3. WIPE_OVERWRITE_ROUNDS=N in the baked wipe.env at the USB root
# 4. default: 1
# Only positive integers are accepted; an invalid value at one source is warned
# about and the next source is tried, so a bad value never silently drops passes.
_valid_rounds() { [[ "$1" =~ ^[1-9][0-9]*$ ]]; }

resolve_overwrite_rounds() {
local c
local cmdline_file="${WIPE_CMDLINE_FILE:-/proc/cmdline}"
local cfg_file="${WIPE_CONFIG_FILE:-$(dirname "${BASH_SOURCE[0]}")/../wipe.env}"

# 1. environment
c="${WIPE_OVERWRITE_ROUNDS:-}"
if [[ -n "$c" ]]; then
if _valid_rounds "$c"; then printf '%s' "$c"; return 0; fi
log "WARN: ignoring invalid WIPE_OVERWRITE_ROUNDS='$c' (env)" >&2
fi

# 2. kernel cmdline
if [[ -r "$cmdline_file" ]]; then
c=$(tr ' ' '\n' < "$cmdline_file" 2>/dev/null | grep -E '^ssr\.overwrite-rounds=' | tail -1 || true)
c="${c#ssr.overwrite-rounds=}"
if [[ -n "$c" ]]; then
if _valid_rounds "$c"; then printf '%s' "$c"; return 0; fi
log "WARN: ignoring invalid ssr.overwrite-rounds='$c' (cmdline)" >&2
fi
fi

# 3. baked config file
if [[ -r "$cfg_file" ]]; then
c=$(grep -E '^[[:space:]]*WIPE_OVERWRITE_ROUNDS=' "$cfg_file" 2>/dev/null | tail -1 | cut -d'=' -f2 | tr -d '[:space:]' || true)
if [[ -n "$c" ]]; then
if _valid_rounds "$c"; then printf '%s' "$c"; return 0; fi
log "WARN: ignoring invalid WIPE_OVERWRITE_ROUNDS='$c' ($cfg_file)" >&2
fi
fi

# 4. default
printf '1'
}

# Overwrite pass count for the audit log: the effective rounds for random-
# overwrite drive types, or the JSON literal "null" for erase/crypto types that
# do not overwrite. (A frozen sata-ssd falls back to overwrite at runtime; the
# audit classifies by nominal method, so that exceptional path records null.)
overwrite_rounds_for_type() {
case "$1" in
nvme-ssd | nvme-ssd-sed | sata-ssd | sata-ssd-sed) printf 'null' ;;
*) resolve_overwrite_rounds ;;
esac
}

wipe_drive() {
local dev="$1"
local dtype="$2"
local method_info
method_info=$(nist_method_for_type "$dtype")
local method_desc
method_desc=$(echo "$method_info" | cut -d'|' -f2)
local rounds
rounds=$(resolve_overwrite_rounds)

log "Wiping /dev/$dev (type=$dtype, method=$method_desc)"

Expand Down Expand Up @@ -155,8 +216,8 @@ wipe_drive() {
# ref: hdparm --security-erase-enhanced (ATA secure erase);
# falls back to nwipe random overwrite if the drive is frozen.
if ! unfreeze_drive "$dev"; then
log "FALLBACK: frozen drive, using nwipe overwrite instead"
nwipe --autonuke --method=random --rounds=1 --verify=last "/dev/$dev" 2>&1
log "FALLBACK: frozen drive, using nwipe overwrite instead ($rounds pass(es))"
nwipe --autonuke --method=random --rounds="$rounds" --verify=last "/dev/$dev" 2>&1
return $?
fi
local wipe_pass
Expand All @@ -171,13 +232,13 @@ wipe_drive() {
return $?
;;
hdd)
# ref: nwipe -m random -r 1 --verify=last (random overwrite + verify)
nwipe --autonuke --method=random --rounds=1 --verify=last "/dev/$dev" 2>&1
# ref: nwipe -m random -r <rounds> --verify=last (random overwrite + verify)
nwipe --autonuke --method=random --rounds="$rounds" --verify=last "/dev/$dev" 2>&1
return $?
;;
usb-flash|*)
# ref: nwipe -m random (best-effort random overwrite, no verify)
nwipe --autonuke --method=random "/dev/$dev" 2>&1
# ref: nwipe -m random -r <rounds> (best-effort random overwrite, no verify)
nwipe --autonuke --method=random --rounds="$rounds" "/dev/$dev" 2>&1
return $?
;;
esac
Expand Down Expand Up @@ -297,6 +358,9 @@ append_drive_audit() {
local started="$9" completed="${10}" result="${11}"
local verify_ok="${12:-}" errors="${13:-}"

local overwrite_rounds
overwrite_rounds=$(overwrite_rounds_for_type "$dtype")

local entry
entry=$(cat << EOF
{
Expand All @@ -307,6 +371,7 @@ append_drive_audit() {
"type": "${dtype}",
"nist_tier": "${nist_tier}",
"method": "${method_desc}",
"overwrite_rounds": ${overwrite_rounds},
"started_at": "${started}",
"completed_at": "${completed}",
"verify": {"sectors_sampled": 1024, "all_zero_or_random": ${verify_ok:-false}},
Expand Down
2 changes: 2 additions & 0 deletions autorun/wipe-now.sh
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ for entry in "${drives[@]}"; do
fi

completed=$(date -u +%Y-%m-%dT%H:%M:%SZ)
overwrite_rounds=$(overwrite_rounds_for_type "$dtype")

# Write per-drive result to a temp file for the parent to collect
cat > "$WIPE_TMP_DIR/wipe-result-${name}.json" << DRIVEEOF
Expand All @@ -96,6 +97,7 @@ for entry in "${drives[@]}"; do
"type": "${dtype}",
"nist_tier": "${nist_tier}",
"method": "${method_desc}",
"overwrite_rounds": ${overwrite_rounds},
"started_at": "${started}",
"completed_at": "${completed}",
"verify": {"sectors_sampled": 1024, "all_zero_or_random": ${verify_ok}},
Expand Down
2 changes: 2 additions & 0 deletions bin/build-wipe-usb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ build_image() {
sudo cp "${REPO_ROOT}/autorun/wipe-wizard.sh" "$mp/autorun/"
sudo cp "${REPO_ROOT}/autorun/wipe-now.sh" "$mp/autorun/"
sudo chmod +x "$mp/autorun/"*.sh
sudo cp "${REPO_ROOT}/config/wipe.env" "$mp/wipe.env"
sudo touch "$mp/.simsys-wipe"
sudo umount "$mp"
rmdir "$mp"
Expand All @@ -129,6 +130,7 @@ build_image() {
mcopy -i "$fat_img" "${REPO_ROOT}/autorun/wipe-lib.sh" ::/autorun/
mcopy -i "$fat_img" "${REPO_ROOT}/autorun/wipe-wizard.sh" ::/autorun/
mcopy -i "$fat_img" "${REPO_ROOT}/autorun/wipe-now.sh" ::/autorun/
mcopy -i "$fat_img" "${REPO_ROOT}/config/wipe.env" ::/wipe.env
marker_file="$(mktemp)"
mcopy -i "$fat_img" "$marker_file" ::/.simsys-wipe
rm -f "$marker_file"
Expand Down
17 changes: 17 additions & 0 deletions config/wipe.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Tunable defaults for the NIST 800-88 wipe USB.
# Baked onto the FAT32 root of the wipe stick at build time. To change after
# flashing, edit /wipe.env on the FAT32 partition of the wipe stick.

# Number of random-overwrite passes for overwrite-based methods (HDD, USB
# flash, and the frozen-SSD fallback). Only positive integers are accepted.
#
# The default of 1 is NIST SP 800-88 Rev. 1 compliant — a single random pass
# is sufficient for modern media. Raise this only if an organizational policy
# mandates stricter-than-NIST multi-pass overwrite (e.g. legacy DoD
# 5220.22-M). Extra passes cost proportionally more wall-clock time and do
# nothing for SSD erase/crypto methods (which ignore this setting).
#
# May also be overridden per boot via the kernel cmdline
# (ssr.overwrite-rounds=N) or the WIPE_OVERWRITE_ROUNDS environment variable,
# both of which take precedence over this file.
WIPE_OVERWRITE_ROUNDS=1
22 changes: 22 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,28 @@ can't touch):
> of Memtest86+ that adds `mt86p.cfg` config support, finite pass counts, and
> ACPI auto-poweroff so this stage is also walk-away.

## Wipe USB — overwrite pass count

Overwrite-based methods (HDD, USB flash, and the frozen-SSD fallback) default to
a **single** random-overwrite pass. This is compliant with NIST SP 800-88 Rev. 1
— a single pass is sufficient for modern media, and additional passes are **not
required**. SSD erase/crypto methods ignore this setting entirely.

If an organizational policy mandates stricter-than-NIST multi-pass overwrite
(e.g. legacy DoD 5220.22-M), raise the pass count via any of these — highest
precedence first:

1. **Environment variable** (scripted/direct runs): `WIPE_OVERWRITE_ROUNDS=3`
2. **Kernel cmdline** (per boot): add `ssr.overwrite-rounds=3` to the boot entry
3. **Baked default**: edit `WIPE_OVERWRITE_ROUNDS` in `/wipe.env` on the FAT32
partition of the wipe stick (set at build time from `config/wipe.env`)

Only positive integers are accepted; an invalid value is ignored (with a warning)
and the next source is tried, so a bad value never silently reduces the pass
count. Each extra pass costs proportionally more wall-clock time. The pass count
actually used is recorded per drive in the JSON audit log as `overwrite_rounds`
(`null` for erase/crypto methods).

## Troubleshooting

- **Box didn't power off after 2.5h.** SystemRescue may have crashed mid-test.
Expand Down
84 changes: 84 additions & 0 deletions tests/test_overwrite_rounds.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/bin/bash
# Tests for the opt-in overwrite pass-count resolver (resolve_overwrite_rounds)
# and the audit mapping (overwrite_rounds_for_type) in wipe-lib.sh.

set -euo pipefail

TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT

REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
# shellcheck disable=SC1091
source "$REPO_ROOT/autorun/wipe-lib.sh"

fails=0
check() {
local name="$1" expected="$2" actual="$3"
if [[ "$actual" == "$expected" ]]; then
echo "PASS: $name -> $actual"
else
echo "FAIL: $name — expected [$expected], got [$actual]"
fails=$((fails + 1))
fi
}

# Empty cmdline / config by default; point the source overrides at temp files.
EMPTY_CMDLINE="$TMP/cmdline.empty"
: > "$EMPTY_CMDLINE"
NO_CFG="$TMP/does-not-exist.env"

rounds() { WIPE_CMDLINE_FILE="${1:-$EMPTY_CMDLINE}" WIPE_CONFIG_FILE="${2:-$NO_CFG}" resolve_overwrite_rounds 2>/dev/null; }

echo "== precedence & default =="

unset WIPE_OVERWRITE_ROUNDS
check "all sources unset -> default 1" "1" "$(rounds)"

# Baked config file only.
echo "WIPE_OVERWRITE_ROUNDS=3" > "$TMP/wipe.env"
check "baked file only -> 3" "3" "$(rounds "$EMPTY_CMDLINE" "$TMP/wipe.env")"

# Kernel cmdline overrides the file.
echo "BOOT_IMAGE=/vmlinuz quiet ssr.overwrite-rounds=5 splash" > "$TMP/cmdline.5"
check "cmdline overrides file (5 vs 3)" "5" "$(rounds "$TMP/cmdline.5" "$TMP/wipe.env")"

# Env overrides everything.
check "env overrides cmdline+file" "7" "$(WIPE_OVERWRITE_ROUNDS=7 rounds "$TMP/cmdline.5" "$TMP/wipe.env")"

echo "== validation / fall-through =="

# Invalid env falls through to a valid cmdline.
check "invalid env -> cmdline" "5" "$(WIPE_OVERWRITE_ROUNDS=abc rounds "$TMP/cmdline.5" "$NO_CFG")"

# Invalid env + invalid cmdline -> valid file.
echo "ssr.overwrite-rounds=-1" > "$TMP/cmdline.bad"
echo "WIPE_OVERWRITE_ROUNDS=4" > "$TMP/wipe4.env"
check "invalid env+cmdline -> file" "4" "$(WIPE_OVERWRITE_ROUNDS=0 rounds "$TMP/cmdline.bad" "$TMP/wipe4.env")"

# Everything invalid -> default 1 (never 0 / never silently drops passes).
echo "WIPE_OVERWRITE_ROUNDS=nope" > "$TMP/wipe.bad"
check "all invalid -> default 1" "1" "$(WIPE_OVERWRITE_ROUNDS=x rounds "$TMP/cmdline.bad" "$TMP/wipe.bad")"

# Zero is rejected (would be a dangerous no-op wipe).
check "zero rejected -> default 1" "1" "$(WIPE_OVERWRITE_ROUNDS=0 rounds)"

echo "== overwrite_rounds_for_type (audit mapping) =="

unset WIPE_OVERWRITE_ROUNDS
otype() { WIPE_CMDLINE_FILE="$EMPTY_CMDLINE" WIPE_CONFIG_FILE="$NO_CFG" overwrite_rounds_for_type "$1" 2>/dev/null; }

check "nvme-ssd -> null (erase)" "null" "$(otype nvme-ssd)"
check "nvme-ssd-sed -> null (crypto)" "null" "$(otype nvme-ssd-sed)"
check "sata-ssd -> null (erase)" "null" "$(otype sata-ssd)"
check "sata-ssd-sed -> null (crypto)" "null" "$(otype sata-ssd-sed)"
check "hdd -> 1 (overwrite, default)" "1" "$(otype hdd)"
check "usb-flash -> 1 (overwrite, default)" "1" "$(otype usb-flash)"
check "unknown type -> 1 (fallback overwrite)" "1" "$(otype weird-type)"
check "hdd honors env override" "2" "$(WIPE_OVERWRITE_ROUNDS=2 WIPE_CMDLINE_FILE="$EMPTY_CMDLINE" WIPE_CONFIG_FILE="$NO_CFG" overwrite_rounds_for_type hdd 2>/dev/null)"

if [[ $fails -eq 0 ]]; then
echo "All overwrite-rounds tests passed."
else
echo "$fails test(s) failed."
/bin/false
fi
Loading