On Mon, 2026-06-29 at 00:47 +0800, Wen Yang wrote:
> On [2-1]~[2-5]: embedded consumer causes UAF on PREEMPT_RT
> -----------------------------------------------------------
>
> The uprobe_bind selftest oopses on PREEMPT_RT(full):
>
> handler_chain+0xc9: mov rax, [r15+0x18] ; advance list iterator
> RAX: 000015ec00001f28 ; garbage — &uprobe->consumers after kfree
>
> handler_chain() reads uc->cons_node.next after uc->handler() returns,
> still inside rcu_read_lock_trace(). That pointer is &uprobe->consumers
> (the list head embedded in struct uprobe), which gets freed through:
>
> put_uprobe()
> -> schedule_work(uprobe_free_deferred) /* async */
> -> call_srcu(&uretprobes_srcu, ...)
> -> call_rcu_tasks_trace(kfree_uprobe)
> -> kfree(uprobe)
>
> rv_uprobe_sync() calls uprobe_unregister_sync() which calls
> synchronize_srcu(&uretprobes_srcu), but that only matters after the
> kworker has submitted work to uretprobes_srcu. On a loaded PREEMPT_RT
> box the kworker may not have run yet, so synchronize_srcu() returns
> immediately and kfree(uprobe) races with the still-iterating
> handler_chain():
>
> CPU A CPU B
> consumer_del() → list_del_rcu rcu_read_lock_trace()
> put_uprobe() → schedule_work uc->handler() returns
> rv_uprobe_sync(): reading cons_node.next...
> synchronize_srcu(&uretprobes_srcu)
If CPU B is in an RCU trace read-side critical section this doesn't
return immediately, it returns after readers are done, doesn't it?
(well, what you really care here is the synchronize_rcu_tasks_trace()
but we do both).
> ← idle; returns immediately
> [kworker fires later]
> kfree(uprobe) ← frees &uprobe->consumers
> cons_node.next = freed mem → CRASH
>
So I had a bit of a look and as far as I understand, we don't have any
control over the uprobe allocation pattern (workqueues and whatnot) and
we don't really care as long as we deregister it appropriately.
What we do control is the uprobe_consumer, that must be freed only after
the uprobe was synchronously deregistered, that should guarantee no
reader is going to reference it, shouldn't it?
uprobe_unregister_nosync() removes it from the cons_node list, so it's
safe to assume any handler_chain() after the next RCU-trace grace period
won't see it.
In the sketch I sent this is happening (all kfree(b) are after
rv_uprobe_unregister() which does the sync). What am I missing here?
uprobe is also freed after both grace periods, so no reader should use
that either.
The situation you're seeing isn't fully clear to me, I applied my sketch
and don't see any splat on a vng box.
> With uc embedded in the binding (as [2-1] suggests), no amount of
> delaying kfree(binding) helps: uprobe->consumers is freed by a chain we
> don't control. The fix is to keep uc->cons_node in memory that outlives
> the handler_chain() iteration, which means a separate allocation freed
> only after rv_uprobe_sync():
>
> rv_uprobe_unregister_nosync() /* list_del_rcu + schedule_work */
> rv_uprobe_sync() /* waits for any already-submitted
> srcu work */
We of course need to do any free after this sync, but I don't get why we
need an additional allocation since the following are both plain
synchronous frees, why isn't a single one (on b) enough?
I'm a bit lost on this..
Thanks,
Gabriele
> rv_uprobe_free() /* kfree(impl) — safe, iteration is
> done */
> kfree(b) /* binding; never contained uc */
>
> v4 keeps the public API shape you suggested with impl private:
>
> struct rv_uprobe {
> struct rv_uprobe_impl *impl; /* allocated by
> rv_uprobe_register() */
> struct inode *inode;
> };
> #define DECLARE_RV_UPROBE(name) struct rv_uprobe name /* [2-2] */
>
> int rv_uprobe_register(const char *binpath, loff_t offset,
> struct rv_uprobe *p, handler_fn,
> ret_handler_fn, void *priv); /* [2-4] */
> void rv_uprobe_unregister(struct rv_uprobe *p);
> void rv_uprobe_unregister_nosync(struct rv_uprobe *p);
> void rv_uprobe_sync(void);
> void rv_uprobe_free(struct rv_uprobe *p); /*
> [2-5] restored */
> bool rv_uprobe_is_registered(const struct rv_uprobe *p); /*
> [2-7] added */
> void *rv_uprobe_get_priv(struct uprobe_consumer *uc);
>
> Handler signature is (struct uprobe_consumer *self, ...) [2-3]; private
> data is retrieved via rv_uprobe_get_priv(self) instead of container_of().
>
> Then, rv_uprobe_free() is restored ([2-5] partially reverted).
>
>
> All other v3 items have been resolved, we are waiting for your comments
> on the above(embedded consumer causes UAF):
>
> [1-1,1-3] `# define` / `# error` space removed
> [1-4] `⟹` -> `=>`
> [1-6,1-7] #if/#else in da_destroy_storage() and da_monitor_init()
> replaced with plain if (DA_MON_ALLOCATION_STRATEGY ==
> DA_ALLOC_POOL)
> [1-8] tracepoint_synchronize_unregister() lifted to
> da_monitor_destroy before the pool/kmalloc branch
>
> [2-2] DECLARE_RV_UPROBE(name) — kept
> [2-3] handler sig (struct uprobe_consumer *self, ...) — kept
> [2-4] rv_uprobe_register() returns int — kept
> [2-5] rv_uprobe_attach/detach removed; rv_uprobe_free() restored (see
> above)
> [2-6] offset, path fields removed; inode used for identity
> [2-7] rv_uprobe_is_registered() added
>
> [6-1] Suggested-by removed
> [6-2] tlob Kconfig entry placed after the deadline monitors marker
> [6-3] Unnecessary includes removed (hrtimer.h, ktime.h, sched.h, tracefs.h)
> [6-4] `#include "../../rv.h"` -> `<rv.h>`
> [6-5,6-6] Verbose comments around DA_MON_POOL_SIZE and da_extra_cleanup
> simplified; early return in tlob_reset_notify() on
> !trace_detail_env_tlob_enabled()
> [6-7] ha_setup_invariants():
> if (atomic_read_acquire(&target->stopping)) return;
> if (next_state < state_max_tlob) ha_start_timer_ns(...);
> else ha_cancel_timer(ha_mon);
> [6-8] offsetof() arithmetic -> enum-indexed array:
> enum tlob_acc_idx { TLOB_ACC_RUNNING, TLOB_ACC_WAITING,
> TLOB_ACC_SLEEPING, TLOB_ACC_MAX };
> u64 accs_ns[TLOB_ACC_MAX];
> [6-9] 1000 → TLOB_MIN_THRESHOLD_NS
> [6-10] WARN_ON_ONCE(!da_mon) -> if (unlikely(WARN_ON_ONCE(!da_mon)))
> [6-11] __free(kfree) + list_add_tail(&no_free_ptr(b)->list, ...) in
> tlob_add_uprobe(); note that "b = no_free_ptr(b)" would restore b
> and re-trigger __free on return — the correct pattern is to use
> no_free_ptr() as the argument to list_add_tail() directly
> [6-15] tlob.h enum/struct order aligned with rvgen output
> [6-16] tracefs_create_file() -> rv_create_file()
>
> [7-1] KUnit tests call tlob_parse_uprobe_line/tlob_parse_remove_line
> directly; no real uprobe creation from unit tests
> [7-2] KUNIT_EXPECT_EQ(test, ..., 0) for valid-line cases
>
> [8-1] ftracetest: walk-up algorithm to locate test.d/functions
> [9-4] tlob_busy/sleep/preempt_work unified to duration_ms
>
>
> --
> Best wishes,
> Wen
>
>
> > Gabriele
> >
> > **Suggested simplification:**
> >
> > ---
> > diff --git a/include/rv/rv_uprobe.h b/include/rv/rv_uprobe.h
> > index 9106c5c927..4fb3f50a63 100644
> > --- a/include/rv/rv_uprobe.h
> > +++ b/include/rv/rv_uprobe.h
> > @@ -7,83 +7,56 @@
> > #ifndef _RV_UPROBE_H
> > #define _RV_UPROBE_H
> >
> > -#include <linux/path.h>
> > #include <linux/types.h>
> > +#include <linux/uprobes.h>
> >
> > struct pt_regs;
> > +struct inode;
> >
> > /**
> > * struct rv_uprobe - a single uprobe registered on behalf of an RV
> > monitor
> > *
> > - * @offset: byte offset within the ELF binary where the probe is
> > installed
> > - * @priv: monitor-private pointer; set at attach time, never touched by
> > - * this layer; passed unchanged to entry_fn / ret_fn
> > - * @path: resolved path of the probed binary (read-only after attach);
> > - * callers may use path.dentry for identity comparisons
> > - *
> > - * The implementation fields (uprobe_consumer, uprobe handle, callbacks)
> > are
> > - * private to rv_uprobe.c and are not exposed here; monitors must not
> > access
> > - * them directly.
> > + * @uc: underlying uprobe consumer (publicly visible)
> > + * @uprobe: active uprobe structure handle
> > + * @inode: inode of the target binary (read-only after registration)
> > */
> > struct rv_uprobe {
> > - /* public: read-only after rv_uprobe_attach*() */
> > - loff_t offset;
> > - void *priv;
> > - struct path path;
> > + struct uprobe_consumer uc;
> > + struct uprobe *uprobe;
> > + struct inode *inode;
> > };
> >
> > -/**
> > - * rv_uprobe_attach_path - register an uprobe given an already-resolved
> > path
> > - * @path: path of the target binary; rv_uprobe takes its own reference
> > - * @offset: byte offset within the binary
> > - * @entry_fn: called on probe hit (entry); may be NULL
> > - * @ret_fn: called on function return (uretprobe); may be NULL
> > - * @priv: opaque pointer forwarded to callbacks unchanged
> > - *
> > - * Use this variant when the caller has already resolved the path (e.g. to
> > - * register multiple probes on the same binary with a single kern_path
> > call).
> > - * The inode is derived internally via d_real_inode(), so inode and path
> > are
> > - * always consistent.
> > - *
> > - * Returns a pointer to the new rv_uprobe on success, ERR_PTR on failure.
> > - */
> > -struct rv_uprobe *rv_uprobe_attach_path(struct path *path, loff_t offset,
> > - int (*entry_fn)(struct rv_uprobe *p, struct pt_regs *regs, __u64
> > *data),
> > - int (*ret_fn)(struct rv_uprobe *p, unsigned long func,
> > - struct pt_regs *regs, __u64 *data),
> > - void *priv);
> > +/* Seamless inline declaration of a named uprobe inside user structs */
> > +#define DECLARE_RV_UPROBE(name) \
> > + struct rv_uprobe name
> >
> > /**
> > - * rv_uprobe_attach - resolve binpath and register an uprobe
> > - * @binpath: absolute path to the target binary
> > - * @offset: byte offset within the binary
> > - * @entry_fn: called on probe hit (entry); may be NULL
> > - * @ret_fn: called on function return (uretprobe); may be NULL
> > - * @priv: opaque pointer forwarded to callbacks unchanged
> > + * rv_uprobe_register - resolve binpath and register an uprobe
> > + * @binpath: absolute path to the target binary
> > + * @offset: byte offset within the binary
> > + * @p: pointer to the allocated/embedded rv_uprobe structure
> > + * @handler: called on probe hit (entry); may be NULL
> > + * @ret_handler: called on function return (uretprobe); may be NULL
> > *
> > - * Resolves binpath via kern_path(), then delegates to
> > rv_uprobe_attach_path().
> > + * Resolves binpath via kern_path(), registers the uprobe directly using
> > the
> > + * embedded `uprobe_consumer`, and immediately releases the path reference.
> > *
> > - * Returns a pointer to the new rv_uprobe on success, ERR_PTR on failure.
> > + * Returns 0 on success, or a negative error code on failure.
> > */
> > -struct rv_uprobe *rv_uprobe_attach(const char *binpath, loff_t offset,
> > - int (*entry_fn)(struct rv_uprobe *p, struct pt_regs *regs, __u64
> > *data),
> > - int (*ret_fn)(struct rv_uprobe *p, unsigned long func,
> > - struct pt_regs *regs, __u64 *data),
> > - void *priv);
> > +int rv_uprobe_register(const char *binpath, loff_t offset, struct rv_uprobe
> > *p,
> > + int (*handler)(struct uprobe_consumer *self, struct pt_regs *regs,
> > __u64 *data),
> > + int (*ret_handler)(struct uprobe_consumer *self, unsigned long
> > func,
> > + struct pt_regs *regs, __u64 *data));
> >
> > /**
> > - * rv_uprobe_detach - synchronously unregister an uprobe and free it
> > - * @p: probe to detach; may be NULL (no-op)
> > - *
> > - * Calls uprobe_unregister_nosync(), then uprobe_unregister_sync() to wait
> > - * for any in-progress handler to finish, then releases the path reference
> > - * and frees the rv_uprobe struct. The caller's priv data is NOT freed.
> > + * rv_uprobe_unregister - synchronously unregister a uprobe
> > + * @p: probe to unregister; may be NULL (no-op)
> > *
> > - * When removing a single probe, prefer this over the three-phase API.
> > - * Safe to call from process context only (uprobe_unregister_sync() may
> > - * schedule).
> > + * Dequeues the uprobe and waits synchronously for all in-flight handlers
> > + * to complete.
> > */
> > -void rv_uprobe_detach(struct rv_uprobe *p);
> > +void rv_uprobe_unregister(struct rv_uprobe *p);
> >
> > /**
> > * rv_uprobe_unregister_nosync - dequeue an uprobe without waiting
> > @@ -91,9 +64,7 @@ void rv_uprobe_detach(struct rv_uprobe *p);
> > *
> > * Removes the uprobe from the uprobe subsystem but does NOT wait for
> > * in-flight handlers to complete. The caller must call rv_uprobe_sync()
> > - * before calling rv_uprobe_free() on the same probe.
> > - *
> > - * Use this to batch multiple deregistrations before a single
> > rv_uprobe_sync().
> > + * before freeing any container holding this probe.
> > */
> > void rv_uprobe_unregister_nosync(struct rv_uprobe *p);
> >
> > @@ -101,19 +72,8 @@ void rv_uprobe_unregister_nosync(struct rv_uprobe *p);
> > * rv_uprobe_sync - wait for all in-flight uprobe handlers to complete
> > *
> > * Global barrier: waits for every in-flight uprobe handler across the
> > system
> > - * to finish. Call once after a batch of rv_uprobe_unregister_nosync()
> > calls
> > - * and before any rv_uprobe_free() call.
> > + * to finish.
> > */
> > void rv_uprobe_sync(void);
> >
> > -/**
> > - * rv_uprobe_free - release resources of a previously deregistered probe
> > - * @p: probe to free; may be NULL (no-op)
> > - *
> > - * Releases the path reference and frees the rv_uprobe struct. Must only
> > - * be called after rv_uprobe_sync() has returned. The caller's priv data
> > - * is NOT freed.
> > - */
> > -void rv_uprobe_free(struct rv_uprobe *p);
> > -
> > #endif /* _RV_UPROBE_H */
> > diff --git a/kernel/trace/rv/monitors/tlob/tlob.c
> > b/kernel/trace/rv/monitors/tlob/tlob.c
> > index d8e0c47947..28a6c740c7 100644
> > --- a/kernel/trace/rv/monitors/tlob/tlob.c
> > +++ b/kernel/trace/rv/monitors/tlob/tlob.c
> > @@ -252,8 +252,8 @@ struct tlob_uprobe_binding {
> > char binpath[TLOB_MAX_PATH];
> > loff_t offset_start;
> > loff_t offset_stop;
> > - struct rv_uprobe *start_probe;
> > - struct rv_uprobe *stop_probe;
> > + DECLARE_RV_UPROBE(start_probe);
> > + DECLARE_RV_UPROBE(stop_probe);
> > };
> >
> > /* RCU callback: free the slab once no readers remain. */
> > @@ -512,16 +512,16 @@ int tlob_stop_task(struct task_struct *task)
> > EXPORT_SYMBOL_GPL(tlob_stop_task);
> >
> >
> > -static int tlob_uprobe_entry_handler(struct rv_uprobe *p, struct pt_regs
> > *regs,
> > +static int tlob_uprobe_entry_handler(struct uprobe_consumer *self, struct
> > pt_regs *regs,
> > __u64 *data)
> > {
> > - struct tlob_uprobe_binding *b = p->priv;
> > + struct tlob_uprobe_binding *b = container_of(self, struct
> > tlob_uprobe_binding, start_probe.uc);
> >
> > tlob_start_task(current, b->threshold_ns);
> > return 0;
> > }
> >
> > -static int tlob_uprobe_stop_handler(struct rv_uprobe *p, struct pt_regs
> > *regs,
> > +static int tlob_uprobe_stop_handler(struct uprobe_consumer *self, struct
> > pt_regs *regs,
> > __u64 *data)
> > {
> > tlob_stop_task(current);
> > @@ -537,6 +537,7 @@ static int tlob_add_uprobe(u64 threshold_ns, const char
> > *binpath,
> > {
> > struct tlob_uprobe_binding *b, *tmp_b;
> > char pathbuf[TLOB_MAX_PATH];
> > + struct inode *inode;
> > struct path path;
> > char *canon;
> > int ret;
> > @@ -561,10 +562,12 @@ static int tlob_add_uprobe(u64 threshold_ns, const
> > char *binpath,
> > goto err_path;
> > }
> >
> > - /* Reject duplicate start offset for the same binary. */
> > + inode = d_real_inode(path.dentry);
> > +
> > + /* Reject duplicate start offset for the same binary inode. */
> > list_for_each_entry(tmp_b, &tlob_uprobe_list, list) {
> > if (tmp_b->offset_start == offset_start &&
> > - tmp_b->start_probe->path.dentry == path.dentry) {
> > + tmp_b->start_probe.inode == inode) {
> > ret = -EEXIST;
> > goto err_path;
> > }
> > @@ -577,29 +580,25 @@ static int tlob_add_uprobe(u64 threshold_ns, const
> > char *binpath,
> > }
> > strscpy(b->binpath, canon, sizeof(b->binpath));
> >
> > - /* Both probes share b (priv) and path; attach_path refs path
> > itself. */
> > - b->start_probe = rv_uprobe_attach_path(&path, offset_start,
> > - tlob_uprobe_entry_handler,
> > NULL, b);
> > - if (IS_ERR(b->start_probe)) {
> > - ret = PTR_ERR(b->start_probe);
> > - b->start_probe = NULL;
> > - goto err_path;
> > - }
> > + path_put(&path);
> > +
> > + /* Both probes are registered directly on the embedded fields */
> > + ret = rv_uprobe_register(b->binpath, offset_start, &b->start_probe,
> > + tlob_uprobe_entry_handler, NULL);
> > + if (ret)
> > + goto err_free;
> >
> > - b->stop_probe = rv_uprobe_attach_path(&path, offset_stop,
> > - tlob_uprobe_stop_handler,
> > NULL, b);
> > - if (IS_ERR(b->stop_probe)) {
> > - ret = PTR_ERR(b->stop_probe);
> > - b->stop_probe = NULL;
> > + ret = rv_uprobe_register(b->binpath, offset_stop, &b->stop_probe,
> > + tlob_uprobe_stop_handler, NULL);
> > + if (ret)
> > goto err_start;
> > - }
> >
> > - path_put(&path);
> > list_add_tail(&b->list, &tlob_uprobe_list);
> > return 0;
> >
> > err_start:
> > - rv_uprobe_detach(b->start_probe);
> > + rv_uprobe_unregister(&b->start_probe);
> > + goto err_free;
> > err_path:
> > path_put(&path);
> > err_free:
> > @@ -611,21 +610,24 @@ static int tlob_remove_uprobe_by_key(loff_t
> > offset_start, const char *binpath)
> > {
> > struct tlob_uprobe_binding *b, *tmp;
> > struct path remove_path;
> > + struct inode *inode;
> > int ret;
> >
> > ret = kern_path(binpath, LOOKUP_FOLLOW, &remove_path);
> > if (ret)
> > return ret;
> >
> > + inode = d_real_inode(remove_path.dentry);
> > +
> > ret = -ENOENT;
> > list_for_each_entry_safe(b, tmp, &tlob_uprobe_list, list) {
> > if (b->offset_start != offset_start)
> > continue;
> > - if (b->start_probe->path.dentry != remove_path.dentry)
> > + if (b->start_probe.inode != inode)
> > continue;
> > list_del(&b->list);
> > - rv_uprobe_detach(b->start_probe);
> > - rv_uprobe_detach(b->stop_probe);
> > + rv_uprobe_unregister(&b->start_probe);
> > + rv_uprobe_unregister(&b->stop_probe);
> > kfree(b);
> > ret = 0;
> > break;
> > @@ -643,8 +645,8 @@ static void tlob_remove_all_uprobes(void)
> > mutex_lock(&tlob_uprobe_mutex);
> > list_for_each_entry_safe(b, tmp, &tlob_uprobe_list, list) {
> > list_move(&b->list, &pending);
> > - rv_uprobe_unregister_nosync(b->start_probe);
> > - rv_uprobe_unregister_nosync(b->stop_probe);
> > + rv_uprobe_unregister_nosync(&b->start_probe);
> > + rv_uprobe_unregister_nosync(&b->stop_probe);
> > }
> > mutex_unlock(&tlob_uprobe_mutex);
> >
> > @@ -658,8 +660,6 @@ static void tlob_remove_all_uprobes(void)
> > rv_uprobe_sync();
> >
> > list_for_each_entry_safe(b, tmp, &pending, list) {
> > - rv_uprobe_free(b->start_probe);
> > - rv_uprobe_free(b->stop_probe);
> > kfree(b);
> > }
> > }
> > diff --git a/kernel/trace/rv/rv_uprobe.c b/kernel/trace/rv/rv_uprobe.c
> > index 3d8b764dde..69b8b0c27e 100644
> > --- a/kernel/trace/rv/rv_uprobe.c
> > +++ b/kernel/trace/rv/rv_uprobe.c
> > @@ -10,149 +10,74 @@
> > #include <linux/uprobes.h>
> > #include <rv/rv_uprobe.h>
> >
> > -/*
> > - * Private extension of struct rv_uprobe. Allocated by rv_uprobe_attach*()
> > - * and returned to callers as &impl->pub.
> > - */
> > -struct rv_uprobe_impl {
> > - struct rv_uprobe pub; /* must be first; callers hold &pub
> > */
> > - struct uprobe_consumer uc;
> > - struct uprobe *uprobe;
> > - int (*entry_fn)(struct rv_uprobe *p, struct pt_regs *regs, __u64
> > *data);
> > - int (*ret_fn)(struct rv_uprobe *p, unsigned long func,
> > - struct pt_regs *regs, __u64 *data);
> > -};
> > -
> > -static int rv_uprobe_handler(struct uprobe_consumer *uc,
> > - struct pt_regs *regs, __u64 *data)
> > -{
> > - struct rv_uprobe_impl *impl = container_of(uc, struct
> > rv_uprobe_impl, uc);
> > -
> > - if (impl->entry_fn)
> > - return impl->entry_fn(&impl->pub, regs, data);
> > - return 0;
> > -}
> > -
> > -static int rv_uprobe_ret_handler(struct uprobe_consumer *uc,
> > - unsigned long func,
> > - struct pt_regs *regs, __u64 *data)
> > -{
> > - struct rv_uprobe_impl *impl = container_of(uc, struct
> > rv_uprobe_impl, uc);
> > -
> > - if (impl->ret_fn)
> > - return impl->ret_fn(&impl->pub, func, regs, data);
> > - return 0;
> > -}
> > -
> > -static struct rv_uprobe *
> > -__rv_uprobe_attach(struct inode *inode, struct path *path, loff_t offset,
> > - int (*entry_fn)(struct rv_uprobe *p, struct pt_regs
> > *regs, __u64 *data),
> > - int (*ret_fn)(struct rv_uprobe *p, unsigned long func,
> > - struct pt_regs *regs, __u64 *data),
> > - void *priv)
> > -{
> > - struct rv_uprobe_impl *impl;
> > - int ret;
> > -
> > - if (!entry_fn && !ret_fn)
> > - return ERR_PTR(-EINVAL);
> > -
> > - impl = kzalloc_obj(*impl, GFP_KERNEL);
> > - if (!impl)
> > - return ERR_PTR(-ENOMEM);
> > -
> > - impl->pub.offset = offset;
> > - impl->pub.priv = priv;
> > - impl->entry_fn = entry_fn;
> > - impl->ret_fn = ret_fn;
> > - path_get(path);
> > - impl->pub.path = *path;
> > -
> > - if (entry_fn)
> > - impl->uc.handler = rv_uprobe_handler;
> > - if (ret_fn)
> > - impl->uc.ret_handler = rv_uprobe_ret_handler;
> > -
> > - impl->uprobe = uprobe_register(inode, offset, 0, &impl->uc);
> > - if (IS_ERR(impl->uprobe)) {
> > - ret = PTR_ERR(impl->uprobe);
> > - path_put(&impl->pub.path);
> > - kfree(impl);
> > - return ERR_PTR(ret);
> > - }
> > -
> > - return &impl->pub;
> > -}
> > -
> > -/**
> > - * rv_uprobe_attach_path - register an uprobe given an already-resolved
> > path
> > - */
> > -struct rv_uprobe *rv_uprobe_attach_path(struct path *path, loff_t offset,
> > - int (*entry_fn)(struct rv_uprobe *p, struct pt_regs *regs, __u64
> > *data),
> > - int (*ret_fn)(struct rv_uprobe *p, unsigned long func,
> > - struct pt_regs *regs, __u64 *data),
> > - void *priv)
> > -{
> > - struct inode *inode = d_real_inode(path->dentry);
> > -
> > - return __rv_uprobe_attach(inode, path, offset, entry_fn, ret_fn,
> > priv);
> > -}
> > -EXPORT_SYMBOL_GPL(rv_uprobe_attach_path);
> > -
> > /**
> > - * rv_uprobe_attach - resolve binpath and register an uprobe
> > + * rv_uprobe_register - resolve binpath and register an uprobe
> > */
> > -struct rv_uprobe *rv_uprobe_attach(const char *binpath, loff_t offset,
> > - int (*entry_fn)(struct rv_uprobe *p, struct pt_regs *regs, __u64
> > *data),
> > - int (*ret_fn)(struct rv_uprobe *p, unsigned long func,
> > - struct pt_regs *regs, __u64 *data),
> > - void *priv)
> > +int rv_uprobe_register(const char *binpath, loff_t offset, struct rv_uprobe
> > *p,
> > + int (*handler)(struct uprobe_consumer *self, struct pt_regs *regs,
> > __u64 *data),
> > + int (*ret_handler)(struct uprobe_consumer *self, unsigned long
> > func,
> > + struct pt_regs *regs, __u64 *data))
> > {
> > - struct rv_uprobe *p;
> > + struct inode *inode;
> > struct path path;
> > int ret;
> >
> > + if (!handler && !ret_handler)
> > + return -EINVAL;
> > +
> > ret = kern_path(binpath, LOOKUP_FOLLOW, &path);
> > if (ret)
> > - return ERR_PTR(ret);
> > + return ret;
> >
> > if (!d_is_reg(path.dentry)) {
> > path_put(&path);
> > - return ERR_PTR(-EINVAL);
> > + return -EINVAL;
> > }
> >
> > - p = rv_uprobe_attach_path(&path, offset, entry_fn, ret_fn, priv);
> > + inode = d_real_inode(path.dentry);
> > +
> > + p->uc.handler = handler;
> > + p->uc.ret_handler = ret_handler;
> > + p->inode = inode;
> > +
> > + p->uprobe = uprobe_register(inode, offset, 0, &p->uc);
> > path_put(&path);
> > - return p;
> > +
> > + if (IS_ERR(p->uprobe)) {
> > + ret = PTR_ERR(p->uprobe);
> > + p->uprobe = NULL;
> > + p->inode = NULL;
> > + return ret;
> > + }
> > +
> > + return 0;
> > }
> > -EXPORT_SYMBOL_GPL(rv_uprobe_attach);
> > +EXPORT_SYMBOL_GPL(rv_uprobe_register);
> >
> > /**
> > - * rv_uprobe_detach - synchronously unregister an uprobe and free it
> > + * rv_uprobe_unregister - synchronously unregister a uprobe
> > */
> > -void rv_uprobe_detach(struct rv_uprobe *p)
> > +void rv_uprobe_unregister(struct rv_uprobe *p)
> > {
> > - if (!p)
> > + if (!p || IS_ERR_OR_NULL(p->uprobe))
> > return;
> >
> > rv_uprobe_unregister_nosync(p);
> > rv_uprobe_sync();
> > - rv_uprobe_free(p);
> > }
> > -EXPORT_SYMBOL_GPL(rv_uprobe_detach);
> > +EXPORT_SYMBOL_GPL(rv_uprobe_unregister);
> >
> > /**
> > * rv_uprobe_unregister_nosync - dequeue an uprobe without waiting
> > */
> > void rv_uprobe_unregister_nosync(struct rv_uprobe *p)
> > {
> > - struct rv_uprobe_impl *impl;
> > -
> > - if (!p)
> > + if (!p || IS_ERR_OR_NULL(p->uprobe))
> > return;
> >
> > - impl = container_of(p, struct rv_uprobe_impl, pub);
> > - uprobe_unregister_nosync(impl->uprobe, &impl->uc);
> > + uprobe_unregister_nosync(p->uprobe, &p->uc);
> > + p->uprobe = NULL;
> > + p->inode = NULL;
> > }
> > EXPORT_SYMBOL_GPL(rv_uprobe_unregister_nosync);
> >
> > @@ -164,19 +89,3 @@ void rv_uprobe_sync(void)
> > uprobe_unregister_sync();
> > }
> > EXPORT_SYMBOL_GPL(rv_uprobe_sync);
> > -
> > -/**
> > - * rv_uprobe_free - release resources of a previously deregistered probe
> > - */
> > -void rv_uprobe_free(struct rv_uprobe *p)
> > -{
> > - struct rv_uprobe_impl *impl;
> > -
> > - if (!p)
> > - return;
> > -
> > - impl = container_of(p, struct rv_uprobe_impl, pub);
> > - path_put(&p->path);
> > - kfree(impl);
> > -}
> > -EXPORT_SYMBOL_GPL(rv_uprobe_free);
> >