You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
tp_marker backs off when another syscall-tracepoint user registers — on vivo that user is the root guard
#3794
Not a feature request — an interoperability report. Closing this if it is out of scope is fine; I am posting it as an idea because the report-only path needs a manager log that does not apply here.
ksu_clear_task_tracepoint_flag_if_needed() (and ksu_mark_running_process()) back off while tracepoint_reg_count > 1:
if (tracepoint_reg_count <= 1) {
ksu_clear_task_tracepoint_flag(t);
}
The intent is to leave the flag alone when ftrace/perf are using the syscall tracepoint. On vivo kernels the same condition is met by vr.ko, and it has the opposite effect.
vr.ko registers its own sys_exit probe and uses TIF_SYSCALL_TRACEPOINT — the exact bit ksu_set_task_tracepoint_flag() sets — as its "task of app origin" marker, killing the task from that probe once it holds euid 0. So:
vr.ko registers, tracepoint_reg_count becomes 2;
KSU stops clearing the bit, and the mark paths set it;
every root process KSU serves keeps the bit and is killed on exit by vr.ko.
Clearing the bit from outside does not help — the next mark/unmark pass sets it again. The guard's trigger and KSU's own marking are indistinguishable at the flag level, which is what makes it effective against KSU. Getting to this state does not require the manager: su starts and then the process does not survive exit.
Dropping the bit unconditionally is not an option while ksu_sys_enter_handler() needs it for syscall redirection (PR #3582 moved registration to INT_MIN priority, but the flag is still required for the redirection to fire).
Suggestion: neutralise the guard's probe rather than the flag. The probe is one entry in the tracepoint's funcs[], and it is identifiable because its .func address lies outside the kernel image (module region) while the kernel's own consumers — ftrace, perf, bpf — are inside it. Pointing that single entry at the tracepoint's own probestub (a ret) makes it a no-op for every task, and leaves both the flag semantics and KSU's hook untouched. The write is data-only: one pointer inside the tracepoint's own funcs[] allocation, no module memory and no page tables.
Deriving it, with no hand-tuned constants:
__tracepoint_sys_exit, __tracepoint_android_rvh_commit_creds, __probestub_sys_exit, __traceiter_sys_exit and _end come from kallsyms by name;
offsetof(struct tracepoint, probestub) — scan __tracepoint_sys_exit for the slot whose value equals __probestub_sys_exit. The field is filled in at build time, so an exact match against a known symbol is self-checking;
offsetof(struct tracepoint, funcs) — disassemble __traceiter_sys_exit; __DO_TRACE inlines rcu_dereference_raw(tp->funcs) as a single ldr xN, [xM, #imm], and imm is the offset;
cross-check funcs == probestub + 24 (regfunc and unregfunc sit between them);
image extent = arm64 Image image_size at header offset 0x10 (= _end - _text), used to separate an in-image consumer from a module-region probe. A rounded bound is dangerous here: oversizing it starts rewriting the kernel's own perf/bpf callbacks.
One caveat worth knowing if this is ever implemented: the layout is configuration dependent. With CONFIG_HAVE_STATIC_CALL the two static_call_* members exist and probestub/funcs are 0x38/0x50; without them they are 0x30/0x48. Hardcoding either pair reads the neighbouring member on the other kind of kernel, silently. Measured on PD2463 (iQOO Neo10 Pro+, 6.6.89-android15-8-g1f71897ac249-abogki467805059-4k, no CONFIG_HAVE_STATIC_CALL): 0x30 / 0x48, and both derivation steps above reproduce them from that image.
Related: #1289 (vivo, ksud/su failing to start), #3061 (vivo, libksud killed by seccomp).
Happy to send a PR against kernel/hook/tp_marker.c for the detection half if there is interest — I have the derivation and the probe-identification logic implemented and tested against a real image, though I cannot build or run the KernelSU module here, so I would rather not open one I cannot verify.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Not a feature request — an interoperability report. Closing this if it is out of scope is fine; I am posting it as an idea because the report-only path needs a manager log that does not apply here.
ksu_clear_task_tracepoint_flag_if_needed()(andksu_mark_running_process()) back off whiletracepoint_reg_count > 1:The intent is to leave the flag alone when ftrace/perf are using the syscall tracepoint. On vivo kernels the same condition is met by
vr.ko, and it has the opposite effect.vr.koregisters its ownsys_exitprobe and usesTIF_SYSCALL_TRACEPOINT— the exact bitksu_set_task_tracepoint_flag()sets — as its "task of app origin" marker, killing the task from that probe once it holds euid 0. So:vr.koregisters,tracepoint_reg_countbecomes 2;vr.ko.Clearing the bit from outside does not help — the next mark/unmark pass sets it again. The guard's trigger and KSU's own marking are indistinguishable at the flag level, which is what makes it effective against KSU. Getting to this state does not require the manager:
sustarts and then the process does not survive exit.Dropping the bit unconditionally is not an option while
ksu_sys_enter_handler()needs it for syscall redirection (PR #3582 moved registration toINT_MINpriority, but the flag is still required for the redirection to fire).Suggestion: neutralise the guard's probe rather than the flag. The probe is one entry in the tracepoint's
funcs[], and it is identifiable because its.funcaddress lies outside the kernel image (module region) while the kernel's own consumers — ftrace, perf, bpf — are inside it. Pointing that single entry at the tracepoint's ownprobestub(aret) makes it a no-op for every task, and leaves both the flag semantics and KSU's hook untouched. The write is data-only: one pointer inside the tracepoint's ownfuncs[]allocation, no module memory and no page tables.Deriving it, with no hand-tuned constants:
__tracepoint_sys_exit,__tracepoint_android_rvh_commit_creds,__probestub_sys_exit,__traceiter_sys_exitand_endcome from kallsyms by name;offsetof(struct tracepoint, probestub)— scan__tracepoint_sys_exitfor the slot whose value equals__probestub_sys_exit. The field is filled in at build time, so an exact match against a known symbol is self-checking;offsetof(struct tracepoint, funcs)— disassemble__traceiter_sys_exit;__DO_TRACEinlinesrcu_dereference_raw(tp->funcs)as a singleldr xN, [xM, #imm], andimmis the offset;funcs == probestub + 24(regfuncandunregfuncsit between them);image_sizeat header offset0x10(=_end - _text), used to separate an in-image consumer from a module-region probe. A rounded bound is dangerous here: oversizing it starts rewriting the kernel's own perf/bpf callbacks.One caveat worth knowing if this is ever implemented: the layout is configuration dependent. With
CONFIG_HAVE_STATIC_CALLthe twostatic_call_*members exist andprobestub/funcsare0x38/0x50; without them they are0x30/0x48. Hardcoding either pair reads the neighbouring member on the other kind of kernel, silently. Measured on PD2463 (iQOO Neo10 Pro+,6.6.89-android15-8-g1f71897ac249-abogki467805059-4k, noCONFIG_HAVE_STATIC_CALL):0x30/0x48, and both derivation steps above reproduce them from that image.Related: #1289 (vivo, ksud/su failing to start), #3061 (vivo, libksud killed by seccomp).
Happy to send a PR against
kernel/hook/tp_marker.cfor the detection half if there is interest — I have the derivation and the probe-identification logic implemented and tested against a real image, though I cannot build or run the KernelSU module here, so I would rather not open one I cannot verify.All reactions