Skip to content

Commit 4d75609

Browse files
olsajiriPeter Zijlstra
authored andcommitted
uprobe: Add support for session consumer
This change allows the uprobe consumer to behave as session which means that 'handler' and 'ret_handler' callbacks are connected in a way that allows to: - control execution of 'ret_handler' from 'handler' callback - share data between 'handler' and 'ret_handler' callbacks The session concept fits to our common use case where we do filtering on entry uprobe and based on the result we decide to run the return uprobe (or not). It's also convenient to share the data between session callbacks. To achive this we are adding new return value the uprobe consumer can return from 'handler' callback: UPROBE_HANDLER_IGNORE - Ignore 'ret_handler' callback for this consumer. And store cookie and pass it to 'ret_handler' when consumer has both 'handler' and 'ret_handler' callbacks defined. We store shared data in the return_consumer object array as part of the return_instance object. This way the handle_uretprobe_chain can find related return_consumer and its shared data. We also store entry handler return value, for cases when there are multiple consumers on single uprobe and some of them are ignored and some of them not, in which case the return probe gets installed and we need to have a way to find out which consumer needs to be ignored. The tricky part is when consumer is registered 'after' the uprobe entry handler is hit. In such case this consumer's 'ret_handler' gets executed as well, but it won't have the proper data pointer set, so we can filter it out. Suggested-by: Oleg Nesterov <[email protected]> Signed-off-by: Jiri Olsa <[email protected]> Signed-off-by: Peter Zijlstra (Intel) <[email protected]> Reviewed-by: Oleg Nesterov <[email protected]> Acked-by: Andrii Nakryiko <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent da09a9e commit 4d75609

File tree

2 files changed

+139
-30
lines changed

2 files changed

+139
-30
lines changed

include/linux/uprobes.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,17 @@ struct inode;
2323
struct notifier_block;
2424
struct page;
2525

26+
/*
27+
* Allowed return values from uprobe consumer's handler callback
28+
* with following meaning:
29+
*
30+
* UPROBE_HANDLER_REMOVE
31+
* - Remove the uprobe breakpoint from current->mm.
32+
* UPROBE_HANDLER_IGNORE
33+
* - Ignore ret_handler callback for this consumer.
34+
*/
2635
#define UPROBE_HANDLER_REMOVE 1
27-
#define UPROBE_HANDLER_MASK 1
36+
#define UPROBE_HANDLER_IGNORE 2
2837

2938
#define MAX_URETPROBE_DEPTH 64
3039

@@ -44,6 +53,8 @@ struct uprobe_consumer {
4453
bool (*filter)(struct uprobe_consumer *self, struct mm_struct *mm);
4554

4655
struct list_head cons_node;
56+
57+
__u64 id; /* set when uprobe_consumer is registered */
4758
};
4859

4960
#ifdef CONFIG_UPROBES
@@ -83,14 +94,22 @@ struct uprobe_task {
8394
unsigned int depth;
8495
};
8596

97+
struct return_consumer {
98+
__u64 cookie;
99+
__u64 id;
100+
};
101+
86102
struct return_instance {
87103
struct uprobe *uprobe;
88104
unsigned long func;
89105
unsigned long stack; /* stack pointer */
90106
unsigned long orig_ret_vaddr; /* original return address */
91107
bool chained; /* true, if instance is nested */
108+
int consumers_cnt;
92109

93110
struct return_instance *next; /* keep as stack */
111+
112+
struct return_consumer consumers[] __counted_by(consumers_cnt);
94113
};
95114

96115
enum rp_check {

kernel/events/uprobes.c

Lines changed: 119 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ struct uprobe {
6464
struct rcu_head rcu;
6565
loff_t offset;
6666
loff_t ref_ctr_offset;
67-
unsigned long flags;
67+
unsigned long flags; /* "unsigned long" so bitops work */
6868

6969
/*
7070
* The generic code assumes that it has two members of unknown type
@@ -823,8 +823,11 @@ static struct uprobe *alloc_uprobe(struct inode *inode, loff_t offset,
823823

824824
static void consumer_add(struct uprobe *uprobe, struct uprobe_consumer *uc)
825825
{
826+
static atomic64_t id;
827+
826828
down_write(&uprobe->consumer_rwsem);
827829
list_add_rcu(&uc->cons_node, &uprobe->consumers);
830+
uc->id = (__u64) atomic64_inc_return(&id);
828831
up_write(&uprobe->consumer_rwsem);
829832
}
830833

@@ -1761,6 +1764,34 @@ static struct uprobe_task *get_utask(void)
17611764
return current->utask;
17621765
}
17631766

1767+
static size_t ri_size(int consumers_cnt)
1768+
{
1769+
struct return_instance *ri;
1770+
1771+
return sizeof(*ri) + sizeof(ri->consumers[0]) * consumers_cnt;
1772+
}
1773+
1774+
#define DEF_CNT 4
1775+
1776+
static struct return_instance *alloc_return_instance(void)
1777+
{
1778+
struct return_instance *ri;
1779+
1780+
ri = kzalloc(ri_size(DEF_CNT), GFP_KERNEL);
1781+
if (!ri)
1782+
return ZERO_SIZE_PTR;
1783+
1784+
ri->consumers_cnt = DEF_CNT;
1785+
return ri;
1786+
}
1787+
1788+
static struct return_instance *dup_return_instance(struct return_instance *old)
1789+
{
1790+
size_t size = ri_size(old->consumers_cnt);
1791+
1792+
return kmemdup(old, size, GFP_KERNEL);
1793+
}
1794+
17641795
static int dup_utask(struct task_struct *t, struct uprobe_task *o_utask)
17651796
{
17661797
struct uprobe_task *n_utask;
@@ -1773,11 +1804,10 @@ static int dup_utask(struct task_struct *t, struct uprobe_task *o_utask)
17731804

17741805
p = &n_utask->return_instances;
17751806
for (o = o_utask->return_instances; o; o = o->next) {
1776-
n = kmalloc(sizeof(struct return_instance), GFP_KERNEL);
1807+
n = dup_return_instance(o);
17771808
if (!n)
17781809
return -ENOMEM;
17791810

1780-
*n = *o;
17811811
/*
17821812
* uprobe's refcnt has to be positive at this point, kept by
17831813
* utask->return_instances items; return_instances can't be
@@ -1870,35 +1900,31 @@ static void cleanup_return_instances(struct uprobe_task *utask, bool chained,
18701900
utask->return_instances = ri;
18711901
}
18721902

1873-
static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs)
1903+
static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs,
1904+
struct return_instance *ri)
18741905
{
18751906
struct uprobe_task *utask = current->utask;
18761907
unsigned long orig_ret_vaddr, trampoline_vaddr;
1877-
struct return_instance *ri;
18781908
bool chained;
18791909

18801910
if (!get_xol_area())
1881-
return;
1911+
goto free;
18821912

18831913
if (utask->depth >= MAX_URETPROBE_DEPTH) {
18841914
printk_ratelimited(KERN_INFO "uprobe: omit uretprobe due to"
18851915
" nestedness limit pid/tgid=%d/%d\n",
18861916
current->pid, current->tgid);
1887-
return;
1917+
goto free;
18881918
}
18891919

18901920
/* we need to bump refcount to store uprobe in utask */
18911921
if (!try_get_uprobe(uprobe))
1892-
return;
1893-
1894-
ri = kmalloc(sizeof(struct return_instance), GFP_KERNEL);
1895-
if (!ri)
1896-
goto fail;
1922+
goto free;
18971923

18981924
trampoline_vaddr = uprobe_get_trampoline_vaddr();
18991925
orig_ret_vaddr = arch_uretprobe_hijack_return_addr(trampoline_vaddr, regs);
19001926
if (orig_ret_vaddr == -1)
1901-
goto fail;
1927+
goto put;
19021928

19031929
/* drop the entries invalidated by longjmp() */
19041930
chained = (orig_ret_vaddr == trampoline_vaddr);
@@ -1916,7 +1942,7 @@ static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs)
19161942
* attack from user-space.
19171943
*/
19181944
uprobe_warn(current, "handle tail call");
1919-
goto fail;
1945+
goto put;
19201946
}
19211947
orig_ret_vaddr = utask->return_instances->orig_ret_vaddr;
19221948
}
@@ -1931,9 +1957,10 @@ static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs)
19311957
utask->return_instances = ri;
19321958

19331959
return;
1934-
fail:
1935-
kfree(ri);
1960+
put:
19361961
put_uprobe(uprobe);
1962+
free:
1963+
kfree(ri);
19371964
}
19381965

19391966
/* Prepare to single-step probed instruction out of line. */
@@ -2077,34 +2104,90 @@ static struct uprobe *find_active_uprobe_rcu(unsigned long bp_vaddr, int *is_swb
20772104
return uprobe;
20782105
}
20792106

2107+
static struct return_instance*
2108+
push_consumer(struct return_instance *ri, int idx, __u64 id, __u64 cookie)
2109+
{
2110+
if (unlikely(ri == ZERO_SIZE_PTR))
2111+
return ri;
2112+
2113+
if (unlikely(idx >= ri->consumers_cnt)) {
2114+
struct return_instance *old_ri = ri;
2115+
2116+
ri->consumers_cnt += DEF_CNT;
2117+
ri = krealloc(old_ri, ri_size(old_ri->consumers_cnt), GFP_KERNEL);
2118+
if (!ri) {
2119+
kfree(old_ri);
2120+
return ZERO_SIZE_PTR;
2121+
}
2122+
}
2123+
2124+
ri->consumers[idx].id = id;
2125+
ri->consumers[idx].cookie = cookie;
2126+
return ri;
2127+
}
2128+
2129+
static struct return_consumer *
2130+
return_consumer_find(struct return_instance *ri, int *iter, int id)
2131+
{
2132+
struct return_consumer *ric;
2133+
int idx = *iter;
2134+
2135+
for (ric = &ri->consumers[idx]; idx < ri->consumers_cnt; idx++, ric++) {
2136+
if (ric->id == id) {
2137+
*iter = idx + 1;
2138+
return ric;
2139+
}
2140+
}
2141+
return NULL;
2142+
}
2143+
2144+
static bool ignore_ret_handler(int rc)
2145+
{
2146+
return rc == UPROBE_HANDLER_REMOVE || rc == UPROBE_HANDLER_IGNORE;
2147+
}
2148+
20802149
static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs)
20812150
{
20822151
struct uprobe_consumer *uc;
2083-
int remove = UPROBE_HANDLER_REMOVE;
2084-
bool need_prep = false; /* prepare return uprobe, when needed */
2085-
bool has_consumers = false;
2152+
bool has_consumers = false, remove = true;
2153+
struct return_instance *ri = NULL;
2154+
int push_idx = 0;
20862155

20872156
current->utask->auprobe = &uprobe->arch;
20882157

20892158
list_for_each_entry_rcu(uc, &uprobe->consumers, cons_node, rcu_read_lock_trace_held()) {
2159+
bool session = uc->handler && uc->ret_handler;
2160+
__u64 cookie = 0;
20902161
int rc = 0;
20912162

20922163
if (uc->handler) {
2093-
rc = uc->handler(uc, regs, NULL);
2094-
WARN(rc & ~UPROBE_HANDLER_MASK,
2164+
rc = uc->handler(uc, regs, &cookie);
2165+
WARN(rc < 0 || rc > 2,
20952166
"bad rc=0x%x from %ps()\n", rc, uc->handler);
20962167
}
20972168

2098-
if (uc->ret_handler)
2099-
need_prep = true;
2100-
2101-
remove &= rc;
2169+
remove &= rc == UPROBE_HANDLER_REMOVE;
21022170
has_consumers = true;
2171+
2172+
if (!uc->ret_handler || ignore_ret_handler(rc))
2173+
continue;
2174+
2175+
if (!ri)
2176+
ri = alloc_return_instance();
2177+
2178+
if (session)
2179+
ri = push_consumer(ri, push_idx++, uc->id, cookie);
21032180
}
21042181
current->utask->auprobe = NULL;
21052182

2106-
if (need_prep && !remove)
2107-
prepare_uretprobe(uprobe, regs); /* put bp at return */
2183+
if (!ZERO_OR_NULL_PTR(ri)) {
2184+
/*
2185+
* The push_idx value has the final number of return consumers,
2186+
* and ri->consumers_cnt has number of allocated consumers.
2187+
*/
2188+
ri->consumers_cnt = push_idx;
2189+
prepare_uretprobe(uprobe, regs, ri);
2190+
}
21082191

21092192
if (remove && has_consumers) {
21102193
down_read(&uprobe->register_rwsem);
@@ -2123,12 +2206,19 @@ static void
21232206
handle_uretprobe_chain(struct return_instance *ri, struct pt_regs *regs)
21242207
{
21252208
struct uprobe *uprobe = ri->uprobe;
2209+
struct return_consumer *ric;
21262210
struct uprobe_consumer *uc;
2211+
int ric_idx = 0;
21272212

21282213
rcu_read_lock_trace();
21292214
list_for_each_entry_rcu(uc, &uprobe->consumers, cons_node, rcu_read_lock_trace_held()) {
2130-
if (uc->ret_handler)
2131-
uc->ret_handler(uc, ri->func, regs, NULL);
2215+
bool session = uc->handler && uc->ret_handler;
2216+
2217+
if (uc->ret_handler) {
2218+
ric = return_consumer_find(ri, &ric_idx, uc->id);
2219+
if (!session || ric)
2220+
uc->ret_handler(uc, ri->func, regs, ric ? &ric->cookie : NULL);
2221+
}
21322222
}
21332223
rcu_read_unlock_trace();
21342224
}

0 commit comments

Comments
 (0)