Repository navigation
Conversation
…orer
action.sh: the injection path needs ${INJECTED_PACKAGE_NAME} to host a 2D
activity and to receive the injected manager dex, neither of which holds in
every environment. When the manager has been installed as a regular package
-- manager.apk ships inside the module for exactly that -- launch its
activity directly and leave the injection path as the fallback.
service.sh: "unshare --propagation slave -m" is GNU util-linux syntax;
toybox builds that implement only a subset reject the flag outright, which
leaves the daemon dead with nothing anywhere to say so. Probe the flag with
a throwaway command and fall back to "unshare -m", then to running the
daemon without a namespace at all.
|
Thanks — this matches a failure we hit on a device that is not a conventional phone either, and the diagnosis was exactly as you describe. Environment: Samsung Galaxy S25 (SM-S931U1), Android 16 / One UI 8, KernelSU v3.2.5 in LKM late-load mode (bootloader locked, root is loaded after boot, so the Reproduced on the unmodified script:
One suggestion for the fallback chain: before degrading to and it really applies it — the namespace becomes a slave of the parent's, instead of a namespace without propagation semantics: (Unknown options are still rejected, so the probe stays meaningful: What we ship — note find_busybox() {
for bb in /data/adb/ksu/bin/busybox /data/adb/magisk/busybox /data/adb/ap/bin/busybox; do
[ -x "$bb" ] && { echo "$bb"; return; }
done
}
BB="$(find_busybox)"
if [ -n "$BB" ] && "$BB" unshare --propagation slave -m true >/dev/null 2>&1; then
"$BB" unshare --propagation slave -m "$MODDIR/daemon" --system-server-max-retry=3 "$@" &
elif unshare --propagation slave -m true >/dev/null 2>&1; then
unshare --propagation slave -m "$MODDIR/daemon" --system-server-max-retry=3 "$@" &
elif unshare -m true >/dev/null 2>&1; then
unshare -m "$MODDIR/daemon" --system-server-max-retry=3 "$@" &
else
"$MODDIR/daemon" --system-server-max-retry=3 "$@" &
fiTwo more things from the same debugging session, both consequences of the same "fails without saying so" property:
if pgrep -f '^vectord' >/dev/null 2>&1; then exit 0; fiThanks for the |
unshare -m on a toybox that rejects --propagation still succeeds, but the new namespace keeps the parent's shared propagation type, so the daemon's tmpfs over /data/resource-cache propagates back and hides the real cache from every process. Probe the root solution's busybox unshare first, since it usually implements --propagation; then make the tree private inside a plain unshare -m; and only when neither works run without that mount. The service stage had no way to say which path it took, and a daemon that never started left no trace at all. Record the chosen path in /data/local/tmp/vector-service.log and mirror it to logcat. Finally, refuse to start a second daemon when one is alive. KernelSU's emulated soft reboot replays service.sh without killing detached processes, and tryLock() does not turn the second one away (JingMatrix#878); the two then claim the same proxy service and break injection.
toybox's mount accepts --make-rprivate, exits 0, and changes nothing. The probe therefore reported a private namespace while /data stayed shared, and the daemon's tmpfs would have propagated to the host on a device whose service stage runs with shared mounts. Check that no mount is still shared before exporting VECTOR_MOUNTNS_ISOLATED, and if the conversion does not take effect, still run the daemon in the namespace -- just without the tmpfs mount, rather than not starting it at all. Verified on a PICO 4 (Android 10): the corrected probe exits 1 under toybox, the Magisk busybox path is selected, and the daemon's resource-cache tmpfs is absent from PID 1's namespace.
What
Two robustness fixes in the Magisk module scripts, both for environments that are not a conventional phone. Each keeps today's behaviour as the first choice and only falls back when the environment cannot support it.
zygisk/module/action.sh— prefer the installed managerThe action button currently only knows the injection path: hand the intent to
${INJECTED_PACKAGE_NAME}and let the Zygisk-injected manager answer it. That needs the shell process to host a 2D activity and to receive the injected manager dex. Neither is guaranteed everywhere: some systems never let the shell host an activity at all, and dynamic code loading restrictions can block the dex (#805 fixed one such case on GrapheneOS). Where the injection path cannot work, the action button is a dead end — even thoughmanager.apkships inside the module and #809 added a standalone install for exactly this situation.When the manager has been installed as a regular package, launch its activity directly instead. If it has not, the injection path runs unchanged.
zygisk/module/service.sh— start the daemon where unshare is poorerunshare --propagation slave -mis GNU util-linux syntax. Android ships toybox, and toybox builds that implement only a subset of util-linux reject the flag outright:The daemon then never starts, and nothing says so:
service.shhas no error path, and the failure only shows up as every Xposed module silently doing nothing. Probe the flag with a throwawaytrueand fall back tounshare -m, then to running the daemon without a namespace. The full invocation still runs first wherever it is supported.Testing
Verified on a PICO 4 (Android 10) VR headset, where
unshare --propagationis rejected and the shell cannot host the manager:unshare --propagation slave -m trueexits 1 withUnknown option propagation;unshare -m trueexits 0. Killedvectord, ran the newservice.sh, and the daemon came back (VectorDaemon: Successfully injected Vector IPC binder for applications).manager.apkinstalled from the module, the action opensorg.matrix.vector.manager/.ui.MainActivitydirectly (force-stopped the manager, ran the action, the process returned). Without the package installed, the injection path still runs.Notes
pm installof the bundledmanager.apkis still a manual step there, and Give the manager ways in again, and name builds by where they came from #809's first-launch prompt lives inside the manager, so it cannot be reached either. Acustomize.shoffer at flash time would close that gap, but it is a bigger change than this PR should carry.