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>
326 lines
11 KiB
Shell
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
|