Commit graph

40 commits

Author SHA1 Message Date
okhsunrog
b85673c81c chore: release v0.6.2 2026-04-17 16:24:53 +03:00
okhsunrog
3fc735572a build: stamp git-describe build version into every artifact
Add scripts/build-version.sh — a single source of truth for the
effective version string:

  * HEAD on tag vX.Y.Z          -> "X.Y.Z"
  * N commits past tag          -> "X.Y.Z-N-gSHA"
  * working tree dirty          -> additional "-dirty" suffix
  * no git / no matching tag    -> VERSION file fallback

Wired into every packaging path:

  * zygisk/build-zip.sh and portshide/build-zip.sh now stage a copy of
    module/ and sed-patch `version=` in the staging copy, so committed
    module.prop files stay at the last-released version.
  * kmod/build-zip.sh now builds into a staging copy too.
  * The kmod CI step runs build-version.sh and sed-patches module.prop
    before zipping (git installed in the DDK container).
  * lsposed/app/build.gradle.kts exec's build-version.sh at configure
    time and assigns the result to `versionName` (versionCode stays
    static, still bumped by release.py).

All actions/checkout@v6 gained `fetch-depth: 0` so git describe sees
the full tag history inside CI containers.

Result: a locally built or CI-from-main APK shows up in Android
Settings as e.g. `0.6.1-16-gf86e5e5`, and the zip inside carries the
same string in module.prop; the Magisk/KSU manager displays it in the
update list. Release tag builds are indistinguishable from before —
clean `X.Y.Z`. Diagnostic bug reports now carry the exact commit in
the App version line of device_info.txt.
2026-04-17 14:42:40 +03:00
okhsunrog
876829d9ad docs: add CONTRIBUTING.md and docs/ with build/release/changelog guides
Split the contributor-facing knowledge that used to live in the local
CLAUDE.md into versioned, public docs:

- CONTRIBUTING.md — PR process, commit conventions, required changelog
  entry for user-visible changes, code-style checks.
- docs/development.md — prereqs, keystore setup, per-module build
  commands, device install, CI lints.
- docs/releasing.md — VERSION bump → update-version.py → tag → CI →
  update-json.sh flow, with the rationale for why update-json is a
  separate post-release commit.
- docs/changelog.md — changelog.json as source of truth, how the two
  generated markdowns are regenerated, when to add an entry.

Extended kmod/BUILDING.md with a Podman variant of the DDK command,
covering rootless + SELinux (Fedora) where --userns=keep-id and :Z are
required. Kept the kmod build docs next to the code since the GKI /
DDK complexity is kmod-specific.

Component READMEs untouched — they document each module's architecture
and belong next to the code.
2026-04-17 13:51:33 +03:00
okhsunrog
00ba398f36 fix(service.sh): wait for PackageManager to index user apps before resolving UIDs
`pm list packages` starts responding to IPC very early in boot but
returns only system packages for several more seconds. service.sh's
previous `pm list packages >/dev/null && break` loop exited as soon as
PM was alive — before user-installed packages (including the vpnhide
app itself and any chosen targets) were indexed. The subsequent
`pm list packages -U | grep "^package:$pkg "` returned nothing, so
/data/system/vpnhide_uids.txt was written empty, and the LSPosed hook
in system_server cached an empty target set for the session. Result:
all Java-level filtering silently disabled until the next reboot where
we got lucky on timing.

Gate the boot wait on our own package being visible in the list (with
a 60s budget instead of 30s). That guarantees PM has moved past the
system-only snapshot before we read target names.

Also add per-call diagnostic logs to the three writeToParcel hooks
(NC, NI, LP) — `VpnHide-NC`, `VpnHide-NI`, `VpnHide-LP` tags — so the
next "Java check fails with zygisk on / passes with zygisk off" style
report can be diagnosed from logs alone instead of a live instrumented
build. The per-call volume is modest compared to system_server's own
logging and the logs live inside the LSPosed bridge log.
2026-04-16 15:33:46 +03:00
okhsunrog
53aacaa870 chore: release v0.6.1 2026-04-16 00:24:58 +03:00
okhsunrog
a37b1b7cd8 fix(modules): add META-INF for Magisk versions before v28
Magisk before v28 requires META-INF/com/google/android/update-binary
+ updater-script in module zips to extract them; without these the
manager fails with an unpack error (issue #23). Magisk v28+ removed
this requirement, which is why the bug only shows up on older managers.

Added the standard Magisk template (same one already used by the
zygisk module) to portshide and kmod. CI's `(cd module && zip -qr)`
step picks up the new files automatically.
2026-04-16 00:23:05 +03:00
okhsunrog
a5c21f262c chore: release v0.6.0 2026-04-15 18:42:08 +03:00
okhsunrog
c882586032 Hook sock_ioctl instead of dev_ifconf for SIOCGIFCONF filtering
On GKI 5.10 kernels built with Clang LTO, dev_ifconf() gets inlined
into sock_do_ioctl(). The symbol remains in kallsyms as a dead stub,
so kretprobe registration succeeds but the probe never fires.
Confirmed by disassembly on Xiaomi 13 Lite (5.10.136) and Lenovo
Legion 2 Pro (5.10.101).

On 6.1+, SIOCGIFCONF was moved from sock_do_ioctl() into sock_ioctl()
directly, so hooking sock_do_ioctl would miss it on newer kernels.

sock_ioctl is the file_operations->unlocked_ioctl callback — used as
a function pointer, so LTO cannot inline it. SIOCGIFCONF passes through
it on every kernel version. After it returns, ifconf data is already in
userspace, so we filter uniformly via copy_from_user/copy_to_user with
no version-specific code paths.
2026-04-15 15:54:37 +03:00
okhsunrog
00a40db04c chore: bump version to v0.5.3 2026-04-15 00:49:37 +03:00
okhsunrog
1a1b69a3e3 Fix clang-format violations in kmod 2026-04-15 00:40:45 +03:00
okhsunrog
f67cb40925 Add debug logging toggle and diagnostic export
kmod: add /proc/vpnhide_debug toggle — writing "1" enables detailed
pr_info logging in all 6 kretprobe hooks (uid, target, interface name,
filter decisions). Disabled by default, zero overhead when off.

app: add "Collect debug log" button on Diagnostics screen. Enables
kmod debug, clears dmesg, runs all checks, captures dmesg, collects
device info, module status, targets, interfaces, /proc/net, kallsyms,
LSPosed config, root manager version, and logcat into a zip. Save to
disk or share via share sheet.

Also: remove unused kmod/symvers/, add kmod/*.lds to .gitignore.
2026-04-15 00:35:29 +03:00
okhsunrog
8420965cd9 chore: bump version to v0.5.2 2026-04-14 17:03:50 +03:00
okhsunrog
ca9773d14f Fix hardcoded v0.1.0 in customize.sh — read version from module.prop 2026-04-14 16:56:57 +03:00
okhsunrog
aa2d6c098c Fix SIOCGIFCONF filtering on kernel 5.10
dev_ifconf() changed its signature between 5.10 and 5.15:

  5.10:  dev_ifconf(struct net *, struct ifconf *ifc, int size)
         x1 = kernel pointer (caller did copy_from_user)
  5.15+: dev_ifconf(struct net *, struct ifconf __user *uifc)
         x1 = userspace pointer

The kretprobe handler assumed 5.15+ (copy_from_user on x1), which
silently failed on 5.10 because copy_from_user on a kernel pointer
returns EFAULT. This left SIOCGIFCONF unfiltered — tun0 visible.

Use LINUX_VERSION_CODE to select the right access method at compile
time. Each kmod build already targets a specific GKI generation, so
this is safe.

Reported by users on Android 12 (5.10) and Android 14 (non-GKI 5.10).
2026-04-14 16:14:14 +03:00
okhsunrog
0073ef7030 Fix false LSPosed warnings and improve target detection
- Detect LSPosed in all known module paths (zygisk_vector, zygisk_lsposed, lsposed)
- Skip LSPosed config warnings when hooks are already active at runtime
- Check all modules for empty targets, not just LSPosed
- Bump version to v0.5.1
2026-04-14 02:57:37 +03:00
okhsunrog
e5a85c5b7d chore: bump version to v0.5.0 2026-04-14 00:39:21 +03:00
okhsunrog
25e7d1d1be feat: add dashboard, per-component target lists, LSPosed status detection
Dashboard as new landing screen with module status cards (kmod, zygisk,
LSPosed), aggregated protection checks (native + Java API), and issue
alerts. Uses sealed types for type-safe state modeling (invalid states
are unrepresentable).

Three separate target lists: kmod, zygisk, and lsposed each have
independent targets.txt. Users can configure per-app which layers
protect it (L/K/Z chips in app picker). Service.sh scripts decoupled —
each resolves only its own component, with migration from unified lists.

HookEntry writes /data/system/vpnhide_hook_active with version and
boot_id so the app can detect if LSPosed hooks are active this boot.

VPN Hide app auto-adds itself to all target lists for self-diagnostics.
If just added, shows "restart needed" instead of stale check results.
App hides itself from the app picker and target counts.

Diagnostics tab no longer runs checks without VPN — shows banner only.
Removed "Run All" button (results are cached per process lifetime).

Splash screen follows system dark/light theme via Material3 DayNight.
2026-04-13 18:03:25 +03:00
okhsunrog
9ba7bfb127 refactor: drop WebUI and action.sh from kmod and zygisk modules
The VPN Hide app is now the sole UI for target management. WebUI was
KernelSU-Next-only and redundant since the app works on both KSU and
Magisk. Remove webroot/, action.sh, and all references across docs,
install scripts, module descriptions, and code comments.
2026-04-13 16:28:39 +03:00
okhsunrog
52d28bd743 chore: bump version to v0.4.2 2026-04-13 03:35:56 +03:00
okhsunrog
bfb840c4c0 feat: add LinkProperties routes filtering, expand ioctl coverage
- lsposed: filter VPN routes from LinkProperties.mRoutes before
  serialization (save-mutate-restore pattern). Previously only
  mIfaceName was cleared but routes with VPN interface names leaked.

- kmod: remove SIOCGIFFLAGS/SIOCGIFNAME whitelist from dev_ioctl_ret.
  Now all dev_ioctl commands return ENODEV for VPN interfaces, covering
  SIOCGIFMTU (MTU fingerprinting), SIOCGIFINDEX, SIOCGIFHWADDR, etc.

- zygisk: replace per-command ioctl checks with a SIOCGIF* range check
  (0x8910-0x8930). Same coverage as kmod — any ioctl with a VPN
  interface name in ifr_name returns ENODEV.

toString() on NetworkCapabilities is already covered: we mutate the
underlying fields before writeToParcel, so the deserialized object
on the client produces a clean toString() output.
2026-04-13 01:42:50 +03:00
okhsunrog
178deace6a fix: run update-version.sh for v0.4.1 2026-04-13 01:07:45 +03:00
okhsunrog
e2d41dea13 style: add clang-format, ktlint, editorconfig and format all code
- Add .editorconfig with ktlint config (disable wildcard-import rule,
  allow PascalCase for @Composable functions)
- Add kmod/.clang-format from upstream kernel tree
- Run clang-format on vpnhide_kmod.c (kernel coding style)
- Run ktlint --format on all Kotlin files (lsposed + test-app)
2026-04-12 23:26:36 +03:00
okhsunrog
e12c58cace fix: shell injection guard, use named constants, bypass own hooks for /proc/self/maps
- WebUI: validate package names against [a-zA-Z0-9_.\-]+ before
  interpolating into shell commands (both kmod and zygisk copies)
- zygisk hooks.rs: use RTM_NEWLINK/RTM_NEWADDR from filter.rs instead
  of magic constants 16/20
- zygisk lib.rs: read /proc/self/maps via raw libc::open in
  scrub_shadowhook_maps to bypass our own hooked_openat
- kmod: add comment explaining why seq->buf access without seq->lock
  is safe in fib_route_ret (seq_read holds the mutex around ->show())
- kmod: add comment clarifying MODULE_LICENSE("GPL") vs MIT SPDX
2026-04-12 23:12:45 +03:00
okhsunrog
33faf8f8aa fix: address race conditions, UB, and correctness issues across all components
kmod:
- Add explicit rcu_read_lock() around ifa->idev->dev->name dereferences
  in inet6_fill_entry, inet_fill_entry, and rtnl_fill_entry
- Remove racy READ_ONCE fast-path in is_target_uid; uncontended spin_lock
  is ~5ns on ARMv8 and the optimization had incorrect TOCTOU semantics
- Fix dev_ifconf_ret: return immediately on copy_from_user/copy_to_user
  failure instead of breaking the loop and writing back a wrong ifc_len
- Fail module load if zero kretprobes register; warn on partial registration

lsposed:
- Fix isSystemServer check-then-set race: use AtomicBoolean.compareAndSet
  to prevent duplicate hook installation from concurrent handleLoadPackage
- Fix NC hook partial state corruption: save all values before mutating,
  restore on exception, only set ThreadLocals after all mutations succeed
- Fix NI/LP hooks: replace param.result=null (which skips writeToParcel
  and corrupts the Parcel stream) with save-mutate-restore pattern
- Synchronize loadTargetUids() with double-checked locking; always cache
  result (even empty) to avoid file I/O on every Binder call
- Fix suExec: drain stderr on background thread, destroy process in finally

zygisk:
- Use std::sync::Once for shadowhook initialization instead of AtomicBool
- Handle write() return value on memfd: loop on short writes, return error
- Make netlink parsers (read_u32_ne/read_u16_ne) return Option instead of
  panicking on out-of-bounds access
2026-04-12 23:06:48 +03:00
okhsunrog
7b867c8b64 chore: add VERSION file and update-version.sh, bump to v0.4.0
Single VERSION file in repo root as the source of truth. The script
update-version.sh propagates it to all 5 locations: kmod module.prop,
zygisk module.prop, zygisk Cargo.toml, lsposed build.gradle.kts,
test-app build.gradle.kts. versionCode = major*10000 + minor*100 + patch.
2026-04-12 03:23:16 +03:00
okhsunrog
1ee26e1de8 docs: add VPN Hide app as primary target management method
The lsposed APK now includes a Compose target picker UI that works
with both kmod and zygisk on any root solution. Update all READMEs
to recommend the app over WebUI (which is KernelSU-only).
2026-04-12 03:17:59 +03:00
okhsunrog
c52a6711ff fix: UID resolution in save commands used printf \n escapes instead of real newlines
The shell variable $UIDS accumulated UIDs with literal \n (two chars)
instead of actual newlines. printf "$UIDS" wrote garbage to
/proc/vpnhide_targets and vpnhide_uids.txt. The empty-targets case
used bare > redirect which never triggers the proc write handler.

Fix: accumulate UIDs with real newlines (like service.sh does), use
echo "$UIDS" for output, and echo (writes \n) for the empty case so
the kmod write handler fires and clears the UID list.

Affects: APK target picker, kmod WebUI, zygisk WebUI.
2026-04-12 03:14:59 +03:00
okhsunrog
a8bed7e044 security: restrict target list visibility from apps
- /proc/vpnhide_targets: change from 0644 to 0600 (root only).
  Apps could read the UID list and discover which apps are targeted.
- Remove /data/local/tmp/vpnhide_targets.txt copies from service.sh
  and WebUI (no longer needed after get_module_dir() fix).
2026-04-12 02:06:39 +03:00
okhsunrog
a9a76b840c docs: remove incorrect vermagic bypass claims 2026-04-12 01:18:32 +03:00
okhsunrog
de958af450 docs: add architecture notes for all 6 kmod hooks 2026-04-11 23:34:20 +03:00
okhsunrog
21ba266a80 docs: update component READMEs to reflect current architecture 2026-04-11 23:24:25 +03:00
okhsunrog
c8ab6933e8 docs: fix Pixel GKI generation info 2026-04-11 23:20:58 +03:00
okhsunrog
32e78292c6 docs: simplify BUILDING.md, add DDK Docker build as primary method 2026-04-11 23:16:56 +03:00
okhsunrog
5eaebd0a12 license: unify entire project under MIT 2026-04-11 21:58:07 +03:00
okhsunrog
5ee50935c4 ci: use AOSP clang in Docker image, fix kmod build
- Add Google's AOSP clang (clang-r487747c, same as Pixel kernel build)
  to the CI Docker image via sparse checkout. Distro clang caused ABI
  mismatches leading to bootloops on device.
- Update kmod workflow to use the Docker image + AOSP clang instead of
  system clang from apt.
- Replace symvers with real vmlinux.symvers from Pixel kernel build
  (8050 symbols vs 4060 from device .ko extraction).
- Add kmod build deps (bc, kmod, cpio, binutils-aarch64) to Docker image.
2026-04-11 20:53:20 +03:00
okhsunrog
c391d90432 feat: add dev_ifconf, inet6/inet_fill_ifaddr hooks for full VPN hiding
Kernel module:
- Add dev_ifconf hook to filter SIOCGIFCONF interface enumeration
  (goes through sock_ioctl -> dev_ifconf, not dev_ioctl)
- Add inet6_fill_ifaddr and inet_fill_ifaddr hooks to filter RTM_GETADDR
  netlink responses. getifaddrs() was leaking tun0 via the address dump
  even though RTM_GETLINK was filtered. Uses skb_trim to undo the fill
  and return 0 (not -EMSGSIZE which causes infinite retry on empty skb).
- All 6 kretprobes now cover: ioctl, SIOCGIFCONF, netlink link dumps,
  netlink address dumps (IPv4+IPv6), and /proc/net/route.

Test app:
- Treat SELinux EACCES/EPERM as PASS — if the app can't access the
  resource, it can't detect VPN through it either.
- Test results: 14/14 passed with VPN active.
2026-04-11 19:09:00 +03:00
okhsunrog
e35cf1a6b9 refactor: overhaul kmod build system, fix kernel module bugs
Build system:
- Replace hardcoded paths in Makefile with env vars (KERNEL_SRC, CLANG_DIR)
- Add .env.example and .envrc for direnv-based config
- Simplify build-zip.sh to delegate to make instead of duplicating build command
- Rewrite BUILDING.md: 5-step happy path with direnv, standalone prep as appendix
- Remove redundant quick-reference script and step 7 (manual module.lds hack)

Kernel module (vpnhide_kmod.c):
- Fix fib_route_seq_show hook: save seq_file pointer and buffer position in entry
  handler instead of reading regs->regs[0] in return handler (which holds the
  return value on arm64, not the original argument). Rewrite buffer scanning as
  clean forward iteration with memmove compaction.
- Remove dead SIOCGIFCONF case from dev_ioctl hook (confirmed in kernel source:
  SIOCGIFCONF goes through sock_ioctl -> dev_ifconf, not dev_ioctl on GKI 6.1)
- Fix header comment: remove false tcp4_seq_show claim, correct rtnl symbol name

Test app:
- Auto-run checks on launch (LaunchedEffect) for easier adb-driven testing
2026-04-11 18:50:18 +03:00
okhsunrog
60b3235dc0 docs: redesign README hierarchy for monorepo
Root README is now the single source for shared content (verified
against, split tunneling, threat model, component overview). Sub-READMEs
focus on component-specific technical details and link back to root.

- Remove ~700 lines of duplicated content across sub-READMEs
- Update all cross-references to use monorepo relative paths
- Add test-app to components table
- Update zygisk README: mark openat/recvmsg/SIOCGIFCONF as implemented
- Fix stale links to archived repos
2026-04-11 16:20:33 +03:00
okhsunrog
ca23630e57 refactor: remove commercial app names, rewrite test app in Compose
- Remove specific commercial app/bank names from all READMEs, comments,
  and log messages to avoid legal issues. Open-source detection tools
  (RKNHardering, YourVPNDead) are kept with links.
- Rewrite test-app in Jetpack Compose with Material3 dynamic colors,
  edge-to-edge, system dark/light theme support.
- Make test UI more verbose with detailed result cards.
- Add full logcat output for all checks (tag: VPNHideTest) for
  automated testing by AI agents.
- Fix 16KB page alignment for Android 15+.
2026-04-11 16:09:31 +03:00
okhsunrog
12daca5c1a monorepo: combine vpnhide-zygisk, vpnhide (lsposed), and vpnhide-kmod
Unified repository for the complete Android VPN-hiding stack:
- zygisk/ — Rust Zygisk module (inline libc hooks via shadowhook)
- lsposed/ — Kotlin LSPosed module (Java API + system_server hooks)
- kmod/ — C kernel module (kretprobe hooks, invisible to anti-tamper)

CI workflows use path filters to build only the changed component.
2026-04-11 15:01:49 +03:00