release: restore ZUPT and harden source-only 5.2.2

This commit is contained in:
Cristian Cezar Moisés 2026-08-31 14:14:36 -03:00
commit ff99770bd0
205 changed files with 19627 additions and 13215 deletions

View file

@ -2,7 +2,7 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
#
# F-09 regression test (Zupt 2.3.1).
# F-09 regression test (VaptVupt 2.3.1).
#
# F-09 closed the per-block frame preface tamper window by:
# 1. Binding the canonical preface (block_type, codec_id, block_flags,
@ -12,18 +12,20 @@
# block's frame preface in read_enc_header (same pattern as F-07
# for the index block in v2.2.5).
#
# This test does the full exhaustive byte sweep on a small v1.6 PQ-SDK
# archive: every byte from 0 to N-1 is flipped one at a time, and we
# assert the extract fails for ALL of them. With pre-F-09 code this
# would show 15-18 silent acceptances; post-F-09 it must show zero.
# This test flips every serialized block-preface byte in a small v1.6 PBKDF2
# archive and asserts that each mutation is rejected. With pre-F-09 code this
# would show silent acceptances; post-F-09 it must show zero. A PQ-SDK archive
# receives the historical full-archive byte sweep when system libvuptsdk is
# enabled.
#
# Why limit to PQ-SDK encrypted: plaintext archives have no HMAC at
# all (XXH64 best-effort only), so per-byte coverage is intentionally
# weaker and a different, separately-tracked promise.
# Plaintext archives have no HMAC (XXH64 best-effort only), so per-byte
# coverage is intentionally weaker and a different, separately-tracked
# promise.
set -u
set -Eeuo pipefail
ZUPT="${ZUPT_BIN:-./zupt}"
repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd -P)
ZUPT="${ZUPT_BIN:-$repo_root/zupt}"
case "$ZUPT" in
/*) ;;
*) ZUPT="$PWD/$ZUPT" ;;
@ -33,58 +35,129 @@ if [ ! -x "$ZUPT" ]; then
echo "$ZUPT not found — run 'make' first" >&2
exit 1
fi
if ! command -v python3 >/dev/null 2>&1; then
echo ' ✗ python3 is required for byte-level archive mutations' >&2
exit 1
fi
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
cd "$TMPDIR"
cd "$TMPDIR" || exit 1
"$ZUPT" keygen --sdk -o k.priv >/dev/null 2>&1
echo "F-09 regression test payload" > input.txt
"$ZUPT" c --pq-sdk k.priv.pub a.zupt input.txt >/dev/null 2>&1
printf 'F-09 regression test payload\n' > input.txt
printf 'source-only-preface-password\n' > password.txt
chmod 600 password.txt
SZ=$(wc -c < a.zupt)
if [ "$SZ" -lt 100 ] || [ "$SZ" -gt 10000 ]; then
echo " ✗ unexpected archive size $SZ" >&2
exit 1
fi
run_sweep() {
local label=$1
local archive=$2
local scope=$3
shift 3
local -a auth_options=("$@")
local -a positions=()
local size
local position
local positions_output
local tested=0
local undetected_positions=''
local undetected_count
local clean_dir="clean-$label"
# Sanity: clean archive extracts.
mkdir -p clean
( cd clean && "$ZUPT" x --pq-sdk ../k.priv ../a.zupt >/dev/null 2>&1 )
if [ ! -f clean/input.txt ]; then
echo " ✗ clean v1.6 PQ-SDK archive doesn't extract" >&2
exit 1
fi
# Exhaustive sweep.
echo " [F-09: exhaustive byte sweep of $SZ-byte v1.6 PQ-SDK archive]"
UNDETECTED_POSITIONS=""
TAMPER_SAMPLED=0
for POS in $(seq 0 $((SZ - 1))); do
cp a.zupt t.zupt
python3 -c "
b=bytearray(open('t.zupt','rb').read())
b[$POS] ^= 1
open('t.zupt','wb').write(bytes(b))"
rm -rf out && mkdir out
( cd out && "$ZUPT" x --pq-sdk ../k.priv ../t.zupt >/dev/null 2>&1 )
TAMPER_SAMPLED=$((TAMPER_SAMPLED + 1))
if [ -f out/input.txt ]; then
UNDETECTED_POSITIONS="$UNDETECTED_POSITIONS $POS"
size=$(wc -c < "$archive")
if [ "$size" -lt 100 ] || [ "$size" -gt 10000 ]; then
echo "$label archive has unexpected size $size" >&2
return 1
fi
done
UNDETECTED_COUNT=$(echo $UNDETECTED_POSITIONS | wc -w)
mkdir "$clean_dir"
if ! "$ZUPT" extract "${auth_options[@]}" -o "$clean_dir" "$archive" \
>/dev/null 2>&1 ||
! cmp input.txt "$clean_dir/input.txt" >/dev/null 2>&1; then
echo " ✗ clean $label archive does not extract byte-exact" >&2
return 1
fi
echo ""
echo " ───────────────────────────────────────"
if [ "$UNDETECTED_COUNT" = 0 ]; then
echo " F-09 regression: $TAMPER_SAMPLED tamper positions tested, 0 silent-accepted ✓"
echo " ───────────────────────────────────────"
exit 0
else
echo " F-09 regression: $UNDETECTED_COUNT silent-accepted positions (must be 0)"
echo " positions:$UNDETECTED_POSITIONS"
echo " ───────────────────────────────────────"
if [ "$scope" = preface ]; then
if ! positions_output=$(python3 \
"$repo_root/tests/archive_surgery.py" preface-positions \
"$archive") || [ -z "$positions_output" ]; then
echo " ✗ could not locate $label block prefaces" >&2
return 1
fi
while IFS= read -r position; do
[ -n "$position" ] && positions+=("$position")
done <<<"$positions_output"
echo " [F-09: all block-preface bytes in $size-byte $label archive]"
elif [ "$scope" = full ]; then
for ((position = 0; position < size; position++)); do
positions+=("$position")
done
echo " [F-09: exhaustive byte sweep of $size-byte $label archive]"
else
echo " ✗ internal error: unknown sweep scope $scope" >&2
return 1
fi
for position in "${positions[@]}"; do
if ! python3 - "$archive" t.zupt "$position" <<'PY'
import pathlib
import sys
source = pathlib.Path(sys.argv[1]).read_bytes()
mutated = bytearray(source)
mutated[int(sys.argv[3])] ^= 0x01
pathlib.Path(sys.argv[2]).write_bytes(mutated)
PY
then
echo " ✗ could not mutate $label archive byte $position" >&2
return 1
fi
tested=$((tested + 1))
if "$ZUPT" test "${auth_options[@]}" t.zupt >/dev/null 2>&1; then
undetected_positions="$undetected_positions $position"
fi
done
undetected_count=$(printf '%s\n' "$undetected_positions" | wc -w)
if [ "$undetected_count" -ne 0 ]; then
echo "$label: $undetected_count silent-accepted positions (must be 0)"
echo " positions:$undetected_positions"
return 1
fi
echo "$label: $tested tamper positions tested, 0 accepted"
}
FAIL=0
if ! "$ZUPT" compress --store --kdf pbkdf2 --pass-file password.txt \
pbkdf2.zupt input.txt >/dev/null 2>&1; then
echo ' ✗ could not create source-only PBKDF2 archive' >&2
exit 1
fi
if ! run_sweep PBKDF2 pbkdf2.zupt preface --pass-file password.txt; then
FAIL=$((FAIL + 1))
fi
version=$("$ZUPT" --version 2>&1)
if grep -Fq 'libvuptsdk=enabled' <<<"$version"; then
if ! "$ZUPT" keygen --sdk -o k.priv >/dev/null 2>&1 ||
! "$ZUPT" compress --store --pq-sdk k.priv.pub pq-sdk.zupt \
input.txt >/dev/null 2>&1; then
echo ' ✗ could not create PQ-SDK archive' >&2
FAIL=$((FAIL + 1))
elif ! run_sweep PQ-SDK pq-sdk.zupt full --pq-sdk k.priv; then
FAIL=$((FAIL + 1))
fi
else
echo ' SKIP: additional PQ-SDK sweep needs WITH_SDK=1 and system libvuptsdk'
fi
echo
echo " ───────────────────────────────────────"
if [ "$FAIL" -eq 0 ]; then
echo " F-09 regression: PASS"
else
echo " F-09 regression: FAIL ($FAIL archive variants)"
fi
echo " ───────────────────────────────────────"
[ "$FAIL" -eq 0 ]