mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 13:03:13 +02:00
phone_focus_mode: prevent Magisk app from disabling Systemless Hosts module
The Magisk app's Modules tab "Disable" / "Remove" buttons work by creating marker files (disable, remove) in /data/adb/modules/hosts/. Tapping Disable in the app on next boot would skip the module's magic-mount of /system/etc/hosts, silently disabling all hosts-file blocking. Defense in depth: 1. deploy.sh chattr +i's the module dir + its hosts file so the Magisk app cannot create disable/remove markers (kernel returns EPERM). The +i attribute survives reboot. 2. hosts_enforcer.sh adds protect_magisk_module(): every poll cycle (and on startup) scans for disable/remove/update markers, deletes them, logs TAMPER, and re-asserts +i on the dir. Safety net in case the lock is bypassed. 3. sync_magisk_module() now drops +i briefly before its cp and re-locks via protect_magisk_module() so workout-state hosts swaps still work. 4. deploy.sh detects the previously-silent failure mode of the module being enabled on disk but not yet magic-mounted (no /system/etc/hosts) and aborts with a clear reboot-required message instead of producing a deploy that does nothing. 5. focus_ctl.sh hosts-status now prints the lock state and warns about any present markers. Verified end-to-end on BL9000EEA0000102: - Pre-reboot: chattr +i set, touch /data/adb/modules/hosts/disable returns Operation not permitted. - Post-reboot: /system/etc/hosts magic-mounted (178303 lines, sha matches canonical), lock survives reboot, ping youtube.com -> 127.0.0.1. - Tamper test: chattr -i + touch disable -> enforcer logs 'TAMPER: removed Magisk module marker' within 15s and re-locks. Documented intentional override path inline (focus_ctl.sh hosts-stop; chattr -i; touch disable).
This commit is contained in:
parent
78c7efbfd8
commit
00c383008a
@ -381,9 +381,18 @@ PY_EOF
|
||||
# Without it, no app-level hosts blocking is possible, so we STOP here
|
||||
# and require user action before the deploy can proceed.
|
||||
local magisk_hosts_ok=0
|
||||
local magisk_hosts_state="absent"
|
||||
if adb_root "test -d /data/adb/modules/hosts" 2>/dev/null; then
|
||||
if adb_root "test ! -f /data/adb/modules/hosts/disable -a ! -f /data/adb/modules/hosts/remove" 2>/dev/null; then
|
||||
if adb_root "test -f /data/adb/modules/hosts/disable -o -f /data/adb/modules/hosts/remove" 2>/dev/null; then
|
||||
magisk_hosts_state="disabled"
|
||||
elif ! adb_root "test -f /system/etc/hosts" 2>/dev/null; then
|
||||
# Module dir exists, no disable marker, but the magic-mount
|
||||
# has not happened yet. Either the user just enabled it but
|
||||
# has not rebooted, or the module is in a broken state.
|
||||
magisk_hosts_state="not-mounted"
|
||||
else
|
||||
magisk_hosts_ok=1
|
||||
magisk_hosts_state="ok"
|
||||
fi
|
||||
fi
|
||||
|
||||
@ -392,25 +401,48 @@ PY_EOF
|
||||
echo "╔══════════════════════════════════════════════════════════════════╗"
|
||||
echo "║ ACTION REQUIRED — Deploy cannot continue ║"
|
||||
echo "╠══════════════════════════════════════════════════════════════════╣"
|
||||
echo "║ The Magisk 'Systemless Hosts' module is not enabled. ║"
|
||||
echo "║ Without it, hosts-file blocking is impossible on this device ║"
|
||||
echo "║ (the system partition is hardware read-only even with root). ║"
|
||||
echo "║ ║"
|
||||
echo "║ Steps to fix: ║"
|
||||
echo "║ 1. Open the Magisk app on the phone ║"
|
||||
echo "║ 2. Tap the Modules tab (puzzle-piece icon) ║"
|
||||
echo "║ 3. Find 'Systemless Hosts' and toggle it ON ║"
|
||||
echo "║ 4. Reboot the phone when prompted ║"
|
||||
echo "║ 5. Re-run this deploy command ║"
|
||||
if [[ "$magisk_hosts_state" == "not-mounted" ]]; then
|
||||
echo "║ Magisk 'Systemless Hosts' module is enabled on disk but the ║"
|
||||
echo "║ /system/etc/hosts magic-mount has NOT happened yet. ║"
|
||||
echo "║ This means the device has not been rebooted since the module ║"
|
||||
echo "║ was last toggled on. Without the magic-mount, no hosts-file ║"
|
||||
echo "║ blocking is possible (the partition is hardware read-only). ║"
|
||||
echo "║ ║"
|
||||
echo "║ Steps to fix: ║"
|
||||
echo "║ 1. Reboot the phone now (adb reboot, or hold power) ║"
|
||||
echo "║ 2. After reboot, re-run this deploy command ║"
|
||||
else
|
||||
echo "║ The Magisk 'Systemless Hosts' module is not enabled. ║"
|
||||
echo "║ Without it, hosts-file blocking is impossible on this device ║"
|
||||
echo "║ (the system partition is hardware read-only even with root). ║"
|
||||
echo "║ ║"
|
||||
echo "║ Steps to fix: ║"
|
||||
echo "║ 1. Open the Magisk app on the phone ║"
|
||||
echo "║ 2. Tap the Modules tab (puzzle-piece icon) ║"
|
||||
echo "║ 3. Find 'Systemless Hosts' and toggle it ON ║"
|
||||
echo "║ 4. Reboot the phone when prompted ║"
|
||||
echo "║ 5. Re-run this deploy command ║"
|
||||
fi
|
||||
echo "╚══════════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
adb_root "mkdir -p /data/adb/modules/hosts/system/etc"
|
||||
# Drop any +i lock the runtime hosts_enforcer may have set on the
|
||||
# module dir / hosts file so we can update them. The enforcer will
|
||||
# re-lock on its next poll cycle. Also pre-emptively delete any
|
||||
# disable/remove markers that may exist on disk before we start.
|
||||
adb_root "chattr -i /data/adb/modules/hosts /data/adb/modules/hosts/system/etc/hosts 2>/dev/null; rm -f /data/adb/modules/hosts/disable /data/adb/modules/hosts/remove /data/adb/modules/hosts/update; true"
|
||||
adb_root "cp $REMOTE_DIR/hosts.canonical /data/adb/modules/hosts/system/etc/hosts"
|
||||
adb_root "chmod 644 /data/adb/modules/hosts/system/etc/hosts"
|
||||
echo " Magisk hosts module populated ($(adb_root "wc -l < /data/adb/modules/hosts/system/etc/hosts" 2>/dev/null | tr -d ' ') lines). Reboot to activate /system/etc/hosts."
|
||||
# Lock the module dir to block the Magisk app's "Disable" / "Remove"
|
||||
# buttons (they create marker files inside the dir). Files already
|
||||
# in the dir stay mutable so the runtime enforcer can still update
|
||||
# the hosts file on workout state changes.
|
||||
adb_root "chattr +i /data/adb/modules/hosts/system/etc/hosts 2>/dev/null; true"
|
||||
adb_root "chattr +i /data/adb/modules/hosts 2>/dev/null; true"
|
||||
echo " Magisk hosts module populated ($(adb_root "wc -l < /data/adb/modules/hosts/system/etc/hosts" 2>/dev/null | tr -d ' ') lines), locked against UI-disable. Reboot to activate /system/etc/hosts."
|
||||
fi
|
||||
adb_root "rm -rf /data/local/tmp/focus_stage"
|
||||
|
||||
|
||||
@ -351,6 +351,26 @@ cmd_hosts_status() {
|
||||
else
|
||||
echo "Canonical hosts file missing - run deploy.sh"
|
||||
fi
|
||||
# Magisk Systemless Hosts module protection state.
|
||||
local module_dir="/data/adb/modules/hosts"
|
||||
if [ -d "$module_dir" ]; then
|
||||
local lock_state="UNLOCKED (Magisk app can disable!)"
|
||||
if lsattr -d "$module_dir" 2>/dev/null | awk '{print $1}' | grep -q i; then
|
||||
lock_state="LOCKED (chattr +i)"
|
||||
fi
|
||||
echo "Magisk dir: $module_dir [$lock_state]"
|
||||
local marker_warn=""
|
||||
for marker in disable remove update; do
|
||||
if [ -e "$module_dir/$marker" ]; then
|
||||
marker_warn="$marker_warn $marker"
|
||||
fi
|
||||
done
|
||||
if [ -n "$marker_warn" ]; then
|
||||
echo "WARN: Magisk markers present:$marker_warn (will be auto-removed by hosts_enforcer)"
|
||||
fi
|
||||
else
|
||||
echo "Magisk dir: <missing - module not installed>"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_hosts_start() {
|
||||
|
||||
@ -187,15 +187,71 @@ assert_bind_mount() {
|
||||
sync_magisk_module() {
|
||||
local canonical="$1"
|
||||
[ -n "$canonical" ] && [ -f "$canonical" ] || return 0
|
||||
[ -d "$(dirname "$HOSTS_MAGISK_MODULE_FILE")" ] || return 0
|
||||
local module_dir
|
||||
module_dir="$(dirname "$(dirname "$(dirname "$HOSTS_MAGISK_MODULE_FILE")")")"
|
||||
[ -d "$module_dir" ] || return 0
|
||||
local module_hash canonical_hash
|
||||
module_hash="$(sha256_of "$HOSTS_MAGISK_MODULE_FILE")"
|
||||
canonical_hash="$(sha256_of "$canonical")"
|
||||
if [ "$module_hash" != "$canonical_hash" ]; then
|
||||
# Drop +i on the module dir + file long enough to update, then re-lock.
|
||||
chattr -i "$module_dir" 2>/dev/null || true
|
||||
chattr -i "$HOSTS_MAGISK_MODULE_FILE" 2>/dev/null || true
|
||||
cp "$canonical" "$HOSTS_MAGISK_MODULE_FILE" 2>/dev/null || return 0
|
||||
chmod 644 "$HOSTS_MAGISK_MODULE_FILE" 2>/dev/null || true
|
||||
chattr +i "$HOSTS_MAGISK_MODULE_FILE" 2>/dev/null || true
|
||||
log "Synced Magisk module hosts to $(basename "$canonical")"
|
||||
fi
|
||||
# Always re-assert dir-level lock at the end so a partial earlier failure
|
||||
# leaves us in the locked state on the next iteration.
|
||||
protect_magisk_module
|
||||
}
|
||||
|
||||
# Defense against the user disabling the Magisk Systemless Hosts module via
|
||||
# the Magisk app UI. The "Disable" / "Remove" buttons work by creating a
|
||||
# marker file inside the module directory:
|
||||
#
|
||||
# /data/adb/modules/hosts/disable # disable on next reboot
|
||||
# /data/adb/modules/hosts/remove # uninstall on next reboot
|
||||
#
|
||||
# We do TWO things every poll cycle:
|
||||
# 1. Delete those markers if they appeared (so a reboot still loads us)
|
||||
# 2. Set chattr +i on the module directory itself so the Magisk app cannot
|
||||
# create those markers in the first place. The +i flag on a directory
|
||||
# blocks adding/removing/renaming entries inside it, which is exactly
|
||||
# what `touch disable` does. Files already inside remain mutable, so
|
||||
# sync_magisk_module() can still rewrite the hosts file (it briefly
|
||||
# drops the lock above to handle the rare case where it must).
|
||||
#
|
||||
# To intentionally disable the module for maintenance, run from a root
|
||||
# shell on the phone:
|
||||
# focus_ctl.sh hosts-stop
|
||||
# chattr -i /data/adb/modules/hosts
|
||||
# touch /data/adb/modules/hosts/disable
|
||||
protect_magisk_module() {
|
||||
local module_dir
|
||||
module_dir="$(dirname "$(dirname "$(dirname "$HOSTS_MAGISK_MODULE_FILE")")")"
|
||||
[ -d "$module_dir" ] || return 0
|
||||
# Step 1: nuke any disable/remove markers that may have been created
|
||||
# since the last poll. We have to chattr -i first because either of the
|
||||
# two locks below may already be in effect.
|
||||
local removed=0
|
||||
for marker in disable remove update; do
|
||||
local f="$module_dir/$marker"
|
||||
if [ -e "$f" ]; then
|
||||
chattr -i "$module_dir" 2>/dev/null || true
|
||||
chattr -i "$f" 2>/dev/null || true
|
||||
if rm -f "$f" 2>/dev/null; then
|
||||
removed=$((removed + 1))
|
||||
log "TAMPER: removed Magisk module marker $f"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
# Step 2: re-lock the module dir so the Magisk app cannot recreate them
|
||||
# via its UI. Best-effort - if the kernel/fs rejects +i, the runtime
|
||||
# delete loop above is still our safety net.
|
||||
chattr +i "$module_dir" 2>/dev/null || true
|
||||
return $removed
|
||||
}
|
||||
|
||||
ensure_canonical_immutable() {
|
||||
@ -267,6 +323,7 @@ main() {
|
||||
log "hosts_enforcer started (PID=$$)"
|
||||
|
||||
ensure_canonical_immutable
|
||||
protect_magisk_module
|
||||
# Initial assertion
|
||||
assert_bind_mount || true
|
||||
|
||||
@ -284,6 +341,7 @@ main() {
|
||||
|
||||
while true; do
|
||||
verify_and_restore
|
||||
protect_magisk_module
|
||||
rotate_log
|
||||
sleep "$HOSTS_CHECK_INTERVAL"
|
||||
done
|
||||
|
||||
Loading…
Reference in New Issue
Block a user