magisk-zapret2/zapret2/scripts/zapret-status.sh
loop-uh e733873a51 Carry the IPv6 reservation across every boundary that reports it
The rule that an unprovable family is not a clean one held inside the
module and was lost at each point where a result becomes visible.

Status projected a committed stopped receipt through its "nothing is
owned" branch, which asserted a verified ruleset unconditionally — so a
teardown that deliberately withheld that claim had it restored by the
next status read, on the only channel still carrying the reservation.

Direct uninstall told the user the family "is cleared by the reboot".
Only the root-manager branch schedules one; the direct branch leaves the
module installed, requires no reboot, and has just deleted the state
that could identify leftover rules. It now names the reboot as the
action that ends the ambiguity instead of implying it is under way.

A partial purge receipt hardcoded every removal fact to zero. An
unremovable external staging workspace was enough to reach it with both
managed trees already gone, denying a removal that did happen on the
same receipt the app reads. The facts are measured now, and the
diagnostic names what actually survived.

Teardown also discarded the preflight's positive proof: a family read
moments earlier and found empty raised a reservation anyway if the
frontend went busy in between. That proof is now recorded and honoured,
for the mode that produced it only.

Tests: the owned baseline mode had no coverage at all, the boot-recovery
fixture forced autostart off so service.sh's new recovery retry was
never entered, and the purge assertions checked that a string was
present rather than that the two scripts agree. Each new test was proven
by reverting its fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 11:09:07 +03:00

326 lines
11 KiB
Shell

#!/system/bin/sh
# Read-only status derived from bounded lifecycle, owner, and snapshot metadata.
# Machine observers never acquire or recover the mutation lock. Explicit
# lifecycle operations own recovery and any deep firewall audit.
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/common.sh"
MACHINE=0
MACHINE_VERSION=0
case "${1:-}" in
"") ;;
--machine) MACHINE=1; MACHINE_VERSION=1 ;;
--machine-v3) MACHINE=1; MACHINE_VERSION=3 ;;
--machine-v4) MACHINE=1; MACHINE_VERSION=4 ;;
--machine-v5) MACHINE=1; MACHINE_VERSION=5 ;;
--machine-v6) MACHINE=1; MACHINE_VERSION=6 ;;
*) echo "ERROR: usage: $0 [--machine|--machine-v3|--machine-v4|--machine-v5|--machine-v6]" >&2; exit 2 ;;
esac
Z2_LIFECYCLE_STATE=unknown
Z2_LIFECYCLE_OWNER_KIND=unknown
emit_lifecycle_barrier() {
local state="$1" kind="$2" code detail
case "$state" in
active)
code=LIFECYCLE_ACTIVE
detail="Another verified lifecycle owner is active"
;;
ambiguous)
code=LIFECYCLE_AMBIGUOUS
detail="Lifecycle ownership metadata is unsafe or cannot be authenticated"
;;
*)
state=recovery_failed
code=LIFECYCLE_RECOVERY_FAILED
detail="A proven stale lifecycle owner requires an explicit lifecycle operation"
;;
esac
z2_error_set LIFECYCLE "$code" LIFECYCLE_OBSERVE "$detail" || exit 2
echo "Z2_PROTOCOL=$MACHINE_VERSION"
echo "Z2_STATUS=degraded"
echo "Z2_OWNED=1"
echo "Z2_PROCESS=0"
echo "Z2_ACTIVE=0"
echo "Z2_PID="
echo "Z2_PID_VERIFIED=0"
echo "Z2_PID_STARTTIME="
echo "Z2_OWNER_GENERATION="
echo "Z2_OWNER_METADATA_VERIFIED=0"
echo "Z2_QNUM="
echo "Z2_IPV4=0"
echo "Z2_IPV6=0"
echo "Z2_RULES=0"
echo "Z2_EXPECTED_RULES=0"
echo "Z2_IPV4_RULES=0"
echo "Z2_IPV6_RULES=0"
echo "Z2_RULESET_VERIFIED=0"
echo "Z2_NFQUEUE=0"
echo "Z2_QUEUE_BYPASS=0"
echo "Z2_UPDATE_BLOCKED=1"
echo "Z2_UNINSTALL_TOMBSTONE=0"
echo "Z2_LIFECYCLE_STATE=$state"
echo "Z2_LIFECYCLE_OWNER_KIND=$kind"
case "$MACHINE_VERSION" in 5|6)
echo "Z2_CHAINS=0"
echo "Z2_ANCHORS=0"
;;
esac
z2_error_emit_machine
echo "Z2_COMPLETE=1"
}
observe_lifecycle_state() {
classify_lifecycle_lock
Z2_LIFECYCLE_OWNER_KIND="$LIFECYCLE_OBSERVED_KIND"
case "$LIFECYCLE_OBSERVED_STATE" in
idle)
Z2_LIFECYCLE_STATE=idle
Z2_LIFECYCLE_OWNER_KIND=none
return 0
;;
active|ambiguous)
if [ "$MACHINE_VERSION" = 6 ] && lifecycle_lock_is_owned_by_caller; then
Z2_LIFECYCLE_STATE=owned
return 0
fi
Z2_LIFECYCLE_STATE="$LIFECYCLE_OBSERVED_STATE"
return 1
;;
stale)
Z2_LIFECYCLE_STATE=recovery_failed
return 1
;;
*)
Z2_LIFECYCLE_STATE=ambiguous
Z2_LIFECYCLE_OWNER_KIND=unknown
return 1
;;
esac
}
case "$MACHINE_VERSION" in 4|5|6)
if ! observe_lifecycle_state; then
emit_lifecycle_barrier "$Z2_LIFECYCLE_STATE" "$Z2_LIFECYCLE_OWNER_KIND"
exit 2
fi
;;
esac
CONFIG_VALID=1
STATE_DIR_SECURE=0
OBSERVER_STATE_DIR_VERIFIED=0
if state_dir_is_secure; then
STATE_DIR_SECURE=1
OBSERVER_STATE_DIR_VERIFIED=1
fi
load_effective_core_config_readonly >/dev/null 2>&1 || CONFIG_VALID=0
normalize_qnum "${QNUM:-}" >/dev/null 2>&1 || CONFIG_VALID=0
[ "$CONFIG_VALID" = 0 ] || QNUM="$QNUM_NORMALIZED"
STATUS_SNAPSHOT_VALID=0
read_iptables_status >/dev/null 2>&1 && STATUS_SNAPSHOT_VALID=1
Z2_PID=""
Z2_PID_STARTTIME=""
Z2_OWNER_GENERATION=""
Z2_PID_VERIFIED=0
Z2_OWNER_METADATA_VERIFIED=0
Z2_PROCESS=0
Z2_ORPHANS=""
Z2_QNUM="${STATUS_FILE_QNUM:-${QNUM:-}}"
Z2_UPDATE_BLOCKED=0
Z2_UNINSTALL_TOMBSTONE=0
{ [ -e "$UNINSTALL_TOMBSTONE" ] || [ -L "$UNINSTALL_TOMBSTONE" ]; } && Z2_UNINSTALL_TOMBSTONE=1
module_removal_pending && Z2_UNINSTALL_TOMBSTONE=1
Z2_IPV4=0
Z2_IPV6=0
Z2_IPV4_RULES=0
Z2_IPV6_RULES=0
IPV4_VERIFIED=0
IPV6_VERIFIED=0
IPV6_UNKNOWN=0
Z2_FAST_SNAPSHOT=0
# The lifecycle operation has already verified owner.meta, the exact process,
# the installation receipt and the firewall generation before atomically
# publishing this snapshot. Ordinary observation consumes that single receipt
# and only proves that its PID/starttime/argv identity is still live.
if [ "$STATUS_SNAPSHOT_VALID" = 1 ] &&
[ "$STATUS_FILE_STATUS" = ok ] &&
[ "$STATUS_FILE_OWNER_METADATA_VERIFIED" = 1 ] &&
[ "$STATUS_FILE_RULESET_VERIFIED" = 1 ] &&
[ "$STATUS_FILE_IPV4_ACTIVE" = 1 ] &&
[ "$STATUS_FILE_RULES_TOTAL" = "$((STATUS_FILE_IPV4_RULES + STATUS_FILE_IPV6_RULES))" ] &&
[ "$STATUS_FILE_RULES_TOTAL" = "$STATUS_FILE_RULES_EXPECTED" ] &&
verify_status_snapshot_pid; then
Z2_PID="$VERIFIED_PID"
Z2_PID_STARTTIME="$VERIFIED_PID_START"
Z2_OWNER_GENERATION="$STATUS_FILE_OWNER_GENERATION"
Z2_QNUM="$STATUS_FILE_QNUM"
Z2_PID_VERIFIED=1
Z2_OWNER_METADATA_VERIFIED=1
Z2_PROCESS=1
Z2_FAST_SNAPSHOT=1
Z2_IPV4="$STATUS_FILE_IPV4_ACTIVE"
Z2_IPV6="$STATUS_FILE_IPV6_ACTIVE"
Z2_IPV4_RULES="$STATUS_FILE_IPV4_RULES"
Z2_IPV6_RULES="$STATUS_FILE_IPV6_RULES"
IPV4_VERIFIED="$Z2_IPV4"
[ "$Z2_IPV6" = 0 ] || IPV6_VERIFIED=1
elif [ "$STATUS_SNAPSHOT_VALID" = 1 ] &&
[ ! -e "$PIDFILE" ] && [ ! -L "$PIDFILE" ] &&
[ ! -e "$OWNER_STATE" ] && [ ! -L "$OWNER_STATE" ] &&
[ "$STATUS_FILE_STATUS" = stopped ] &&
[ "$STATUS_FILE_RULES_TOTAL" = 0 ] &&
[ "$STATUS_FILE_RULESET_VERIFIED" = 1 ]; then
Z2_FAST_SNAPSHOT=1
IPV4_VERIFIED=1
fi
if [ "$Z2_FAST_SNAPSHOT" = 0 ] && [ "$STATUS_FILE_IPV6_ACTIVE" = 1 ]; then
IPV6_UNKNOWN=1
fi
Z2_RULES=$((Z2_IPV4_RULES + Z2_IPV6_RULES))
case "$STATUS_FILE_RULES_EXPECTED" in
''|*[!0-9]*) Z2_EXPECTED_RULES=0 ;;
*) Z2_EXPECTED_RULES="$STATUS_FILE_RULES_EXPECTED" ;;
esac
Z2_RULESET_VERIFIED=0
if [ "$IPV4_VERIFIED" = 1 ] && [ "$IPV6_UNKNOWN" = 0 ]; then
if { [ "$STATUS_FILE_IPV6_ACTIVE" = 0 ] && [ "$Z2_IPV6" = 0 ]; } ||
{ [ "$STATUS_FILE_IPV6_ACTIVE" = 1 ] && [ "$IPV6_VERIFIED" = 1 ]; }; then
[ "$Z2_RULES" = "$Z2_EXPECTED_RULES" ] && Z2_RULESET_VERIFIED=1
fi
fi
Z2_OWNED=0
[ "$Z2_PROCESS" = 1 ] && Z2_OWNED=1
[ "$Z2_IPV4" = 1 ] && Z2_OWNED=1
[ "$Z2_IPV6" = 1 ] && Z2_OWNED=1
[ "$IPV6_UNKNOWN" = 1 ] && Z2_OWNED=1
[ -e "$PIDFILE" ] && Z2_OWNED=1
[ -e "$OWNER_STATE" ] && Z2_OWNED=1
[ "$Z2_UNINSTALL_TOMBSTONE" = 1 ] && Z2_OWNED=1
[ "$STATE_DIR_SECURE" = 1 ] || {
if [ -e "$STATE_DIR" ] || [ -L "$STATE_DIR" ]; then Z2_OWNED=1; fi
}
Z2_ACTIVE=0
Z2_NFQUEUE=0
Z2_QUEUE_BYPASS=0
Z2_CHAINS=0
Z2_ANCHORS=0
Z2_STATUS=degraded
if [ "$Z2_OWNER_METADATA_VERIFIED" = 1 ] && [ "$Z2_RULESET_VERIFIED" = 1 ] &&
[ "$Z2_QNUM" = "$STATUS_FILE_QNUM" ]; then
Z2_ACTIVE=1
Z2_NFQUEUE=1
Z2_QUEUE_BYPASS=1
Z2_STATUS=ok
Z2_CHAINS="$STATUS_FILE_CHAINS"
Z2_ANCHORS="$STATUS_FILE_ANCHORS"
elif [ "$Z2_OWNED" = 0 ]; then
Z2_STATUS=stopped
Z2_EXPECTED_RULES=0
# Nothing of ours is owned, so there is no ruleset left to disprove — with
# one exception: a committed teardown from this boot that recorded it could
# not read one family. Overwriting that would launder the one reservation
# the module deliberately kept, on the only channel that still carries it.
if [ "$STATUS_SNAPSHOT_VALID" = 1 ] && [ "$STATUS_FILE_STATUS" = stopped ] &&
[ "$STATUS_FILE_RULESET_VERIFIED" = 0 ]; then
Z2_RULESET_VERIFIED=0
else
Z2_RULESET_VERIFIED=1
fi
else
Z2_NFQUEUE="$STATUS_FILE_NFQUEUE_SUPPORTED"
Z2_QUEUE_BYPASS="$STATUS_FILE_QUEUE_BYPASS_SUPPORTED"
fi
if [ "$CONFIG_VALID" = 0 ]; then
runtime_config_error_code "$RUNTIME_CONFIG_ERROR"
z2_error_set CONFIG "$RUNTIME_CONFIG_ERROR_CODE" RUNTIME_PARSE \
"${RUNTIME_CONFIG_ERROR:-runtime.ini validation failed}"
elif [ "$STATUS_FILE_ERROR_SCHEMA" = "$Z2_ERROR_SCHEMA_VERSION" ] &&
[ "$STATUS_FILE_ERROR_STATUS" = ERROR ] &&
z2_error_fields_are_valid "$STATUS_FILE_ERROR_STATUS" "$STATUS_FILE_ERROR_DOMAIN" \
"$STATUS_FILE_ERROR_STAGE" "$STATUS_FILE_ERROR_CODE" "$STATUS_FILE_ERROR_DETAIL"; then
z2_error_set "$STATUS_FILE_ERROR_DOMAIN" "$STATUS_FILE_ERROR_CODE" \
"$STATUS_FILE_ERROR_STAGE" "$STATUS_FILE_ERROR_DETAIL"
elif [ "$Z2_STATUS" = degraded ]; then
z2_error_set STATUS STATUS_DEGRADED STATUS_QUERY \
"Service state is degraded; inspect the lifecycle log for full details"
else
z2_error_clear
fi
emit_machine() {
case "$MACHINE_VERSION" in 3|4|5|6) echo "Z2_PROTOCOL=$MACHINE_VERSION" ;; esac
echo "Z2_STATUS=$Z2_STATUS"
echo "Z2_OWNED=$Z2_OWNED"
echo "Z2_PROCESS=$Z2_PROCESS"
echo "Z2_ACTIVE=$Z2_ACTIVE"
echo "Z2_PID=$Z2_PID"
echo "Z2_PID_VERIFIED=$Z2_PID_VERIFIED"
echo "Z2_PID_STARTTIME=$Z2_PID_STARTTIME"
echo "Z2_OWNER_GENERATION=$Z2_OWNER_GENERATION"
echo "Z2_OWNER_METADATA_VERIFIED=$Z2_OWNER_METADATA_VERIFIED"
echo "Z2_QNUM=$Z2_QNUM"
echo "Z2_IPV4=$Z2_IPV4"
echo "Z2_IPV6=$Z2_IPV6"
echo "Z2_RULES=$Z2_RULES"
echo "Z2_EXPECTED_RULES=$Z2_EXPECTED_RULES"
echo "Z2_IPV4_RULES=$Z2_IPV4_RULES"
echo "Z2_IPV6_RULES=$Z2_IPV6_RULES"
echo "Z2_RULESET_VERIFIED=$Z2_RULESET_VERIFIED"
echo "Z2_NFQUEUE=$Z2_NFQUEUE"
echo "Z2_QUEUE_BYPASS=$Z2_QUEUE_BYPASS"
echo "Z2_UPDATE_BLOCKED=$Z2_UPDATE_BLOCKED"
echo "Z2_UNINSTALL_TOMBSTONE=$Z2_UNINSTALL_TOMBSTONE"
case "$MACHINE_VERSION" in 4|5|6)
echo "Z2_LIFECYCLE_STATE=$Z2_LIFECYCLE_STATE"
echo "Z2_LIFECYCLE_OWNER_KIND=$Z2_LIFECYCLE_OWNER_KIND"
;;
esac
case "$MACHINE_VERSION" in 5|6)
echo "Z2_CHAINS=$Z2_CHAINS"
echo "Z2_ANCHORS=$Z2_ANCHORS"
;;
esac
case "$MACHINE_VERSION" in 3|4|5|6) z2_error_emit_machine ;; esac
# Terminal sentinel lets strict callers reject truncated shell output.
echo "Z2_COMPLETE=1"
}
if [ "$MACHINE" = 1 ]; then
emit_machine
else
echo "=========================================="
echo " Zapret2 Status"
echo "=========================================="
echo "Status: $Z2_STATUS"
echo "Verified process: $Z2_PROCESS (PID: ${Z2_PID:-none}, metadata: $Z2_OWNER_METADATA_VERIFIED)"
[ -n "$Z2_ORPHANS" ] && echo "Exact module-owned recovery candidates: $Z2_ORPHANS"
echo "Owned IPv4: $Z2_IPV4 ($Z2_IPV4_RULES rules)"
echo "Owned IPv6: $Z2_IPV6 ($Z2_IPV6_RULES rules)"
echo "Ruleset verified: $Z2_RULESET_VERIFIED ($Z2_RULES/$Z2_EXPECTED_RULES)"
echo "Queue: ${Z2_QNUM:-unknown}"
echo "Config source: $CORE_CONFIG_SOURCE_PATH"
echo "Runtime config status: $RUNTIME_CONFIG_STATUS"
[ "$Z2_UNINSTALL_TOMBSTONE" = 1 ] && echo "Removal gate: active"
if [ -f "$LOGFILE" ] && [ ! -L "$LOGFILE" ]; then
echo ""
echo "Last log entries:"
tail -10 "$LOGFILE" 2>/dev/null
fi
fi
case "$Z2_STATUS" in
ok) exit 0 ;;
stopped) exit 1 ;;
*) exit 2 ;;
esac