- 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)
- 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
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
- /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).
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.
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
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.