The DNS manager used to apply a selection by writing system/etc/hosts into the live module directory and asking for a reboot, so the root manager would pick it up while assembling /system. That put the module's own tree in the path of whatever the manager does between boots: KernelSU v3 moved that assembly into a separate metamodule, a second hosts module publishing the same path raced it silently, and an upgrade replaced the tree and discarded the user's selection with it. The published file now lives in /data/adb/zapret2-hosts and the module bind-mounts it over /system/etc/hosts from post-fs-data.sh, which is the same mechanism on Magisk, KernelSU and APatch. hosts-overlay.sh owns that mount: it snapshots the untouched system file before covering it, carries a pre-2.3.0 in-tree overlay over and takes it out of the tree, relabels the source so netd can still read it, and yields to another enabled hosts module instead of fighting it for the path. Because the module publishes the mount itself, an edit now takes effect immediately: publication is still an atomic rename, and --apply republishes the mount rather than waiting for a reboot. A rename leaves the live mount on the replaced inode, so the mount identity is recorded when it is published and consulted only against a live mount — a foreign mount is never released. Rollback, uninstall and purge follow the file to its new home, and the installer carries an existing selection across the upgrade that would otherwise drop it. Also releases the tethering runtime key and owner state v9, and lifts the packaged lifecycle contract to 9.
177 lines
8.4 KiB
Shell
177 lines
8.4 KiB
Shell
#!/bin/sh
|
|
set -eu
|
|
|
|
ROOT=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd)
|
|
TMP=${Z2_TEST_TMP:?}
|
|
CASE="$TMP/owner-generation"
|
|
MOD="$CASE/module"
|
|
STATE="$CASE/state"
|
|
MOCK="$CASE/bin"
|
|
fail() { echo "FAIL: owner-generation: $*" >&2; exit 1; }
|
|
command -v chmod >/dev/null 2>&1 || chmod() { :; }
|
|
command -v sync >/dev/null 2>&1 || sync() { :; }
|
|
|
|
mkdir -p "$MOD/zapret2/scripts" "$STATE" "$MOCK"
|
|
chmod 0700 "$STATE"
|
|
cp "$ROOT/zapret2/scripts/common.sh" "$MOD/zapret2/scripts/common.sh"
|
|
cp "$ROOT/zapret2/scripts/firewall-reconciler.sh" "$MOD/zapret2/scripts/firewall-reconciler.sh"
|
|
: > "$MOD/zapret2/nfqws2"
|
|
cat > "$MOD/zapret2/install-generation.meta" <<EOF
|
|
version=1
|
|
module_dir=$MOD
|
|
generation=owner-test-install
|
|
archive_sha256=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
|
EOF
|
|
chmod 0600 "$MOD/zapret2/install-generation.meta"
|
|
|
|
cat > "$MOCK/iptables" <<'EOF'
|
|
#!/bin/sh
|
|
case " $* " in
|
|
*' -t mangle -L OUTPUT -n '*) exit 0 ;;
|
|
*' -t mangle -S '*)
|
|
echo '-N ZAPRET2_OUT'
|
|
echo '-N ZAPRET2_IN'
|
|
[ "${Z2_SNAPSHOT_MODE:-ok}" = missing-anchor ] ||
|
|
echo '-A OUTPUT -j ZAPRET2_OUT'
|
|
echo '-A INPUT -j ZAPRET2_IN'
|
|
if [ "${Z2_SNAPSHOT_MODE:-ok}" = split ]; then
|
|
tail=' -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass'
|
|
for p in 80 443; do
|
|
echo "-A ZAPRET2_OUT -p tcp --dport $p -m connbytes --connbytes 1:20 --connbytes-dir original --connbytes-mode packets$tail"
|
|
done
|
|
for p in 443 3478 5349 19302; do
|
|
echo "-A ZAPRET2_OUT -p udp --dport $p -m connbytes --connbytes 1:20 --connbytes-dir original --connbytes-mode packets$tail"
|
|
done
|
|
for p in 80 443; do
|
|
echo "-A ZAPRET2_IN -p tcp --sport $p -m connbytes --connbytes 1:10 --connbytes-dir reply --connbytes-mode packets$tail"
|
|
done
|
|
for p in 443 3478 5349 19302; do
|
|
echo "-A ZAPRET2_IN -p udp --sport $p -m connbytes --connbytes 1:10 --connbytes-dir reply --connbytes-mode packets$tail"
|
|
done
|
|
exit 0
|
|
fi
|
|
if [ "${Z2_SNAPSHOT_MODE:-ok}" = wrong-payload ]; then
|
|
echo '-A ZAPRET2_OUT -p tcp -m multiport --dports 80,443 -m connbytes --connbytes 1:21 --connbytes-dir original --connbytes-mode packets -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass'
|
|
else
|
|
echo '-A ZAPRET2_OUT -p tcp -m multiport --dports 80,443 -m connbytes --connbytes 1:20 --connbytes-dir original --connbytes-mode packets -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass'
|
|
fi
|
|
echo '-A ZAPRET2_OUT -p udp -m multiport --dports 443,3478,5349,19302 -m connbytes --connbytes 1:20 --connbytes-dir original --connbytes-mode packets -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass'
|
|
echo '-A ZAPRET2_IN -p tcp -m multiport --sports 80,443 -m connbytes --connbytes 1:10 --connbytes-dir reply --connbytes-mode packets -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass'
|
|
echo '-A ZAPRET2_IN -p udp -m multiport --sports 443,3478,5349,19302 -m connbytes --connbytes 1:10 --connbytes-dir reply --connbytes-mode packets -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass'
|
|
[ "${Z2_SNAPSHOT_MODE:-ok}" != extra-payload ] ||
|
|
echo '-A ZAPRET2_OUT -j RETURN'
|
|
;;
|
|
*' -t mangle -C OUTPUT -j ZAPRET2_OUT '*)
|
|
[ "${Z2_SNAPSHOT_MODE:-ok}" != missing-anchor ]
|
|
;;
|
|
*' -t mangle -C INPUT -j ZAPRET2_IN '*) exit 0 ;;
|
|
*' -t mangle -C ZAPRET2_OUT '*)
|
|
[ "${Z2_SNAPSHOT_MODE:-ok}" != wrong-payload ]
|
|
;;
|
|
*' -t mangle -C ZAPRET2_IN '*) exit 0 ;;
|
|
*' -t mangle -S ZAPRET2_OUT '*)
|
|
echo '-N ZAPRET2_OUT'
|
|
echo '-A ZAPRET2_OUT -p tcp -j NFQUEUE'
|
|
echo '-A ZAPRET2_OUT -p udp -j NFQUEUE'
|
|
[ "${Z2_SNAPSHOT_MODE:-ok}" != extra-payload ] ||
|
|
echo '-A ZAPRET2_OUT -j RETURN'
|
|
;;
|
|
*' -t mangle -S ZAPRET2_IN '*)
|
|
echo '-N ZAPRET2_IN'
|
|
echo '-A ZAPRET2_IN -p tcp -j NFQUEUE'
|
|
echo '-A ZAPRET2_IN -p udp -j NFQUEUE'
|
|
;;
|
|
*) exit 1 ;;
|
|
esac
|
|
EOF
|
|
cp "$MOCK/iptables" "$MOCK/ip6tables"
|
|
chmod 0755 "$MOCK/iptables" "$MOCK/ip6tables"
|
|
|
|
Z2_REAL_AWK="$(command -v awk)" || fail "awk is unavailable"
|
|
export Z2_REAL_AWK
|
|
cat > "$MOCK/awk" <<'EOF'
|
|
#!/bin/sh
|
|
previous=""
|
|
newline='
|
|
'
|
|
for argument in "$@"; do
|
|
if [ "$previous" = -v ]; then
|
|
case "$argument" in
|
|
*"$newline"*)
|
|
echo "test awk: newline in -v assignment" >&2
|
|
exit 97
|
|
;;
|
|
esac
|
|
fi
|
|
previous="$argument"
|
|
done
|
|
exec "$Z2_REAL_AWK" "$@"
|
|
EOF
|
|
chmod 0755 "$MOCK/awk"
|
|
|
|
export STATE_DIR="$STATE" SCRIPT_DIR="$MOD/zapret2/scripts" ZAPRET_DIR="$MOD/zapret2" MODDIR="$MOD" PATH="$MOCK:$PATH"
|
|
. "$SCRIPT_DIR/common.sh"
|
|
# Host-independent security shims keep this pure parser/firewall-identity test
|
|
# executable in the stripped Git shell; ownership/mode checks have dedicated
|
|
# installer/package tests.
|
|
path_uid_is_root() { :; }; path_mode_is_0600() { :; }; path_nlink_is_one() { :; }
|
|
state_file_is_secure() { [ -f "$1" ]; }; state_file_target_is_safe() { [ ! -L "$1" ]; }
|
|
read_current_boot_id() { CURRENT_BOOT_ID=11111111-1111-1111-1111-111111111111; }
|
|
if ! command -v sha256sum >/dev/null 2>&1; then sha256sum() { cat >/dev/null; echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; }; fi
|
|
new_lifecycle_token() { printf '%s\n' 'AbCdEf1234-generation'; }
|
|
|
|
QNUM=200; PORTS_TCP=80,443; PORTS_UDP=443,3478,5349,19302; 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
|
|
IPV4_CONNBYTES=1; IPV4_MULTIPORT=1; IPV4_MARK=1; IPV6_CONNBYTES=1; IPV6_MULTIPORT=1; IPV6_MARK=1
|
|
ZAPRET2_LIFECYCLE_TOKEN=app-request-generation
|
|
export ZAPRET2_LIFECYCLE_TOKEN
|
|
prepare_new_firewall_identity || fail "schema-v9 firewall identity preparation failed"
|
|
[ "$FIREWALL_TAG:$ZAPRET2_OUT:$ZAPRET2_IN" = stable0001:ZAPRET2_OUT:ZAPRET2_IN ] ||
|
|
fail "stable firewall identity changed"
|
|
[ "$PENDING_OWNER_GENERATION" = "$ZAPRET2_LIFECYCLE_TOKEN" ] ||
|
|
fail "owner generation is not bound to the exact lifecycle request"
|
|
unset ZAPRET2_LIFECYCLE_TOKEN
|
|
prepare_owner_generation_spec 1 0 || fail "owner generation preparation failed"
|
|
write_owner_state 123 456 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 200 generation-a active || fail "owner publication failed"
|
|
read_owner_state || fail "owner v9 round trip failed"
|
|
[ "$OWNER_STATE_SCHEMA_VERSION:$OWNER_STATE_FIREWALL_TAG:$OWNER_STATE_OUT_CHAIN:$OWNER_STATE_IN_CHAIN" = 9:stable0001:ZAPRET2_OUT:ZAPRET2_IN ] ||
|
|
fail "owner identity fields changed"
|
|
|
|
owner_family_generation_healthy iptables ipv4 || fail "healthy per-rule generation was rejected"
|
|
for mode in missing-anchor extra-payload wrong-payload; do
|
|
Z2_SNAPSHOT_MODE="$mode"; export Z2_SNAPSHOT_MODE
|
|
if owner_family_generation_healthy iptables ipv4; then fail "$mode generation was accepted"; fi
|
|
done
|
|
unset Z2_SNAPSHOT_MODE
|
|
|
|
# A kernel without xt_multiport, or a port list past the fifteen values the
|
|
# parser accepts, publishes one rule per interval and records multiport=0.
|
|
# That is a generation this module created, so re-reading it must recognise it
|
|
# — refusing would make the start path rebuild the ruleset on every run for the
|
|
# entire life of such a device. mark is different: every rule carries it, so a
|
|
# record claiming otherwise describes something the module did not publish.
|
|
Z2_SNAPSHOT_MODE=split; export Z2_SNAPSHOT_MODE
|
|
OWNER_STATE_IPV4_MULTIPORT=0
|
|
OWNER_STATE_IPV4_RULES=12
|
|
owner_family_generation_healthy iptables ipv4 ||
|
|
fail "a generation published without multiport was refused by its own module"
|
|
# The record still has to describe what is actually loaded: the same split
|
|
# ruleset read as a multiport generation is a mismatch, not a detail.
|
|
OWNER_STATE_IPV4_MULTIPORT=1
|
|
OWNER_STATE_IPV4_RULES=4
|
|
if owner_family_generation_healthy iptables ipv4; then
|
|
fail "a split ruleset was accepted as a multiport generation"
|
|
fi
|
|
unset Z2_SNAPSHOT_MODE
|
|
OWNER_STATE_IPV4_MULTIPORT=1
|
|
OWNER_STATE_IPV4_MARK=0
|
|
if owner_family_generation_healthy iptables ipv4; then
|
|
fail "a generation claiming no mark match was accepted"
|
|
fi
|
|
OWNER_STATE_IPV4_MARK=1
|
|
OWNER_STATE_IPV4_MULTIPORT=maybe
|
|
if owner_family_generation_healthy iptables ipv4; then
|
|
fail "a non-boolean multiport record was accepted"
|
|
fi
|
|
OWNER_STATE_IPV4_MULTIPORT=1
|
|
|
|
echo "Owner generation shell tests passed"
|