Commit graph

26 commits

Author SHA1 Message Date
okhsunrog
c059c64582 fix(zygisk): cover libc socket-interface binds 2026-07-13 21:57:05 +03:00
okhsunrog
bca81b80e7 fix: block socket binds to hidden VPN interfaces 2026-07-13 20:22:51 +03:00
okhsunrog
a29f26be7d refactor: separate activation and config responsibilities 2026-07-10 11:31:38 +03:00
okhsunrog
04f772e7b0 fix: close native backend race and filtering gaps 2026-07-10 10:21:29 +03:00
okhsunrog
e6723861bf chore: release v1.1.1 2026-07-05 15:11:38 +03:00
okhsunrog
6b6ed490ed chore: release v1.1.0 2026-07-04 23:32:51 +03:00
Danila Gornushko
995f218f16
Fix FolkPatch KPM activation (#237)
* Fix FolkPatch KPM activation

* Keep APatch default supercall version
2026-07-04 20:10:02 +03:00
Danila Gornushko
5902c0f86c
Upgrade diagnostic captures (#227)
Some checks are pending
CI / kmod-activator (push) Blocked by required conditions
CI / kmod (android12-5.10) (push) Blocked by required conditions
CI / kmod (android13-5.10) (push) Blocked by required conditions
CI / kmod (android13-5.15) (push) Blocked by required conditions
CI / kmod (android14-5.15) (push) Blocked by required conditions
CI / kmod (android14-6.1) (push) Blocked by required conditions
CI / kmod (android15-6.6) (push) Blocked by required conditions
CI / kmod (android16-6.12) (push) Blocked by required conditions
CI / kmod-qemu (android12-5.10) (push) Blocked by required conditions
CI / kmod-qemu (android13-5.10) (push) Blocked by required conditions
CI / kmod-qemu (android13-5.15) (push) Blocked by required conditions
CI / kmod-qemu (android14-5.15) (push) Blocked by required conditions
CI / kmod-qemu (android14-6.1) (push) Blocked by required conditions
CI / kmod-qemu (android15-6.6) (push) Blocked by required conditions
CI / kmod-qemu (android16-6.12) (push) Blocked by required conditions
CI / kpm-qemu (android12-5.10) (push) Blocked by required conditions
CI / kpm-qemu (android13-5.10) (push) Blocked by required conditions
CI / kpm-qemu (android13-5.15) (push) Blocked by required conditions
CI / kpm-qemu (android14-5.15) (push) Blocked by required conditions
CI / kpm-qemu (android14-6.1) (push) Blocked by required conditions
CI / kpm-qemu (android15-6.6) (push) Blocked by required conditions
CI / kpm-qemu (android16-6.12) (push) Blocked by required conditions
CI / kpm-qemu-legacy (4.14) (push) Blocked by required conditions
CI / kpm-qemu-legacy (4.19) (push) Blocked by required conditions
CI / kpm-qemu-legacy (5.4) (push) Blocked by required conditions
CI / kpm (push) Blocked by required conditions
CI / zygisk (push) Blocked by required conditions
CI / lsposed (push) Blocked by required conditions
CI / portshide (push) Blocked by required conditions
CI / release (push) Blocked by required conditions
* feat: upgrade diagnostic captures

Collect richer debug and full-logcat bundles with device, backend, hook, portshide, and kernel partition evidence.

Add an explicit kernel image export for maintainer-requested kernel analysis.

* fix: harden diagnostic capture sessions

* fix(lsposed): harden diagnostic export error paths from review

Address correctness findings from a review of the diagnostic-capture upgrade,
all validated on a Pixel 4a via the agent bridge (kernel/debug/logcat exports).

- Kernel image export: wrap exportKernelImagesZip so a zip/IO failure returns
  null (and cleans the parts dir) instead of escaping and stranding the export
  button disabled; add try/finally at the DiagnosticsScreen call site. Cap the
  per-partition dd (skip unknown/oversized partitions, bound with count=) so a
  mislabelled or huge partition can't fill /data.
- HookDiagnostics: drive counter deltas off an explicit "baseline captured"
  flag instead of baselineCounters.isEmpty(), so a fresh boot with an empty
  baseline no longer reports every delta as n/a. Add HookDiagnosticsTest.
- DebugShellSnapshot: raise the 20s snapshot timeout to 60s and preserve+flag a
  timeout-truncated section instead of silently dropping it and every later one.
- LogcatRecorder: delete the intermediate raw logcat file after zipping and on
  failed start, so repeated captures stop leaking multi-MB files into cache.
- DebugExport: rethrow CancellationException before the generic catch so
  navigating away mid-capture cancels cleanly instead of logging a false error.
- DebugCaptureLogging: when the on-disk canonical config is present but
  unparseable, refuse the targets-snapshot rebuild (which would reset settings
  and clear autoHiddenPackages); fall back to an app-process-only toggle.
- Dashboard: don't warn "ports rules not active" for a module the user disabled
  via their manager (new ports_disabled probe, mirrors the activator's guard).
- activator write_atomic: per-process temp name so a boot-time and app-triggered
  activation can't corrupt each other's load_status.
- Drop the wasted multi-user pm enumeration from the counter baseline command.
2026-07-02 16:25:53 +03:00
okhsunrog
3c7853d2d5 chore: bump internal crates (activator, protocol) to 1.0.0
Don't leave the shared vpnhide_activator / vpnhide_protocol crates on the
old version while the release is 1.0.0. release.py now bumps them too.
2026-07-01 22:48:01 +03:00
Danila Gornushko
1aa221c955
fix(kpm): make the ctl0 status/stats channel reliable on real devices (#216)
* fix(kpm): build with -mcmodel=large so .rodata addressing survives the loader

The KPM ctl0 status/stats replies came back corrupted on a real APatch
(KernelPatch) device — garbled/duplicated string literals ("vpnhide vpnhide",
"backendr", a stray "1 stats" prefix). That made the app's root snapshot
fail to parse the kpm_state section and show "Startup preparation failed",
with no working KPM control channel.

Root cause: the .kpm was built with -mcmodel=small, which emits ADRP
(ADR_PREL_PG_HI21, PC-relative ±4 GB) relocations for .rodata string literals.
KernelPatch loads the module in a vmalloc region that can sit more than 4 GB
from where those literals were assumed, so the relocations overflow and resolve
to garbage addresses. The string-formatting code (vpnhide_format_status/stats,
shared with the .ko) is correct — it's only the .kpm's address materialisation
that breaks. The .ko is unaffected because Kbuild handles module addressing.

Switch the KPM build to -mcmodel=large, which materialises absolute addresses
via MOVW_UABS (no ±4 GB range limit). Verified on a real Pixel 4a (APatch,
4.14): ctl0 status/stats now decode cleanly and the app starts. QEMU KPM
harness still passes 18/18 with no panic on android16-6.12 (the larger .kpm
still boots KernelPatch) and android14-6.1.

* fix(activator): trust kpatch ctl0-read stdout, not its byte-count exit status

The kpatch CLI prints a ctl0 reply to stdout and exits with the supercall's
return value, which for a READ is the reply byte count (e.g. 64), not 0.
run_kpatch_kpm_ctl0_read treated any non-zero exit as failure, so every KPM
status/stats read failed on KernelSU / KPatch-Next and the dashboard showed no
KPM counters. (On APatch the supercall is read directly, so that path was
unaffected — its bug was the .rodata corruption fixed by the -mcmodel change.)

Capture stdout regardless of exit code: the reply text is authoritative, and on
a real error the CLI leaves its buffer empty so stdout is empty. The config path
already tolerates the byte-count exit status (kpatch_ctl0_config_status_ok); this
brings reads in line. Verified on a real Pixel 8 Pro (KSU-Next + KPatch-Next):
'activator state' now returns the clean status/stats reply instead of erroring.
2026-06-30 18:09:26 +03:00
Danila Gornushko
c5d89c3dd9
Block the whole loopback range; warn on native-target overflow (#208)
* fix(ports): block the whole loopback range; warn on native-target overflow

Two activator findings from the codebase audit.

The localhost port blocker only rejected 127.0.0.1, so a proxy/VPN daemon bound
to the wildcard 0.0.0.0 (the common allow-lan / TUN setup for Clash, sing-box,
V2Ray) stayed reachable on any other 127.x.x.x alias — an observer could
connect(127.0.0.2:port) and get a handshake a clean device would refuse, a
positive fingerprint. Reject the whole 127.0.0.0/8 block instead (::1 already is
the entire IPv6 loopback).

The native-target cap (64) was restated as a bare literal in three places (the
activator and both C backends) and projection truncated the overflow with a
silent .take(), dropping the highest-UID apps from protection with no diagnostic.
Export the cap once from the shared protocol crate, alias it in the activator,
cross-reference it from the C #defines, and warn on stderr when the target set
exceeds it.

* style: rustfmt activator loopback comment

* docs(changelog): note the native-target overflow warning
2026-06-30 11:31:18 +03:00
Danila Gornushko
5cbdc54e4f
Drop dead proc diagnostics, fix misaligned reads, align parser parity (#206)
* fix(native): drop dead proc diagnostics, fix misaligned reads, parser parity

Three native-correctness fixes from the codebase audit.

Diagnostics: /proc/net/{tcp,tcp6,udp,udp6,fib_trie} expose the VPN only as a
hex local address, never as an interface name, so the name-matching probe could
only ever report a green "no VPN entries" regardless of an actual leak — and
from inside the targeted (self-hidden) process there is no reference tunnel
address to decode and compare. Remove those five checks rather than keep a
meaningless always-pass; the address-leak vector on these files is covered by
zygisk's socket-table filters. /proc/net/{route,ipv6_route,if_inet6,dev}, which
do carry interface names, are unchanged.

UB: SIOCGIFCONF and the netlink dumps reinterpret the kernel's bytes as ifreq /
nlmsghdr / rtattr over plain [u8; N] buffers (align 1), so forming those refs is
undefined behaviour. Back the three buffers with an 8-byte-aligned wrapper so
every such reference is well-formed.

Protocol parity: the Rust parse_header skipped a non-ASCII first significant
line and searched on, while C (kmod/KPM) and Kotlin (LSPosed) reject the whole
payload — same wire bytes, different accept decision across the mutually
exclusive backends. Distinguish "blank/comment" (skip) from "significant but
non-ASCII" (reject) so all three agree. Clarify the §4.2 corner in the spec and
add header-position golden vectors (cfg + kind) that all three parsers now pass.

* style: rustfmt protocol parse_header
2026-06-30 11:30:11 +03:00
okhsunrog
52b137b78e Add backend-specific native hook selection 2026-06-29 19:24:03 +03:00
okhsunrog
3b08c79ff5 fix: device-verified KPatch runtime detection + root_manager order
Verified on a Pixel 4a (APatch, kernel 4.14) and a Pixel 8 Pro (KSU-Next +
KPatch-Next-Module, GKI 6.1):

- KPM runtime probe: drop the dead /data/adb/ksu/bin/kpatch guess (KSU-Next
  ships kpatch via the KPatch-Next-Module, not ksu/bin) and the unused PATH
  check. The real signals are /data/adb/ap (APatch — loads KPMs via supercall,
  no CLI on disk) and /data/adb/modules/KPatch-Next/bin/kpatch (the module, same
  on Magisk/KSU/KSU-Next). Mirror the same trim in the activator's find_kpatch
  and the KPM post-fs-data, documenting that APatch goes through the supercall.
- root_manager: APatch and KSU both ship a /data/adb/magisk compat layer (the
  APatch 4a has /data/adb/magisk + magisk.db), so the old order reported APatch
  as 'magisk'. Check magisk first and let the exclusive ksu/ap dirs override it.
- reword the KPM needs-runtime string: drop the misplaced 'APatch already
  includes it' note (an APatch user never sees this string — the runtime flag is
  already true via /data/adb/ap).
2026-06-29 19:06:37 +03:00
okhsunrog
a3fc3bac9c fix(activator): tolerate per-hook Java selection in canonical config
The app writes a per-app Java hook selection as a string array ("java":
["lsposed_network", ...]), the same shape as "native". The Rust activator
typed "java" as a plain bool, so serde rejected the whole canonical with
'invalid type: sequence, expected a boolean'. Because the native activator
parses the entire config, a partial Java selection for a single app silently
disabled the kmod/KPM/Zygisk backend for every app.

The activator never acts on "java" (LSPosed self-reads and owns Java hook
selection), it only needs to parse it. Accept both the bool and the array
form, collapsing to enabled-ness.

- activator: de_bool_or_hook_list deserializer for the java field; regression
  test covering the array form (and an empty array = disabled), asserting
  native projection is unaffected
- testdata/storage_config_v1.json: give org.example.proxy a per-hook java
  array so the shared Rust+Kotlin parity fixture exercises the shape that
  previously slipped through; update both fixture assertions
2026-06-29 18:03:01 +03:00
okhsunrog
78aa6da36b fix(kpm): report truthful conflict status and warn in app
When the KPM stands down for a co-installed .ko backend (§1.5), the boot
flow previously let the activator's silent skip be recorded by service.sh
as 'activator 1 configured' — load_status falsely claimed the KPM was
loaded. The .ko+KPM conflict banner also keyed on both backends being
active, a state that can never be observed (two live kernel hookers freeze
the device), so users were never warned about the redundant co-install.

- activator: activate_kpm_boot returns KpmBootOutcome; a kmod conflict is
  DeferredConflict (not silent Ok), mapped to exit code 3 in the kpm bin.
- kpm service.sh: conflict dir-check before the APatch superkey branch
  (so a co-installed .ko isn't masked as awaiting_superkey), and exit 3
  is recorded as a truthful 'conflict' status instead of 'configured'.
- app: surface the deferred-conflict (load_status runtime=conflict for the
  current boot) as a warning; new dashboard_issue_native_conflict_deferred
  string (EN+RU) telling the user to remove one kernel backend.
- post-fs-data.sh: replace the comment claiming a non-existent kernel-half
  guard with the actual layered, fail-safe userspace design.
2026-06-29 17:24:42 +03:00
okhsunrog
0cfa16d331 feat(portshide): add configurable port ranges 2026-06-29 15:56:59 +03:00
okhsunrog
8c071815c0 Add statistics tab 2026-06-29 10:56:32 +03:00
okhsunrog
552762a246 Add LSPosed interception stats 2026-06-29 02:43:23 +03:00
okhsunrog
fef99af08a fix: tighten kpm follow-up diagnostics 2026-06-28 22:53:37 +03:00
okhsunrog
dfa0b99cbf fix(kpm): probe APatch supercall command format 2026-06-28 19:48:44 +03:00
okhsunrog
0f829e52bd fix(kpm): support APatch supercall activation 2026-06-28 19:28:12 +03:00
okhsunrog
69f0a92716 feat(storage): apply ports through activator 2026-06-28 19:01:44 +03:00
okhsunrog
05a3c0520f fix(storage): move boot wait into activator 2026-06-28 18:33:23 +03:00
okhsunrog
b5790766e7 feat(storage): migrate app to canonical config 2026-06-28 18:27:51 +03:00
okhsunrog
dd2a8038e9 feat(storage): add native config activators 2026-06-28 17:47:45 +03:00