Skip to content

Commit cc01bd0

Browse files
anakryikoPeter Zijlstra
authored andcommitted
uprobes: travers uprobe's consumer list locklessly under SRCU protection
uprobe->register_rwsem is one of a few big bottlenecks to scalability of uprobes, so we need to get rid of it to improve uprobe performance and multi-CPU scalability. First, we turn uprobe's consumer list to a typical doubly-linked list and utilize existing RCU-aware helpers for traversing such lists, as well as adding and removing elements from it. For entry uprobes we already have SRCU protection active since before uprobe lookup. For uretprobe we keep refcount, guaranteeing that uprobe won't go away from under us, but we add SRCU protection around consumer list traversal. Lastly, to keep handler_chain()'s UPROBE_HANDLER_REMOVE handling simple, we remember whether any removal was requested during handler calls, but then we double-check the decision under a proper register_rwsem using consumers' filter callbacks. Handler removal is very rare, so this extra lock won't hurt performance, overall, but we also avoid the need for any extra protection (e.g., seqcount locks). Signed-off-by: Andrii Nakryiko <[email protected]> Signed-off-by: Peter Zijlstra (Intel) <[email protected]> Reviewed-by: Oleg Nesterov <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent 59da880 commit cc01bd0

File tree

2 files changed

+70
-44
lines changed

2 files changed

+70
-44
lines changed

include/linux/uprobes.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,21 @@ struct page;
2929
#define MAX_URETPROBE_DEPTH 64
3030

3131
struct uprobe_consumer {
32+
/*
33+
* handler() can return UPROBE_HANDLER_REMOVE to signal the need to
34+
* unregister uprobe for current process. If UPROBE_HANDLER_REMOVE is
35+
* returned, filter() callback has to be implemented as well and it
36+
* should return false to "confirm" the decision to uninstall uprobe
37+
* for the current process. If filter() is omitted or returns true,
38+
* UPROBE_HANDLER_REMOVE is effectively ignored.
39+
*/
3240
int (*handler)(struct uprobe_consumer *self, struct pt_regs *regs);
3341
int (*ret_handler)(struct uprobe_consumer *self,
3442
unsigned long func,
3543
struct pt_regs *regs);
3644
bool (*filter)(struct uprobe_consumer *self, struct mm_struct *mm);
3745

38-
struct uprobe_consumer *next;
46+
struct list_head cons_node;
3947
};
4048

4149
#ifdef CONFIG_UPROBES

kernel/events/uprobes.c

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ struct uprobe {
5959
struct rw_semaphore register_rwsem;
6060
struct rw_semaphore consumer_rwsem;
6161
struct list_head pending_list;
62-
struct uprobe_consumer *consumers;
62+
struct list_head consumers;
6363
struct inode *inode; /* Also hold a ref to inode */
6464
struct rcu_head rcu;
6565
loff_t offset;
@@ -783,6 +783,7 @@ static struct uprobe *alloc_uprobe(struct inode *inode, loff_t offset,
783783
uprobe->inode = inode;
784784
uprobe->offset = offset;
785785
uprobe->ref_ctr_offset = ref_ctr_offset;
786+
INIT_LIST_HEAD(&uprobe->consumers);
786787
init_rwsem(&uprobe->register_rwsem);
787788
init_rwsem(&uprobe->consumer_rwsem);
788789
RB_CLEAR_NODE(&uprobe->rb_node);
@@ -808,32 +809,19 @@ static struct uprobe *alloc_uprobe(struct inode *inode, loff_t offset,
808809
static void consumer_add(struct uprobe *uprobe, struct uprobe_consumer *uc)
809810
{
810811
down_write(&uprobe->consumer_rwsem);
811-
uc->next = uprobe->consumers;
812-
uprobe->consumers = uc;
812+
list_add_rcu(&uc->cons_node, &uprobe->consumers);
813813
up_write(&uprobe->consumer_rwsem);
814814
}
815815

816816
/*
817817
* For uprobe @uprobe, delete the consumer @uc.
818-
* Return true if the @uc is deleted successfully
819-
* or return false.
818+
* Should never be called with consumer that's not part of @uprobe->consumers.
820819
*/
821-
static bool consumer_del(struct uprobe *uprobe, struct uprobe_consumer *uc)
820+
static void consumer_del(struct uprobe *uprobe, struct uprobe_consumer *uc)
822821
{
823-
struct uprobe_consumer **con;
824-
bool ret = false;
825-
826822
down_write(&uprobe->consumer_rwsem);
827-
for (con = &uprobe->consumers; *con; con = &(*con)->next) {
828-
if (*con == uc) {
829-
*con = uc->next;
830-
ret = true;
831-
break;
832-
}
833-
}
823+
list_del_rcu(&uc->cons_node);
834824
up_write(&uprobe->consumer_rwsem);
835-
836-
return ret;
837825
}
838826

839827
static int __copy_insn(struct address_space *mapping, struct file *filp,
@@ -929,7 +917,8 @@ static bool filter_chain(struct uprobe *uprobe, struct mm_struct *mm)
929917
bool ret = false;
930918

931919
down_read(&uprobe->consumer_rwsem);
932-
for (uc = uprobe->consumers; uc; uc = uc->next) {
920+
list_for_each_entry_srcu(uc, &uprobe->consumers, cons_node,
921+
srcu_read_lock_held(&uprobes_srcu)) {
933922
ret = consumer_filter(uc, mm);
934923
if (ret)
935924
break;
@@ -1125,18 +1114,29 @@ void uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc)
11251114
int err;
11261115

11271116
down_write(&uprobe->register_rwsem);
1128-
if (WARN_ON(!consumer_del(uprobe, uc))) {
1129-
err = -ENOENT;
1130-
} else {
1131-
err = register_for_each_vma(uprobe, NULL);
1132-
/* TODO : cant unregister? schedule a worker thread */
1133-
if (unlikely(err))
1134-
uprobe_warn(current, "unregister, leaking uprobe");
1135-
}
1117+
consumer_del(uprobe, uc);
1118+
err = register_for_each_vma(uprobe, NULL);
11361119
up_write(&uprobe->register_rwsem);
11371120

1138-
if (!err)
1139-
put_uprobe(uprobe);
1121+
/* TODO : cant unregister? schedule a worker thread */
1122+
if (unlikely(err)) {
1123+
uprobe_warn(current, "unregister, leaking uprobe");
1124+
goto out_sync;
1125+
}
1126+
1127+
put_uprobe(uprobe);
1128+
1129+
out_sync:
1130+
/*
1131+
* Now that handler_chain() and handle_uretprobe_chain() iterate over
1132+
* uprobe->consumers list under RCU protection without holding
1133+
* uprobe->register_rwsem, we need to wait for RCU grace period to
1134+
* make sure that we can't call into just unregistered
1135+
* uprobe_consumer's callbacks anymore. If we don't do that, fast and
1136+
* unlucky enough caller can free consumer's memory and cause
1137+
* handler_chain() or handle_uretprobe_chain() to do an use-after-free.
1138+
*/
1139+
synchronize_srcu(&uprobes_srcu);
11401140
}
11411141
EXPORT_SYMBOL_GPL(uprobe_unregister);
11421142

@@ -1214,13 +1214,20 @@ EXPORT_SYMBOL_GPL(uprobe_register);
12141214
int uprobe_apply(struct uprobe *uprobe, struct uprobe_consumer *uc, bool add)
12151215
{
12161216
struct uprobe_consumer *con;
1217-
int ret = -ENOENT;
1217+
int ret = -ENOENT, srcu_idx;
12181218

12191219
down_write(&uprobe->register_rwsem);
1220-
for (con = uprobe->consumers; con && con != uc ; con = con->next)
1221-
;
1222-
if (con)
1223-
ret = register_for_each_vma(uprobe, add ? uc : NULL);
1220+
1221+
srcu_idx = srcu_read_lock(&uprobes_srcu);
1222+
list_for_each_entry_srcu(con, &uprobe->consumers, cons_node,
1223+
srcu_read_lock_held(&uprobes_srcu)) {
1224+
if (con == uc) {
1225+
ret = register_for_each_vma(uprobe, add ? uc : NULL);
1226+
break;
1227+
}
1228+
}
1229+
srcu_read_unlock(&uprobes_srcu, srcu_idx);
1230+
12241231
up_write(&uprobe->register_rwsem);
12251232

12261233
return ret;
@@ -2085,10 +2092,12 @@ static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs)
20852092
struct uprobe_consumer *uc;
20862093
int remove = UPROBE_HANDLER_REMOVE;
20872094
bool need_prep = false; /* prepare return uprobe, when needed */
2095+
bool has_consumers = false;
20882096

2089-
down_read(&uprobe->register_rwsem);
20902097
current->utask->auprobe = &uprobe->arch;
2091-
for (uc = uprobe->consumers; uc; uc = uc->next) {
2098+
2099+
list_for_each_entry_srcu(uc, &uprobe->consumers, cons_node,
2100+
srcu_read_lock_held(&uprobes_srcu)) {
20922101
int rc = 0;
20932102

20942103
if (uc->handler) {
@@ -2101,31 +2110,40 @@ static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs)
21012110
need_prep = true;
21022111

21032112
remove &= rc;
2113+
has_consumers = true;
21042114
}
21052115
current->utask->auprobe = NULL;
21062116

21072117
if (need_prep && !remove)
21082118
prepare_uretprobe(uprobe, regs); /* put bp at return */
21092119

2110-
if (remove && uprobe->consumers) {
2111-
WARN_ON(!uprobe_is_active(uprobe));
2112-
unapply_uprobe(uprobe, current->mm);
2120+
if (remove && has_consumers) {
2121+
down_read(&uprobe->register_rwsem);
2122+
2123+
/* re-check that removal is still required, this time under lock */
2124+
if (!filter_chain(uprobe, current->mm)) {
2125+
WARN_ON(!uprobe_is_active(uprobe));
2126+
unapply_uprobe(uprobe, current->mm);
2127+
}
2128+
2129+
up_read(&uprobe->register_rwsem);
21132130
}
2114-
up_read(&uprobe->register_rwsem);
21152131
}
21162132

21172133
static void
21182134
handle_uretprobe_chain(struct return_instance *ri, struct pt_regs *regs)
21192135
{
21202136
struct uprobe *uprobe = ri->uprobe;
21212137
struct uprobe_consumer *uc;
2138+
int srcu_idx;
21222139

2123-
down_read(&uprobe->register_rwsem);
2124-
for (uc = uprobe->consumers; uc; uc = uc->next) {
2140+
srcu_idx = srcu_read_lock(&uprobes_srcu);
2141+
list_for_each_entry_srcu(uc, &uprobe->consumers, cons_node,
2142+
srcu_read_lock_held(&uprobes_srcu)) {
21252143
if (uc->ret_handler)
21262144
uc->ret_handler(uc, ri->func, regs);
21272145
}
2128-
up_read(&uprobe->register_rwsem);
2146+
srcu_read_unlock(&uprobes_srcu, srcu_idx);
21292147
}
21302148

21312149
static struct return_instance *find_next_ret_chain(struct return_instance *ri)

0 commit comments

Comments
 (0)