diff --git a/zygisk/module/action.sh b/zygisk/module/action.sh index c3ea8c1a34..58b7d206d4 100644 --- a/zygisk/module/action.sh +++ b/zygisk/module/action.sh @@ -1,4 +1,15 @@ MANAGER_PACKAGE_NAME="@MANAGER_PACKAGE_NAME@" INJECTED_PACKAGE_NAME="@INJECTED_PACKAGE_NAME@" +# The manager also ships inside this module as manager.apk and may have been +# installed as a regular package. When it has, launching its activity directly +# is more reliable than the injection path below, which needs +# ${INJECTED_PACKAGE_NAME} to start a 2D activity at all and to receive the +# injected manager dex -- neither holds in every environment (some systems +# never let the shell host an activity, and dynamic code loading restrictions +# can block the dex the way GrapheneOS does, see #805). +if pm path "$MANAGER_PACKAGE_NAME" >/dev/null 2>&1; then + am start -n "$MANAGER_PACKAGE_NAME/.ui.MainActivity" >/dev/null 2>&1 && exit 0 +fi + am start -c "${MANAGER_PACKAGE_NAME}.LAUNCH_MANAGER" "${INJECTED_PACKAGE_NAME}/.BugreportWarningActivity" diff --git a/zygisk/module/daemon b/zygisk/module/daemon index 3b1dec4a5f..8cc8fb4f5d 100644 --- a/zygisk/module/daemon +++ b/zygisk/module/daemon @@ -26,8 +26,11 @@ if [ "$debug" = "true" ]; then fi # Mount an empty temporary file system over /data/resource-cache to avoid possible overlay conflicts. -# Note that this script runs inside an isolated mount namespace (via unshare). -mount tmpfs -t tmpfs /data/resource-cache +# Only when service.sh has established a mount namespace that cannot propagate this mount back to the +# host: without one it would hide the real resource cache from every process on the device. +if [ "${VECTOR_MOUNTNS_ISOLATED:-0}" = 1 ]; then + mount tmpfs -t tmpfs /data/resource-cache +fi # Wait for Zygote socket using a polling loop if [ ! -S "/dev/socket/zygote" ]; then diff --git a/zygisk/module/service.sh b/zygisk/module/service.sh index e49f8859b2..c726aa5970 100644 --- a/zygisk/module/service.sh +++ b/zygisk/module/service.sh @@ -1,6 +1,78 @@ -# Extract the directory path and change directory +# Extract the directory path and change directory MODDIR="${0%/*}" cd "$MODDIR" || exit 1 -# Start the daemon directly in the background within a private mount namespace -unshare --propagation slave -m "$MODDIR/daemon" --system-server-max-retry=3 "$@" & +# The service stage has no console, and the daemon cannot log anything before it +# exists, so a start that fails is invisible by construction. Keep one line per +# boot, and mirror it to logcat when log is around as well. +TRACE_LOG=/data/local/tmp/vector-service.log +trace() { + printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S' 2>/dev/null)" "$*" >>"$TRACE_LOG" 2>/dev/null + command -v log >/dev/null 2>&1 && log -p i -t VectorService "$*" >/dev/null 2>&1 +} + +# A replayed service stage (KernelSU's emulated soft reboot) leaves the old +# daemon alive, and tryLock() in the daemon does not reliably turn the second +# one away (#878): the two then claim the same proxy service and break +# injection. The survivor handles a system_server restart through its binder +# death recipient, so leaving it alone is the repair; a second daemon is what +# breaks injection. +daemon_running() { + command -v pgrep >/dev/null 2>&1 && pgrep -f '^vectord' >/dev/null 2>&1 && return 0 + command -v pidof >/dev/null 2>&1 && pidof vectord >/dev/null 2>&1 && return 0 + return 1 +} + +if daemon_running; then + trace "vectord already running, not starting a second one" + exit 0 +fi + +# toybox builds that implement only a subset of util-linux reject +# --propagation outright, which leaves the daemon dead with nothing anywhere to +# say so. The root solution usually ships a fuller busybox, so prefer its +# unshare, then the system one, and only then a namespace whose propagation +# type cannot be set. +find_busybox() { + for bb in "${MAGISKTMP:-/nonexistent}/busybox" \ + "${MAGISKTMP:-/nonexistent}/.magisk/busybox" \ + /data/adb/ksu/bin/busybox \ + /data/adb/magisk/busybox \ + /data/adb/ap/bin/busybox \ + /system/xbin/busybox; do + [ -f "$bb" ] && [ -x "$bb" ] && { printf '%s\n' "$bb"; return 0; } + done + bb="$(command -v busybox 2>/dev/null)" && [ -f "$bb" ] && [ -x "$bb" ] && printf '%s\n' "$bb" +} +BB="$(find_busybox)" +DAEMON="$MODDIR/daemon" + +# The daemon mounts a tmpfs over /data/resource-cache, which is only safe when +# this namespace cannot propagate mounts back to the host. +# VECTOR_MOUNTNS_ISOLATED records that service.sh has established exactly that. +if [ -n "$BB" ] && "$BB" unshare --propagation slave -m true >/dev/null 2>&1; then + trace "starting vectord with $BB unshare --propagation slave -m" + VECTOR_MOUNTNS_ISOLATED=1 "$BB" unshare --propagation slave -m "$DAEMON" --system-server-max-retry=3 "$@" & +elif unshare --propagation slave -m true >/dev/null 2>&1; then + trace "starting vectord with unshare --propagation slave -m" + VECTOR_MOUNTNS_ISOLATED=1 unshare --propagation slave -m "$DAEMON" --system-server-max-retry=3 "$@" & +elif unshare -m /system/bin/sh -c ' + mount --make-rprivate / 2>/dev/null || mount --make-rslave / 2>/dev/null || exit 1 + ! grep -q " shared:" /proc/self/mountinfo + ' >/dev/null 2>&1; then + trace "starting vectord with unshare -m and a private mount tree" + unshare -m /system/bin/sh -c ' + if mount --make-rprivate / 2>/dev/null || mount --make-rslave / 2>/dev/null; then + if ! grep -q " shared:" /proc/self/mountinfo; then + export VECTOR_MOUNTNS_ISOLATED=1 + fi + fi + exec "$0" "$@" + ' "$DAEMON" --system-server-max-retry=3 "$@" & +elif unshare -m true >/dev/null 2>&1; then + trace "starting vectord with unshare -m (propagation unchanged, mount skipped)" + unshare -m "$DAEMON" --system-server-max-retry=3 "$@" & +else + trace "starting vectord without a mount namespace (resource-cache mount skipped)" + "$DAEMON" --system-server-max-retry=3 "$@" & +fi