* 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.
8.6 KiB
ADB root debugging
How to make adb shell su -c ... useful on rooted test devices, and how to
debug the common "root but Permission denied" failures.
This is test-device guidance. Do not ship broad diagnostic SELinux policy or all-capability shell profiles as part of production modules.
What "root" must include
For diagnostics, UID 0 is not enough. A useful adb root shell needs all three:
- UID/GID/groups that can pass Unix DAC checks.
- Linux capabilities, especially DAC override/read-search.
- SELinux allow rules for the file labels being read.
Check all three before assuming a device path is inaccessible:
adb -s SERIAL shell 'su -c "
id
getenforce
echo root_shell_pid=\$\$
grep -E \"Cap(Inh|Prm|Eff|Bnd|Amb)\" /proc/\$\$/status
ls -ldZ /data /data/adb /data/adb/modules /data/system
head -c 120 /data/system/vpnhide_config.json 2>&1
cat /data/adb/vpnhide_kpm/load_status 2>&1 | head
"'
Command chains and quoting
When running more than one command through su -c, quote the whole remote
command for adb shell, then quote the payload passed to su -c.
Do not write chained commands like this:
adb -s SERIAL shell su -c 'id; iptables -S OUTPUT'
The local shell strips those quotes before adb sends the command, so Android's
remote shell can parse it as su -c id; iptables -S OUTPUT. Only id runs under
su; the rest runs as the ordinary adb shell (uid=2000, u:r:shell:s0,
usually with no useful capabilities).
Use one of these forms instead:
adb -s SERIAL shell "su -c 'id; iptables -S OUTPUT'"
adb -s SERIAL shell 'su -c "id; iptables -S OUTPUT"'
When checking the root shell itself, prefer /proc/\$\$/status inside the
su -c payload. /proc/self/status describes the process that opens the file
(cat, grep, etc.), which can hide quoting mistakes while debugging command
chains.
Healthy examples:
- KernelSU Next shell:
uid=0, groups includeroot,system,shell, andCapEff/CapPrmare non-zero, for example000001ffffffffff. - APatch shell:
uid=0, SELinux context commonlyu:r:magisk:s0, andCapEffis non-zero.
Broken examples:
uid=0(root)butCapEff: 0000000000000000: KernelSU granted UID 0 but no capabilities. Root will still fail ordinary Unix permission checks such as/data/local/tmpor0600files.ls -lZworks butcatfails withPermission denied: SELinux allows metadata/stat but deniesopen/read./data/adb/ksu/bin/ksud: inaccessible or not found: often an SELinuxexecute/searchdenial or a PATH/mount namespace issue, not a missing file.
vpnhide paths worth checking
adb -s SERIAL shell 'su -c "
cat /data/system/vpnhide_config.json
cat /data/system/vpnhide_lsposed_state 2>/dev/null
cat /data/adb/vpnhide_kmod/load_status 2>/dev/null
cat /data/adb/vpnhide_kpm/load_status 2>/dev/null
cat /data/adb/vpnhide_ports/load_status 2>/dev/null
cat /data/adb/vpnhide_ports/load_log 2>/dev/null
ls -lZ /data/adb/modules/vpnhide_kmod /data/adb/modules/vpnhide_kpm /data/adb/modules/vpnhide_ports 2>&1
"'
The canonical JSON is /data/system/vpnhide_config.json, not /data/adb.
/data/adb is still important for module files, KPM status, superkey storage,
KernelSU/APatch CLIs, and LSPosed module state.
APK variants for LSPosed/Vector smoke tests
Use the default debug APK (:app:assembleDebug) or the release APK for
cold-start checks. The default debug APK remains debuggable, but it is
R8/resource-shrunk so LSPosed/Vector has less dex to prepare after the APK is
updated as an Xposed module.
The rawDebug variant (:app:assembleRawDebug) keeps the old unminified debug
behavior for Studio/debugger work. It can be much larger; after updating that
APK, LSPosed/Vector may spend long enough preparing module code before the app
attaches that Android logs Process ... failed to attach / start timeout.
That symptom does not mean VPN Hide is in its own LSPosed scope. The expected
module scope is still System Framework only.
KernelSU Next Shell profile
If com.android.shell has UID 0 but zero capabilities, fix the KernelSU Next
app profile:
- KernelSU Next app -> Superuser -> Shell (
com.android.shell). - Enable Superuser.
- Use Custom profile.
- Keep mount namespace
Inheritedunless debugging module mounts. - Set
uid = 0,gid = 0. - Set groups to at least
root,system, andshell. - For a dedicated test device, enable all capabilities. Minimal diagnostic subsets are easy to miss; all caps makes failures clearly SELinux-related.
- Keep SELinux context as
u:r:ksu:s0unless you know the target domain exists.
After changing the profile, open a new adb shell su -c ... session and verify:
adb -s SERIAL shell 'su -c "
id
echo root_shell_pid=\$\$
grep -E \"Cap(Prm|Eff|Bnd)\" /proc/\$\$/status
echo ok >/data/local/tmp/vpnhide_diag_write_test
cat /data/local/tmp/vpnhide_diag_write_test
rm /data/local/tmp/vpnhide_diag_write_test
"'
For SELinux read access in the u:r:ksu:s0 domain, apply a narrow Shell policy
through KernelSU Next:
adb -s SERIAL shell 'cat >/data/local/tmp/vpnhide-ksu-shell.te <<EOF
allow ksu adb_data_file dir { getattr search open read }
allow ksu adb_data_file file { getattr open read map execute execute_no_trans }
allow ksu adb_data_file lnk_file { getattr open read }
allow ksu system_file dir { getattr search open read }
allow ksu system_file file { getattr open read map execute execute_no_trans }
allow ksu system_file lnk_file { getattr open read }
allow ksu system_data_file dir { getattr search open read }
allow ksu system_data_file file { getattr open read map }
allow ksu packages_list_file file { getattr open read map }
allow ksu shell_data_file dir { getattr search open read write add_name remove_name }
allow ksu shell_data_file file { getattr open read write create append unlink setattr map }
EOF'
adb -s SERIAL shell 'su -c "
PATH=/data/adb/ksu/bin:\$PATH
ksud sepolicy apply /data/local/tmp/vpnhide-ksu-shell.te
POLICY=\$(tr \"\n\" \";\" </data/local/tmp/vpnhide-ksu-shell.te)
ksud profile set-sepolicy com.android.shell \"\$POLICY\"
"'
ksud sepolicy apply is live. ksud profile set-sepolicy makes the same Shell
policy persistent across reboot.
Do not manually create random diagnostic directories under
/data/adb/modules/ on KernelSU Next. Use app profiles or a properly installed
module zip. Broken module metadata can make ksud module list fail.
APatch and Magisk-style shells
APatch shells usually run in u:r:magisk:s0 and have non-zero capabilities by
default. If SELinux blocks /data/adb or /data/system, use magiskpolicy:
adb -s SERIAL shell 'cat >/data/local/tmp/vpnhide-magisk-shell.te <<EOF
allow magisk adb_data_file dir { getattr search open read }
allow magisk adb_data_file file { getattr open read map execute execute_no_trans }
allow magisk adb_data_file lnk_file { getattr open read }
allow magisk system_file dir { getattr search open read }
allow magisk system_file file { getattr open read map execute execute_no_trans }
allow magisk system_file lnk_file { getattr open read }
allow magisk system_data_file dir { getattr search open read }
allow magisk system_data_file file { getattr open read map }
allow magisk packages_list_file file { getattr open read map }
EOF'
adb -s SERIAL shell 'su -c "
PATH=/data/adb/ap/bin:/data/adb/magisk:\$PATH
magiskpolicy --live --apply /data/local/tmp/vpnhide-magisk-shell.te
"'
For persistence on APatch/Magisk test devices, install a small test-only module
with sepolicy.rule. APatch loads active modules' sepolicy.rule at boot.
Keep such modules out of release zips.
KPM and KernelPatch diagnostics
Useful KPatch-Next checks:
adb -s SERIAL shell 'su -c "
PATH=/data/adb/modules/KPatch-Next/bin:/data/adb/ksu/bin:\$PATH
kpatch hello
kpatch kpm list
cat /data/adb/vpnhide_kpm/load_status
dmesg | grep -iE \"vpnhide|kpatch|kpm|KernelPatch\" | tail -n 80
"'
kpatch kpm ctl0 exits with the KPM handler return value. Current vpnhide KPM
builds return 0 after applying config. Older builds returned the configured
target count, so exit status: 15 can mean "15 targets configured", not
failure. Actual parse failures commonly surface as 255 because the KPM
returned -1.
Quick recovery checklist
- New
susession, then checkidandCapEff. - Check labels with
ls -lZ. - Check whether the failing operation is DAC (
CapEff=0, groups missing) or SELinux (CapEffnon-zero, labels visible,open/read/executedenied). - On KernelSU Next, fix Shell groups/capabilities in the app profile first.
- Apply only the SELinux rules needed for diagnostics.
- Re-test
ksud module list, vpnhide status files, and a write to/data/local/tmp.