magisk-zapret2/tests/shell/owner-state-v8.sh
loop-uh eb55359f27 Remove dead legacy migration, track journals, and WAL reader
The legacy direct-rule migration ran unconditionally on every start and
stop to prove and retire firewall rules written by pre-chain module
generations. Reboot is the migration barrier: iptables state does not
survive it and updated module code never runs before it, so the entire
transactional prover (~665 lines) is replaced by deletion-on-sight of
its marker. The track-journal grammar (~324 lines) and the
firewall-teardown WAL reader validated formats with no remaining
producer; obsolete artifacts are now retired wholesale under the
lifecycle lock by retire_obsolete_state_artifacts. Also drops the dead
owner-generation reload cluster and the write-only nfqws2.cmdline
mirror.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 02:16:40 +03:00

176 lines
9.7 KiB
Shell

#!/bin/sh
set -eu
ROOT=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd)
TMP=${Z2_TEST_TMP:?}
CASE="$TMP/owner-state-v8"
MOD="$CASE/module"
STATE="$CASE/state"
MOCK="$CASE/bin"
BOOT_A=11111111-1111-1111-1111-111111111111
BOOT_B=22222222-2222-2222-2222-222222222222
fail() { echo "FAIL: owner-state-v8: $*" >&2; exit 1; }
REAL_CHMOD=$(command -v chmod)
chmod() { :; }
sync() { :; }
sha256sum() { cat >/dev/null; echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; }
mkdir -p "$MOD/zapret2/scripts" "$STATE" "$MOCK"
chmod 0700 "$STATE"
cp "$ROOT/zapret2/scripts/common.sh" "$MOD/zapret2/scripts/common.sh"
: > "$MOD/zapret2/nfqws2"
cat > "$MOD/zapret2/install-generation.meta" <<EOF
version=1
module_dir=$MOD
generation=owner-v8-install
archive_sha256=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
EOF
chmod 0600 "$MOD/zapret2/install-generation.meta"
cat > "$MOCK/iptables" <<'EOF'
#!/bin/sh
exit 0
EOF
"$REAL_CHMOD" 0755 "$MOCK/iptables"
export STATE_DIR="$STATE" SCRIPT_DIR="$MOD/zapret2/scripts" ZAPRET_DIR="$MOD/zapret2" MODDIR="$MOD" PATH="$MOCK:$PATH"
. "$SCRIPT_DIR/common.sh"
state_file_is_secure() { [ -f "$1" ]; }
path_mode_is_0600() { :; }
path_uid_is_root() { :; }
path_nlink_is_one() { :; }
state_file_target_is_safe() { [ ! -L "$1" ]; }
Z2_TEST_CURRENT_BOOT="$BOOT_A"
Z2_TEST_PROCESS=dead
Z2_TEST_SCAN=clean
Z2_TEST_FIREWALL=absent
Z2_TEST_LOCKED=0
read_current_boot_id() {
[ "${Z2_TEST_BOOT_QUERY:-ok}" = ok ] || return 1
CURRENT_BOOT_ID="$Z2_TEST_CURRENT_BOOT"
}
verify_nfqws_pid() { [ "$Z2_TEST_PROCESS" = live ]; }
scan_exact_owned_nfqws() {
OWNED_SCAN_PIDS=""
case "$Z2_TEST_SCAN" in
clean) return 0 ;;
live) OWNED_SCAN_PIDS=123; return 0 ;;
*) return 1 ;;
esac
}
owned_family_present() {
case "$Z2_TEST_FIREWALL" in absent) return 1;; present) return 0;; *) return 2;; esac
}
caller_holds_exact_lifecycle_lock() { [ "$Z2_TEST_LOCKED" = 1 ]; }
QNUM=200; PORTS_TCP=80,443; PORTS_UDP=443; TCP_PKT_OUT=20; TCP_PKT_IN=10; UDP_PKT_OUT=20; UDP_PKT_IN=10; PKT_OUT=20; PKT_IN=10; DESYNC_MARK=0x40000000
FIREWALL_TAG=AbCdEf1234; ZAPRET2_OUT=Z2O_AbCdEf1234; ZAPRET2_IN=Z2I_AbCdEf1234; PENDING_OWNER_GENERATION=owner-v8
IPV4_CONNBYTES=1; IPV4_MULTIPORT=1; IPV4_MARK=1; IPV6_CONNBYTES=1; IPV6_MULTIPORT=1; IPV6_MARK=1
ARGV_SHA256=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
prepare_owner_generation_spec 1 0 || fail "could not prepare canonical owner generation"
write_owner_state 123 456 "$ARGV_SHA256" 200 owner-v8 active || fail "could not write v8 owner"
chmod 0600 "$OWNER_STATE"
cp "$OWNER_STATE" "$CASE/owner.v8"
[ "$(sed -n '1p' "$CASE/owner.v8")" = version=8 ] || fail "writer did not publish v8"
[ "$(awk -F= '{ printf "%s%s", NR==1?"":"|", $1 }' "$CASE/owner.v8")" = "$OWNER_STATE_V8_FIELD_SEQUENCE" ] || fail "v8 field order is not canonical"
grep -Fqx "boot_id=$BOOT_A" "$CASE/owner.v8" || fail "writer did not bind current boot"
grep -Fqx "argv_sha256=$ARGV_SHA256" "$CASE/owner.v8" || fail "writer did not publish the command digest"
[ "$(wc -c < "$CASE/owner.v8")" -lt 4096 ] || fail "v8 owner metadata is not compact"
OWNER_WRITE_QNUM=""; OWNER_WRITE_PORTS_TCP=""; OWNER_WRITE_PORTS_UDP=""; OWNER_WRITE_STUN_PORTS=""
OWNER_WRITE_TCP_PKT_OUT=""; OWNER_WRITE_TCP_PKT_IN=""; OWNER_WRITE_UDP_PKT_OUT=""; OWNER_WRITE_UDP_PKT_IN=""; OWNER_WRITE_DESYNC_MARK=""; OWNER_WRITE_READY=0
read_owner_state && owner_state_is_current_boot || fail "v8 write/read round trip failed"
# Reload every generation field from the parsed owner state so the rewrite
# below must reproduce the committed record byte for byte.
OWNER_WRITE_QNUM="$OWNER_STATE_QNUM"; OWNER_WRITE_PORTS_TCP="$OWNER_STATE_PORTS_TCP"; OWNER_WRITE_PORTS_UDP="$OWNER_STATE_PORTS_UDP"; OWNER_WRITE_STUN_PORTS="$OWNER_STATE_STUN_PORTS"
OWNER_WRITE_FIREWALL_TAG="$OWNER_STATE_FIREWALL_TAG"; OWNER_WRITE_OUT_CHAIN="$OWNER_STATE_OUT_CHAIN"; OWNER_WRITE_IN_CHAIN="$OWNER_STATE_IN_CHAIN"
FIREWALL_TAG="$OWNER_STATE_FIREWALL_TAG"; ZAPRET2_OUT="$OWNER_STATE_OUT_CHAIN"; ZAPRET2_IN="$OWNER_STATE_IN_CHAIN"
OWNER_WRITE_TCP_PKT_OUT="$OWNER_STATE_TCP_PKT_OUT"; OWNER_WRITE_TCP_PKT_IN="$OWNER_STATE_TCP_PKT_IN"
OWNER_WRITE_UDP_PKT_OUT="$OWNER_STATE_UDP_PKT_OUT"; OWNER_WRITE_UDP_PKT_IN="$OWNER_STATE_UDP_PKT_IN"
OWNER_WRITE_DESYNC_MARK="$OWNER_STATE_DESYNC_MARK"
OWNER_WRITE_IPV4_ACTIVE="$OWNER_STATE_IPV4_ACTIVE"; OWNER_WRITE_IPV6_ACTIVE="$OWNER_STATE_IPV6_ACTIVE"
OWNER_WRITE_IPV4_CONNBYTES="$OWNER_STATE_IPV4_CONNBYTES"; OWNER_WRITE_IPV4_MULTIPORT="$OWNER_STATE_IPV4_MULTIPORT"; OWNER_WRITE_IPV4_MARK="$OWNER_STATE_IPV4_MARK"
OWNER_WRITE_IPV6_CONNBYTES="$OWNER_STATE_IPV6_CONNBYTES"; OWNER_WRITE_IPV6_MULTIPORT="$OWNER_STATE_IPV6_MULTIPORT"; OWNER_WRITE_IPV6_MARK="$OWNER_STATE_IPV6_MARK"
OWNER_WRITE_IPV4_RULES="$OWNER_STATE_IPV4_RULES"; OWNER_WRITE_IPV6_RULES="$OWNER_STATE_IPV6_RULES"; OWNER_WRITE_IPV4_SPEC="$OWNER_STATE_IPV4_SPEC"; OWNER_WRITE_IPV6_SPEC="$OWNER_STATE_IPV6_SPEC"
OWNER_WRITE_FIREWALL_FINGERPRINT="$OWNER_STATE_FIREWALL_FINGERPRINT"; OWNER_WRITE_INSTALL_GENERATION="$OWNER_STATE_INSTALL_GENERATION"; OWNER_WRITE_INSTALL_ARCHIVE_SHA256="$OWNER_STATE_INSTALL_ARCHIVE_SHA256"; OWNER_WRITE_SOURCE_GENERATION="$OWNER_STATE_GENERATION"; OWNER_WRITE_READY=1
write_owner_state 123 456 "$ARGV_SHA256" 200 owner-v8 active || fail "canonical owner rewrite failed"
cmp -s "$CASE/owner.v8" "$OWNER_STATE" || fail "v8 byte-for-byte round trip changed"
oversized_generation="$(awk 'BEGIN { for (i=0; i<65536; i++) printf "a" }')"
if write_owner_state 123 456 "$ARGV_SHA256" 200 "$oversized_generation" active; then
fail "oversized v8 owner metadata was published"
fi
cmp -s "$CASE/owner.v8" "$OWNER_STATE" || fail "rejected oversized owner changed the committed record"
# xt_connbytes is optional on Android kernels. The authenticated v8 owner
# contract represents upstream's outgoing-only KEEPALIVE topology with
# connbytes=0 and exactly one direction worth of payload rules.
IPV4_CONNBYTES=0
prepare_owner_generation_spec 1 0 || fail "could not prepare connbytes fallback owner generation"
[ "$OWNER_WRITE_IPV4_RULES" = 2 ] || fail "connbytes fallback owner rule count is not outgoing-only"
write_owner_state 123 456 "$ARGV_SHA256" 200 owner-v8-fallback active ||
fail "could not write connbytes fallback owner"
chmod 0600 "$OWNER_STATE"
read_owner_state && owner_state_is_current_boot || fail "connbytes fallback owner round trip failed"
[ "$OWNER_STATE_IPV4_CONNBYTES:$OWNER_STATE_IPV4_RULES" = 0:2 ] ||
fail "connbytes fallback owner topology changed after read"
IPV4_CONNBYTES=1
cp "$CASE/owner.v8" "$OWNER_STATE"; chmod 0600 "$OWNER_STATE"
read_owner_state && owner_state_is_current_boot || fail "canonical owner was not restored after fallback test"
assert_owner_rejected() {
candidate="$1"
cp "$candidate" "$OWNER_STATE"; chmod 0600 "$OWNER_STATE"
if read_owner_state; then fail "malformed owner was accepted: $candidate"; fi
}
sed '/^firewall_tag=/d' "$CASE/owner.v8" > "$CASE/missing"
sed 's/^firewall_tag=.*/firewall_tag=unsafe!/' "$CASE/owner.v8" > "$CASE/malformed"
sed 's/^out_chain=.*/out_chain=Z2O_wrongchain/' "$CASE/owner.v8" > "$CASE/bad-out-chain"
sed 's/^in_chain=.*/in_chain=Z2I_wrongchain/' "$CASE/owner.v8" > "$CASE/bad-in-chain"
sed 's/^argv_sha256=.*/argv_sha256=ABCDEF/' "$CASE/owner.v8" > "$CASE/bad-argv-digest"
sed '/^firewall_tag=/p' "$CASE/owner.v8" > "$CASE/duplicate"
cp "$CASE/owner.v8" "$CASE/unknown"; printf 'future=value\n' >> "$CASE/unknown"
for candidate in "$CASE/missing" "$CASE/malformed" "$CASE/bad-out-chain" "$CASE/bad-in-chain" "$CASE/bad-argv-digest" "$CASE/duplicate" "$CASE/unknown"; do
chmod 0600 "$candidate"; assert_owner_rejected "$candidate"
done
reset_case() {
cp "$1" "$OWNER_STATE"; chmod 0600 "$OWNER_STATE"
rm -f "$PIDFILE"
Z2_TEST_PROCESS=dead; Z2_TEST_SCAN=clean; Z2_TEST_FIREWALL=absent
Z2_TEST_LOCKED=0; Z2_TEST_BOOT_QUERY=ok; Z2_TEST_CURRENT_BOOT="$BOOT_A"
}
assert_preserved_failure() {
if recover_stale_owner_publication; then fail "$1 unexpectedly succeeded"; fi
[ -f "$OWNER_STATE" ] || fail "$1 retired owner evidence"
}
reset_case "$CASE/owner.v8"; Z2_TEST_PROCESS=live
recover_stale_owner_publication || fail "same-boot live v8 was not recognized"
[ -f "$OWNER_STATE" ] || fail "same-boot live owner was retired"
# A modules_update installer loads helpers from a candidate path while owner v8
# still names the canonical live binary. The audit-local override authenticates
# that live publication without mutating the staged NFQWS2 global on either
# success or failure.
reset_case "$CASE/owner.v8"; Z2_TEST_PROCESS=live
live_nfqws="$MOD/zapret2/nfqws2"; staged_nfqws="$CASE/staged/zapret2/nfqws2"
NFQWS2="$staged_nfqws"
audit_recovery_artifacts install "$live_nfqws" || fail "live-path install audit rejected exact running owner"
[ "$NFQWS2" = "$staged_nfqws" ] || fail "successful live-path audit leaked its override"
Z2_TEST_PROCESS=dead
if audit_recovery_artifacts install "$live_nfqws"; then fail "live-path audit accepted same-boot dead owner"; fi
[ "$NFQWS2" = "$staged_nfqws" ] || fail "failed live-path audit leaked its override"
NFQWS2="$live_nfqws"
reset_case "$CASE/owner.v8"
assert_preserved_failure "same-boot dead v8"
reset_case "$CASE/owner.v8"; Z2_TEST_CURRENT_BOOT="$BOOT_B"; Z2_TEST_SCAN=live
assert_preserved_failure "cross-boot live v8"
reset_case "$CASE/owner.v8"; Z2_TEST_CURRENT_BOOT="$BOOT_B"; Z2_TEST_LOCKED=1
printf 'status=ok\n' > "$STATUS_SNAPSHOT"; chmod 0600 "$STATUS_SNAPSHOT"
BOOT_STALE_RUNTIME_RECOVERY=1
recover_stale_owner_publication || fail "locked clean cross-boot retirement failed"
[ ! -e "$OWNER_STATE" ] || fail "locked clean cross-boot owner was retained"
[ ! -e "$STATUS_SNAPSHOT" ] || fail "locked clean cross-boot status was retained"
[ "$STALE_OWNER_PUBLICATION_RETIRED" = 1 ] || fail "cross-boot retirement was not reported"
BOOT_STALE_RUNTIME_RECOVERY=0
echo "Owner state v8 shell tests passed"